From 5ed264ce8ebed04c5649d1c5244aa2c22b66e8d2 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Wed, 12 Mar 2025 00:47:26 +1100 Subject: [PATCH] Allow reroutes to be re-connected (#749) - Resolves #305 - Allows links to be dragged from inputs to reroutes --- src/Reroute.ts | 19 ++++ src/canvas/LinkConnector.ts | 102 +++++++++++++----- .../LinkConnectorEventTarget.ts | 5 + src/interfaces.ts | 1 + 4 files changed, 100 insertions(+), 27 deletions(-) diff --git a/src/Reroute.ts b/src/Reroute.ts index 5528e7433..2e561ff90 100644 --- a/src/Reroute.ts +++ b/src/Reroute.ts @@ -221,6 +221,25 @@ export class Reroute implements Positionable, LinkSegment, Serialisableinput link on reroute is not impl. + if (reroute && this.state.connectingTo === "output") { + this.dropOnReroute(reroute, event) + } else { + this.dropOnNothing(event) + } + } + + this.events.dispatch("after-drop-links", { renderLinks, event }) + this.reset() + } + + dropOnNode(node: LGraphNode, event: CanvasPointerEvent) { + const { renderLinks, state } = this + const { connectingTo } = state + const { canvasX, canvasY } = event // To output if (connectingTo === "output") { @@ -278,7 +296,7 @@ export class LinkConnector { if (output) { this.#dropOnOutput(node, output) } else { - this.dropOnNode(node, event) + this.#dropOnNodeBackground(node, event) } // To input } else if (connectingTo === "input") { @@ -287,21 +305,66 @@ export class LinkConnector { // Input slot if (input) { this.#dropOnInput(node, input) - } else if (this.overWidget && this.renderLinks[0] instanceof ToInputRenderLink) { + } else if (this.overWidget && renderLinks[0] instanceof ToInputRenderLink) { // Widget this.events.dispatch("dropped-on-widget", { - link: this.renderLinks[0], + link: renderLinks[0], node, widget: this.overWidget, }) this.overWidget = undefined } else { // Node background / title - this.dropOnNode(node, event) + this.#dropOnNodeBackground(node, event) } } + } - this.events.dispatch("after-drop-links", { renderLinks, event }) + dropOnReroute(reroute: Reroute, event: CanvasPointerEvent): void { + const mayContinue = this.events.dispatch("dropped-on-reroute", { reroute, event }) + if (mayContinue === false) return + + for (const link of this.renderLinks) { + if (link.toType !== "output") continue + + const result = reroute.findSourceOutput() + if (!result) return + + const { node, output } = result + + if (link instanceof MovingRenderLink) { + const { inputNode, inputSlot, outputSlot, fromReroute } = link + // Link is already connected here + if (outputSlot === output) continue + + // Connect the first reroute of the link being dragged to the reroute being dropped on + if (fromReroute) { + fromReroute.parentId = reroute.id + } else { + // If there are no reroutes, directly connect the link + link.link.parentId = reroute.id + } + // Use the last reroute id on the link to retain all reroutes + node.connectSlots(output, inputNode, inputSlot, link.link.parentId) + this.events.dispatch("output-moved", link) + } else { + const { node: inputNode, fromSlot } = link + const newLink = node.connectSlots(output, inputNode, fromSlot, reroute?.id) + this.events.dispatch("link-created", newLink) + } + } + } + + dropOnNothing(event: CanvasPointerEvent): void { + // For external event only. + if (this.state.connectingTo === "input") { + for (const link of this.renderLinks) { + if (link instanceof MovingRenderLink) { + link.inputNode.disconnectInput(link.inputIndex, true) + } + } + } + this.events.dispatch("dropped-on-canvas", event) this.reset() } @@ -310,7 +373,7 @@ export class LinkConnector { * @param node The node that the links are being dropped on * @param event Contains the drop location, in canvas space */ - dropOnNode(node: LGraphNode, event: CanvasPointerEvent): void { + #dropOnNodeBackground(node: LGraphNode, event: CanvasPointerEvent): void { const { state: { connectingTo } } = this const mayContinue = this.events.dispatch("dropped-on-node", { node, event }) @@ -328,7 +391,7 @@ export class LinkConnector { return } this.#dropOnOutput(node, output) - return this.reset() + return } // Dragging input links @@ -339,7 +402,7 @@ export class LinkConnector { return } this.#dropOnInput(node, input) - return this.reset() + return } // Dropping new output link @@ -369,21 +432,6 @@ export class LinkConnector { } } } - - this.reset() - } - - dropOnNothing(event: CanvasPointerEvent): void { - // For external event only. - if (this.state.connectingTo === "input") { - for (const link of this.renderLinks) { - if (link instanceof MovingRenderLink) { - link.inputNode.disconnectInput(link.inputIndex, true) - } - } - } - this.events.dispatch("dropped-on-canvas", event) - this.reset() } #dropOnInput(node: LGraphNode, input: INodeInputSlot): void { diff --git a/src/infrastructure/LinkConnectorEventTarget.ts b/src/infrastructure/LinkConnectorEventTarget.ts index cc439910f..08315d854 100644 --- a/src/infrastructure/LinkConnectorEventTarget.ts +++ b/src/infrastructure/LinkConnectorEventTarget.ts @@ -3,6 +3,7 @@ import type { RenderLink } from "@/canvas/RenderLink" import type { ToInputRenderLink } from "@/canvas/ToInputRenderLink" import type { LGraphNode } from "@/LGraphNode" import type { LLink } from "@/LLink" +import type { Reroute } from "@/Reroute" import type { CanvasPointerEvent } from "@/types/events" import type { IWidget } from "@/types/widgets" @@ -26,6 +27,10 @@ export interface LinkConnectorEventMap { "link-created": LLink | null | undefined + "dropped-on-reroute": { + reroute: Reroute + event: CanvasPointerEvent + } "dropped-on-node": { node: LGraphNode event: CanvasPointerEvent diff --git a/src/interfaces.ts b/src/interfaces.ts index 9fa8b4483..97edb52f4 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -125,6 +125,7 @@ export interface LinkNetwork { */ export interface ItemLocator { getNodeOnPos(x: number, y: number, nodeList?: LGraphNode[]): LGraphNode | null + getRerouteOnPos(x: number, y: number): Reroute | undefined } /** Contains a cached 2D canvas path and a centre point, with an optional forward angle. */