diff --git a/src/LGraphCanvas.ts b/src/LGraphCanvas.ts index 1a6d670f4..a0177c584 100644 --- a/src/LGraphCanvas.ts +++ b/src/LGraphCanvas.ts @@ -72,6 +72,7 @@ import { ComboWidget } from "./widgets/ComboWidget" import { NumberWidget } from "./widgets/NumberWidget" import { ButtonWidget } from "./widgets/ButtonWidget" import { TextWidget } from "./widgets/TextWidget" +import { SliderWidget } from "./widgets/SliderWidget" interface IShowSearchOptions { node_to?: LGraphNode @@ -2574,17 +2575,15 @@ export class LGraphCanvas implements ConnectionColorContext { } break case "slider": { - if (widget.options.read_only) break - - pointer.onDrag = (eMove) => { - const x = eMove.canvasX - node.pos[0] - const slideFactor = clamp((x - 15) / (width - 30), 0, 1) - widget.value = widget.options.min + (widget.options.max - widget.options.min) * slideFactor - if (oldValue != widget.value) { - setWidgetValue(this, node, widget, widget.value) - } - this.dirty_canvas = true - } + pointer.onClick = () => toClass(SliderWidget, widget).onClick({ + e, + node, + canvas: this, + }) + pointer.onDrag = eMove => toClass(SliderWidget, widget).onDrag({ + e: eMove, + node, + }) break } case "number": { @@ -5845,50 +5844,9 @@ export class LGraphCanvas implements ConnectionColorContext { case "toggle": toClass(BooleanWidget, w).drawWidget(ctx, { y, width: widget_width, show_text, margin }) break - case "slider": { - ctx.fillStyle = background_color - ctx.fillRect(margin, y, widget_width - margin * 2, H) - const range = w.options.max - w.options.min - let nvalue = (w.value - w.options.min) / range - if (nvalue < 0.0) nvalue = 0.0 - if (nvalue > 1.0) nvalue = 1.0 - ctx.fillStyle = w.options.hasOwnProperty("slider_color") - ? w.options.slider_color - : active_widget == w - ? "#89A" - : "#678" - ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H) - if (show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H) - if (w.marker) { - let marker_nvalue = (w.marker - w.options.min) / range - if (marker_nvalue < 0.0) marker_nvalue = 0.0 - if (marker_nvalue > 1.0) marker_nvalue = 1.0 - ctx.fillStyle = w.options.hasOwnProperty("marker_color") - ? w.options.marker_color - : "#AA9" - ctx.fillRect( - margin + marker_nvalue * (widget_width - margin * 2), - y, - 2, - H, - ) - } - if (show_text) { - ctx.textAlign = "center" - ctx.fillStyle = text_color - ctx.fillText( - (w.label || w.name) + - " " + - Number(w.value).toFixed( - w.options.precision != null ? w.options.precision : 3, - ), - widget_width * 0.5, - y + H * 0.7, - ) - } + case "slider": + toClass(SliderWidget, w).drawWidget(ctx, { y, width: widget_width, show_text, margin }) break - } case "combo": toClass(ComboWidget, w).drawWidget(ctx, { y, width: widget_width, show_text, margin }) break diff --git a/src/LGraphNode.ts b/src/LGraphNode.ts index ed57982cf..1bda43142 100644 --- a/src/LGraphNode.ts +++ b/src/LGraphNode.ts @@ -38,6 +38,7 @@ import { NumberWidget } from "./widgets/NumberWidget" import { ButtonWidget } from "./widgets/ButtonWidget" import { NodeInputSlot, NodeOutputSlot } from "./NodeSlot" import { TextWidget } from "./widgets/TextWidget" +import { SliderWidget } from "./widgets/SliderWidget" export type NodeId = number | string @@ -1688,6 +1689,9 @@ export class LGraphNode implements Positionable, IPinnable { case "string": widget = new TextWidget(custom_widget) break + case "slider": + widget = new SliderWidget(custom_widget) + break default: widget = custom_widget } diff --git a/src/types/widgets.ts b/src/types/widgets.ts index dc6d8f750..d1bc83dc2 100644 --- a/src/types/widgets.ts +++ b/src/types/widgets.ts @@ -21,6 +21,14 @@ export interface IWidgetOptions extends Record { + min: number + max: number + step: number + slider_color?: CanvasColour + marker_color?: CanvasColour +} + /** * A widget for a node. * All types are based on IBaseWidget - additions can be made there or directly on individual types. @@ -36,6 +44,7 @@ export type IWidget = | IMultilineStringWidget | IComboWidget | ICustomWidget + | ISliderWidget export interface IBooleanWidget extends IBaseWidget { type?: "toggle" @@ -44,10 +53,16 @@ export interface IBooleanWidget extends IBaseWidget { /** Any widget that uses a numeric backing */ export interface INumericWidget extends IBaseWidget { - type?: "slider" | "number" + type?: "number" value: number } +export interface ISliderWidget extends IBaseWidget { + type?: "slider" + value: number + options: IWidgetSliderOptions +} + /** A combo-box widget (dropdown, select, etc) */ export interface IComboWidget extends IBaseWidget { type?: "combo" diff --git a/src/widgets/SliderWidget.ts b/src/widgets/SliderWidget.ts new file mode 100644 index 000000000..5bebfc2be --- /dev/null +++ b/src/widgets/SliderWidget.ts @@ -0,0 +1,139 @@ +import type { ISliderWidget, IWidgetSliderOptions } from "@/types/widgets" +import { BaseWidget } from "./BaseWidget" +import type { LGraphNode } from "@/LGraphNode" +import type { CanvasMouseEvent } from "@/types/events" +import type { LGraphCanvas } from "@/LGraphCanvas" +import { clamp } from "@/litegraph" + +export class SliderWidget extends BaseWidget implements ISliderWidget { + // ISliderWidget properties + declare type: "slider" + declare value: number + declare options: IWidgetSliderOptions + + constructor(widget: ISliderWidget) { + super(widget) + this.type = "slider" + this.value = widget.value + this.options = widget.options + } + + /** + * 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: widget_width, show_text = true, margin = 15 } = options + const H = this.height + + // Draw background + ctx.fillStyle = this.background_color + ctx.fillRect(margin, y, widget_width - margin * 2, H) + + // Calculate normalized value + const range = this.options.max - this.options.min + let nvalue = (this.value - this.options.min) / range + nvalue = clamp(nvalue, 0, 1) + + // Draw slider bar + ctx.fillStyle = this.options.slider_color ?? "#678" + ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H) + + // Draw outline if not disabled + if (show_text && !this.disabled) { + ctx.strokeStyle = this.outline_color + ctx.strokeRect(margin, y, widget_width - margin * 2, H) + } + + // Draw marker if present + if (this.marker != null) { + let marker_nvalue = (this.marker - this.options.min) / range + marker_nvalue = clamp(marker_nvalue, 0, 1) + ctx.fillStyle = this.options.marker_color ?? "#AA9" + ctx.fillRect( + margin + marker_nvalue * (widget_width - margin * 2), + y, + 2, + H, + ) + } + + // Draw text + if (show_text) { + ctx.textAlign = "center" + ctx.fillStyle = this.text_color + ctx.fillText( + (this.label || this.name) + + " " + + Number(this.value).toFixed( + this.options.precision != null ? this.options.precision : 3, + ), + widget_width * 0.5, + y + H * 0.7, + ) + } + + // Restore original context attributes + ctx.textAlign = originalTextAlign + ctx.strokeStyle = originalStrokeStyle + ctx.fillStyle = originalFillStyle + } + + /** + * Handles click events for the slider widget + */ + override onClick(options: { + e: CanvasMouseEvent + node: LGraphNode + canvas: LGraphCanvas + }) { + if (this.options.read_only) return + + const { e, node } = options + const width = this.width || node.size[0] + const x = e.canvasX - node.pos[0] + + // Calculate new value based on click position + const slideFactor = clamp((x - 15) / (width - 30), 0, 1) + const newValue = this.options.min + (this.options.max - this.options.min) * slideFactor + + if (newValue !== this.value) { + this.setValue(newValue, options) + } + } + + /** + * Handles drag events for the slider widget + */ + override onDrag(options: { + e: CanvasMouseEvent + node: LGraphNode + }) { + if (this.options.read_only) return false + + const { e, node } = options + const width = this.width || node.size[0] + const x = e.canvasX - node.pos[0] + + // Calculate new value based on drag position + const slideFactor = clamp((x - 15) / (width - 30), 0, 1) + const newValue = this.options.min + (this.options.max - this.options.min) * slideFactor + + if (newValue !== this.value) { + this.value = newValue + return true + } + return false + } +}