diff --git a/src/composables/canvas/useSelectionToolboxPosition.ts b/src/composables/canvas/useSelectionToolboxPosition.ts index 4d725dd746..70be10bd3e 100644 --- a/src/composables/canvas/useSelectionToolboxPosition.ts +++ b/src/composables/canvas/useSelectionToolboxPosition.ts @@ -5,7 +5,7 @@ import type { Ref } from 'vue' import { useSelectedLiteGraphItems } from '@/composables/canvas/useSelectedLiteGraphItems' import { useVueFeatureFlags } from '@/composables/useVueFeatureFlags' import type { ReadOnlyRect } from '@/lib/litegraph/src/interfaces' -import { LGraphNode } from '@/lib/litegraph/src/litegraph' +import { LGraphGroup, LGraphNode } from '@/lib/litegraph/src/litegraph' import { useCanvasStore } from '@/renderer/core/canvas/canvasStore' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' import { isLGraphGroup, isLGraphNode } from '@/utils/litegraphUtil' @@ -89,7 +89,7 @@ export function useSelectionToolboxPosition( } } else { // Fallback to LiteGraph bounds for regular nodes or non-string IDs - if (item instanceof LGraphNode) { + if (item instanceof LGraphNode || item instanceof LGraphGroup) { const bounds = item.getBounding() allBounds.push([bounds[0], bounds[1], bounds[2], bounds[3]] as const) } diff --git a/src/lib/litegraph/src/LGraphCanvas.ts b/src/lib/litegraph/src/LGraphCanvas.ts index 2ec731ff04..38a79dfac7 100644 --- a/src/lib/litegraph/src/LGraphCanvas.ts +++ b/src/lib/litegraph/src/LGraphCanvas.ts @@ -6,7 +6,9 @@ import { LitegraphLinkAdapter } from '@/renderer/core/canvas/litegraph/litegraphLinkAdapter' import { getSlotPosition } from '@/renderer/core/canvas/litegraph/slotCalculations' +import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations' import { layoutStore } from '@/renderer/core/layout/store/layoutStore' +import { LayoutSource } from '@/renderer/core/layout/types' import { CanvasPointer } from './CanvasPointer' import type { ContextMenu } from './ContextMenu' @@ -3431,8 +3433,13 @@ export class LGraphCanvas const deltaX = delta[0] / this.ds.scale const deltaY = delta[1] / this.ds.scale - for (const item of allItems) { - item.move(deltaX, deltaY, true) + + if (LiteGraph.vueNodesMode) { + this.moveChildNodesInGroupVueMode(allItems, deltaX, deltaY) + } else { + for (const item of allItems) { + item.move(deltaX, deltaY, true) + } } this.#dirty() @@ -8452,4 +8459,120 @@ export class LGraphCanvas const setDirty = () => this.setDirty(true, true) this.ds.animateToBounds(bounds, setDirty, options) } + + /** + * Calculate new position with delta + */ + private calculateNewPosition( + node: LGraphNode, + deltaX: number, + deltaY: number + ): { x: number; y: number } { + return { + x: node.pos[0] + deltaX, + y: node.pos[1] + deltaY + } + } + + /** + * Apply batched node position updates + */ + private applyNodePositionUpdates( + nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }>, + mutations: ReturnType + ): void { + for (const { node, newPos } of nodesToMove) { + // Update LiteGraph position first so next drag uses correct base position + node.pos[0] = newPos.x + node.pos[1] = newPos.y + // Then update layout store which will update Vue nodes + mutations.moveNode(node.id, newPos) + } + } + + /** + * Initialize layout mutations with Canvas source + */ + private initLayoutMutations(): ReturnType { + const mutations = useLayoutMutations() + mutations.setSource(LayoutSource.Canvas) + return mutations + } + + /** + * Collect all nodes that are children of groups in the selection + */ + private collectNodesInGroups(items: Set): Set { + const nodesInGroups = new Set() + for (const item of items) { + if (item instanceof LGraphGroup) { + for (const child of item._children) { + if (child instanceof LGraphNode) { + nodesInGroups.add(child) + } + } + } + } + return nodesInGroups + } + + /** + * Move group children (both nodes and non-nodes) + */ + private moveGroupChildren( + group: LGraphGroup, + deltaX: number, + deltaY: number, + nodesToMove: Array<{ node: LGraphNode; newPos: { x: number; y: number } }> + ): void { + for (const child of group._children) { + if (child instanceof LGraphNode) { + const node = child as LGraphNode + nodesToMove.push({ + node, + newPos: this.calculateNewPosition(node, deltaX, deltaY) + }) + } else { + // Non-node children (nested groups, reroutes) + child.move(deltaX, deltaY) + } + } + } + + moveChildNodesInGroupVueMode( + allItems: Set, + deltaX: number, + deltaY: number + ) { + const mutations = this.initLayoutMutations() + const nodesInMovingGroups = this.collectNodesInGroups(allItems) + const nodesToMove: Array<{ + node: LGraphNode + newPos: { x: number; y: number } + }> = [] + + // First, collect all the moves we need to make + for (const item of allItems) { + const isNode = item instanceof LGraphNode + if (isNode) { + const node = item as LGraphNode + if (nodesInMovingGroups.has(node)) { + continue + } + nodesToMove.push({ + node, + newPos: this.calculateNewPosition(node, deltaX, deltaY) + }) + } else if (item instanceof LGraphGroup) { + item.move(deltaX, deltaY, true) + this.moveGroupChildren(item, deltaX, deltaY, nodesToMove) + } else { + // Other items (reroutes, etc.) + item.move(deltaX, deltaY, true) + } + } + + // Now apply all the node moves at once + this.applyNodePositionUpdates(nodesToMove, mutations) + } } diff --git a/src/lib/litegraph/src/LGraphGroup.ts b/src/lib/litegraph/src/LGraphGroup.ts index 5d922dcf5d..9e14cf277c 100644 --- a/src/lib/litegraph/src/LGraphGroup.ts +++ b/src/lib/litegraph/src/LGraphGroup.ts @@ -106,6 +106,10 @@ export class LGraphGroup implements Positionable, IPinnable, IColorable { return this._bounding } + getBounding() { + return this._bounding + } + get nodes() { return this._nodes }