Skip to content

Commit d9a283b

Browse files
committed
Add canvas drop to useSlotLinkInteraction.ts
1 parent fafd374 commit d9a283b

File tree

1 file changed

+99
-113
lines changed

1 file changed

+99
-113
lines changed

src/renderer/extensions/vueNodes/composables/useSlotLinkInteraction.ts

Lines changed: 99 additions & 113 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,16 @@ import type {
1212
INodeOutputSlot
1313
} from '@/lib/litegraph/src/interfaces'
1414
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'
15+
import {
16+
clearCanvasPointerHistory,
17+
toCanvasPointerEvent
18+
} from '@/renderer/core/canvas/interaction/canvasPointerEvent'
1519
import { createLinkConnectorAdapter } from '@/renderer/core/canvas/links/linkConnectorAdapter'
1620
import type { LinkConnectorAdapter } from '@/renderer/core/canvas/links/linkConnectorAdapter'
21+
import {
22+
resolveNodeSurfaceCandidate,
23+
resolveSlotTargetCandidate
24+
} from '@/renderer/core/canvas/links/linkDropOrchestrator'
1725
import {
1826
type SlotDropCandidate,
1927
useSlotLinkDragState
@@ -98,102 +106,38 @@ export function useSlotLinkInteraction({
98106
// Per-drag drag-state cache
99107
const dragSession = createSlotLinkDragSession()
100108

101-
function candidateFromTarget(
102-
target: EventTarget | null
103-
): SlotDropCandidate | null {
104-
if (!(target instanceof HTMLElement)) return null
105-
const elWithKey = target.closest<HTMLElement>('[data-slot-key]')
106-
const key = elWithKey?.dataset['slotKey']
107-
if (!key) return null
108-
109-
const layout = layoutStore.getSlotLayout(key)
110-
if (!layout) return null
111-
112-
const candidate: SlotDropCandidate = { layout, compatible: false }
113-
114-
const graph = app.canvas?.graph
115-
const adapter = ensureActiveAdapter()
116-
if (graph && adapter) {
117-
const cached = dragSession.compatCache.get(key)
118-
if (cached != null) {
119-
candidate.compatible = cached
120-
} else {
121-
const compatible =
122-
layout.type === 'input'
123-
? adapter.isInputValidDrop(layout.nodeId, layout.index)
124-
: adapter.isOutputValidDrop(layout.nodeId, layout.index)
125-
dragSession.compatCache.set(key, compatible)
126-
candidate.compatible = compatible
127-
}
109+
const resolveRenderLinkSource = (link: RenderLink): Point | null => {
110+
if (link.fromReroute) {
111+
const rerouteLayout = layoutStore.getRerouteLayout(link.fromReroute.id)
112+
if (rerouteLayout) return rerouteLayout.position
113+
const [x, y] = link.fromReroute.pos
114+
return toPoint(x, y)
128115
}
129116

130-
return candidate
131-
}
132-
133-
function candidateFromNodeTarget(
134-
target: EventTarget | null
135-
): SlotDropCandidate | null {
136-
if (!(target instanceof HTMLElement)) return null
137-
const elWithNode = target.closest<HTMLElement>('[data-node-id]')
138-
const nodeIdStr = elWithNode?.dataset['nodeId']
139-
if (!nodeIdStr) return null
140-
141-
const adapter = ensureActiveAdapter()
142-
const graph = app.canvas?.graph
143-
if (!adapter || !graph) return null
144-
145-
const nodeId = Number(nodeIdStr)
146-
147-
// Cached preferred slot for this node within this drag
148-
const cachedPreferred = dragSession.nodePreferred.get(nodeId)
149-
if (cachedPreferred !== undefined) {
150-
return cachedPreferred
151-
? { layout: cachedPreferred.layout, compatible: true }
152-
: null
117+
const nodeId = link.node.id
118+
if (nodeId != null) {
119+
const isInputFrom = link.toType === 'output'
120+
const key = getSlotKey(String(nodeId), link.fromSlotIndex, isInputFrom)
121+
const layout = layoutStore.getSlotLayout(key)
122+
if (layout) return layout.position
153123
}
154124

155-
const node = graph.getNodeById(nodeId)
156-
if (!node) return null
157-
158-
const firstLink = adapter.renderLinks[0]
159-
if (!firstLink) return null
160-
const connectingTo = adapter.linkConnector.state.connectingTo
161-
162-
if (connectingTo !== 'input' && connectingTo !== 'output') return null
163-
164-
const isInput = connectingTo === 'input'
165-
const slotType = firstLink.fromSlot.type
166-
167-
const res = isInput
168-
? node.findInputByType(slotType)
169-
: node.findOutputByType(slotType)
170-
171-
const index = res?.index
172-
if (index == null) return null
173-
174-
const key = getSlotKey(String(nodeId), index, isInput)
175-
const layout = layoutStore.getSlotLayout(key)
176-
if (!layout) return null
177-
178-
const compatible = isInput
179-
? adapter.isInputValidDrop(nodeId, index)
180-
: adapter.isOutputValidDrop(nodeId, index)
181-
182-
if (compatible) {
183-
dragSession.compatCache.set(key, true)
184-
const preferred = { index, key, layout }
185-
dragSession.nodePreferred.set(nodeId, preferred)
186-
return { layout, compatible: true }
187-
} else {
188-
dragSession.compatCache.set(key, false)
189-
dragSession.nodePreferred.set(nodeId, null)
190-
return null
191-
}
125+
const pos = link.fromPos
126+
return toPoint(pos[0], pos[1])
192127
}
193128

194-
const ensureActiveAdapter = (): LinkConnectorAdapter | null => {
195-
if (!activeAdapter) activeAdapter = createLinkConnectorAdapter()
196-
return activeAdapter
129+
const syncRenderLinkOrigins = () => {
130+
if (!activeAdapter) return
131+
for (const link of activeAdapter.renderLinks) {
132+
const origin = resolveRenderLinkSource(link)
133+
if (!origin) continue
134+
const x = origin.x
135+
const y = origin.y
136+
if (link.fromPos[0] !== x || link.fromPos[1] !== y) {
137+
link.fromPos[0] = x
138+
link.fromPos[1] = y
139+
}
140+
}
197141
}
198142

199143
function hasCanConnectToReroute(
@@ -317,6 +261,9 @@ export function useSlotLinkInteraction({
317261
}
318262

319263
const cleanupInteraction = () => {
264+
if (state.pointerId != null) {
265+
clearCanvasPointerHistory(state.pointerId)
266+
}
320267
activeAdapter?.reset()
321268
pointerSession.clear()
322269
endDrag()
@@ -347,6 +294,8 @@ export function useSlotLinkInteraction({
347294
])
348295
updatePointerPosition(data.clientX, data.clientY, canvasX, canvasY)
349296

297+
syncRenderLinkOrigins()
298+
350299
let hoveredSlotKey: string | null = null
351300
let hoveredNodeId: number | null = null
352301
const target = data.target
@@ -368,10 +317,13 @@ export function useSlotLinkInteraction({
368317
let candidate: SlotDropCandidate | null = state.candidate
369318

370319
if (hoverChanged) {
371-
const slotCandidate = candidateFromTarget(target)
320+
const adapter = activeAdapter
321+
const graph = app.canvas?.graph ?? null
322+
const context = { adapter, graph, session: dragSession }
323+
const slotCandidate = resolveSlotTargetCandidate(target, context)
372324
const nodeCandidate = slotCandidate
373325
? null
374-
: candidateFromNodeTarget(target)
326+
: resolveNodeSurfaceCandidate(target, context)
375327
candidate = slotCandidate ?? nodeCandidate
376328
dragSession.lastHoverSlotKey = hoveredSlotKey
377329
dragSession.lastHoverNodeId = hoveredNodeId
@@ -391,7 +343,17 @@ export function useSlotLinkInteraction({
391343
dragSession.lastCandidateKey = newCandidateKey
392344
}
393345

394-
app.canvas?.setDirty(true)
346+
if (activeAdapter) {
347+
const snapX = newCandidate
348+
? newCandidate.layout.position.x
349+
: state.pointer.canvas.x
350+
const snapY = newCandidate
351+
? newCandidate.layout.position.y
352+
: state.pointer.canvas.y
353+
activeAdapter.linkConnector.state.snapLinksPos = [snapX, snapY]
354+
}
355+
356+
app.canvas?.setDirty(true, true)
395357
}
396358
const raf = createRafBatch(processPointerMoveFrame)
397359

@@ -411,7 +373,7 @@ export function useSlotLinkInteraction({
411373
): boolean => {
412374
if (!candidate?.compatible) return false
413375
const graph = app.canvas?.graph
414-
const adapter = ensureActiveAdapter()
376+
const adapter = activeAdapter
415377
if (!graph || !adapter) return false
416378

417379
const nodeId = Number(candidate.layout.nodeId)
@@ -444,7 +406,7 @@ export function useSlotLinkInteraction({
444406
y: state.pointer.canvas.y
445407
})
446408
const graph = app.canvas?.graph
447-
const adapter = ensureActiveAdapter()
409+
const adapter = activeAdapter
448410
if (!rerouteLayout || !graph || !adapter) return false
449411

450412
const reroute = graph.getReroute(rerouteLayout.id)
@@ -492,13 +454,14 @@ export function useSlotLinkInteraction({
492454

493455
const finishInteraction = (event: PointerEvent) => {
494456
if (!pointerSession.matches(event)) return
495-
event.preventDefault()
457+
const canvasEvent = toCanvasPointerEvent(event)
458+
canvasEvent.preventDefault()
496459

497460
raf.flush()
498461

499462
if (!state.source) {
500463
cleanupInteraction()
501-
app.canvas?.setDirty(true)
464+
app.canvas?.setDirty(true, true)
502465
return
503466
}
504467

@@ -511,24 +474,32 @@ export function useSlotLinkInteraction({
511474

512475
// Fallback to DOM slot under pointer (if any), then node fallback, then reroute
513476
if (!connected) {
514-
const domCandidate = candidateFromTarget(event.target)
477+
const adapter = activeAdapter
478+
const graph = app.canvas?.graph ?? null
479+
const context = { adapter, graph, session: dragSession }
480+
const domCandidate = resolveSlotTargetCandidate(
481+
canvasEvent.target,
482+
context
483+
)
515484
connected = tryConnectToCandidate(domCandidate)
516-
}
517485

518-
if (!connected) {
519-
const nodeCandidate = candidateFromNodeTarget(event.target)
520-
connected = tryConnectToCandidate(nodeCandidate)
486+
if (!connected) {
487+
const nodeCandidate = resolveNodeSurfaceCandidate(
488+
canvasEvent.target,
489+
context
490+
)
491+
connected = tryConnectToCandidate(nodeCandidate)
492+
}
521493
}
522494

523495
if (!connected) connected = tryConnectViaRerouteAtPointer() || connected
524496

525-
// Drop on canvas: disconnect moving input link(s)
526-
if (!connected && !snappedCandidate && state.source.type === 'input') {
527-
ensureActiveAdapter()?.disconnectMovingLinks()
497+
if (!connected) {
498+
if (activeAdapter) activeAdapter.dropOnCanvas(canvasEvent)
528499
}
529500

530501
cleanupInteraction()
531-
app.canvas?.setDirty(true)
502+
app.canvas?.setDirty(true, true)
532503
}
533504

534505
const handlePointerUp = (event: PointerEvent) => {
@@ -539,8 +510,9 @@ export function useSlotLinkInteraction({
539510
if (!pointerSession.matches(event)) return
540511

541512
raf.flush()
513+
toCanvasPointerEvent(event)
542514
cleanupInteraction()
543-
app.canvas?.setDirty(true)
515+
app.canvas?.setDirty(true, true)
544516
}
545517

546518
const onPointerDown = (event: PointerEvent) => {
@@ -552,7 +524,8 @@ export function useSlotLinkInteraction({
552524
const graph = canvas?.graph
553525
if (!canvas || !graph) return
554526

555-
ensureActiveAdapter()
527+
activeAdapter = createLinkConnectorAdapter()
528+
if (!activeAdapter) return
556529
raf.cancel()
557530
dragSession.reset()
558531

@@ -610,19 +583,24 @@ export function useSlotLinkInteraction({
610583
const shouldMoveExistingInput =
611584
isInputSlot && !shouldBreakExistingInputLink && hasExistingInputLink
612585

613-
const adapter = ensureActiveAdapter()
614-
if (adapter) {
586+
if (activeAdapter) {
615587
if (isOutputSlot) {
616-
adapter.beginFromOutput(numericNodeId, index, {
588+
activeAdapter.beginFromOutput(numericNodeId, index, {
617589
moveExisting: shouldMoveExistingOutput
618590
})
619591
} else {
620-
adapter.beginFromInput(numericNodeId, index, {
592+
activeAdapter.beginFromInput(numericNodeId, index, {
621593
moveExisting: shouldMoveExistingInput
622594
})
623595
}
596+
597+
if (shouldMoveExistingInput && existingInputLink) {
598+
existingInputLink._dragging = true
599+
}
624600
}
625601

602+
syncRenderLinkOrigins()
603+
626604
const direction = existingAnchor?.direction ?? baseDirection
627605
const startPosition = existingAnchor?.position ?? {
628606
x: layout.position.x,
@@ -646,8 +624,16 @@ export function useSlotLinkInteraction({
646624

647625
pointerSession.begin(event.pointerId)
648626

627+
toCanvasPointerEvent(event)
649628
updatePointerState(event)
650629

630+
if (activeAdapter) {
631+
activeAdapter.linkConnector.state.snapLinksPos = [
632+
state.pointer.canvas.x,
633+
state.pointer.canvas.y
634+
]
635+
}
636+
651637
pointerSession.register(
652638
useEventListener(window, 'pointermove', handlePointerMove, {
653639
capture: true
@@ -659,7 +645,7 @@ export function useSlotLinkInteraction({
659645
capture: true
660646
})
661647
)
662-
app.canvas?.setDirty(true)
648+
app.canvas?.setDirty(true, true)
663649
event.preventDefault()
664650
event.stopPropagation()
665651
}

0 commit comments

Comments
 (0)