Skip to content
Merged
49 changes: 49 additions & 0 deletions browser_tests/tests/vueNodes/nodeStates/colors.spec.ts
Original file line number Diff line number Diff line change
@@ -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'
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aside from the screenshot, can we have another test that reads the color or backgroundColor prop from the node?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What additional signal would that provide?

)
})

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'
)
})
})
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 23 additions & 1 deletion src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ export interface VueNodeData {
collapsed?: boolean
pinned?: boolean
}
color?: string
bgcolor?: string
}

export interface GraphNodeManager {
Expand Down Expand Up @@ -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
}
}

Expand Down Expand Up @@ -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 (
Expand Down
4 changes: 3 additions & 1 deletion src/lib/litegraph/src/LGraphNodeProperties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ const DEFAULT_TRACKED_PROPERTIES: string[] = [
'title',
'flags.collapsed',
'flags.pinned',
'mode'
'mode',
'color',
'bgcolor'
]

/**
Expand Down
33 changes: 30 additions & 3 deletions src/renderer/extensions/vueNodes/components/LGraphNode.vue
Original file line number Diff line number Diff line change
Expand Up @@ -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
]"
Expand All @@ -47,9 +49,14 @@
<SlotConnectionDot multi class="absolute left-0 -translate-x-1/2" />
<SlotConnectionDot multi class="absolute right-0 translate-x-1/2" />
</template>
<!-- Header only updates on title/color changes -->
<NodeHeader
v-memo="[nodeData.title, isCollapsed, nodeData.flags?.pinned]"
v-memo="[
nodeData.title,
nodeData.color,
nodeData.bgcolor,
isCollapsed,
nodeData.flags?.pinned
]"
:node-data="nodeData"
:readonly="readonly"
:collapsed="isCollapsed"
Expand Down Expand Up @@ -139,6 +146,7 @@ import type { VueNodeData } from '@/composables/graph/useGraphNodeManager'
import { toggleNodeOptions } from '@/composables/graph/useMoreOptionsMenu'
import { useErrorHandling } from '@/composables/useErrorHandling'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { useSettingStore } from '@/platform/settings/settingStore'
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
import { useCanvasInteractions } from '@/renderer/core/canvas/useCanvasInteractions'
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
Expand All @@ -148,9 +156,11 @@ import { useVueElementTracking } from '@/renderer/extensions/vueNodes/composable
import { useNodeExecutionState } from '@/renderer/extensions/vueNodes/execution/useNodeExecutionState'
import { useNodeLayout } from '@/renderer/extensions/vueNodes/layout/useNodeLayout'
import { useNodePreviewState } from '@/renderer/extensions/vueNodes/preview/useNodePreviewState'
import { applyLightThemeColor } from '@/renderer/extensions/vueNodes/utils/nodeStyleUtils'
import { app } from '@/scripts/app'
import { useExecutionStore } from '@/stores/executionStore'
import { useNodeOutputStore } from '@/stores/imagePreviewStore'
import { useColorPaletteStore } from '@/stores/workspace/colorPaletteStore'
import {
getLocatorIdFromNodeData,
getNodeByLocatorId
Expand Down Expand Up @@ -221,6 +231,23 @@ const hasAnyError = computed((): boolean => {
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()

Expand Down
23 changes: 22 additions & 1 deletion src/renderer/extensions/vueNodes/components/NodeHeader.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@
</div>
<div
v-else
class="lg-node-header p-4 rounded-t-2xl cursor-move"
class="lg-node-header p-4 rounded-t-2xl w-full cursor-move"
:style="headerStyle"
:data-testid="`node-header-${nodeData?.id || ''}`"
@dblclick="handleDoubleClick"
>
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
14 changes: 14 additions & 0 deletions src/renderer/extensions/vueNodes/utils/nodeStyleUtils.ts
Original file line number Diff line number Diff line change
@@ -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 })
}