From 6a01b08ebfc8578e3f39b55d8428c89178074e3c Mon Sep 17 00:00:00 2001 From: AustinMroz Date: Tue, 16 Sep 2025 19:17:35 -0700 Subject: [PATCH] Subgraph widget promotion - Part 1 (#5537) * Prerequisite tweaks for subgraph widget promotion * Clean up DOMWidget tracking on graph change * Mark migrated CombOWidget functions private * Cleanup placeholder node cast --- knip.config.ts | 4 +- src/components/graph/DomWidgets.vue | 2 +- src/lib/litegraph/src/widgets/ComboWidget.ts | 18 ++++----- .../src/widgets/DisconnectedWidget.ts | 38 +++++++++++++++++++ src/scripts/app.ts | 30 +++++++++------ 5 files changed, 70 insertions(+), 22 deletions(-) create mode 100644 src/lib/litegraph/src/widgets/DisconnectedWidget.ts diff --git a/knip.config.ts b/knip.config.ts index 9df077d772..81911a7361 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -25,7 +25,9 @@ const config: KnipConfig = { 'src/types/generatedManagerTypes.ts', 'src/types/comfyRegistryTypes.ts', // Used by a custom node (that should move off of this) - 'src/scripts/ui/components/splitButton.ts' + 'src/scripts/ui/components/splitButton.ts', + // Staged for for use with subgraph widget promotion + 'src/lib/litegraph/src/widgets/DisconnectedWidget.ts' ], compilers: { // https://github.com/webpro-nl/knip/issues/1008#issuecomment-3207756199 diff --git a/src/components/graph/DomWidgets.vue b/src/components/graph/DomWidgets.vue index da174cf22f..a68e580c9a 100644 --- a/src/components/graph/DomWidgets.vue +++ b/src/components/graph/DomWidgets.vue @@ -34,7 +34,7 @@ const updateWidgets = () => { const widget = widgetState.widget // Early exit for non-visible widgets - if (!widget.isVisible()) { + if (!widget.isVisible() || !widgetState.active) { widgetState.visible = false continue } diff --git a/src/lib/litegraph/src/widgets/ComboWidget.ts b/src/lib/litegraph/src/widgets/ComboWidget.ts index 6eab342d3c..01e10d0f1e 100644 --- a/src/lib/litegraph/src/widgets/ComboWidget.ts +++ b/src/lib/litegraph/src/widgets/ComboWidget.ts @@ -45,7 +45,7 @@ export class ComboWidget return typeof this.value === 'number' ? String(this.value) : this.value } - #getValues(node: LGraphNode): Values { + private getValues(node: LGraphNode): Values { const { values } = this.options if (values == null) throw new Error('[ComboWidget]: values is required') @@ -57,7 +57,7 @@ export class ComboWidget * @param increment `true` if checking the use of the increment button, `false` for decrement * @returns `true` if the value is at the given index, otherwise `false`. */ - #canUseButton(increment: boolean): boolean { + private canUseButton(increment: boolean): boolean { const { values } = this.options // If using legacy duck-typed method, false is the most permissive return value if (typeof values === 'function') return false @@ -78,23 +78,23 @@ export class ComboWidget * Handles edge case where the value is both the first and last item in the list. */ override canIncrement(): boolean { - return this.#canUseButton(true) + return this.canUseButton(true) } override canDecrement(): boolean { - return this.#canUseButton(false) + return this.canUseButton(false) } override incrementValue(options: WidgetEventOptions): void { - this.#tryChangeValue(1, options) + this.tryChangeValue(1, options) } override decrementValue(options: WidgetEventOptions): void { - this.#tryChangeValue(-1, options) + this.tryChangeValue(-1, options) } - #tryChangeValue(delta: number, options: WidgetEventOptions): void { - const values = this.#getValues(options.node) + private tryChangeValue(delta: number, options: WidgetEventOptions): void { + const values = this.getValues(options.node) const indexedValues = toArray(values) // avoids double click event @@ -128,7 +128,7 @@ export class ComboWidget if (x > width - 40) return this.incrementValue({ e, node, canvas }) // Otherwise, show dropdown menu - const values = this.#getValues(node) + const values = this.getValues(node) const values_list = toArray(values) // Handle center click - show dropdown menu diff --git a/src/lib/litegraph/src/widgets/DisconnectedWidget.ts b/src/lib/litegraph/src/widgets/DisconnectedWidget.ts new file mode 100644 index 0000000000..5e32d74fc7 --- /dev/null +++ b/src/lib/litegraph/src/widgets/DisconnectedWidget.ts @@ -0,0 +1,38 @@ +import { LGraphNode } from '@/lib/litegraph/src/LGraphNode' +import type { IButtonWidget } from '@/lib/litegraph/src/types/widgets' + +import { BaseWidget, type DrawWidgetOptions } from './BaseWidget' + +class DisconnectedWidget extends BaseWidget { + constructor(widget: IButtonWidget) { + super(widget, new LGraphNode('DisconnectedPlaceholder')) + this.disabled = true + } + + override drawWidget( + ctx: CanvasRenderingContext2D, + { width, showText = true }: DrawWidgetOptions + ) { + ctx.save() + this.drawWidgetShape(ctx, { width, showText }) + if (showText) { + this.drawTruncatingText({ ctx, width, leftPadding: 0, rightPadding: 0 }) + } + ctx.restore() + } + + override onClick() {} + + override get _displayValue() { + return 'Disconnected' + } +} +const conf: IButtonWidget = { + type: 'button', + value: undefined, + name: 'Disconnected', + options: {}, + y: 0, + clicked: false +} +export const disconnectedWidget = new DisconnectedWidget(conf) diff --git a/src/scripts/app.ts b/src/scripts/app.ts index 57ddfbb2cb..0455f26ad3 100644 --- a/src/scripts/app.ts +++ b/src/scripts/app.ts @@ -35,6 +35,7 @@ import { isComboInputSpecV1, isComboInputSpecV2 } from '@/schemas/nodeDefSchema' +import { type BaseDOMWidget, DOMWidgetImpl } from '@/scripts/domWidget' import { getFromWebmFile } from '@/scripts/metadata/ebml' import { getGltfBinaryMetadata } from '@/scripts/metadata/gltf' import { getFromIsobmffFile } from '@/scripts/metadata/isobmff' @@ -837,22 +838,29 @@ export class ComfyApp { this.canvas.canvas.addEventListener<'litegraph:set-graph'>( 'litegraph:set-graph', (e) => { - // Assertion: Not yet defined in litegraph. const { newGraph } = e.detail - const nodeSet = new Set(newGraph.nodes) const widgetStore = useDomWidgetStore() - // Assertions: UnwrapRef - for (const { widget } of widgetStore.activeWidgetStates) { - if (!nodeSet.has(widget.node)) { - widgetStore.deactivateWidget(widget.id) - } - } + const activeWidgets: Record< + string, + BaseDOMWidget + > = Object.fromEntries( + newGraph.nodes + .flatMap((node) => node.widgets ?? []) + .filter((w) => w instanceof DOMWidgetImpl) + .map((w) => [w.id, w]) + ) - for (const { widget } of widgetStore.inactiveWidgetStates) { - if (nodeSet.has(widget.node)) { - widgetStore.activateWidget(widget.id) + for (const [ + widgetId, + widgetState + ] of widgetStore.widgetStates.entries()) { + if (widgetId in activeWidgets) { + widgetState.active = true + widgetState.widget = activeWidgets[widgetId] + } else { + widgetState.active = false } } }