mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-06 16:10:09 +00:00
Implement SliderWidget (#485)
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -21,6 +21,14 @@ export interface IWidgetOptions<TValue = unknown> extends Record<string, unknown
|
||||
callback?: IWidget["callback"]
|
||||
}
|
||||
|
||||
export interface IWidgetSliderOptions extends IWidgetOptions<number> {
|
||||
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"
|
||||
|
||||
139
src/widgets/SliderWidget.ts
Normal file
139
src/widgets/SliderWidget.ts
Normal file
@@ -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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user