mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-02-27 18:24:11 +00:00
Fade invalid increment/decrement widget buttons (#978)
When a widget cannot be incremented/decremented further, show a disabled effect on the widget buttons. https://github.com/user-attachments/assets/7770a9f1-31f6-430b-ab02-420327621148
This commit is contained in:
@@ -66,6 +66,7 @@ export class LiteGraphGlobal {
|
||||
WIDGET_ADVANCED_OUTLINE_COLOR = "rgba(56, 139, 253, 0.8)"
|
||||
WIDGET_TEXT_COLOR = "#DDD"
|
||||
WIDGET_SECONDARY_TEXT_COLOR = "#999"
|
||||
WIDGET_DISABLED_TEXT_COLOR = "#666"
|
||||
|
||||
LINK_COLOR = "#9A9"
|
||||
EVENT_LINK_COLOR = "#A86"
|
||||
|
||||
57
src/widgets/BaseSteppedWidget.ts
Normal file
57
src/widgets/BaseSteppedWidget.ts
Normal file
@@ -0,0 +1,57 @@
|
||||
import { BaseWidget, type WidgetEventOptions } from "./BaseWidget"
|
||||
|
||||
/**
|
||||
* Base class for widgets that have increment and decrement buttons.
|
||||
*/
|
||||
export abstract class BaseSteppedWidget extends BaseWidget {
|
||||
/**
|
||||
* Whether the widget can increment its value
|
||||
* @returns `true` if the widget can increment its value, otherwise `false`
|
||||
*/
|
||||
abstract canIncrement(): boolean
|
||||
/**
|
||||
* Whether the widget can decrement its value
|
||||
* @returns `true` if the widget can decrement its value, otherwise `false`
|
||||
*/
|
||||
abstract canDecrement(): boolean
|
||||
/**
|
||||
* Increment the value of the widget
|
||||
* @param options The options for the widget event
|
||||
*/
|
||||
abstract incrementValue(options: WidgetEventOptions): void
|
||||
/**
|
||||
* Decrement the value of the widget
|
||||
* @param options The options for the widget event
|
||||
*/
|
||||
abstract decrementValue(options: WidgetEventOptions): void
|
||||
|
||||
/**
|
||||
* Draw the arrow buttons for the widget
|
||||
* @param ctx The canvas rendering context
|
||||
* @param margin The margin of the widget
|
||||
* @param y The y position of the widget
|
||||
* @param width The width of the widget
|
||||
*/
|
||||
drawArrowButtons(ctx: CanvasRenderingContext2D, margin: number, y: number, width: number) {
|
||||
const { height, text_color, disabledTextColor } = this
|
||||
const { arrowMargin, arrowWidth } = BaseWidget
|
||||
const arrowTipX = margin + arrowMargin
|
||||
const arrowInnerX = arrowTipX + arrowWidth
|
||||
|
||||
// Draw left arrow
|
||||
ctx.fillStyle = this.canDecrement() ? text_color : disabledTextColor
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(arrowInnerX, y + 5)
|
||||
ctx.lineTo(arrowTipX, y + height * 0.5)
|
||||
ctx.lineTo(arrowInnerX, y + height - 5)
|
||||
ctx.fill()
|
||||
|
||||
// Draw right arrow
|
||||
ctx.fillStyle = this.canIncrement() ? text_color : disabledTextColor
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(width - arrowInnerX, y + 5)
|
||||
ctx.lineTo(width - arrowTipX, y + height * 0.5)
|
||||
ctx.lineTo(width - arrowInnerX, y + height - 5)
|
||||
ctx.fill()
|
||||
}
|
||||
}
|
||||
@@ -88,6 +88,10 @@ export abstract class BaseWidget implements IBaseWidget {
|
||||
return LiteGraph.WIDGET_SECONDARY_TEXT_COLOR
|
||||
}
|
||||
|
||||
get disabledTextColor() {
|
||||
return LiteGraph.WIDGET_DISABLED_TEXT_COLOR
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the widget
|
||||
* @param ctx The canvas context
|
||||
@@ -97,28 +101,6 @@ export abstract class BaseWidget implements IBaseWidget {
|
||||
*/
|
||||
abstract drawWidget(ctx: CanvasRenderingContext2D, options: DrawWidgetOptions): void
|
||||
|
||||
drawArrowButtons(ctx: CanvasRenderingContext2D, margin: number, y: number, width: number) {
|
||||
const { height } = this
|
||||
const { arrowMargin, arrowWidth } = BaseWidget
|
||||
const arrowTipX = margin + arrowMargin
|
||||
const arrowInnerX = arrowTipX + arrowWidth
|
||||
|
||||
// Draw left arrow
|
||||
ctx.fillStyle = this.text_color
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(arrowInnerX, y + 5)
|
||||
ctx.lineTo(arrowTipX, y + height * 0.5)
|
||||
ctx.lineTo(arrowInnerX, y + height - 5)
|
||||
ctx.fill()
|
||||
|
||||
// Draw right arrow
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(width - arrowInnerX, y + 5)
|
||||
ctx.lineTo(width - arrowTipX, y + height * 0.5)
|
||||
ctx.lineTo(width - arrowInnerX, y + height - 5)
|
||||
ctx.fill()
|
||||
}
|
||||
|
||||
/**
|
||||
* Handles the click event for the widget
|
||||
* @param options The options for handling the click event
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
import type { IComboWidget, IWidgetOptions } from "@/types/widgets"
|
||||
|
||||
import { LiteGraph } from "@/litegraph"
|
||||
import { clamp, LiteGraph } from "@/litegraph"
|
||||
|
||||
import { BaseSteppedWidget } from "./BaseSteppedWidget"
|
||||
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
|
||||
|
||||
export class ComboWidget extends BaseWidget implements IComboWidget {
|
||||
type Values = string[] | Record<string, string>
|
||||
|
||||
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
|
||||
declare options: IWidgetOptions<string>
|
||||
// @ts-expect-error Workaround for Record<string, string> not being typed in IWidgetOptions
|
||||
declare options: Omit<IWidgetOptions<string>, "values"> & { values: Values }
|
||||
|
||||
constructor(widget: IComboWidget) {
|
||||
super(widget)
|
||||
@@ -16,6 +24,66 @@ export class ComboWidget extends BaseWidget implements IComboWidget {
|
||||
this.value = widget.value
|
||||
}
|
||||
|
||||
#getValues(): Values {
|
||||
const { values } = this.options
|
||||
if (values == null) throw new Error("[ComboWidget]: values is required")
|
||||
|
||||
return typeof values === "function"
|
||||
// @ts-expect-error handle () => string[] type that is not typed in IWidgetOptions
|
||||
? values(this, node)
|
||||
: values
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the value is {@link Array.at at} the given index in the combo list.
|
||||
* @param index The index to check against
|
||||
* @returns `true` if the value is at the given index, otherwise `false`.
|
||||
*/
|
||||
#valueIsAt(index: number): boolean {
|
||||
const { values } = this.options
|
||||
// If using legacy duck-typed method, just return true
|
||||
if (typeof values === "function") return true
|
||||
|
||||
const valuesArray = toArray(values)
|
||||
return this.value === valuesArray.at(index)
|
||||
}
|
||||
|
||||
override canIncrement(): boolean {
|
||||
return !this.#valueIsAt(-1)
|
||||
}
|
||||
|
||||
override canDecrement(): boolean {
|
||||
return !this.#valueIsAt(0)
|
||||
}
|
||||
|
||||
override incrementValue(options: WidgetEventOptions): void {
|
||||
this.#tryChangeValue(1, options)
|
||||
}
|
||||
|
||||
override decrementValue(options: WidgetEventOptions): void {
|
||||
this.#tryChangeValue(-1, options)
|
||||
}
|
||||
|
||||
#tryChangeValue(delta: number, options: WidgetEventOptions): void {
|
||||
const values = this.#getValues()
|
||||
const indexedValues = toArray(values)
|
||||
|
||||
// avoids double click event
|
||||
options.canvas.last_mouseclick = 0
|
||||
|
||||
const foundIndex = typeof values === "object"
|
||||
? indexedValues.indexOf(String(this.value)) + delta
|
||||
// @ts-expect-error handle non-string values
|
||||
: indexedValues.indexOf(this.value) + delta
|
||||
|
||||
const index = clamp(foundIndex, 0, indexedValues.length - 1)
|
||||
|
||||
const value = Array.isArray(values)
|
||||
? values[index]
|
||||
: index
|
||||
this.setValue(value, options)
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws the widget
|
||||
* @param ctx The canvas context
|
||||
@@ -123,45 +191,14 @@ export class ComboWidget extends BaseWidget implements IComboWidget {
|
||||
const width = this.width || node.size[0]
|
||||
|
||||
// Determine if clicked on left/right arrows
|
||||
const delta = x < 40
|
||||
? -1
|
||||
: (x > width - 40
|
||||
? 1
|
||||
: 0)
|
||||
if (x < 40) return this.decrementValue({ e, node, canvas })
|
||||
if (x > width - 40) return this.incrementValue({ e, node, canvas })
|
||||
|
||||
// Get values
|
||||
let values = this.options.values
|
||||
if (typeof values === "function") {
|
||||
// @ts-expect-error handle () => string[] type that is not typed in IWidgetOptions
|
||||
values = values(this, node)
|
||||
}
|
||||
// @ts-expect-error Record<string, string> is not typed in IWidgetOptions
|
||||
const values_list = Array.isArray(values) ? values : Object.keys(values)
|
||||
|
||||
// Handle left/right arrow clicks
|
||||
if (delta) {
|
||||
let index = -1
|
||||
// avoids double click event
|
||||
canvas.last_mouseclick = 0
|
||||
index = typeof values === "object"
|
||||
? values_list.indexOf(String(this.value)) + delta
|
||||
// @ts-expect-error handle non-string values
|
||||
: values_list.indexOf(this.value) + delta
|
||||
|
||||
if (index >= values_list.length) index = values_list.length - 1
|
||||
if (index < 0) index = 0
|
||||
|
||||
this.setValue(
|
||||
Array.isArray(values)
|
||||
? values[index]
|
||||
: index,
|
||||
{ e, node, canvas },
|
||||
)
|
||||
return
|
||||
}
|
||||
// Otherwise, show dropdown menu
|
||||
const values = this.#getValues()
|
||||
const values_list = toArray(values)
|
||||
|
||||
// Handle center click - show dropdown menu
|
||||
// @ts-expect-error Record<string, string> is not typed in IWidgetOptions
|
||||
const text_values = values != values_list ? Object.values(values) : values
|
||||
new LiteGraph.ContextMenu(text_values, {
|
||||
scale: Math.max(1, canvas.ds.scale),
|
||||
|
||||
@@ -2,9 +2,10 @@ import type { INumericWidget, IWidgetOptions } from "@/types/widgets"
|
||||
|
||||
import { getWidgetStep } from "@/utils/widget"
|
||||
|
||||
import { BaseSteppedWidget } from "./BaseSteppedWidget"
|
||||
import { BaseWidget, type DrawWidgetOptions, type WidgetEventOptions } from "./BaseWidget"
|
||||
|
||||
export class NumberWidget extends BaseWidget implements INumericWidget {
|
||||
export class NumberWidget extends BaseSteppedWidget implements INumericWidget {
|
||||
// INumberWidget properties
|
||||
declare type: "number"
|
||||
declare value: number
|
||||
@@ -16,6 +17,24 @@ export class NumberWidget extends BaseWidget implements INumericWidget {
|
||||
this.value = widget.value
|
||||
}
|
||||
|
||||
override canIncrement(): boolean {
|
||||
const { max } = this.options
|
||||
return max == null || this.value < max
|
||||
}
|
||||
|
||||
override canDecrement(): boolean {
|
||||
const { min } = this.options
|
||||
return min == null || this.value > min
|
||||
}
|
||||
|
||||
override incrementValue(options: WidgetEventOptions): void {
|
||||
this.setValue(this.value + getWidgetStep(this.options), options)
|
||||
}
|
||||
|
||||
override decrementValue(options: WidgetEventOptions): void {
|
||||
this.setValue(this.value - getWidgetStep(this.options), options)
|
||||
}
|
||||
|
||||
override setValue(value: number, options: WidgetEventOptions) {
|
||||
let newValue = value
|
||||
if (this.options.min != null && newValue < this.options.min) {
|
||||
@@ -126,8 +145,7 @@ export class NumberWidget extends BaseWidget implements INumericWidget {
|
||||
* Handles drag events for the number widget
|
||||
* @param options The options for handling the drag event
|
||||
*/
|
||||
override onDrag(options: WidgetEventOptions) {
|
||||
const { e, node, canvas } = options
|
||||
override onDrag({ e, node, canvas }: WidgetEventOptions) {
|
||||
const width = this.width || node.width
|
||||
const x = e.canvasX - node.pos[0]
|
||||
const delta = x < 40
|
||||
|
||||
@@ -133,6 +133,7 @@ LiteGraphGlobal {
|
||||
"VERTICAL_LAYOUT": "vertical",
|
||||
"WIDGET_ADVANCED_OUTLINE_COLOR": "rgba(56, 139, 253, 0.8)",
|
||||
"WIDGET_BGCOLOR": "#222",
|
||||
"WIDGET_DISABLED_TEXT_COLOR": "#666",
|
||||
"WIDGET_OUTLINE_COLOR": "#666",
|
||||
"WIDGET_SECONDARY_TEXT_COLOR": "#999",
|
||||
"WIDGET_TEXT_COLOR": "#DDD",
|
||||
|
||||
Reference in New Issue
Block a user