From 2d688a896dc608677822d384ab5758099998d98e Mon Sep 17 00:00:00 2001 From: Chenlei Hu Date: Sat, 8 Feb 2025 17:01:54 -0500 Subject: [PATCH] Implement TextWidget (#484) --- src/LGraphCanvas.ts | 42 +++------------- src/LGraphNode.ts | 5 ++ src/widgets/TextWidget.ts | 101 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 112 insertions(+), 36 deletions(-) create mode 100644 src/widgets/TextWidget.ts diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 1caf88472..1a6d670f4 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -71,6 +71,7 @@ import { NodeInputSlot, NodeOutputSlot, type ConnectionColorContext } from "./No import { ComboWidget } from "./widgets/ComboWidget" import { NumberWidget } from "./widgets/NumberWidget" import { ButtonWidget } from "./widgets/ButtonWidget" +import { TextWidget } from "./widgets/TextWidget" interface IShowSearchOptions { node_to?: LGraphNode @@ -2625,13 +2626,11 @@ export class LGraphCanvas implements ConnectionColorContext { break case "string": case "text": - pointer.onClick = () => this.prompt( - "Value", - widget.value, - (v: any) => setWidgetValue(this, node, widget, v), + pointer.onClick = () => toClass(TextWidget, widget).onClick({ e, - widget.options ? widget.options.multiline : false, - ) + node, + canvas: this, + }) break default: // Legacy custom widget callback @@ -5898,36 +5897,7 @@ export class LGraphCanvas implements ConnectionColorContext { break case "string": case "text": - ctx.textAlign = "left" - ctx.strokeStyle = outline_color - ctx.fillStyle = background_color - ctx.beginPath() - - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) - else - ctx.rect(margin, y, widget_width - margin * 2, H) - ctx.fill() - if (show_text) { - if (!w.disabled) ctx.stroke() - ctx.save() - ctx.beginPath() - ctx.rect(margin, y, widget_width - margin * 2, H) - ctx.clip() - - // ctx.stroke(); - ctx.fillStyle = secondary_text_color - const label = w.label || w.name - if (label != null) ctx.fillText(label, margin * 2, y + H * 0.7) - ctx.fillStyle = text_color - ctx.textAlign = "right" - ctx.fillText( - String(w.value).substr(0, 30), - widget_width - margin * 2, - y + H * 0.7, - ) // 30 chars max - ctx.restore() - } + toClass(TextWidget, w).drawWidget(ctx, { y, width: widget_width, show_text, margin }) break // Custom widgets default: diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index 2324166ba..ed57982cf 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -37,6 +37,7 @@ import { ComboWidget } from "./widgets/ComboWidget" import { NumberWidget } from "./widgets/NumberWidget" import { ButtonWidget } from "./widgets/ButtonWidget" import { NodeInputSlot, NodeOutputSlot } from "./NodeSlot" +import { TextWidget } from "./widgets/TextWidget" export type NodeId = number | string @@ -1683,6 +1684,10 @@ export class LGraphNode implements Positionable, IPinnable { case "button": widget = new ButtonWidget(custom_widget) break + case "text": + case "string": + widget = new TextWidget(custom_widget) + break default: widget = custom_widget } diff --git a/src/widgets/TextWidget.ts b/src/widgets/TextWidget.ts new file mode 100644 index 000000000..4aacb2c57 --- /dev/null +++ b/src/widgets/TextWidget.ts @@ -0,0 +1,101 @@ +import type { IStringWidget, IWidgetOptions } from "@/types/widgets" +import { BaseWidget } from "./BaseWidget" +import type { LGraphNode } from "@/LGraphNode" +import type { CanvasMouseEvent } from "@/types/events" +import type { LGraphCanvas } from "@/LGraphCanvas" + +export class TextWidget extends BaseWidget implements IStringWidget { + // IStringWidget properties + declare type: "text" | "string" + declare value: string + declare options: IWidgetOptions + + constructor(widget: IStringWidget) { + super(widget) + this.type = widget.type as "text" | "string" + this.value = widget.value?.toString() ?? "" + } + + /** + * Draws the widget + * @param ctx - The canvas context + * @param options - The options for drawing the widget + */ + override drawWidget(ctx: CanvasRenderingContext2D, options: { + y: number + width: number + show_text?: boolean + margin?: number + }) { + // Store original context attributes + const originalTextAlign = ctx.textAlign + const originalStrokeStyle = ctx.strokeStyle + const originalFillStyle = ctx.fillStyle + + const { y, width, show_text = true, margin = 15 } = options + const widget_width = width + const H = this.height + + ctx.textAlign = "left" + ctx.strokeStyle = this.outline_color + ctx.fillStyle = this.background_color + ctx.beginPath() + + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]) + else + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.fill() + + if (show_text) { + if (!this.disabled) ctx.stroke() + ctx.save() + ctx.beginPath() + ctx.rect(margin, y, widget_width - margin * 2, H) + ctx.clip() + + // Draw label + ctx.fillStyle = this.secondary_text_color + const label = this.label || this.name + if (label != null) { + ctx.fillText(label, margin * 2, y + H * 0.7) + } + + // Draw value + ctx.fillStyle = this.text_color + ctx.textAlign = "right" + ctx.fillText( + String(this.value).substr(0, 30), // 30 chars max + widget_width - margin * 2, + y + H * 0.7, + ) + ctx.restore() + } + + // Restore original context attributes + ctx.textAlign = originalTextAlign + ctx.strokeStyle = originalStrokeStyle + ctx.fillStyle = originalFillStyle + } + + override onClick(options: { + e: CanvasMouseEvent + node: LGraphNode + canvas: LGraphCanvas + }) { + const { e, node, canvas } = options + + // Show prompt dialog for text input + canvas.prompt( + "Value", + this.value, + (v: string) => { + if (v !== null) { + this.setValue(v, { e, node, canvas }) + } + }, + e, + this.options?.multiline ?? false, + ) + } +}