Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
53fb612
cleanup: Add simpler type for node event interaction
DrJKL Nov 19, 2025
af5ca36
refactor: Extract a cloneNodes method without unused arguments
DrJKL Nov 19, 2025
82bebc7
cleanup: Remove some state from useNodePointerInteractions
DrJKL Nov 20, 2025
5b11f5b
refactor: Function declarations, direct naming
DrJKL Nov 20, 2025
eb28d78
cleanup: Unused returns from useNodeLayout
DrJKL Nov 20, 2025
e17cfae
cleanup: Grab the node select action from the composable instead of t…
DrJKL Nov 20, 2025
9d4959b
yagni: unused functions
DrJKL Nov 20, 2025
bc1c372
cleanup: reorganizing handling, return to layoutStore dragging state
DrJKL Nov 20, 2025
3d2fdb9
refactor: style and type changes
DrJKL Nov 20, 2025
7c0265c
cleanup: use a shared composable instead of wiring it through the DI …
DrJKL Nov 20, 2025
306b85e
cleanup: remove undefined checks
DrJKL Nov 20, 2025
d330050
refactor: Split up useNodeLayout drag specific functionality
DrJKL Nov 20, 2025
b179276
part 1: Remove the baked in nodeId
DrJKL Nov 20, 2025
a6032c8
cleanup: Only depend on the node id in useNodePointerInteractions
DrJKL Nov 20, 2025
ee48a34
WIP: cloning cleanup
DrJKL Nov 20, 2025
65eb83c
cleanup: remove ensureNodeSelectedForShiftDrag
DrJKL Nov 20, 2025
0ed0f87
refactor: Only depend on the node ID, not all the data
DrJKL Nov 20, 2025
ee6a051
cleanup: Remove pointerDown state
DrJKL Nov 20, 2025
42b839e
cleanup: remove wasSelectedAtPointerDown
DrJKL Nov 20, 2025
5d533db
TO FIX: useNodePointerInteractions unit tests
DrJKL Nov 20, 2025
c02bc3a
TO FIX: useNodeEventHandlers unit tests
DrJKL Nov 20, 2025
dccb88c
WIP: Shared drag state
DrJKL Nov 20, 2025
632408b
WIP: Select/Deselect fix
DrJKL Nov 21, 2025
f8f158d
WIP: so close
DrJKL Nov 21, 2025
9981b85
WIP: Changing pointer capture logic, works as long as the source node…
DrJKL Nov 21, 2025
b86640d
Finally: Make sure the cloned node is the one that gets dragged.
DrJKL Nov 21, 2025
6f17802
test: Fix useTransformState since it's now a singleton composable.
DrJKL Nov 21, 2025
fa4eeda
cleanup: Missed commented console log
DrJKL Nov 21, 2025
34021d1
hacky: Need a better solution long-term
DrJKL Nov 21, 2025
08b0e18
fix: Slot pointer events on the window shouldn't propagate to nodes
DrJKL Nov 21, 2025
665211e
test: More granular moving for drag and drop to give time for pointer…
DrJKL Nov 21, 2025
3660a8c
fix: pinned node handling on move.
DrJKL Nov 21, 2025
873d66b
nits: comments and types
DrJKL Nov 21, 2025
dd6bd37
test: Update test expectations, not in love with these tests, they're…
DrJKL Nov 21, 2025
46350e8
test: Update tests for useNodePointerInteractions. Still don't like t…
DrJKL Nov 21, 2025
00621a0
[automated] Update test expectations
invalid-email-address Nov 21, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion browser_tests/fixtures/ComfyPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -557,7 +557,7 @@ export class ComfyPage {
async dragAndDrop(source: Position, target: Position) {
await this.page.mouse.move(source.x, source.y)
await this.page.mouse.down()
await this.page.mouse.move(target.x, target.y)
await this.page.mouse.move(target.x, target.y, { steps: 100 })
await this.page.mouse.up()
await this.nextFrame()
}
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 2 additions & 1 deletion src/composables/graph/useGraphNodeManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import type {
import type { IBaseWidget } from '@/lib/litegraph/src/types/widgets'
import { useLayoutMutations } from '@/renderer/core/layout/operations/layoutMutations'
import { LayoutSource } from '@/renderer/core/layout/types'
import type { NodeId } from '@/renderer/core/layout/types'
import type { InputSpec } from '@/schemas/nodeDef/nodeDefSchemaV2'
import { isDOMWidget } from '@/scripts/domWidget'
import { useNodeDefStore } from '@/stores/nodeDefStore'
Expand Down Expand Up @@ -46,7 +47,7 @@ export interface SafeWidgetData {
}

export interface VueNodeData {
id: string
id: NodeId
title: string
type: string
mode: number
Expand Down
25 changes: 13 additions & 12 deletions src/lib/litegraph/src/LGraphCanvas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1771,18 +1771,19 @@ export class LGraphCanvas
}

static onMenuNodeClone(
// @ts-expect-error - unused parameter
value: IContextMenuValue,
// @ts-expect-error - unused parameter
options: IContextMenuOptions,
// @ts-expect-error - unused parameter
e: MouseEvent,
// @ts-expect-error - unused parameter
menu: ContextMenu,
_value: IContextMenuValue,
_options: IContextMenuOptions,
_e: MouseEvent,
_menu: ContextMenu,
node: LGraphNode
): void {
const canvas = LGraphCanvas.active_canvas
const nodes = canvas.selectedItems.size ? canvas.selectedItems : [node]
const nodes = canvas.selectedItems.size ? [...canvas.selectedItems] : [node]
if (nodes.length) LGraphCanvas.cloneNodes(nodes)
}

static cloneNodes(nodes: Positionable[]) {
const canvas = LGraphCanvas.active_canvas

// Find top-left-most boundary
let offsetX = Infinity
Expand All @@ -1792,11 +1793,11 @@ export class LGraphCanvas
throw new TypeError(
'Invalid node encountered on clone. `pos` was null.'
)
if (item.pos[0] < offsetX) offsetX = item.pos[0]
if (item.pos[1] < offsetY) offsetY = item.pos[1]
offsetX = Math.min(offsetX, item.pos[0])
offsetY = Math.min(offsetY, item.pos[1])
}

canvas._deserializeItems(canvas._serializeItems(nodes), {
return canvas._deserializeItems(canvas._serializeItems(nodes), {
position: [offsetX + 5, offsetY + 5]
})
}
Expand Down
31 changes: 0 additions & 31 deletions src/renderer/core/layout/injectionKeys.ts

This file was deleted.

19 changes: 2 additions & 17 deletions src/renderer/core/layout/transform/TransformPane.vue
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,9 @@

<script setup lang="ts">
import { useRafFn } from '@vueuse/core'
import { computed, provide } from 'vue'
import { computed } from 'vue'

import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { TransformStateKey } from '@/renderer/core/layout/injectionKeys'
import { useTransformSettling } from '@/renderer/core/layout/transform/useTransformSettling'
import { useTransformState } from '@/renderer/core/layout/transform/useTransformState'
import { useLOD } from '@/renderer/extensions/vueNodes/lod/useLOD'
Expand All @@ -32,14 +31,7 @@ interface TransformPaneProps {

const props = defineProps<TransformPaneProps>()

const {
camera,
transformStyle,
syncWithCanvas,
canvasToScreen,
screenToCanvas,
isNodeInViewport
} = useTransformState()
const { camera, transformStyle, syncWithCanvas } = useTransformState()

const { isLOD } = useLOD(camera)

Expand All @@ -48,13 +40,6 @@ const { isTransforming: isInteracting } = useTransformSettling(canvasElement, {
settleDelay: 512
})

provide(TransformStateKey, {
camera,
canvasToScreen,
screenToCanvas,
isNodeInViewport
})

const emit = defineEmits<{
transformUpdate: []
}>()
Expand Down
45 changes: 25 additions & 20 deletions src/renderer/core/layout/transform/useTransformState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
import { computed, reactive, readonly } from 'vue'

import type { LGraphCanvas } from '@/lib/litegraph/src/litegraph'
import { createSharedComposable } from '@vueuse/core'

interface Point {
x: number
Expand All @@ -64,7 +65,7 @@ interface Camera {
z: number // scale/zoom
}

export const useTransformState = () => {
function useTransformStateIndividual() {
// Reactive state mirroring LiteGraph's canvas transform
const camera = reactive<Camera>({
x: 0,
Expand All @@ -91,7 +92,7 @@ export const useTransformState = () => {
*
* @param canvas - LiteGraph canvas instance with DragAndScale (ds) transform state
*/
const syncWithCanvas = (canvas: LGraphCanvas) => {
function syncWithCanvas(canvas: LGraphCanvas) {
if (!canvas || !canvas.ds) return

// Mirror LiteGraph's transform state to Vue's reactive state
Expand All @@ -112,7 +113,7 @@ export const useTransformState = () => {
* @param point - Point in canvas coordinate system
* @returns Point in screen coordinate system
*/
const canvasToScreen = (point: Point): Point => {
function canvasToScreen(point: Point): Point {
return {
x: (point.x + camera.x) * camera.z,
y: (point.y + camera.y) * camera.z
Expand All @@ -138,10 +139,10 @@ export const useTransformState = () => {
}

// Get node's screen bounds for culling
const getNodeScreenBounds = (
pos: ArrayLike<number>,
size: ArrayLike<number>
): DOMRect => {
function getNodeScreenBounds(
pos: [number, number],
size: [number, number]
): DOMRect {
const topLeft = canvasToScreen({ x: pos[0], y: pos[1] })
const width = size[0] * camera.z
const height = size[1] * camera.z
Expand All @@ -150,23 +151,23 @@ export const useTransformState = () => {
}

// Helper: Calculate zoom-adjusted margin for viewport culling
const calculateAdjustedMargin = (baseMargin: number): number => {
function calculateAdjustedMargin(baseMargin: number): number {
if (camera.z < 0.1) return Math.min(baseMargin * 5, 2.0)
if (camera.z > 3.0) return Math.max(baseMargin * 0.5, 0.05)
return baseMargin
}

// Helper: Check if node is too small to be visible at current zoom
const isNodeTooSmall = (nodeSize: ArrayLike<number>): boolean => {
function isNodeTooSmall(nodeSize: [number, number]): boolean {
const nodeScreenSize = Math.max(nodeSize[0], nodeSize[1]) * camera.z
return nodeScreenSize < 4
}

// Helper: Calculate expanded viewport bounds with margin
const getExpandedViewportBounds = (
function getExpandedViewportBounds(
viewport: { width: number; height: number },
margin: number
) => {
) {
const marginX = viewport.width * margin
const marginY = viewport.height * margin
return {
Expand All @@ -178,11 +179,11 @@ export const useTransformState = () => {
}

// Helper: Test if node intersects with viewport bounds
const testViewportIntersection = (
function testViewportIntersection(
screenPos: { x: number; y: number },
nodeSize: ArrayLike<number>,
nodeSize: [number, number],
bounds: { left: number; right: number; top: number; bottom: number }
): boolean => {
): boolean {
const nodeRight = screenPos.x + nodeSize[0] * camera.z
const nodeBottom = screenPos.y + nodeSize[1] * camera.z

Expand All @@ -195,12 +196,12 @@ export const useTransformState = () => {
}

// Check if node is within viewport with frustum and size-based culling
const isNodeInViewport = (
nodePos: ArrayLike<number>,
nodeSize: ArrayLike<number>,
function isNodeInViewport(
nodePos: [number, number],
nodeSize: [number, number],
viewport: { width: number; height: number },
margin: number = 0.2
): boolean => {
): boolean {
// Early exit for tiny nodes
if (isNodeTooSmall(nodeSize)) return false

Expand All @@ -212,10 +213,10 @@ export const useTransformState = () => {
}

// Get viewport bounds in canvas coordinates (for spatial index queries)
const getViewportBounds = (
function getViewportBounds(
viewport: { width: number; height: number },
margin: number = 0.2
) => {
) {
const marginX = viewport.width * margin
const marginY = viewport.height * margin

Expand Down Expand Up @@ -244,3 +245,7 @@ export const useTransformState = () => {
getViewportBounds
}
}

export const useTransformState = createSharedComposable(
useTransformStateIndividual
)
6 changes: 3 additions & 3 deletions src/renderer/core/spatial/boundsCalculator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@ interface SpatialBounds {
height: number
}

interface PositionedNode {
pos: ArrayLike<number>
size: ArrayLike<number>
export interface PositionedNode {
pos: [number, number]
size: [number, number]
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { LGraph } from '@/lib/litegraph/src/litegraph'
import { calculateNodeBounds } from '@/renderer/core/spatial/boundsCalculator'
import type { PositionedNode } from '@/renderer/core/spatial/boundsCalculator'

import type {
IMinimapDataSource,
Expand Down Expand Up @@ -29,10 +30,12 @@ export abstract class AbstractMinimapDataSource implements IMinimapDataSource {
}

// Convert MinimapNodeData to the format expected by calculateNodeBounds
const compatibleNodes = nodes.map((node) => ({
pos: [node.x, node.y],
size: [node.width, node.height]
}))
const compatibleNodes = nodes.map(
(node): PositionedNode => ({
pos: [node.x, node.y],
size: [node.width, node.height]
})
)

const bounds = calculateNodeBounds(compatibleNodes)
if (!bounds) {
Expand Down
Loading
Loading