diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png index 82f47f83d..7a141ece8 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-create-group-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png index 182ff4917..fb25107ee 100644 Binary files a/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png and b/browser_tests/tests/vueNodes/groups/groups.spec.ts-snapshots/vue-groups-fit-to-contents-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png index 67b03598b..9e191c96c 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-dragging-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png index 7eba53d21..38196bcd2 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-ctrl-alt-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png index d968736d6..3aa401197 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-input-drag-reuses-origin-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png index 180aa802d..fa0391d22 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-input-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png index 6cb1eb482..da9ba132b 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-reroute-output-shift-drag-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png index 9cc0ef3ea..4ba2892a4 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-shift-output-multi-link-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png index 158948695..200b5d543 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-node-chromium-linux.png differ diff --git a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png index 06d18dbcb..92e567d56 100644 Binary files a/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png and b/browser_tests/tests/vueNodes/interactions/links/linkInteraction.spec.ts-snapshots/vue-node-snap-to-slot-chromium-linux.png differ 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 index 127f25d30..1fc641878 100644 Binary files a/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png and b/browser_tests/tests/vueNodes/nodeStates/colors.spec.ts-snapshots/vue-node-custom-color-blue-chromium-linux.png differ diff --git a/src/composables/graph/useVueNodeLifecycle.ts b/src/composables/graph/useVueNodeLifecycle.ts index d2c8cf678..f58317566 100644 --- a/src/composables/graph/useVueNodeLifecycle.ts +++ b/src/composables/graph/useVueNodeLifecycle.ts @@ -1,4 +1,4 @@ -import { createSharedComposable } from '@vueuse/core' +import { createSharedComposable, whenever } from '@vueuse/core' import { shallowRef, watch } from 'vue' import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' @@ -82,8 +82,9 @@ function useVueNodeLifecycleIndividual() { (enabled, wasEnabled) => { if (enabled) { initializeNodeManager() - ensureCorrectLayoutScale() - + ensureCorrectLayoutScale( + comfyApp.canvas?.graph?.extra.workflowRendererVersion + ) if (!wasEnabled && !isVueNodeToastDismissed.value) { useToastStore().add({ group: 'vue-nodes-migration', @@ -91,14 +92,22 @@ function useVueNodeLifecycleIndividual() { life: 0 }) } - } else { - comfyApp.canvas?.setDirty(true, true) - disposeNodeManagerAndSyncs() } }, { immediate: true } ) + whenever( + () => !shouldRenderVueNodes.value, + () => { + ensureCorrectLayoutScale( + comfyApp.canvas?.graph?.extra.workflowRendererVersion + ) + disposeNodeManagerAndSyncs() + comfyApp.canvas?.setDirty(true, true) + } + ) + // Consolidated watch for slot layout sync management watch( () => shouldRenderVueNodes.value, diff --git a/src/lib/litegraph/src/LGraph.ts b/src/lib/litegraph/src/LGraph.ts index 4678576a5..6f46527cc 100644 --- a/src/lib/litegraph/src/LGraph.ts +++ b/src/lib/litegraph/src/LGraph.ts @@ -79,6 +79,8 @@ export type { LGraphTriggerParam } from './types/graphTriggers' +export type rendererType = 'LG' | 'Vue' + export interface LGraphState { lastGroupId: number lastNodeId: number @@ -104,6 +106,7 @@ export interface LGraphExtra extends Dictionary { reroutes?: SerialisableReroute[] linkExtensions?: { id: number; parentId: number | undefined }[] ds?: DragAndScaleState + workflowRendererVersion?: rendererType } export interface BaseLGraph { diff --git a/src/platform/settings/constants/coreSettings.ts b/src/platform/settings/constants/coreSettings.ts index 5d47e1e80..c76381d76 100644 --- a/src/platform/settings/constants/coreSettings.ts +++ b/src/platform/settings/constants/coreSettings.ts @@ -1101,7 +1101,7 @@ export const CORE_SETTINGS: SettingParams[] = [ 'Automatically scale node positions when switching to Vue rendering to prevent overlap', type: 'boolean', experimental: true, - defaultValue: false, + defaultValue: true, versionAdded: '1.30.3' }, { diff --git a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts index e3ca8ff9e..9d28b5333 100644 --- a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts +++ b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts @@ -86,15 +86,16 @@ const resizeObserver = new ResizeObserver((entries) => { if (!elementType || !elementId) continue - // Use contentBoxSize when available; fall back to contentRect for older engines/tests - const contentBox = Array.isArray(entry.contentBoxSize) - ? entry.contentBoxSize[0] + // Use borderBoxSize when available; fall back to contentRect for older engines/tests + // Border box is the border included FULL wxh DOM value. + const borderBox = Array.isArray(entry.borderBoxSize) + ? entry.borderBoxSize[0] : { inlineSize: entry.contentRect.width, blockSize: entry.contentRect.height } - const width = contentBox.inlineSize - const height = contentBox.blockSize + const width = borderBox.inlineSize + const height = borderBox.blockSize // Screen-space rect const rect = element.getBoundingClientRect() diff --git a/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts b/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts index 3d1050e43..cb3d95ef9 100644 --- a/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts +++ b/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts @@ -1,13 +1,21 @@ +import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' +import type { LGraph, rendererType } from '@/lib/litegraph/src/LGraph' import { LiteGraph } from '@/lib/litegraph/src/litegraph' import { createBounds } from '@/lib/litegraph/src/measure' import { useSettingStore } from '@/platform/settings/settingStore' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import type { NodeBoundsUpdate } from '@/renderer/core/layout/types' import { app as comfyApp } from '@/scripts/app' +import type { SubgraphInputNode } from '@/lib/litegraph/src/subgraph/SubgraphInputNode' +import type { SubgraphOutputNode } from '@/lib/litegraph/src/subgraph/SubgraphOutputNode' const SCALE_FACTOR = 1.75 -export function ensureCorrectLayoutScale() { +export function ensureCorrectLayoutScale( + renderer?: rendererType, + targetGraph?: LGraph +) { const settingStore = useSettingStore() const autoScaleLayoutSetting = settingStore.get( @@ -18,77 +26,178 @@ export function ensureCorrectLayoutScale() { return } + const { shouldRenderVueNodes } = useVueFeatureFlags() + const canvas = comfyApp.canvas - const graph = canvas?.graph + const graph = targetGraph ?? canvas?.graph if (!graph || !graph.nodes) return - if (graph.extra?.vueNodesScaled === true) { + // Use renderer from graph, default to 'LG' for the check (but don't modify graph yet) + if (!renderer) { + // Always assume legacy LG format when unknown (pre-dates this feature) + renderer = 'LG' + } + + const doesntNeedScale = + (renderer === 'LG' && shouldRenderVueNodes.value === false) || + (renderer === 'Vue' && shouldRenderVueNodes.value === true) + + if (doesntNeedScale) { + // Don't scale, but ensure workflowRendererVersion is set for future checks + if (!graph.extra.workflowRendererVersion) { + graph.extra.workflowRendererVersion = renderer + } return } - const vueNodesEnabled = settingStore.get('Comfy.VueNodes.Enabled') - if (!vueNodesEnabled) { - return - } + const needsUpscale = renderer === 'LG' && shouldRenderVueNodes.value === true + const needsDownscale = + renderer === 'Vue' && shouldRenderVueNodes.value === false const lgBounds = createBounds(graph.nodes) if (!lgBounds) return - const allVueNodes = layoutStore.getAllNodes().value - const originX = lgBounds[0] const originY = lgBounds[1] - const lgNodesById = new Map( - graph.nodes.map((node) => [String(node.id), node]) - ) + const lgNodesById = new Map(graph.nodes.map((node) => [node.id, node])) const yjsMoveNodeUpdates: NodeBoundsUpdate[] = [] - for (const vueNode of allVueNodes.values()) { - const lgNode = lgNodesById.get(String(vueNode.id)) + const scaleFactor = needsUpscale + ? SCALE_FACTOR + : needsDownscale + ? 1 / SCALE_FACTOR + : 1 + + for (const node of graph.nodes) { + const lgNode = lgNodesById.get(node.id) if (!lgNode) continue - const lgBodyY = lgNode.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + const lgBodyY = lgNode.pos[1] const relativeX = lgNode.pos[0] - originX const relativeY = lgBodyY - originY - const newX = originX + relativeX * SCALE_FACTOR - const newY = originY + relativeY * SCALE_FACTOR - const newWidth = lgNode.width * SCALE_FACTOR - const newHeight = lgNode.height * SCALE_FACTOR + const newX = originX + relativeX * scaleFactor + const newY = originY + relativeY * scaleFactor + const newWidth = lgNode.width * scaleFactor + const newHeight = lgNode.height * scaleFactor - yjsMoveNodeUpdates.push({ - nodeId: vueNode.id, - bounds: { - x: newX, - y: newY, - width: newWidth, - height: newHeight - } - }) + // Directly update LiteGraph node to ensure immediate consistency + // Dont need to reference vue directly because the pos and dims are already in yjs + lgNode.pos[0] = newX + lgNode.pos[1] = newY + lgNode.size[0] = newWidth + lgNode.size[1] = + newHeight - (needsDownscale ? LiteGraph.NODE_TITLE_HEIGHT : 0) + + // Track updates for layout store (only if this is the active graph) + if (!targetGraph || targetGraph === canvas?.graph) { + yjsMoveNodeUpdates.push({ + nodeId: String(lgNode.id), + bounds: { + x: newX, + y: newY, + width: newWidth, + height: newHeight - (needsDownscale ? LiteGraph.NODE_TITLE_HEIGHT : 0) + } + }) + } } - layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates) + if ( + (!targetGraph || targetGraph === canvas?.graph) && + yjsMoveNodeUpdates.length > 0 + ) { + layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates) + } + + for (const reroute of graph.reroutes.values()) { + const oldX = reroute.pos[0] + const oldY = reroute.pos[1] + + const relativeX = oldX - originX + const relativeY = oldY - originY + const newX = originX + relativeX * scaleFactor + const newY = originY + relativeY * scaleFactor + + reroute.pos = [newX, newY] + + if ( + (!targetGraph || targetGraph === canvas?.graph) && + shouldRenderVueNodes.value + ) { + const layoutMutations = useLayoutMutations() + layoutMutations.moveReroute( + reroute.id, + { x: newX, y: newY }, + { x: oldX, y: oldY } + ) + } + } + + if ('inputNode' in graph && 'outputNode' in graph) { + const ioNodes = [ + graph.inputNode as SubgraphInputNode, + graph.outputNode as SubgraphOutputNode + ] + for (const ioNode of ioNodes) { + const oldX = ioNode.pos[0] + const oldY = ioNode.pos[1] + const oldWidth = ioNode.size[0] + const oldHeight = ioNode.size[1] + + const relativeX = oldX - originX + const relativeY = oldY - originY + const newX = originX + relativeX * scaleFactor + const newY = originY + relativeY * scaleFactor + const newWidth = oldWidth * scaleFactor + const newHeight = oldHeight * scaleFactor + + ioNode.pos = [newX, newY] + ioNode.size = [newWidth, newHeight] + } + } graph.groups.forEach((group) => { - const groupBodyY = group.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + const originalPosX = group.pos[0] + const originalPosY = group.pos[1] + const originalWidth = group.size[0] + const originalHeight = group.size[1] - const relativeX = group.pos[0] - originX - const relativeY = groupBodyY - originY + const adjustedY = needsDownscale + ? originalPosY - LiteGraph.NODE_TITLE_HEIGHT + : originalPosY - const newPosY = - originY + relativeY * SCALE_FACTOR + LiteGraph.NODE_TITLE_HEIGHT + const relativeX = originalPosX - originX + const relativeY = adjustedY - originY - group.pos = [originX + relativeX * SCALE_FACTOR, newPosY] - group.size = [group.size[0] * SCALE_FACTOR, group.size[1] * SCALE_FACTOR] + const newWidth = originalWidth * scaleFactor + const newHeight = originalHeight * scaleFactor + + const scaledX = originX + relativeX * scaleFactor + const scaledY = originY + relativeY * scaleFactor + + const finalY = needsUpscale + ? scaledY + LiteGraph.NODE_TITLE_HEIGHT + : scaledY + + group.pos = [scaledX, finalY] + group.size = [newWidth, newHeight] }) - const originScreen = canvas.ds.convertOffsetToCanvas([originX, originY]) - canvas.ds.changeScale(canvas.ds.scale / SCALE_FACTOR, originScreen) + if ((!targetGraph || targetGraph === canvas?.graph) && canvas) { + const originScreen = canvas.ds.convertOffsetToCanvas([originX, originY]) + canvas.ds.changeScale(canvas.ds.scale / scaleFactor, originScreen) + } - if (!graph.extra) graph.extra = {} - graph.extra.vueNodesScaled = true + if (needsUpscale) { + graph.extra.workflowRendererVersion = 'Vue' + } + + if (needsDownscale) { + graph.extra.workflowRendererVersion = 'LG' + } } diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 0390bd157..7139373ec 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -825,6 +825,23 @@ export class ComfyApp { } } ) + + // Ensure subgraphs are scaled when entering them + this.canvas.canvas.addEventListener<'litegraph:set-graph'>( + 'litegraph:set-graph', + (e) => { + const { newGraph, oldGraph } = e.detail + // Only scale when switching between graphs (not during initial setup) + // oldGraph is null/undefined during initial setup, so skip scaling then + if (oldGraph) { + ensureCorrectLayoutScale( + newGraph.extra.workflowRendererVersion, + newGraph + ) + } + } + ) + registerProxyWidgets(this.canvas) this.graph.start() @@ -1177,7 +1194,20 @@ export class ComfyApp { // @ts-expect-error Discrepancies between zod and litegraph - in progress this.graph.configure(graphData) - ensureCorrectLayoutScale() + // Save original renderer version before scaling (it gets modified during scaling) + const originalMainGraphRenderer = this.graph.extra.workflowRendererVersion + + // Scale main graph + ensureCorrectLayoutScale(originalMainGraphRenderer) + + // Scale all subgraphs that were loaded with the workflow + // Use original main graph renderer as fallback (not the modified one) + for (const subgraph of this.graph.subgraphs.values()) { + ensureCorrectLayoutScale( + subgraph.extra.workflowRendererVersion || originalMainGraphRenderer, + subgraph + ) + } if ( restore_view &&