Skip to content

Commit 2970692

Browse files
authored
Move Frame Vue Nodes (#5886)
This pull request improves the selection and movement logic for groups and nodes on the LiteGraph canvas, especially when using Vue-based node rendering. The most notable changes are the addition of proper bounding box handling for groups and a new coordinated movement mechanism that updates both LiteGraph internals and the Vue layout store when dragging nodes and groups. **Selection and bounding box calculation:** * Added support for including `LGraphGroup` bounding rectangles when calculating the selection toolbox position, so groups are now properly considered in selection overlays. [[1]](diffhunk://#diff-57a51ac5e656e64ae7fd276d71b115058631621755de33b1eb8e8a4731d48713L8-R8) [[2]](diffhunk://#diff-57a51ac5e656e64ae7fd276d71b115058631621755de33b1eb8e8a4731d48713R95-R97) **Node and group movement synchronization (Vue nodes mode):** * Introduced a new movement logic in `LGraphCanvas` for Vue nodes mode: when dragging, groups and their child nodes are moved together, and all affected node positions are batch-updated in both LiteGraph and the Vue layout store via `moveNode`. This ensures canvas and UI stay in sync. * Added imports for layout mutation operations and types to support the above synchronization. These changes make group selection and movement more robust and ensure that UI and internal state remain consistent when using the Vue-based node system. https://github.com/user-attachments/assets/153792dc-08f2-4b53-b2bf-b0591ee76559 ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-5886-Move-Frame-Vue-Nodes-2806d73d365081e48b5ef96d6c6b6d6b) by [Unito](https://www.unito.io)
1 parent 4b1c165 commit 2970692

File tree

3 files changed

+131
-4
lines changed

3 files changed

+131
-4
lines changed

src/composables/canvas/useSelectionToolboxPosition.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import type { Ref } from 'vue'
55
import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems'
66
import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags'
77
import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces'
8-
import { LGraphNode } from '@/lib/litegraph/src/litegraph'
8+
import { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph'
99
import { useCanvasStore } from '@/renderer/core/canvas/canvasStore'
1010
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
1111
import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil'
@@ -89,7 +89,7 @@ export function useSelectionToolboxPosition(
8989
}
9090
} else {
9191
// Fallback to LiteGraph bounds for regular nodes or non-string IDs
92-
if (item instanceof LGraphNode) {
92+
if (item instanceof LGraphNode || item instanceof LGraphGroup) {
9393
const bounds = item.getBounding()
9494
allBounds.push([bounds[0], bounds[1], bounds[2], bounds[3]] as const)
9595
}

src/lib/litegraph/src/LGraphCanvas.ts

Lines changed: 125 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import {
66
LitegraphLinkAdapter
77
} from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter'
88
import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations'
9+
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
910
import { layoutStore } from '@/renderer/core/layout/store/layoutStore'
11+
import { LayoutSource } from '@/renderer/core/layout/types'
1012

1113
import { CanvasPointer } from './CanvasPointer'
1214
import type { ContextMenu } from './ContextMenu'
@@ -3431,8 +3433,13 @@ export class LGraphCanvas
34313433

34323434
const deltaX = delta[0] / this.ds.scale
34333435
const deltaY = delta[1] / this.ds.scale
3434-
for (const item of allItems) {
3435-
item.move(deltaX, deltaY, true)
3436+
3437+
if (LiteGraph.vueNodesMode) {
3438+
this.moveChildNodesInGroupVueMode(allItems, deltaX, deltaY)
3439+
} else {
3440+
for (const item of allItems) {
3441+
item.move(deltaX, deltaY, true)
3442+
}
34363443
}
34373444

34383445
this.#dirty()
@@ -8452,4 +8459,120 @@ export class LGraphCanvas
84528459
const setDirty = () => this.setDirty(true, true)
84538460
this.ds.animateToBounds(bounds, setDirty, options)
84548461
}
8462+
8463+
/**
8464+
* Calculate new position with delta
8465+
*/
8466+
private calculateNewPosition(
8467+
node: LGraphNode,
8468+
deltaX: number,
8469+
deltaY: number
8470+
): { x: number; y: number } {
8471+
return {
8472+
x: node.pos[0] + deltaX,
8473+
y: node.pos[1] + deltaY
8474+
}
8475+
}
8476+
8477+
/**
8478+
* Apply batched node position updates
8479+
*/
8480+
private applyNodePositionUpdates(
8481+
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>,
8482+
mutations: ReturnType<typeof useLayoutMutations>
8483+
): void {
8484+
for (const { node, newPos } of nodesToMove) {
8485+
// Update LiteGraph position first so next drag uses correct base position
8486+
node.pos[0] = newPos.x
8487+
node.pos[1] = newPos.y
8488+
// Then update layout store which will update Vue nodes
8489+
mutations.moveNode(node.id, newPos)
8490+
}
8491+
}
8492+
8493+
/**
8494+
* Initialize layout mutations with Canvas source
8495+
*/
8496+
private initLayoutMutations(): ReturnType<typeof useLayoutMutations> {
8497+
const mutations = useLayoutMutations()
8498+
mutations.setSource(LayoutSource.Canvas)
8499+
return mutations
8500+
}
8501+
8502+
/**
8503+
* Collect all nodes that are children of groups in the selection
8504+
*/
8505+
private collectNodesInGroups(items: Set<Positionable>): Set<LGraphNode> {
8506+
const nodesInGroups = new Set<LGraphNode>()
8507+
for (const item of items) {
8508+
if (item instanceof LGraphGroup) {
8509+
for (const child of item._children) {
8510+
if (child instanceof LGraphNode) {
8511+
nodesInGroups.add(child)
8512+
}
8513+
}
8514+
}
8515+
}
8516+
return nodesInGroups
8517+
}
8518+
8519+
/**
8520+
* Move group children (both nodes and non-nodes)
8521+
*/
8522+
private moveGroupChildren(
8523+
group: LGraphGroup,
8524+
deltaX: number,
8525+
deltaY: number,
8526+
nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>
8527+
): void {
8528+
for (const child of group._children) {
8529+
if (child instanceof LGraphNode) {
8530+
const node = child as LGraphNode
8531+
nodesToMove.push({
8532+
node,
8533+
newPos: this.calculateNewPosition(node, deltaX, deltaY)
8534+
})
8535+
} else {
8536+
// Non-node children (nested groups, reroutes)
8537+
child.move(deltaX, deltaY)
8538+
}
8539+
}
8540+
}
8541+
8542+
moveChildNodesInGroupVueMode(
8543+
allItems: Set<Positionable>,
8544+
deltaX: number,
8545+
deltaY: number
8546+
) {
8547+
const mutations = this.initLayoutMutations()
8548+
const nodesInMovingGroups = this.collectNodesInGroups(allItems)
8549+
const nodesToMove: Array<{
8550+
node: LGraphNode
8551+
newPos: { x: number; y: number }
8552+
}> = []
8553+
8554+
// First, collect all the moves we need to make
8555+
for (const item of allItems) {
8556+
const isNode = item instanceof LGraphNode
8557+
if (isNode) {
8558+
const node = item as LGraphNode
8559+
if (nodesInMovingGroups.has(node)) {
8560+
continue
8561+
}
8562+
nodesToMove.push({
8563+
node,
8564+
newPos: this.calculateNewPosition(node, deltaX, deltaY)
8565+
})
8566+
} else if (item instanceof LGraphGroup) {
8567+
item.move(deltaX, deltaY, true)
8568+
this.moveGroupChildren(item, deltaX, deltaY, nodesToMove)
8569+
} else {
8570+
// Other items (reroutes, etc.)
8571+
item.move(deltaX, deltaY, true)
8572+
}
8573+
}
8574+
8575+
// Now apply all the node moves at once
8576+
this.applyNodePositionUpdates(nodesToMove, mutations)
8577+
}
84558578
}

src/lib/litegraph/src/LGraphGroup.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,10 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable {
106106
return this._bounding
107107
}
108108

109+
getBounding() {
110+
return this._bounding
111+
}
112+
109113
get nodes() {
110114
return this._nodes
111115
}

0 commit comments

Comments
 (0)