diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index b7b494c93..f406fcd31 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -576,6 +576,11 @@ export class LGraphCanvas implements ConnectionColorContext { onNodeSelected?: (node: LGraphNode) => void onNodeDeselected?: (node: LGraphNode) => void onRender?: (canvas: HTMLCanvasElement, ctx: CanvasRenderingContext2D) => void + /** Implement this function to allow conversion of widget types to input types, e.g. number -> INT or FLOAT for widget link validation checks */ + getWidgetLinkType?: ( + widget: IWidget, + node: LGraphNode, + ) => string | null | undefined /** * Creates a new instance of LGraphCanvas. @@ -2596,6 +2601,20 @@ export class LGraphCanvas implements ConnectionColorContext { // No link, or none of the dragged links may be dropped here } else if (linkConnector.state.connectingTo === "input") { if (inputId === -1 && outputId === -1) { + // Allow support for linking to widgets, handled externally to LiteGraph + if (this.getWidgetLinkType && overWidget) { + const widgetLinkType = this.getWidgetLinkType(overWidget, node) + if ( + widgetLinkType && + LiteGraph.isValidConnection(linkConnector.renderLinks[0]?.fromSlot.type, widgetLinkType) && + firstLink.node.isValidWidgetLink?.(firstLink.fromSlotIndex, node, overWidget) !== false + ) { + const { pos: [nodeX, nodeY] } = node + highlightPos = [nodeX + 10, nodeY + 10 + overWidget.y] + linkConnector.overWidget = overWidget + linkConnector.overWidgetType = widgetLinkType + } + } // Node background / title under the pointer if (!linkConnector.overWidget) { const result = node.findInputByType(firstLink.fromSlot.type) @@ -5185,7 +5204,12 @@ export class LGraphCanvas implements ConnectionColorContext { posY: number, ctx: CanvasRenderingContext2D, ): void { + const { linkConnector } = this + node.drawWidgets(ctx, { + colorContext: this, + linkOverWidget: linkConnector.overWidget, + linkOverWidgetType: linkConnector.overWidgetType, lowQuality: this.low_quality, editorAlpha: this.editor_alpha, }) diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 84ad98913..0fe072f47 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -1585,7 +1585,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { let widgets_height = 0 if (this.widgets?.length) { for (const widget of this.widgets) { - if (!this.isWidgetVisible(widget)) continue + if (widget.hidden || (widget.advanced && !this.showAdvanced)) continue let widget_height = 0 if (widget.computeSize) { @@ -1929,8 +1929,9 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { for (const widget of widgets) { if ( - (widget.computedDisabled && !includeDisabled) || - !this.isWidgetVisible(widget) + (widget.disabled && !includeDisabled) || + widget.hidden || + (widget.advanced && !this.showAdvanced) ) { continue } @@ -3363,25 +3364,16 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { } } - /** - * Returns `true` if the widget is visible, otherwise `false`. - */ - isWidgetVisible(widget: IWidget): boolean { - const isHidden = ( - this.collapsed || - widget.hidden || - (widget.advanced && !this.showAdvanced) - ) - return !isHidden - } - drawWidgets(ctx: CanvasRenderingContext2D, options: { + colorContext: ConnectionColorContext + linkOverWidget: IWidget | null | undefined + linkOverWidgetType?: ISlotType lowQuality?: boolean editorAlpha?: number }): void { if (!this.widgets) return - const { lowQuality = false, editorAlpha = 1 } = options + const { colorContext, linkOverWidget, linkOverWidgetType, lowQuality = false, editorAlpha = 1 } = options const width = this.size[0] const widgets = this.widgets @@ -3392,19 +3384,25 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { const margin = 15 for (const w of widgets) { - if (!this.isWidgetVisible(w)) continue - + if (w.hidden || (w.advanced && !this.showAdvanced)) continue const y = w.y const outline_color = w.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR - w.last_y = y - // Disable widget if it is disabled or if the value is passed from socket connection. - w.computedDisabled = w.disabled || this.getSlotFromWidget(w)?.link != null + if (w === linkOverWidget) { + // Manually draw a slot next to the widget simulating an input + new NodeInputSlot({ + name: "", + // @ts-expect-error https://github.com/Comfy-Org/litegraph.js/issues/616 + type: linkOverWidgetType, + link: 0, + }).draw(ctx, { pos: [10, y + 10], colorContext }) + } + w.last_y = y ctx.strokeStyle = outline_color ctx.fillStyle = "#222" ctx.textAlign = "left" - if (w.computedDisabled) ctx.globalAlpha *= 0.5 + if (w.disabled) ctx.globalAlpha *= 0.5 const widget_width = w.width || width const WidgetClass: typeof WIDGET_TYPE_MAP[string] = WIDGET_TYPE_MAP[w.type] @@ -3526,25 +3524,6 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { return this.#getMouseOverSlot(slot) === slot } - #isMouseOverWidget(widget: IWidget): boolean { - return this.mouseOver?.overWidget === widget - } - - /** - * Returns the input slot that is associated with the given widget. - */ - getSlotFromWidget(widget: IWidget): INodeInputSlot | undefined { - return this.inputs.find(slot => isWidgetInputSlot(slot) && slot.widget.name === widget.name) - } - - /** - * Returns the widget that is associated with the given input slot. - */ - getWidgetFromSlot(slot: INodeInputSlot): IWidget | undefined { - if (!isWidgetInputSlot(slot)) return - return this.widgets?.find(w => w.name === slot.widget.name) - } - /** * Draws the node's input and output slots. */ @@ -3565,18 +3544,7 @@ export class LGraphNode implements Positionable, IPinnable, IColorable { const labelColor = highlight ? this.highlightColor : LiteGraph.NODE_TEXT_COLOR - - // Show slot if it's not a widget input slot - // or if it's a widget input slot and satisfies one of the following: - // - the mouse is over the widget - // - the slot is valid during link drop - // - the slot is connected - const showSlot = !isWidgetInputSlot(slot) || - this.#isMouseOverWidget(this.getWidgetFromSlot(slot)!) || - (fromSlot && slotInstance.isValidTarget(fromSlot)) || - slotInstance.isConnected() - - ctx.globalAlpha = showSlot ? (isValid ? editorAlpha : 0.4 * editorAlpha) : 0 + ctx.globalAlpha = isValid ? editorAlpha : 0.4 * editorAlpha slotInstance.draw(ctx, { pos: layoutElement?.center ?? [0, 0], diff --git a/src/NodeSlot.ts b/src/NodeSlot.ts index 0f5a11937..9b962afab 100644 --- a/src/NodeSlot.ts +++ b/src/NodeSlot.ts @@ -214,8 +214,7 @@ export abstract class NodeSlot implements INodeSlot { if (!lowQuality && doStroke) ctx.stroke() // render slot label - const hideLabel = lowQuality || isWidgetInputSlot(this) - if (!hideLabel) { + if (!lowQuality) { const text = this.renderingLabel if (text) { // TODO: Finish impl. Highlight text on mouseover unless we're connecting links. diff --git a/src/types/widgets.ts b/src/types/widgets.ts index 0a9c36cd8..fe7431828 100644 --- a/src/types/widgets.ts +++ b/src/types/widgets.ts @@ -153,38 +153,24 @@ export interface IBaseWidget { /** * The computed height of the widget. Used by customized node resize logic. * See scripts/domWidget.ts for more details. - * @readonly [Computed] This property is computed by the node. */ computedHeight?: number /** * The starting y position of the widget after layout. - * @readonly [Computed] This property is computed by the node. */ y: number /** * The y position of the widget after drawing (rendering). - * @readonly [Computed] This property is computed by the node. * @deprecated There is no longer dynamic y adjustment on rendering anymore. * Use {@link IBaseWidget.y} instead. */ last_y?: number width?: number - /** - * Whether the widget is disabled. Disabled widgets are rendered at half opacity. - * See also {@link IBaseWidget.computedDisabled}. - */ disabled?: boolean - /** - * The disabled state used for rendering based on various conditions including - * {@link IBaseWidget.disabled}. - * @readonly [Computed] This property is computed by the node. - */ - computedDisabled?: boolean - hidden?: boolean advanced?: boolean diff --git a/src/widgets/BaseWidget.ts b/src/widgets/BaseWidget.ts index 0fb59d981..2ca7b9401 100644 --- a/src/widgets/BaseWidget.ts +++ b/src/widgets/BaseWidget.ts @@ -23,7 +23,6 @@ export abstract class BaseWidget implements IBaseWidget { last_y?: number width?: number disabled?: boolean - computedDisabled?: boolean hidden?: boolean advanced?: boolean tooltip?: string diff --git a/src/widgets/BooleanWidget.ts b/src/widgets/BooleanWidget.ts index e87cc684e..b0dad4231 100644 --- a/src/widgets/BooleanWidget.ts +++ b/src/widgets/BooleanWidget.ts @@ -33,7 +33,7 @@ export class BooleanWidget extends BaseWidget implements IBooleanWidget { ctx.roundRect(margin, y, width - margin * 2, height, [height * 0.5]) else ctx.rect(margin, y, width - margin * 2, height) ctx.fill() - if (show_text && !this.computedDisabled) ctx.stroke() + if (show_text && !this.disabled) ctx.stroke() ctx.fillStyle = this.value ? "#89A" : "#333" ctx.beginPath() ctx.arc( diff --git a/src/widgets/ButtonWidget.ts b/src/widgets/ButtonWidget.ts index 8685056e7..2f0410072 100644 --- a/src/widgets/ButtonWidget.ts +++ b/src/widgets/ButtonWidget.ts @@ -45,7 +45,7 @@ export class ButtonWidget extends BaseWidget implements IButtonWidget { ctx.fillRect(margin, y, width - margin * 2, height) // Draw button outline if not disabled - if (show_text && !this.computedDisabled) { + if (show_text && !this.disabled) { ctx.strokeStyle = this.outline_color ctx.strokeRect(margin, y, width - margin * 2, height) } diff --git a/src/widgets/ComboWidget.ts b/src/widgets/ComboWidget.ts index ddc7b7b76..0d4bd53cc 100644 --- a/src/widgets/ComboWidget.ts +++ b/src/widgets/ComboWidget.ts @@ -49,7 +49,7 @@ export class ComboWidget extends BaseWidget implements IComboWidget { ctx.fill() if (show_text) { - if (!this.computedDisabled) { + if (!this.disabled) { ctx.stroke() // Draw left arrow ctx.fillStyle = this.text_color diff --git a/src/widgets/KnobWidget.ts b/src/widgets/KnobWidget.ts index f01b5e861..f7536618a 100644 --- a/src/widgets/KnobWidget.ts +++ b/src/widgets/KnobWidget.ts @@ -158,7 +158,7 @@ export class KnobWidget extends BaseWidget implements IKnobWidget { ctx.closePath() // Draw outline if not disabled - if (show_text && !this.computedDisabled) { + if (show_text && !this.disabled) { ctx.strokeStyle = this.outline_color // Draw value ctx.beginPath() diff --git a/src/widgets/NumberWidget.ts b/src/widgets/NumberWidget.ts index 9a95a51e0..8622fbc67 100644 --- a/src/widgets/NumberWidget.ts +++ b/src/widgets/NumberWidget.ts @@ -64,7 +64,7 @@ export class NumberWidget extends BaseWidget implements INumericWidget { ctx.fill() if (show_text) { - if (!this.computedDisabled) { + if (!this.disabled) { ctx.stroke() // Draw left arrow ctx.fillStyle = this.text_color diff --git a/src/widgets/SliderWidget.ts b/src/widgets/SliderWidget.ts index 3c04f2260..be9fabd59 100644 --- a/src/widgets/SliderWidget.ts +++ b/src/widgets/SliderWidget.ts @@ -54,7 +54,7 @@ export class SliderWidget extends BaseWidget implements ISliderWidget { ctx.fillRect(margin, y, nvalue * (width - margin * 2), height) // Draw outline if not disabled - if (show_text && !this.computedDisabled) { + if (show_text && !this.disabled) { ctx.strokeStyle = this.outline_color ctx.strokeRect(margin, y, width - margin * 2, height) } diff --git a/src/widgets/TextWidget.ts b/src/widgets/TextWidget.ts index 41f097136..0c2d2b86b 100644 --- a/src/widgets/TextWidget.ts +++ b/src/widgets/TextWidget.ts @@ -47,7 +47,7 @@ export class TextWidget extends BaseWidget implements IStringWidget { ctx.fill() if (show_text) { - if (!this.computedDisabled) ctx.stroke() + if (!this.disabled) ctx.stroke() ctx.save() ctx.beginPath() ctx.rect(margin, y, width - margin * 2, height)