Skip to content

Commit b227eef

Browse files
authored
Improve connecting link logic (#697)
### Current - Connections are disconnected the moment a link starts being dragged - Reseating a connection where it came from creates a new connection - If the process is somehow interrupted, the links are already gone ### Proposed - Connection is disconnected after a new connection is made - Rendering is bypassed for the link segment being moved - Does nothing if dropping a link exactly where it came from - Adds early return when trying to connect a node to itself
1 parent cef6ab6 commit b227eef

File tree

4 files changed

+64
-26
lines changed

4 files changed

+64
-26
lines changed

src/LGraphCanvas.ts

Lines changed: 46 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ import {
6969
TitleMode,
7070
} from "./types/globalEnums"
7171
import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange"
72-
import { findFirstNode, getAllNestedItems } from "./utils/collections"
72+
import { findFirstNode, getAllNestedItems, isDraggingLink } from "./utils/collections"
7373
import { toClass } from "./utils/type"
7474
import { WIDGET_TYPE_MAP } from "./widgets/widgetMap"
7575

@@ -2253,6 +2253,7 @@ export class LGraphCanvas implements ConnectionColorContext {
22532253
output: null,
22542254
pos,
22552255
direction: LinkDirection.RIGHT,
2256+
link,
22562257
})
22572258
}
22582259

@@ -2321,17 +2322,20 @@ export class LGraphCanvas implements ConnectionColorContext {
23212322
output: linked_node.outputs[slot],
23222323
pos: linked_node.getConnectionPos(false, slot),
23232324
afterRerouteId: link_info.parentId,
2325+
link: link_info,
23242326
}
2325-
this.connecting_links = [connecting]
2327+
const connectingLinks = [connecting]
2328+
this.connecting_links = connectingLinks
23262329

