[refactor] Use getSlotPosition for Vue nodes in link rendering (#5400)

* Remove COMFY_VUE_NODE_DIMENSIONS

* [refactor] Use getSlotPosition for Vue nodes in link rendering

Replace direct node position calls with getSlotPosition utility when Vue nodes mode is enabled. This ensures consistent slot positioning across the canvas rendering system.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

* Fix getSlotPosition readonly return value (#5433)

* Update accordingly to new type

* Fix canvas/screen conversion formulas in useTransformState (#5406)

* Fix conversion formulas

* update test expectations

* Remove unused type import

---------

Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: filtered <176114999+webfiltered@users.noreply.github.com>
This commit is contained in:
Benjamin Lu
2025-09-08 14:27:55 -07:00
committed by GitHub
parent 76bfc9e678
commit aa7f8912a7
4 changed files with 41 additions and 33 deletions

View File

@@ -6,6 +6,7 @@ import {
type LinkRenderContext,
LitegraphLinkAdapter
} from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations'
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
import { CanvasPointer } from './CanvasPointer'
@@ -5559,7 +5560,9 @@ export class LGraphCanvas
const link = graph._links.get(link_id)
if (!link) continue
const endPos = node.getInputPos(i)
const endPos: Point = LiteGraph.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
? getSlotPosition(node, i, true)
: node.getInputPos(i)
// find link info
const start_node = graph.getNodeById(link.origin_id)
@@ -5569,7 +5572,9 @@ export class LGraphCanvas
const startPos: Point =
outputId === -1
? [start_node.pos[0] + 10, start_node.pos[1] + 10]
: start_node.getOutputPos(outputId)
: LiteGraph.vueNodesMode // TODO: still use LG get pos if vue nodes is off until stable
? getSlotPosition(start_node, outputId, false)
: start_node.getOutputPos(outputId)
const output = start_node.outputs[outputId]
if (!output) continue

View File

@@ -9,8 +9,7 @@ import type { LGraphNode } from '@/lib/litegraph/src/LGraphNode'
import type {
INodeInputSlot,
INodeOutputSlot,
Point,
ReadOnlyPoint
Point
} from '@/lib/litegraph/src/interfaces'
import { LiteGraph } from '@/lib/litegraph/src/litegraph'
import { isWidgetInputSlot } from '@/lib/litegraph/src/node/slotUtils'
@@ -138,7 +137,7 @@ export function getSlotPosition(
node: LGraphNode,
slotIndex: number,
isInput: boolean
): ReadOnlyPoint {
): Point {
// Try to get precise position from slot layout (DOM-registered)
const slotKey = getSlotKey(String(node.id), slotIndex, isInput)
const slotLayout = layoutStore.getSlotLayout(slotKey)

View File

@@ -74,6 +74,10 @@ export const useTransformState = () => {
// Computed transform string for CSS
const transformStyle = computed(() => ({
// Match LiteGraph DragAndScale.toCanvasContext():
// ctx.scale(scale); ctx.translate(offset)
// CSS applies right-to-left, so "scale() translate()" -> translate first, then scale
// Effective mapping: screen = (canvas + offset) * scale
transform: `scale(${camera.z}) translate(${camera.x}px, ${camera.y}px)`,
transformOrigin: '0 0'
}))
@@ -103,15 +107,15 @@ export const useTransformState = () => {
* Applies the same transform that LiteGraph uses for rendering.
* Essential for positioning Vue components to align with canvas elements.
*
* Formula: screen = canvas * scale + offset
* Formula: screen = (canvas + offset) * scale
*
* @param point - Point in canvas coordinate system
* @returns Point in screen coordinate system
*/
const canvasToScreen = (point: Point): Point => {
return {
x: point.x * camera.z + camera.x,
y: point.y * camera.z + camera.y
x: (point.x + camera.x) * camera.z,
y: (point.y + camera.y) * camera.z
}
}
@@ -121,15 +125,15 @@ export const useTransformState = () => {
* Inverse of canvasToScreen. Useful for hit testing and converting
* mouse events back to canvas space.
*
* Formula: canvas = (screen - offset) / scale
* Formula: canvas = screen / scale - offset
*
* @param point - Point in screen coordinate system
* @returns Point in canvas coordinate system
*/
const screenToCanvas = (point: Point): Point => {
return {
x: (point.x - camera.x) / camera.z,
y: (point.y - camera.y) / camera.z
x: point.x / camera.z - camera.x,
y: point.y / camera.z - camera.y
}
}

View File

@@ -121,24 +121,24 @@ describe('useTransformState', () => {
const canvasPoint = { x: 10, y: 20 }
const screenPoint = canvasToScreen(canvasPoint)
// screen = canvas * scale + offset
// x: 10 * 2 + 100 = 120
// y: 20 * 2 + 50 = 90
expect(screenPoint).toEqual({ x: 120, y: 90 })
// screen = (canvas + offset) * scale
// x: (10 + 100) * 2 = 220
// y: (20 + 50) * 2 = 140
expect(screenPoint).toEqual({ x: 220, y: 140 })
})
it('should handle zero coordinates', () => {
const { canvasToScreen } = transformState
const screenPoint = canvasToScreen({ x: 0, y: 0 })
expect(screenPoint).toEqual({ x: 100, y: 50 })
expect(screenPoint).toEqual({ x: 200, y: 100 })
})
it('should handle negative coordinates', () => {
const { canvasToScreen } = transformState
const screenPoint = canvasToScreen({ x: -10, y: -20 })
expect(screenPoint).toEqual({ x: 80, y: 10 })
expect(screenPoint).toEqual({ x: 180, y: 60 })
})
})
@@ -146,12 +146,12 @@ describe('useTransformState', () => {
it('should convert screen coordinates to canvas coordinates', () => {
const { screenToCanvas } = transformState
const screenPoint = { x: 120, y: 90 }
const screenPoint = { x: 220, y: 140 }
const canvasPoint = screenToCanvas(screenPoint)
// canvas = (screen - offset) / scale
// x: (120 - 100) / 2 = 10
// y: (90 - 50) / 2 = 20
// canvas = screen / scale - offset
// x: 220 / 2 - 100 = 10
// y: 140 / 2 - 50 = 20
expect(canvasPoint).toEqual({ x: 10, y: 20 })
})
@@ -183,11 +183,11 @@ describe('useTransformState', () => {
const nodeSize = [200, 100]
const bounds = getNodeScreenBounds(nodePos, nodeSize)
// Top-left: canvasToScreen(10, 20) = (120, 90)
// Top-left: canvasToScreen(10, 20) = (220, 140)
// Width: 200 * 2 = 400
// Height: 100 * 2 = 200
expect(bounds.x).toBe(120)
expect(bounds.y).toBe(90)
expect(bounds.x).toBe(220)
expect(bounds.y).toBe(140)
expect(bounds.width).toBe(400)
expect(bounds.height).toBe(200)
})
@@ -288,14 +288,14 @@ describe('useTransformState', () => {
// topLeft in screen: (-200, -120)
// bottomRight in screen: (1200, 720)
// Convert to canvas coordinates:
// topLeft: ((-200 - 100) / 2, (-120 - 50) / 2) = (-150, -85)
// bottomRight: ((1200 - 100) / 2, (720 - 50) / 2) = (550, 335)
// Convert to canvas coordinates (canvas = screen / scale - offset):
// topLeft: (-200 / 2 - 100, -120 / 2 - 50) = (-200, -110)
// bottomRight: (1200 / 2 - 100, 720 / 2 - 50) = (500, 310)
expect(bounds.x).toBe(-150)
expect(bounds.y).toBe(-85)
expect(bounds.width).toBe(700) // 550 - (-150)
expect(bounds.height).toBe(420) // 335 - (-85)
expect(bounds.x).toBe(-200)
expect(bounds.y).toBe(-110)
expect(bounds.width).toBe(700) // 500 - (-200)
expect(bounds.height).toBe(420) // 310 - (-110)
})
it('should handle zero margin', () => {
@@ -305,8 +305,8 @@ describe('useTransformState', () => {
const bounds = getViewportBounds(viewport, 0)
// No margin, so viewport bounds are exact
expect(bounds.x).toBe(-50) // (0 - 100) / 2
expect(bounds.y).toBe(-25) // (0 - 50) / 2
expect(bounds.x).toBe(-100) // 0 / 2 - 100
expect(bounds.y).toBe(-50) // 0 / 2 - 50
expect(bounds.width).toBe(500) // 1000 / 2
expect(bounds.height).toBe(300) // 600 / 2
})