Skip to content
Open
Show file tree
Hide file tree
Changes from 36 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
9a18d37
Add dragging test
benceruleanlu Sep 19, 2025
939cbe0
Litegraph? Never heard if it
benceruleanlu Sep 19, 2025
e3e1d2e
Add more test cases
benceruleanlu Sep 19, 2025
5c6c21c
Update test expectations [skip ci]
invalid-email-address Sep 19, 2025
f624940
Merge branch 'bl-tests' into bl-more-slots
benceruleanlu Sep 19, 2025
48f5087
More test cases v1
benceruleanlu Sep 19, 2025
369da53
review comments
benceruleanlu Sep 19, 2025
22a1c61
Merge remote-tracking branch 'origin/bl-tests' into bl-more-slots
benceruleanlu Sep 19, 2025
70651dc
Allow moving links and support reroutes
benceruleanlu Sep 21, 2025
c227d60
Add dragging input to input drags existing link test
benceruleanlu Sep 21, 2025
9d668a1
Merge remote-tracking branch 'origin/main' into bl-more-slots
benceruleanlu Sep 21, 2025
b99d70d
nit
benceruleanlu Sep 21, 2025
19c538c
Support dragging from output to output
benceruleanlu Sep 22, 2025
263b280
Add snapshot
benceruleanlu Sep 22, 2025
bef712e
o-o shift test
benceruleanlu Sep 22, 2025
e7f0ee4
Update test expectation
benceruleanlu Sep 22, 2025
20d136d
Switch to adapter approach
benceruleanlu Sep 22, 2025
3f4a806
clean up onPointerDown
benceruleanlu Sep 23, 2025
e136b89
Add reroute anchor tests
benceruleanlu Sep 23, 2025
6685e00
Fix double links
benceruleanlu Sep 23, 2025
0aa971b
Merge remote-tracking branch 'origin/main' into bl-more-slots
benceruleanlu Sep 23, 2025
9d32b4c
temp screenshots
benceruleanlu Sep 23, 2025
e879bd5
improve typing
benceruleanlu Sep 23, 2025
f348902
nit
benceruleanlu Sep 23, 2025
d780296
cleanup unused
benceruleanlu Sep 23, 2025
8eec7fb
huh?
benceruleanlu Sep 23, 2025
f99d8c1
I am the one who knocks
benceruleanlu Sep 23, 2025
65ec322
Those who type
benceruleanlu Sep 23, 2025
a2be36a
fix bad fallback and remove logging
benceruleanlu Sep 23, 2025
88cd60f
nit
benceruleanlu Sep 23, 2025
381d97a
nit
benceruleanlu Sep 23, 2025
e9ffce4
nit
benceruleanlu Sep 23, 2025
99aaa4e
Merge remote-tracking branch 'origin/main' into bl-more-slots
benceruleanlu Sep 23, 2025
57810b9
nit
benceruleanlu Sep 24, 2025
9b39835
refactor linkInteraction.spec.ts
benceruleanlu Sep 24, 2025
c050115
those who know
benceruleanlu Sep 24, 2025
839d8a5
sure
benceruleanlu Sep 25, 2025
0627a71
those who know cont
benceruleanlu Sep 26, 2025
1ca3d75
nit
benceruleanlu Sep 26, 2025
ecc5bed
type
benceruleanlu Sep 26, 2025
1c11dcc
Merge remote-tracking branch 'origin/main' into bl-more-slots
benceruleanlu Sep 27, 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
687 changes: 578 additions & 109 deletions browser_tests/tests/vueNodes/linkInteraction.spec.ts

Large diffs are not rendered by default.

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.
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.
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.
28 changes: 25 additions & 3 deletions src/lib/litegraph/src/canvas/MovingInputLink.ts
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

question (non-blocking): Can you explain these changes briefly?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this was for testing/iteration, and was supposed to be removed in the 'those who know' commit, but wasn't cleaned up fully

it didn't have any callers, I just removed it now

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was I supposed to know that?

Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export class MovingInputLink extends MovingLinkBase {
return this.node.canConnectTo(inputNode, input, this.outputSlot)
}

