Allow reroutes to be re-connected (#749)

- Resolves #305
- Allows links to be dragged from inputs to reroutes
This commit is contained in:
filtered
2025-03-12 00:47:26 +11:00
committed by GitHub
parent 35dd90b6b0
commit 5ed264ce8e
4 changed files with 100 additions and 27 deletions

View File

@@ -221,6 +221,25 @@ export class Reroute implements Positionable, LinkSegment, Serialisable<Serialis
?.findNextReroute(withParentId, visited)
}
findSourceOutput() {
const network = this.#network.deref()
const originId = this.linkIds.values().next().value
if (!network || !originId) return
const link = network.links.get(originId)
if (!link) return
const node = network.getNodeById(link.origin_id)
if (!node) return
return {
node,
output: node.outputs[link.origin_slot],
outputIndex: link.origin_slot,
link,
}
}
/** @inheritdoc */
move(deltaX: number, deltaY: number) {
this.#pos[0] += deltaX

View File

@@ -261,15 +261,33 @@ export class LinkConnector {
dropLinks(locator: ItemLocator, event: CanvasPointerEvent): void {
if (!this.isConnecting) return this.reset()
const { renderLinks, state } = this
const { connectingTo } = state
const { renderLinks } = this
const mayContinue = this.events.dispatch("before-drop-links", { renderLinks, event })
if (mayContinue === false) return this.reset()
const { canvasX, canvasY } = event
const node = locator.getNodeOnPos(canvasX, canvasY) ?? undefined
if (!node) return this.dropOnNothing(event)
if (node) {
this.dropOnNode(node, event)
} else {
// Get reroute if no node is found
const reroute = locator.getRerouteOnPos(canvasX, canvasY)
// Drop output->input 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 {

View File

@@ -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

View File

@@ -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. */