From 2dbd5f4cf0513d8184b0f3536d7c931f856c0122 Mon Sep 17 00:00:00 2001 From: filtered <176114999+webfiltered@users.noreply.github.com> Date: Sat, 22 Mar 2025 09:23:20 +1100 Subject: [PATCH] Allow creating floating reroutes from new link menu (#818) Adds an `Add Reroute` option to the new link menu. Creates a new floating reroute connected to the source slot. --- src/LGraphCanvas.ts | 26 ++++++++++++++++++++++---- src/LGraphNode.ts | 27 ++++++++++++++++++++++++++- src/interfaces.ts | 4 ++-- 3 files changed, 50 insertions(+), 7 deletions(-) diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 18950fd01..7089b4c45 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -5292,8 +5292,7 @@ export class LGraphCanvas implements ConnectionColorContext { const node_left = graph.getNodeById(origin_id) const fromType = node_left?.outputs?.[origin_slot]?.type - const options = ["Add Node", null, "Delete", null] - options.splice(1, 0, "Add Reroute") + const options = ["Add Node", "Add Reroute", null, "Delete", null] const menu = new LiteGraph.ContextMenu(options, { event: e, @@ -5511,6 +5510,7 @@ export class LGraphCanvas implements ConnectionColorContext { showSearchBox: this.showSearchBox, }, optPass || {}) const that = this + const { graph } = this const { afterRerouteId } = opts const isFrom = opts.nodeFrom && opts.slotFrom @@ -5553,7 +5553,7 @@ export class LGraphCanvas implements ConnectionColorContext { return } - const options = ["Add Node", null] + const options = ["Add Node", "Add Reroute", null] if (opts.allow_searchbox) { options.push("Search", null) @@ -5577,6 +5577,7 @@ export class LGraphCanvas implements ConnectionColorContext { // build menu const menu = new LiteGraph.ContextMenu(options, { event: opts.e, + extra: slotX, title: (slotX && slotX.name != "" ? slotX.name + (fromSlotType ? " | " : "") @@ -5584,8 +5585,10 @@ export class LGraphCanvas implements ConnectionColorContext { callback: inner_clicked, }) + const dirty = () => this.#dirty() + // callback - function inner_clicked(v: string, options: unknown, e: MouseEvent) { + function inner_clicked(v: string | undefined, options: IContextMenuOptions, e: MouseEvent) { // console.log("Process showConnectionMenu selection"); switch (v) { case "Add Node": @@ -5599,6 +5602,21 @@ export class LGraphCanvas implements ConnectionColorContext { } }) break + case "Add Reroute":{ + const node = isFrom ? opts.nodeFrom : opts.nodeTo + const slot = options.extra + + if (!graph) throw new NullGraphError() + if (!node) throw new TypeError("Cannot add reroute: node was null") + if (!slot) throw new TypeError("Cannot add reroute: slot was null") + if (!opts.e) throw new TypeError("Cannot add reroute: CanvasPointerEvent was null") + + const reroute = node.connectFloatingReroute([opts.e.canvasX, opts.e.canvasY], slot) + if (!reroute) throw new Error("Failed to create reroute") + + dirty() + break + } case "Search": if (isFrom) { opts.showSearchBox(e, { node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType }) diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 7897cd0d8..f1c158161 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -20,7 +20,7 @@ import type { Size, } from "./interfaces" import type { LGraph } from "./LGraph" -import type { RerouteId } from "./Reroute" +import type { Reroute, RerouteId } from "./Reroute" import type { CanvasMouseEvent } from "./types/events" import type { ISerialisedNode } from "./types/serialisation" import type { IBaseWidget, IWidget, IWidgetOptions, TWidgetType, TWidgetValue } from "./types/widgets" @@ -2545,6 +2545,31 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { return link } + connectFloatingReroute(pos: Point, slot: INodeInputSlot | INodeOutputSlot): Reroute { + const { graph, id } = this + if (!graph) throw new NullGraphError() + + // Assertion: It's either there or it isn't. + const inputIndex = this.inputs.indexOf(slot as INodeInputSlot) + const outputIndex = this.outputs.indexOf(slot as INodeOutputSlot) + if (inputIndex === -1 && outputIndex === -1) throw new Error("Invalid slot") + + const link = new LLink( + -1, + slot.type, + outputIndex === -1 ? -1 : id, + outputIndex, + inputIndex === -1 ? -1 : id, + inputIndex, + ) + const slotType = outputIndex === -1 ? "input" : "output" + const reroute = graph.setReroute({ pos, linkIds: [], floating: { slotType } }) + + link.parentId = reroute.id + graph.addFloatingLink(link) + return reroute + } + /** * disconnect one output to an specific node * @param slot (could be the number of the slot or the string with the name of the slot) diff --git a/src/interfaces.ts b/src/interfaces.ts index 4eef000d2..47cb66b85 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -341,11 +341,11 @@ interface IContextMenuBase { } /** ContextMenu */ -export interface IContextMenuOptions extends IContextMenuBase { +export interface IContextMenuOptions extends IContextMenuBase { ignore_item_callbacks?: boolean parentMenu?: ContextMenu event?: MouseEvent - extra?: unknown + extra?: TExtra /** @deprecated Context menu scrolling is now controlled by the browser */ scroll_speed?: number left?: number