From 4dd29e3c0be6ba7bef97fa8992be9da543ec4bbd Mon Sep 17 00:00:00 2001 From: --list <18093452+simula-r@users.noreply.github.com> Date: Mon, 3 Nov 2025 11:47:34 -0800 Subject: [PATCH] test: got it working --- src/components/common/EditableText.vue | 3 +- src/composables/graph/useVueNodeLifecycle.ts | 45 +++++---- .../core/layout/sync/useLayoutSync.ts | 1 - .../vueNodes/components/LGraphNode.vue | 32 +++++-- .../composables/useVueNodeResizeTracking.ts | 41 +++++--- .../layout/ensureCorrectLayoutScale.ts | 94 ------------------- src/scripts/app.ts | 3 - 7 files changed, 79 insertions(+), 140 deletions(-) delete mode 100644 src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts diff --git a/src/components/common/EditableText.vue b/src/components/common/EditableText.vue index 9ed0a92b4..20a85fc9d 100644 --- a/src/components/common/EditableText.vue +++ b/src/components/common/EditableText.vue @@ -10,8 +10,9 @@ v-model:model-value="inputValue" v-focus type="text" - size="small" + size="large" fluid + class="text-2xl" :pt="{ root: { onBlur: finishEditing, diff --git a/src/composables/graph/useVueNodeLifecycle.ts b/src/composables/graph/useVueNodeLifecycle.ts index d2c8cf678..57b31c3ef 100644 --- a/src/composables/graph/useVueNodeLifecycle.ts +++ b/src/composables/graph/useVueNodeLifecycle.ts @@ -4,15 +4,13 @@ import { shallowRef, watch } from 'vue' import { useGraphNodeManager } from '@/composables/graph/useGraphNodeManager' import type { GraphNodeManager } from '@/composables/graph/useGraphNodeManager' import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' -import { useVueNodesMigrationDismissed } from '@/composables/useVueNodesMigrationDismissed' import type { LGraphNode } from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { useLayoutSync } from '@/renderer/core/layout/sync/useLayoutSync' -import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale' + import { app as comfyApp } from '@/scripts/app' -import { useToastStore } from '@/platform/updates/common/toastStore' function useVueNodeLifecycleIndividual() { const canvasStore = useCanvasStore() @@ -21,9 +19,7 @@ function useVueNodeLifecycleIndividual() { const nodeManager = shallowRef(null) - const { startSync } = useLayoutSync() - - const isVueNodeToastDismissed = useVueNodesMigrationDismissed() + const { startSync, stopSync } = useLayoutSync() const initializeNodeManager = () => { // Use canvas graph if available (handles subgraph contexts), fallback to app graph @@ -34,13 +30,22 @@ function useVueNodeLifecycleIndividual() { const manager = useGraphNodeManager(activeGraph) nodeManager.value = manager - // Initialize layout system with existing nodes from active graph - const nodes = activeGraph._nodes.map((node: LGraphNode) => ({ - id: node.id.toString(), - pos: [node.pos[0], node.pos[1]] as [number, number], - size: [node.size[0], node.size[1]] as [number, number] - })) - layoutStore.initializeFromLiteGraph(nodes) + // Only initialize layout store if it's empty (first time enabling Vue nodes) + // On subsequent mode switches, preserve existing layout data to prevent drift + const hasExistingLayouts = activeGraph._nodes.some( + (node: LGraphNode) => + layoutStore.getNodeLayoutRef(node.id.toString()).value !== null + ) + + if (!hasExistingLayouts) { + // First time: initialize from Litegraph + const nodes = activeGraph._nodes.map((node: LGraphNode) => ({ + id: node.id.toString(), + pos: [node.pos[0], node.pos[1]] as [number, number], + size: [node.size[0], node.size[1]] as [number, number] + })) + layoutStore.initializeFromLiteGraph(nodes) + } // Seed reroutes into the Layout Store so hit-testing uses the new path for (const reroute of activeGraph.reroutes.values()) { @@ -74,23 +79,17 @@ function useVueNodeLifecycleIndividual() { /* empty */ } nodeManager.value = null + + // Stop layout sync when Vue nodes are disabled + stopSync() } // Watch for Vue nodes enabled state changes watch( () => shouldRenderVueNodes.value && Boolean(comfyApp.canvas?.graph), - (enabled, wasEnabled) => { + (enabled) => { if (enabled) { initializeNodeManager() - ensureCorrectLayoutScale() - - if (!wasEnabled && !isVueNodeToastDismissed.value) { - useToastStore().add({ - group: 'vue-nodes-migration', - severity: 'info', - life: 0 - }) - } } else { comfyApp.canvas?.setDirty(true, true) disposeNodeManagerAndSyncs() diff --git a/src/renderer/core/layout/sync/useLayoutSync.ts b/src/renderer/core/layout/sync/useLayoutSync.ts index c3cbb9ebd..af6c09e77 100644 --- a/src/renderer/core/layout/sync/useLayoutSync.ts +++ b/src/renderer/core/layout/sync/useLayoutSync.ts @@ -47,7 +47,6 @@ export function useLayoutSync() { liteNode.size[0] !== layout.size.width || liteNode.size[1] !== layout.size.height ) { - // Use setSize() to trigger onResize callback liteNode.setSize([layout.size.width, layout.size.height]) } } diff --git a/src/renderer/extensions/vueNodes/components/LGraphNode.vue b/src/renderer/extensions/vueNodes/components/LGraphNode.vue index 0a9d13b40..449012070 100644 --- a/src/renderer/extensions/vueNodes/components/LGraphNode.vue +++ b/src/renderer/extensions/vueNodes/components/LGraphNode.vue @@ -33,10 +33,12 @@ " :style="[ { - transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px)`, + // Position in Litegraph coordinates, then scale down from Vue DOM size to visual size + transform: `translate(${position.x ?? 0}px, ${(position.y ?? 0) - LiteGraph.NODE_TITLE_HEIGHT}px) scale(${VUE_TO_LITEGRAPH_SCALE})`, zIndex: zIndex, opacity: nodeOpacity, - '--node-component-surface': nodeBodyBackgroundColor + '--node-component-surface': nodeBodyBackgroundColor, + transformOrigin: 'top left' }, dragStyle ]" @@ -285,16 +287,25 @@ const handleContextMenu = (event: MouseEvent) => { } } +// Coordinate system constants: +// - Layout store uses Litegraph coordinate system (smaller values) +// - Vue nodes need larger intrinsic size due to padding/spacing +// - We scale up DOM by 2, then scale down visually by 1/2 via CSS transform +// - This achieves correct visual size while allowing proper intrinsic sizing +const LITEGRAPH_TO_VUE_SCALE = 2 +const VUE_TO_LITEGRAPH_SCALE = 1 / 2 // 0.5 + onMounted(() => { - // Set initial DOM size from layout store, but respect intrinsic content minimum + // Set initial DOM size from layout store (convert Litegraph coords to Vue DOM coords) + // Layout store contains Litegraph coordinates, we scale up for Vue's larger intrinsic size if (size.value && nodeContainerRef.value) { nodeContainerRef.value.style.setProperty( '--node-width', - `${size.value.width}px` + `${size.value.width * LITEGRAPH_TO_VUE_SCALE}px` ) nodeContainerRef.value.style.setProperty( '--node-height', - `${size.value.height}px` + `${(size.value.height + LiteGraph.NODE_TITLE_HEIGHT) * LITEGRAPH_TO_VUE_SCALE}px` ) } }) @@ -341,9 +352,16 @@ const { startResize } = useNodeResize( (result, element) => { if (isCollapsed.value) return + // Convert from visual/canvas coordinates to Vue DOM coordinates + // result.size is the visual size from getBoundingClientRect (after CSS transform) + // This already includes NODE_TITLE_HEIGHT in the visual measurement + // We just need to scale up to DOM size by inverting the transform scale + const domWidth = result.size.width * LITEGRAPH_TO_VUE_SCALE + const domHeight = result.size.height * LITEGRAPH_TO_VUE_SCALE + // Apply size directly to DOM element - ResizeObserver will pick this up - element.style.setProperty('--node-width', `${result.size.width}px`) - element.style.setProperty('--node-height', `${result.size.height}px`) + element.style.setProperty('--node-width', `${domWidth}px`) + element.style.setProperty('--node-height', `${domHeight}px`) const currentPosition = position.value const deltaX = Math.abs(result.position.x - currentPosition.x) diff --git a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts index e3ca8ff9e..1bc78f326 100644 --- a/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts +++ b/src/renderer/extensions/vueNodes/composables/useVueNodeResizeTracking.ts @@ -86,25 +86,44 @@ 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] - : { - inlineSize: entry.contentRect.width, - blockSize: entry.contentRect.height - } - const width = contentBox.inlineSize - const height = contentBox.blockSize + // Use borderBoxSize to include borders in measurements + // This matches the visual size that will be scaled by CSS transform + // Fallback to getBoundingClientRect for older engines + let width: number + let height: number + + if (entry.borderBoxSize) { + const borderBox = Array.isArray(entry.borderBoxSize) + ? entry.borderBoxSize[0] + : entry.borderBoxSize + width = borderBox.inlineSize + height = borderBox.blockSize + } else { + // Fallback: use getBoundingClientRect which gives us borderBox size + const rect = element.getBoundingClientRect() + width = rect.width + height = rect.height + } // Screen-space rect const rect = element.getBoundingClientRect() const [cx, cy] = conv.clientPosToCanvasPos([rect.left, rect.top]) const topLeftCanvas = { x: cx, y: cy } + + // Convert Vue DOM coordinates (scaled by 2x) to Litegraph coordinates + // - Layout store uses Litegraph coordinate system as single source of truth + // - Vue nodes are rendered at 2x size in DOM, then scaled down 0.5 via CSS transform + // - We use borderBoxSize (includes border) to match the visual size after transform + // - This prevents drift: borderBox 200px → store 100px → next cycle borderBox 200px ✓ + const VUE_TO_LITEGRAPH_SCALE = 0.5 const bounds: Bounds = { x: topLeftCanvas.x, y: topLeftCanvas.y + LiteGraph.NODE_TITLE_HEIGHT, - width: Math.max(0, width), - height: Math.max(0, height - LiteGraph.NODE_TITLE_HEIGHT) + width: Math.max(0, width * VUE_TO_LITEGRAPH_SCALE), + height: Math.max( + 0, + height * VUE_TO_LITEGRAPH_SCALE - LiteGraph.NODE_TITLE_HEIGHT + ) } let updates = updatesByType.get(elementType) diff --git a/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts b/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts deleted file mode 100644 index 3d1050e43..000000000 --- a/src/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale.ts +++ /dev/null @@ -1,94 +0,0 @@ -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 type { NodeBoundsUpdate } from '@/renderer/core/layout/types' -import { app as comfyApp } from '@/scripts/app' - -const SCALE_FACTOR = 1.75 - -export function ensureCorrectLayoutScale() { - const settingStore = useSettingStore() - - const autoScaleLayoutSetting = settingStore.get( - 'Comfy.VueNodes.AutoScaleLayout' - ) - - if (autoScaleLayoutSetting === false) { - return - } - - const canvas = comfyApp.canvas - const graph = canvas?.graph - - if (!graph || !graph.nodes) return - - if (graph.extra?.vueNodesScaled === true) { - return - } - - const vueNodesEnabled = settingStore.get('Comfy.VueNodes.Enabled') - if (!vueNodesEnabled) { - return - } - - 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 yjsMoveNodeUpdates: NodeBoundsUpdate[] = [] - - for (const vueNode of allVueNodes.values()) { - const lgNode = lgNodesById.get(String(vueNode.id)) - if (!lgNode) continue - - const lgBodyY = lgNode.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - - 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 - - yjsMoveNodeUpdates.push({ - nodeId: vueNode.id, - bounds: { - x: newX, - y: newY, - width: newWidth, - height: newHeight - } - }) - } - - layoutStore.batchUpdateNodeBounds(yjsMoveNodeUpdates) - - graph.groups.forEach((group) => { - const groupBodyY = group.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - - const relativeX = group.pos[0] - originX - const relativeY = groupBodyY - originY - - const newPosY = - originY + relativeY * SCALE_FACTOR + LiteGraph.NODE_TITLE_HEIGHT - - group.pos = [originX + relativeX * SCALE_FACTOR, newPosY] - group.size = [group.size[0] * SCALE_FACTOR, group.size[1] * SCALE_FACTOR] - }) - - const originScreen = canvas.ds.convertOffsetToCanvas([originX, originY]) - canvas.ds.changeScale(canvas.ds.scale / SCALE_FACTOR, originScreen) - - if (!graph.extra) graph.extra = {} - graph.extra.vueNodesScaled = true -} diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 6fa25d051..19481b8c1 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -101,7 +101,6 @@ import { $el, ComfyUI } from './ui' import { ComfyAppMenu } from './ui/menu/index' import { clone } from './utils' import { type ComfyWidgetConstructor } from './widgets' -import { ensureCorrectLayoutScale } from '@/renderer/extensions/vueNodes/layout/ensureCorrectLayoutScale' export const ANIM_PREVIEW_WIDGET = '$$comfy_animation_preview' @@ -1201,8 +1200,6 @@ export class ComfyApp { // @ts-expect-error Discrepancies between zod and litegraph - in progress this.graph.configure(graphData) - ensureCorrectLayoutScale() - if ( restore_view && useSettingStore().get('Comfy.EnableWorkflowViewRestore')