23272330
pointer.onDragStart = () => {
23282331
connecting.output = linked_node.outputs[slot]
23292332
}
23302333
pointer.onDragEnd = (upEvent) => {
2331-
if (this.allow_reconnect_links && !LiteGraph.click_do_break_link_to) {
2334+
const shouldDisconnect = this.#processConnectingLinks(upEvent, connectingLinks)
2335+
2336+
if (shouldDisconnect && this.allow_reconnect_links && !LiteGraph.click_do_break_link_to) {
23322337
node.disconnectInput(i)
23332338
}
2334-
this.#processConnectingLinks(upEvent)
23352339
connecting.output = linked_node.outputs[slot]
23362340
this.connecting_links = null
23372341
}
@@ -2712,7 +2716,7 @@ export class LGraphCanvas implements ConnectionColorContext {
27122716
// Node background / title under the pointer
27132717
if (!linkOverWidget) {
27142718
const targetSlotId = firstLink.node.findConnectByTypeSlot(true, node, firstLink.output.type)
2715-
if (targetSlotId !== null && targetSlotId >= 0) {
2719+
if (targetSlotId !== undefined && targetSlotId >= 0) {
27162720
node.getConnectionPos(true, targetSlotId, pos)
27172721
highlightPos = pos
27182722
highlightInput = node.inputs[targetSlotId]
@@ -2739,7 +2743,7 @@ export class LGraphCanvas implements ConnectionColorContext {
27392743
if (inputId === -1 && outputId === -1) {
27402744
const targetSlotId = firstLink.node.findConnectByTypeSlot(false, node, firstLink.input.type)
27412745

2742-
if (targetSlotId !== null && targetSlotId >= 0) {
2746+
if (targetSlotId !== undefined && targetSlotId >= 0) {
27432747
node.getConnectionPos(false, targetSlotId, pos)
27442748
highlightPos = pos
27452749
}
@@ -2912,7 +2916,7 @@ export class LGraphCanvas implements ConnectionColorContext {
29122916

29132917
if (this.connecting_links?.length) {
29142918
// node below mouse
2915-
this.#processConnectingLinks(e)
2919+
this.#processConnectingLinks(e, this.connecting_links)
29162920
} else {
29172921
this.dirty_canvas = true
29182922

@@ -2944,25 +2948,33 @@ export class LGraphCanvas implements ConnectionColorContext {
29442948
return
29452949
}
29462950

2947-
#processConnectingLinks(e: CanvasPointerEvent) {
2948-
const { graph, connecting_links } = this
2951+
#processConnectingLinks(e: CanvasPointerEvent, connecting_links: ConnectingLink[]): boolean | undefined {
2952+
const { graph } = this
29492953
if (!graph) throw new NullGraphError()
2950-
if (!connecting_links) return
29512954

29522955
const { canvasX: x, canvasY: y } = e
29532956
const node = graph.getNodeOnPos(x, y, this.visible_nodes)
29542957
const firstLink = connecting_links[0]
29552958

29562959
if (node) {
2960+
let madeNewLink: boolean | undefined
2961+
29572962
for (const link of connecting_links) {
29582963
// dragging a connection
29592964
this.#dirty()
29602965

2966+
// One should avoid linking things to oneself
2967+
if (node === link.node) continue
2968+
29612969
// slot below mouse? connect
29622970
if (link.output) {
29632971
const slot = this.isOverNodeInput(node, x, y)
29642972
if (slot != -1) {
2965-
link.node.connect(link.slot, node, slot, link.afterRerouteId)
2973+
// Trying to move link onto itself
2974+
if (link.link?.target_id === node.id && link.link?.target_slot === slot) return
2975+
2976+
const newLink = link.node.connect(link.slot, node, slot, link.afterRerouteId)
2977+
madeNewLink ||= newLink !== null
29662978
} else if (this.link_over_widget) {
29672979
this.emitEvent({
29682980
subType: "connectingWidgetLink",
@@ -2974,28 +2986,33 @@ export class LGraphCanvas implements ConnectionColorContext {
29742986
} else {
29752987
// not on top of an input
29762988
// look for a good slot
2977-
link.node.connectByType(link.slot, node, link.output.type, {
2978-
afterRerouteId: link.afterRerouteId,
2979-
})
2989+
const slotIndex = link.node.findConnectByTypeSlot(true, node, link.output.type)
2990+
if (slotIndex !== undefined) {
2991+
// Trying to move link onto itself
2992+
if (link.link?.target_id === node.id && link.link?.target_slot === slotIndex) return
2993+
2994+
const newLink = link.node.connect(link.slot, node, slotIndex, link.afterRerouteId)
2995+
madeNewLink ||= newLink !== null
2996+
}
29802997
}
29812998
} else if (link.input) {
29822999
const slot = this.isOverNodeOutput(node, x, y)
29833000

2984-
if (slot != -1) {
3001+
const newLink = slot != -1
29853002
// this is inverted has output-input nature like
2986-
node.connect(slot, link.node, link.slot, link.afterRerouteId)
2987-
} else {
3003+
? node.connect(slot, link.node, link.slot, link.afterRerouteId)
29883004
// not on top of an input
29893005
// look for a good slot
2990-
link.node.connectByTypeOutput(
3006+
: link.node.connectByTypeOutput(
29913007
link.slot,
29923008
node,
29933009
link.input.type,
29943010
{ afterRerouteId: link.afterRerouteId },
29953011
)
2996-
}
3012+
madeNewLink ||= newLink !== null
29973013
}
29983014
}
3015+
return madeNewLink
29993016
} else if (firstLink.input || firstLink.output) {
30003017
// For external event only.
30013018
const linkReleaseContextExtended: LinkReleaseContextExtended = {
@@ -3033,6 +3050,7 @@ export class LGraphCanvas implements ConnectionColorContext {
30333050
}
30343051
}
30353052
}
3053+
return true
30363054
}
30373055
}
30383056

@@ -4897,6 +4915,8 @@ export class LGraphCanvas implements ConnectionColorContext {
48974915
const link = this.graph._links.get(link_id)
48984916
if (!link) continue
48994917

4918+
const draggingLink = isDraggingLink(link.id, this.connecting_links)
4919+
49004920
// find link info
49014921
const start_node = this.graph.getNodeById(link.origin_id)
49024922
if (start_node == null) continue
@@ -4960,6 +4980,8 @@ export class LGraphCanvas implements ConnectionColorContext {
49604980
const startPos = prevReroute?.pos ?? start_node_slotpos
49614981
reroute.calculateAngle(this.last_draw_time, this.graph, startPos)
49624982

4983+
// Skip the first segment if it is being dragged
4984+
if (j === 0 && draggingLink?.input) continue
49634985
this.renderLink(
49644986
ctx,
49654987
startPos,
@@ -4984,6 +5006,9 @@ export class LGraphCanvas implements ConnectionColorContext {
49845006
startControl = [dist * reroute.cos, dist * reroute.sin]
49855007
}
49865008

5009+
// Skip the last segment if it is being dragged
5010+
if (draggingLink?.output) continue
5011+
49875012
// Use runtime fallback; TypeScript cannot evaluate this correctly.
49885013
const segmentStartPos = points.at(-2) ?? start_node_slotpos
49895014

@@ -5000,7 +5025,8 @@ export class LGraphCanvas implements ConnectionColorContext {
50005025
end_dir,
50015026
{ startControl },
50025027
)
5003-
} else {
5028+
// Skip normal render when link is being dragged
5029+
} else if (!draggingLink) {
50045030
this.renderLink(
50055031
ctx,
50065032
start_node_slotpos,

src/LGraphNode.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2170,7 +2170,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
21702170
node: LGraphNode,
21712171
slotType: ISlotType,
21722172
options?: ConnectByTypeOptions,
2173-
): number | null {
2173+
): number | undefined {
21742174
// LEGACY: Old options names
21752175
if (options && typeof options === "object") {
21762176
if ("firstFreeIfInputGeneralInCase" in options) options.wildcardToTyped = !!options.firstFreeIfInputGeneralInCase
@@ -2188,7 +2188,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
21882188

21892189
if (node && typeof node === "number") {
21902190
const nodeById = this.graph.getNodeById(node)
2191-
if (!nodeById) return null
2191+
if (!nodeById) return
21922192

21932193
node = nodeById
21942194
}
@@ -2217,7 +2217,6 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
22172217
: node.findOutputSlotFree(opt)
22182218
if (nonEventSlot >= 0) return nonEventSlot
22192219
}
2220-
return null
22212220
}
22222221

22232222
/**
@@ -2239,7 +2238,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
22392238
target_slotType,
22402239
optsIn,
22412240
)
2242-
if (slotIndex !== null)
2241+
if (slotIndex !== undefined)
22432242
return this.connect(slot, target_node, slotIndex, optsIn?.afterRerouteId)
22442243

22452244
console.debug("[connectByType]: no way to connect type:", target_slotType, "to node:", target_node)
@@ -2270,7 +2269,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable {
22702269
source_slotType,
22712270
optsIn,
22722271
)
2273-
if (slotIndex !== null)
2272+
if (slotIndex !== undefined)
22742273
return source_node.connect(slotIndex, this, slot, optsIn?.afterRerouteId)
22752274

22762275
console.debug("[connectByType]: no way to connect type:", source_slotType, "to node:", source_node)

src/interfaces.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -290,6 +290,8 @@ export interface ConnectingLink extends IInputOrOutput {
290290
pos: Point
291291
direction?: LinkDirection
292292
afterRerouteId?: RerouteId
293+
/** The link being moved, or `undefined` if creating a new link. */
294+
link?: LLink
293295
}
294296

295297
interface IContextMenuBase {

src/utils/collections.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import type { Positionable } from "../interfaces"
1+
import type { ConnectingLink, Positionable } from "../interfaces"
2+
import type { LinkId } from "@/LLink"
23

34
import { LGraphNode } from "@/LGraphNode"
45

@@ -35,3 +36,13 @@ export function findFirstNode(items: Iterable<Positionable>): LGraphNode | undef
3536
if (item instanceof LGraphNode) return item
3637
}
3738
}
39+
40+
/** @returns `true` if the provided link ID is currently being dragged. */
41+
export function isDraggingLink(linkId: LinkId, connectingLinks: ConnectingLink[] | null | undefined): ConnectingLink | undefined {
42+
if (connectingLinks == null) return
43+
44+
for (const connectingLink of connectingLinks) {
45+
if (connectingLink.link == null) continue
46+
if (linkId === connectingLink.link.id) return connectingLink
47+
}
48+
}

0 commit comments

Comments
 (0)