diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 13e04b353..f998815a6 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -73,9 +73,8 @@ import { } from "./types/globalEnums" import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange" import { findFirstNode, getAllNestedItems } from "./utils/collections" -import { toClass } from "./utils/type" import { BaseWidget } from "./widgets/BaseWidget" -import { WIDGET_TYPE_MAP } from "./widgets/widgetMap" +import { toConcreteWidget } from "./widgets/widgetMap" interface IShowSearchOptions { node_to?: LGraphNode | null @@ -2397,9 +2396,8 @@ export class LGraphCanvas { const x = pos[0] - node.pos[0] const y = pos[1] - node.pos[1] - const WidgetClass = WIDGET_TYPE_MAP[widget.type] - if (WidgetClass) { - const widgetInstance = toClass(WidgetClass, widget) + const widgetInstance = toConcreteWidget(widget) + if (widgetInstance) { pointer.onClick = () => widgetInstance.onClick({ e, node, diff --git a/src/interfaces.ts b/src/interfaces.ts index eae429efa..d01b69efb 100644 --- a/src/interfaces.ts +++ b/src/interfaces.ts @@ -20,6 +20,9 @@ export type WhenNullish = T & {} | (T extends null ? Result : T exten /** A type with each of the {@link Properties} made optional. */ export type OptionalProps = Omit & { [K in Properties]?: T[K] } +/** A type with each of the {@link Properties} marked as required. */ +export type RequiredProps = Omit & { [K in Properties]-?: T[K] } + /** Bitwise AND intersection of two types; returns a new, non-union type that includes only properties that exist on both types. */ export type SharedIntersection = { [P in keyof T1 as P extends keyof T2 ? P : never]: T1[P] diff --git a/src/types/widgets.ts b/src/types/widgets.ts index 8b9465725..b1752d615 100644 --- a/src/types/widgets.ts +++ b/src/types/widgets.ts @@ -1,9 +1,9 @@ import type { CanvasPointer, LGraphCanvas, LGraphNode } from "../litegraph" import type { CanvasMouseEvent, CanvasPointerEvent } from "./events" -import { CanvasColour, Point, Size } from "../interfaces" +import { CanvasColour, Point, type RequiredProps, Size } from "../interfaces" -export interface IWidgetOptions extends Record { +export interface IWidgetOptions { on?: string off?: string max?: number @@ -27,7 +27,7 @@ export interface IWidgetOptions extends Record> { type: "toggle" value: boolean } /** Any widget that uses a numeric backing */ -export interface INumericWidget extends IBaseWidget { +export interface INumericWidget extends IBaseWidget> { type: "number" value: number } -export interface ISliderWidget extends IBaseWidget { +export interface ISliderWidget extends IBaseWidget { type: "slider" value: number - options: IWidgetSliderOptions marker?: number } -export interface IKnobWidget extends IBaseWidget { +export interface IKnobWidget extends IBaseWidget { type: "knob" value: number options: IWidgetKnobOptions } +type ComboWidgetValues = string[] | Record | ((widget?: IComboWidget, node?: LGraphNode) => string[]) + /** A combo-box widget (dropdown, select, etc) */ -export interface IComboWidget extends IBaseWidget { +export interface IComboWidget extends IBaseWidget< + string | number, + "combo", + RequiredProps, "values"> +> { type: "combo" value: string | number - options: IWidgetOptions } -export type IStringWidgetType = IStringWidget["type"] | IMultilineStringWidget["type"] - /** A widget with a string value */ -export interface IStringWidget extends IBaseWidget { +export interface IStringWidget extends IBaseWidget> { type: "string" | "text" value: string } -export interface IButtonWidget extends IBaseWidget { +export interface IButtonWidget extends IBaseWidget> { type: "button" value: undefined clicked: boolean } -/** A widget with a string value and a multiline text input */ -export interface IMultilineStringWidget extends - IBaseWidget { - - type: "multiline" - value: string - - /** HTML textarea element */ - element?: TElement -} - /** A custom widget - accepts any value and has no built-in special handling */ -export interface ICustomWidget extends IBaseWidget { +export interface ICustomWidget extends IBaseWidget> { type: "custom" value: string | object } @@ -141,16 +131,16 @@ export type TWidgetValue = IWidget["value"] * The base type for all widgets. Should not be implemented directly. * @see IWidget */ -export interface IBaseWidget { +export interface IBaseWidget { linkedWidgets?: IWidget[] name: string - options: IWidgetOptions + options: TOptions label?: string /** Widget type (see {@link TWidgetType}) */ - type: TWidgetType - value?: TWidgetValue + type: TType + value?: TValue /** * Whether the widget value should be serialized on node serialization. diff --git a/src/widgets/BaseSteppedWidget.ts b/src/widgets/BaseSteppedWidget.ts index a3da8cf30..9928f74fb 100644 --- a/src/widgets/BaseSteppedWidget.ts +++ b/src/widgets/BaseSteppedWidget.ts @@ -1,9 +1,11 @@ +import type { IWidget } from "@/types/widgets" + import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" /** * Base class for widgets that have increment and decrement buttons. */ -export abstract class BaseSteppedWidget extends BaseWidget { +export abstract class BaseSteppedWidget extends BaseWidget { /** * Whether the widget can increment its value * @returns `true` if the widget can increment its value, otherwise `false` diff --git a/src/widgets/BaseWidget.ts b/src/widgets/BaseWidget.ts index e421dce56..b5d0b469c 100644 --- a/src/widgets/BaseWidget.ts +++ b/src/widgets/BaseWidget.ts @@ -1,6 +1,6 @@ import type { CanvasPointer, LGraphCanvas, LGraphNode, Size } from "@/litegraph" import type { CanvasMouseEvent, CanvasPointerEvent } from "@/types/events" -import type { IBaseWidget, IWidget, IWidgetOptions, TWidgetType, TWidgetValue } from "@/types/widgets" +import type { IBaseWidget, IWidget } from "@/types/widgets" import { drawTextInArea } from "@/draw" import { Rectangle } from "@/infrastructure/Rectangle" @@ -29,7 +29,7 @@ export interface WidgetEventOptions { canvas: LGraphCanvas } -export abstract class BaseWidget implements IBaseWidget { +export abstract class BaseWidget implements IBaseWidget { /** From node edge to widget edge */ static margin = 15 /** From widget edge to tip of arrow button */ @@ -43,10 +43,10 @@ export abstract class BaseWidget implements IBaseWidget { linkedWidgets?: IWidget[] name: string - options: IWidgetOptions + options: TWidget["options"] label?: string - type: TWidgetType - value?: TWidgetValue + type: TWidget["type"] + value: TWidget["value"] y: number = 0 last_y?: number width?: number @@ -74,11 +74,12 @@ export abstract class BaseWidget implements IBaseWidget { computeSize?(width?: number): Size onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean - constructor(widget: IBaseWidget) { + constructor(widget: TWidget) { Object.assign(this, widget) this.name = widget.name this.options = widget.options this.type = widget.type + this.value = widget.value } get outline_color() { @@ -232,7 +233,7 @@ export abstract class BaseWidget implements IBaseWidget { * @param value The value to set * @param options The options for setting the value */ - setValue(value: TWidgetValue, { e, node, canvas }: WidgetEventOptions) { + setValue(value: TWidget["value"], { e, node, canvas }: WidgetEventOptions) { const oldValue = this.value if (value === this.value) return diff --git a/src/widgets/BooleanWidget.ts b/src/widgets/BooleanWidget.ts index 7aeac651c..505358f05 100644 --- a/src/widgets/BooleanWidget.ts +++ b/src/widgets/BooleanWidget.ts @@ -2,16 +2,8 @@ import type { IBooleanWidget } from "@/types/widgets" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" -export class BooleanWidget extends BaseWidget implements IBooleanWidget { - // IBooleanWidget properties - declare type: "toggle" - declare value: boolean - - constructor(widget: IBooleanWidget) { - super(widget) - this.type = "toggle" - this.value = widget.value - } +export class BooleanWidget extends BaseWidget implements IBooleanWidget { + override type = "toggle" as const override drawWidget(ctx: CanvasRenderingContext2D, { width, diff --git a/src/widgets/ButtonWidget.ts b/src/widgets/ButtonWidget.ts index a37fb7d03..24c62f0d1 100644 --- a/src/widgets/ButtonWidget.ts +++ b/src/widgets/ButtonWidget.ts @@ -1,18 +1,14 @@ -import type { IButtonWidget, IWidgetOptions } from "@/types/widgets" +import type { IButtonWidget } from "@/types/widgets" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" -export class ButtonWidget extends BaseWidget implements IButtonWidget { - // IButtonWidget properties - declare type: "button" - declare options: IWidgetOptions - declare clicked: boolean - declare value: undefined +export class ButtonWidget extends BaseWidget implements IButtonWidget { + override type = "button" as const + clicked: boolean constructor(widget: IButtonWidget) { super(widget) - this.type = "button" - this.clicked = widget.clicked ?? false + this.clicked ??= false } /** diff --git a/src/widgets/ComboWidget.ts b/src/widgets/ComboWidget.ts index 5bbbac578..246d7af8c 100644 --- a/src/widgets/ComboWidget.ts +++ b/src/widgets/ComboWidget.ts @@ -1,6 +1,6 @@ import type { WidgetEventOptions } from "./BaseWidget" import type { LGraphNode } from "@/LGraphNode" -import type { IComboWidget, IWidgetOptions } from "@/types/widgets" +import type { IComboWidget } from "@/types/widgets" import { clamp, LiteGraph } from "@/litegraph" import { warnDeprecated } from "@/utils/feedback" @@ -19,12 +19,8 @@ function toArray(values: Values): string[] { return Array.isArray(values) ? values : Object.keys(values) } -export class ComboWidget extends BaseSteppedWidget implements IComboWidget { - // IComboWidget properties - declare type: "combo" - declare value: string | number - // @ts-expect-error Workaround for Record not being typed in IWidgetOptions - declare options: Omit, "values"> & { values: Values } +export class ComboWidget extends BaseSteppedWidget implements IComboWidget { + override type = "combo" as const override get displayValue() { const { values: rawValues } = this.options @@ -38,12 +34,6 @@ export class ComboWidget extends BaseSteppedWidget implements IComboWidget { return typeof this.value === "number" ? String(this.value) : this.value } - constructor(widget: IComboWidget) { - super(widget) - this.type = "combo" - this.value = widget.value - } - #getValues(node: LGraphNode): Values { const { values } = this.options if (values == null) throw new Error("[ComboWidget]: values is required") diff --git a/src/widgets/KnobWidget.ts b/src/widgets/KnobWidget.ts index 337ac959a..23295a305 100644 --- a/src/widgets/KnobWidget.ts +++ b/src/widgets/KnobWidget.ts @@ -1,21 +1,12 @@ -import type { IKnobWidget, IWidgetKnobOptions } from "@/types/widgets" +import type { IKnobWidget } from "@/types/widgets" import { clamp } from "@/litegraph" import { getWidgetStep } from "@/utils/widget" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" -export class KnobWidget extends BaseWidget implements IKnobWidget { - declare type: "knob" - declare value: number - declare options: IWidgetKnobOptions - - constructor(widget: IKnobWidget) { - super(widget) - this.type = "knob" - this.value = widget.value - this.options = widget.options - } +export class KnobWidget extends BaseWidget implements IKnobWidget { + override type = "knob" as const computedHeight?: number /** diff --git a/src/widgets/NumberWidget.ts b/src/widgets/NumberWidget.ts index 1ba13bbb5..4899c7d13 100644 --- a/src/widgets/NumberWidget.ts +++ b/src/widgets/NumberWidget.ts @@ -1,15 +1,12 @@ import type { WidgetEventOptions } from "./BaseWidget" -import type { INumericWidget, IWidgetOptions } from "@/types/widgets" +import type { INumericWidget } from "@/types/widgets" import { getWidgetStep } from "@/utils/widget" import { BaseSteppedWidget } from "./BaseSteppedWidget" -export class NumberWidget extends BaseSteppedWidget implements INumericWidget { - // INumberWidget properties - declare type: "number" - declare value: number - declare options: IWidgetOptions +export class NumberWidget extends BaseSteppedWidget implements INumericWidget { + override type = "number" as const override get displayValue() { return Number(this.value).toFixed( @@ -19,12 +16,6 @@ export class NumberWidget extends BaseSteppedWidget implements INumericWidget { ) } - constructor(widget: INumericWidget) { - super(widget) - this.type = "number" - this.value = widget.value - } - override canIncrement(): boolean { const { max } = this.options return max == null || this.value < max diff --git a/src/widgets/SliderWidget.ts b/src/widgets/SliderWidget.ts index 3bea50aa4..7e9054cf5 100644 --- a/src/widgets/SliderWidget.ts +++ b/src/widgets/SliderWidget.ts @@ -1,23 +1,13 @@ -import type { ISliderWidget, IWidgetSliderOptions } from "@/types/widgets" +import type { ISliderWidget } from "@/types/widgets" import { clamp } from "@/litegraph" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" -export class SliderWidget extends BaseWidget implements ISliderWidget { - // ISliderWidget properties - declare type: "slider" - declare value: number - declare options: IWidgetSliderOptions - marker?: number +export class SliderWidget extends BaseWidget implements ISliderWidget { + override type = "slider" as const - constructor(widget: ISliderWidget) { - super(widget) - this.type = "slider" - this.value = widget.value - this.options = widget.options - this.marker = widget.marker - } + marker?: number /** * Draws the widget diff --git a/src/widgets/TextWidget.ts b/src/widgets/TextWidget.ts index 2d7c46ed5..505ea923e 100644 --- a/src/widgets/TextWidget.ts +++ b/src/widgets/TextWidget.ts @@ -1,16 +1,11 @@ -import type { IStringWidget, IWidgetOptions } from "@/types/widgets" +import type { IStringWidget } from "@/types/widgets" import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget" -export class TextWidget extends BaseWidget implements IStringWidget { - // IStringWidget properties - declare type: "text" | "string" - declare value: string - declare options: IWidgetOptions - +export class TextWidget extends BaseWidget implements IStringWidget { constructor(widget: IStringWidget) { super(widget) - this.type = widget.type ?? "string" + this.type ??= "string" this.value = widget.value?.toString() ?? "" } diff --git a/src/widgets/widgetMap.ts b/src/widgets/widgetMap.ts index 68fa29943..e0a336828 100644 --- a/src/widgets/widgetMap.ts +++ b/src/widgets/widgetMap.ts @@ -1,4 +1,4 @@ -import type { IBaseWidget } from "@/types/widgets" +import type { IBaseWidget, IWidget } from "@/types/widgets" import { BaseWidget } from "./BaseWidget" import { BooleanWidget } from "./BooleanWidget" @@ -9,6 +9,21 @@ import { NumberWidget } from "./NumberWidget" import { SliderWidget } from "./SliderWidget" import { TextWidget } from "./TextWidget" +export function toConcreteWidget(widget: IWidget): BaseWidget | undefined { + if (widget instanceof BaseWidget) return widget + + switch (widget.type) { + case "button": return new ButtonWidget(widget) + case "toggle": return new BooleanWidget(widget) + case "slider": return new SliderWidget(widget) + case "knob": return new KnobWidget(widget) + case "combo": return new ComboWidget(widget) + case "number": return new NumberWidget(widget) + case "string": return new TextWidget(widget) + case "text": return new TextWidget(widget) + } +} + type WidgetConstructor = { new (plain: IBaseWidget): BaseWidget }