canConnectToOutput(): false {
canConnectToOutput(): boolean {
return false
}

Expand All @@ -73,8 +73,30 @@ export class MovingInputLink extends MovingLinkBase {
return link
}

connectToOutput(): never {
throw new Error('MovingInputLink cannot connect to an output.')
connectToOutput(
outputNode: LGraphNode,
output: INodeOutputSlot,
events: CustomEventTarget<LinkConnectorEventMap>
): LLink | null | undefined {
if (
outputNode === this.outputNode &&
output === this.outputSlot &&
this.inputSlot === this.inputNode.inputs[this.inputIndex]
) {
return
}

const afterRerouteId = this.fromReroute?.id ?? this.link.parentId

this.inputNode.disconnectInput(this.inputIndex, true)
const newLink = outputNode.connectSlots(
output,
this.inputNode,
this.inputSlot,
afterRerouteId
)
if (newLink) events.dispatch('input-moved', this)
return newLink
}

connectToSubgraphInput(): void {
Expand Down
9 changes: 9 additions & 0 deletions src/lib/litegraph/src/canvas/MovingLinkBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
} from '@/lib/litegraph/src/interfaces'
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
import type { NodeLike } from '@/lib/litegraph/src/types/NodeLike'
import { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'

import type { RenderLink } from './RenderLink'
Expand Down Expand Up @@ -99,6 +100,14 @@ export abstract class MovingLinkBase implements RenderLink {
this.inputPos = inputNode.getInputPos(inputIndex)
}

abstract canConnectToInput(
inputNode: NodeLike,
input: INodeInputSlot
): boolean
abstract canConnectToOutput(
outputNode: NodeLike,
output: INodeOutputSlot
): boolean
abstract connectToInput(
node: LGraphNode,
input: INodeInputSlot,
Expand Down
12 changes: 12 additions & 0 deletions src/lib/litegraph/src/canvas/RenderLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type {
import type { SubgraphIONodeBase } from '@/lib/litegraph/src/subgraph/SubgraphIONodeBase'
import type { SubgraphInput } from '@/lib/litegraph/src/subgraph/SubgraphInput'
import type { SubgraphOutput } from '@/lib/litegraph/src/subgraph/SubgraphOutput'
import type { NodeLike } from '@/lib/litegraph/src/types/NodeLike'
import type { LinkDirection } from '@/lib/litegraph/src/types/globalEnums'

export interface RenderLink {
Expand Down Expand Up @@ -38,6 +39,17 @@ export interface RenderLink {
/** The reroute that the link is being connected from. */
readonly fromReroute?: Reroute

/**
* Capability checks used for hit-testing and validation during drag.
* Implementations should return `false` when a connection is not possible
* rather than throwing.
*/
canConnectToInput(node: NodeLike, input: INodeInputSlot): boolean
canConnectToOutput(node: NodeLike, output: INodeOutputSlot): boolean
/** Optional: only some links support validating subgraph IO or reroutes. */
canConnectToSubgraphInput?(input: SubgraphInput): boolean
canConnectToReroute?(reroute: Reroute): boolean

connectToInput(
node: LGraphNode,
input: INodeInputSlot,
Expand Down
152 changes: 152 additions & 0 deletions src/renderer/core/canvas/links/linkConnectorAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import type { LGraph } from '@/lib/litegraph/src/LGraph'
import type { NodeId } from '@/lib/litegraph/src/LGraphNode'
import type { RerouteId } from '@/lib/litegraph/src/Reroute'
import { LinkConnector } from '@/lib/litegraph/src/canvas/LinkConnector'
import type { RenderLink } from '@/lib/litegraph/src/canvas/RenderLink'
import type { ConnectingLink } from '@/lib/litegraph/src/interfaces'
import { app } from '@/scripts/app'

// Keep one adapter per graph so rendering and interaction share state.
const adapterByGraph = new WeakMap<LGraph, LinkConnectorAdapter>()

/**
* Renderer‑agnostic adapter around LiteGraph's LinkConnector.
*
* - Uses layoutStore for hit‑testing (nodes/reroutes).
* - Exposes minimal, imperative APIs to begin link drags and query drop validity.
* - Preserves existing Vue composable behavior.
*/
export class LinkConnectorAdapter {
readonly linkConnector: LinkConnector

constructor(
/** Network the links belong to (typically `app.canvas.graph`). */
readonly network: LGraph
) {
// No-op legacy setter to avoid side effects when connectors update
const setConnectingLinks: (value: ConnectingLink[]) => void = () => {}
this.linkConnector = new LinkConnector(setConnectingLinks)
}

/**
* The currently rendered/dragged links, typed for consumer use.
* Prefer this over accessing `linkConnector.renderLinks` directly.
*/
get renderLinks(): ReadonlyArray<RenderLink> {
return this.linkConnector.renderLinks
}

// Drag helpers

/**
* Begin dragging from an output slot.
* @param nodeId Output node id
* @param outputIndex Output slot index
* @param opts Optional: moveExisting (shift), fromRerouteId
*/
beginFromOutput(
nodeId: NodeId,
outputIndex: number,
opts?: { moveExisting?: boolean; fromRerouteId?: RerouteId }
): void {
const node = this.network.getNodeById(nodeId)
const output = node?.outputs?.[outputIndex]
if (!node || !output) return

const fromReroute = this.network.getReroute(opts?.fromRerouteId)

if (opts?.moveExisting) {
this.linkConnector.moveOutputLink(this.network, output)
} else {
this.linkConnector.dragNewFromOutput(
this.network,
node,
output,
fromReroute
)
}
}

/**
* Begin dragging from an input slot.
* @param nodeId Input node id
* @param inputIndex Input slot index
* @param opts Optional: moveExisting (when a link/floating exists), fromRerouteId
*/
beginFromInput(
nodeId: NodeId,
inputIndex: number,
opts?: { moveExisting?: boolean; fromRerouteId?: RerouteId }
): void {
const node = this.network.getNodeById(nodeId)
const input = node?.inputs?.[inputIndex]
if (!node || !input) return

const fromReroute = this.network.getReroute(opts?.fromRerouteId)

if (opts?.moveExisting) {
this.linkConnector.moveInputLink(this.network, input)
} else {
this.linkConnector.dragNewFromInput(
this.network,
node,
input,
fromReroute
)
}
}

// Validation helpers

isNodeValidDrop(nodeId: NodeId): boolean {
const node = this.network.getNodeById(nodeId)
if (!node) return false
return this.linkConnector.isNodeValidDrop(node)
}

isInputValidDrop(nodeId: NodeId, inputIndex: number): boolean {
const node = this.network.getNodeById(nodeId)
const input = node?.inputs?.[inputIndex]
if (!node || !input) return false
return this.linkConnector.isInputValidDrop(node, input)
}

isOutputValidDrop(nodeId: NodeId, outputIndex: number): boolean {
const node = this.network.getNodeById(nodeId)
const output = node?.outputs?.[outputIndex]
if (!node || !output) return false
return this.linkConnector.renderLinks.some((link) =>
link.canConnectToOutput(node, output)
)
}

isRerouteValidDrop(rerouteId: RerouteId): boolean {
const reroute = this.network.getReroute(rerouteId)
if (!reroute) return false
return this.linkConnector.isRerouteValidDrop(reroute)
}

// Drop/cancel helpers for future flows

/** Disconnects moving links (drop on canvas/no target). */
disconnectMovingLinks(): void {
this.linkConnector.disconnectLinks()
}

/** Resets connector state and clears any temporary flags. */
reset(): void {
this.linkConnector.reset()
}
}

/** Convenience creator using the current app canvas graph. */
export function createLinkConnectorAdapter(): LinkConnectorAdapter | null {
const graph = app.canvas?.graph as LGraph | undefined
if (!graph) return null
let adapter = adapterByGraph.get(graph)
if (!adapter) {
adapter = new LinkConnectorAdapter(graph)
adapterByGraph.set(graph, adapter)
}
return adapter
}
73 changes: 0 additions & 73 deletions src/renderer/core/canvas/links/slotLinkCompatibility.ts

This file was deleted.

4 changes: 3 additions & 1 deletion src/renderer/core/canvas/links/slotLinkDragState.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ import type { Point, SlotLayout } from '@/renderer/core/layout/types'

type SlotDragType = 'input' | 'output'

export interface SlotDragSource {
interface SlotDragSource {
nodeId: string
slotIndex: number
type: SlotDragType
direction: LinkDirection
position: Readonly<Point>
linkId?: number
movingExistingOutput?: boolean
}

export interface SlotDropCandidate {
Expand Down
Loading