diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 440c854f9..8aedbb01e 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -4662,13 +4662,10 @@ export class LGraphCanvas implements ConnectionColorContext { // render inputs and outputs if (!node.collapsed) { - node.layoutSlots() - const slotsBounds = createBounds( - node.slots.map(slot => slot._layoutElement), - /** padding= */ 0, - ) + const slotsBounds = node.layoutSlots() const widgetStartY = slotsBounds ? slotsBounds[1] + slotsBounds[3] : 0 node.layoutWidgets({ widgetStartY }) + node.layoutWidgetInputSlots() node.drawSlots(ctx, { connectingLink: this.connecting_links?.[0], diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 93ebdfff0..b49bd1621 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -33,9 +33,9 @@ import { } from "./types/globalEnums" import { BadgePosition, LGraphBadge } from "./LGraphBadge" import { type LGraphNodeConstructor, LiteGraph } from "./litegraph" -import { isInRectangle, isInRect, snapPoint } from "./measure" +import { isInRectangle, isInRect, snapPoint, createBounds } from "./measure" import { LLink } from "./LLink" -import { ConnectionColorContext, isINodeInputSlot, NodeInputSlot, NodeOutputSlot, serializeSlot, toNodeSlotClass } from "./NodeSlot" +import { ConnectionColorContext, isINodeInputSlot, isWidgetInputSlot, NodeInputSlot, NodeOutputSlot, serializeSlot, toNodeSlotClass } from "./NodeSlot" import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" import { toClass } from "./utils/type" import { LayoutElement } from "./utils/layout" @@ -3218,17 +3218,26 @@ export class LGraphNode implements Positionable, IPinnable { }) } - layoutSlots(): void { + layoutSlots(): ReadOnlyRect | null { + const slots: LayoutElement[] = [] + for (const [i, slot] of this.inputs.entries()) { + /** Widget input slots are handled in {@link layoutWidgetInputSlots} */ + if (isWidgetInputSlot(slot)) continue + this.layoutSlot(slot, { slotIndex: i, }) + slots.push(slot._layoutElement) } for (const [i, slot] of this.outputs.entries()) { this.layoutSlot(slot, { slotIndex: i, }) + slots.push(slot._layoutElement) } + + return slots.length ? createBounds(slots, /** padding= */ 0) : null } #getMouseOverSlot(slot: INodeSlot): INodeSlot | null { @@ -3345,4 +3354,30 @@ export class LGraphNode implements Positionable, IPinnable { y += w.computedHeight } } + + /** + * Lays out the node's widget input slots. + */ + layoutWidgetInputSlots(): void { + if (!this.widgets) return + + const slotByWidgetName = new Map() + + for (const [i, slot] of this.inputs.entries()) { + if (!isWidgetInputSlot(slot)) continue + + slotByWidgetName.set(slot.widget?.name, { ...slot, index: i }) + } + if (!slotByWidgetName.size) return + + for (const widget of this.widgets) { + const slot = slotByWidgetName.get(widget.name) + if (!slot) continue + + const actualSlot = this.inputs[slot.index] + const offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5 + actualSlot.pos = [offset, widget.y + offset] + this.layoutSlot(actualSlot, { slotIndex: slot.index }) + } + } } diff --git a/src/NodeSlot.ts b/src/NodeSlot.ts index 241c02ae3..4cfa104b6 100644 --- a/src/NodeSlot.ts +++ b/src/NodeSlot.ts @@ -45,6 +45,14 @@ export function toNodeSlotClass(slot: INodeSlot): NodeSlot { throw new Error("Invalid slot type") } +/** + * Whether this slot is an input slot and attached to a widget. + * @param slot - The slot to check. + */ +export function isWidgetInputSlot(slot: INodeSlot): boolean { + return isINodeInputSlot(slot) && !!slot.widget +} + export abstract class NodeSlot implements INodeSlot { name: string localized_name?: string