diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts new file mode 100644 index 000000000..e61e3ca01 --- /dev/null +++ b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts @@ -0,0 +1,49 @@ +import { + comfyExpect as expect, + comfyPageFixture as test +} from '../../../fixtures/ComfyPage' + +test.describe('Vue Node Custom Colors', () => { + test.beforeEach(async ({ comfyPage }) => { + await comfyPage.setSetting('Comfy.UseNewMenu', 'Top') + await comfyPage.setSetting('Comfy.Canvas.SelectionToolbox', true) + await comfyPage.setSetting('Comfy.VueNodes.Enabled', true) + await comfyPage.vueNodes.waitForNodes() + }) + + test('displays color picker button and allows color selection', async ({ + comfyPage + }) => { + const loadCheckpointNode = comfyPage.page.locator('[data-node-id]').filter({ + hasText: 'Load Checkpoint' + }) + await loadCheckpointNode.getByText('Load Checkpoint').click() + + await comfyPage.page.locator('.selection-toolbox .pi-circle-fill').click() + await comfyPage.page + .locator('.color-picker-container') + .locator('i[data-testid="blue"]') + .click() + + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-custom-color-blue.png' + ) + }) + + test('should load node colors from workflow', async ({ comfyPage }) => { + await comfyPage.loadWorkflow('nodes/every_node_color') + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-custom-colors-dark-all-colors.png' + ) + }) + + test('should show brightened node colors on light theme', async ({ + comfyPage + }) => { + await comfyPage.setSetting('Comfy.ColorPalette', 'light') + await comfyPage.loadWorkflow('nodes/every_node_color') + await expect(comfyPage.canvas).toHaveScreenshot( + 'vue-node-custom-colors-light-all-colors.png' + ) + }) +}) diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png new file mode 100644 index 000000000..3f02b8cf6 Binary files /dev/null and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png new file mode 100644 index 000000000..e3f693e8f Binary files /dev/null and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-dark-all-colors-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png new file mode 100644 index 000000000..77d7c5fb0 Binary files /dev/null and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-colors-light-all-colors-chromium-linux.png differ diff --git a/src/composables/graph/useGraphNodeManager.ts b/src/composables/graph/useGraphNodeManager.ts index beaca8d4c..55b06cabd 100644 --- a/src/composables/graph/useGraphNodeManager.ts +++ b/src/composables/graph/useGraphNodeManager.ts @@ -41,6 +41,8 @@ export interface VueNodeData { collapsed?: boolean pinned?: boolean } + color?: string + bgcolor?: string } export interface GraphNodeManager { @@ -126,7 +128,9 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { widgets: safeWidgets, inputs: node.inputs ? [...node.inputs] : undefined, outputs: node.outputs ? [...node.outputs] : undefined, - flags: node.flags ? { ...node.flags } : undefined + flags: node.flags ? { ...node.flags } : undefined, + color: node.color || undefined, + bgcolor: node.bgcolor || undefined } } @@ -449,6 +453,24 @@ export function useGraphNodeManager(graph: LGraph): GraphNodeManager { ...currentData, mode: typeof event.newValue === 'number' ? event.newValue : 0 }) + break + case 'color': + vueNodeData.set(nodeId, { + ...currentData, + color: + typeof event.newValue === 'string' + ? event.newValue + : undefined + }) + break + case 'bgcolor': + vueNodeData.set(nodeId, { + ...currentData, + bgcolor: + typeof event.newValue === 'string' + ? event.newValue + : undefined + }) } } } else if ( diff --git a/src/lib/litegraph/src/LGraphNodeProperties.ts b/src/lib/litegraph/src/LGraphNodeProperties.ts index 271d98e1e..f4a5d0c13 100644 --- a/src/lib/litegraph/src/LGraphNodeProperties.ts +++ b/src/lib/litegraph/src/LGraphNodeProperties.ts @@ -7,7 +7,9 @@ const DEFAULT_TRACKED_PROPERTIES: string[] = [ 'title', 'flags.collapsed', 'flags.pinned', - 'mode' + 'mode', + 'color', + 'bgcolor' ] /** diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index a86d8de3a..dc03dae00 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -34,7 +34,9 @@ :style="[ { transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`, - zIndex: zIndex + zIndex: zIndex, + backgroundColor: nodeBodyBackgroundColor, + opacity: nodeOpacity }, dragStyle ]" @@ -47,9 +49,14 @@ - { const bypassed = computed((): boolean => nodeData.mode === 4) const muted = computed((): boolean => nodeData.mode === 2) // NEVER mode +const nodeBodyBackgroundColor = computed(() => { + const colorPaletteStore = useColorPaletteStore() + + if (!nodeData.bgcolor) { + return '' + } + + return applyLightThemeColor( + nodeData.bgcolor, + Boolean(colorPaletteStore.completedActivePalette.light_theme) + ) +}) + +const nodeOpacity = computed( + () => useSettingStore().get('Comfy.Node.Opacity') ?? 1 +) + // Use canvas interactions for proper wheel event handling and pointer event capture control const { handleWheel, shouldHandleNodePointerEvents } = useCanvasInteractions() diff --git a/src/renderer/extensions/vueNodes/components/NodeHeader.vue b/src/renderer/extensions/vueNodes/components/NodeHeader.vue index d625a79e5..4f84ff531 100644 --- a/src/renderer/extensions/vueNodes/components/NodeHeader.vue +++ b/src/renderer/extensions/vueNodes/components/NodeHeader.vue @@ -4,7 +4,8 @@
@@ -71,8 +72,11 @@ import EditableText from '@/components/common/EditableText.vue' import type { VueNodeData } from '@/composables/graph/useGraphNodeManager' import { useErrorHandling } from '@/composables/useErrorHandling' import { st } from '@/i18n' +import { useSettingStore } from '@/platform/settings/settingStore' import { useNodeTooltips } from '@/renderer/extensions/vueNodes/composables/useNodeTooltips' +import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils' import { app } from '@/scripts/app' +import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore' import { normalizeI18nKey } from '@/utils/formatUtil' import { getLocatorIdFromNodeData, @@ -123,6 +127,23 @@ const tooltipConfig = computed(() => { return createTooltipConfig(description) }) +const headerStyle = computed(() => { + const colorPaletteStore = useColorPaletteStore() + + const opacity = useSettingStore().get('Comfy.Node.Opacity') ?? 1 + + if (!nodeData?.color) { + return { backgroundColor: '', opacity } + } + + const headerColor = applyLightThemeColor( + nodeData.color, + Boolean(colorPaletteStore.completedActivePalette.light_theme) + ) + + return { backgroundColor: headerColor, opacity } +}) + const resolveTitle = (info: VueNodeData | undefined) => { const title = (info?.title ?? '').trim() if (title.length > 0) return title diff --git a/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts new file mode 100644 index 000000000..143684d13 --- /dev/null +++ b/src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts @@ -0,0 +1,14 @@ +import { adjustColor } from '@/utils/colorUtil' + +/** + * Applies light theme color adjustments to a color + */ +export function applyLightThemeColor( + color: string, + isLightTheme: boolean +): string { + if (!color || !isLightTheme) { + return color + } + return adjustColor(color, { lightness: 0.5 }) +}