mirror of
https://github.com/Comfy-Org/ComfyUI_frontend.git
synced 2026-04-29 02:32:18 +00:00
Implement BooleanWidget (#466)
* Implement BooleanWidget * Merge function of addWidget * Class conversion * nit
This commit is contained in:
@@ -65,6 +65,8 @@ import { alignNodes, distributeNodes, getBoundaryNodes } from "./utils/arrange"
|
|||||||
import { Reroute, type RerouteId } from "./Reroute"
|
import { Reroute, type RerouteId } from "./Reroute"
|
||||||
import { getAllNestedItems, findFirstNode } from "./utils/collections"
|
import { getAllNestedItems, findFirstNode } from "./utils/collections"
|
||||||
import { CanvasPointer } from "./CanvasPointer"
|
import { CanvasPointer } from "./CanvasPointer"
|
||||||
|
import { BooleanWidget } from "./widgets/BooleanWidget"
|
||||||
|
import { toClass } from "./utils/type"
|
||||||
|
|
||||||
interface IShowSearchOptions {
|
interface IShowSearchOptions {
|
||||||
node_to?: LGraphNode
|
node_to?: LGraphNode
|
||||||
@@ -5954,39 +5956,7 @@ export class LGraphCanvas {
|
|||||||
}
|
}
|
||||||
break
|
break
|
||||||
case "toggle":
|
case "toggle":
|
||||||
ctx.textAlign = "left"
|
toClass(BooleanWidget, w).drawWidget(ctx, { y, width: widget_width, show_text, margin })
|
||||||
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 && !w.disabled) ctx.stroke()
|
|
||||||
ctx.fillStyle = w.value ? "#89A" : "#333"
|
|
||||||
ctx.beginPath()
|
|
||||||
ctx.arc(
|
|
||||||
widget_width - margin * 2,
|
|
||||||
y + H * 0.5,
|
|
||||||
H * 0.36,
|
|
||||||
0,
|
|
||||||
Math.PI * 2,
|
|
||||||
)
|
|
||||||
ctx.fill()
|
|
||||||
if (show_text) {
|
|
||||||
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 = w.value ? text_color : secondary_text_color
|
|
||||||
ctx.textAlign = "right"
|
|
||||||
ctx.fillText(
|
|
||||||
w.value ? w.options.on || "true" : w.options.off || "false",
|
|
||||||
widget_width - 40,
|
|
||||||
y + H * 0.7,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
break
|
break
|
||||||
case "slider": {
|
case "slider": {
|
||||||
ctx.fillStyle = background_color
|
ctx.fillStyle = background_color
|
||||||
|
|||||||
@@ -32,6 +32,7 @@ import { BadgePosition, LGraphBadge } from "./LGraphBadge"
|
|||||||
import { type LGraphNodeConstructor, LiteGraph } from "./litegraph"
|
import { type LGraphNodeConstructor, LiteGraph } from "./litegraph"
|
||||||
import { isInRectangle, isInRect, snapPoint } from "./measure"
|
import { isInRectangle, isInRect, snapPoint } from "./measure"
|
||||||
import { LLink } from "./LLink"
|
import { LLink } from "./LLink"
|
||||||
|
import { BooleanWidget } from "./widgets/BooleanWidget"
|
||||||
|
|
||||||
export type NodeId = number | string
|
export type NodeId = number | string
|
||||||
|
|
||||||
@@ -1655,15 +1656,26 @@ export class LGraphNode implements Positionable, IPinnable {
|
|||||||
if (type == "combo" && !w.options.values) {
|
if (type == "combo" && !w.options.values) {
|
||||||
throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"
|
throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"
|
||||||
}
|
}
|
||||||
this.widgets.push(w)
|
|
||||||
|
const widget = this.addCustomWidget(w)
|
||||||
this.setSize(this.computeSize())
|
this.setSize(this.computeSize())
|
||||||
return w
|
return widget
|
||||||
}
|
}
|
||||||
|
|
||||||
addCustomWidget(custom_widget: IWidget): IWidget {
|
addCustomWidget(custom_widget: IWidget): IWidget {
|
||||||
this.widgets ||= []
|
this.widgets ||= []
|
||||||
this.widgets.push(custom_widget)
|
|
||||||
return custom_widget
|
let widget: IWidget
|
||||||
|
switch (custom_widget.type) {
|
||||||
|
case "toggle":
|
||||||
|
widget = new BooleanWidget(custom_widget)
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
widget = custom_widget
|
||||||
|
}
|
||||||
|
|
||||||
|
this.widgets.push(widget)
|
||||||
|
return widget
|
||||||
}
|
}
|
||||||
|
|
||||||
move(deltaX: number, deltaY: number): void {
|
move(deltaX: number, deltaY: number): void {
|
||||||
|
|||||||
@@ -159,5 +159,5 @@ export interface IBaseWidget<TElement extends HTMLElement = HTMLElement> {
|
|||||||
* @return Returning `true` from this callback forces Litegraph to ignore the event and
|
* @return Returning `true` from this callback forces Litegraph to ignore the event and
|
||||||
* not process it any further.
|
* not process it any further.
|
||||||
*/
|
*/
|
||||||
onPointerDown(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean
|
onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean
|
||||||
}
|
}
|
||||||
|
|||||||
9
src/utils/type.ts
Normal file
9
src/utils/type.ts
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
/**
|
||||||
|
* Converts a plain object to a class instance if it is not already an instance of the class.
|
||||||
|
* @param cls The class to convert to
|
||||||
|
* @param obj The object to convert
|
||||||
|
* @returns The class instance
|
||||||
|
*/
|
||||||
|
export function toClass<P, C>(cls: new (plain: P) => C, obj: P | C): C {
|
||||||
|
return obj instanceof cls ? obj : new cls(obj as P)
|
||||||
|
}
|
||||||
73
src/widgets/BaseWidget.ts
Normal file
73
src/widgets/BaseWidget.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { Point } from "@/interfaces"
|
||||||
|
import { LiteGraph } from "@/litegraph"
|
||||||
|
import type { CanvasPointer, LGraphCanvas, LGraphNode, Size } from "@/litegraph"
|
||||||
|
import type { CanvasMouseEvent, CanvasPointerEvent } from "@/types/events"
|
||||||
|
import type { IBaseWidget, IWidget, IWidgetOptions } from "@/types/widgets"
|
||||||
|
|
||||||
|
export abstract class BaseWidget implements IBaseWidget {
|
||||||
|
linkedWidgets?: IWidget[]
|
||||||
|
options: IWidgetOptions<unknown>
|
||||||
|
marker?: number
|
||||||
|
label?: string
|
||||||
|
clicked?: boolean
|
||||||
|
name?: string
|
||||||
|
type?: "string" | "number" | "combo" | "button" | "toggle" | "slider" | "text" | "multiline" | "custom"
|
||||||
|
value?: string | number | boolean | object
|
||||||
|
y?: number
|
||||||
|
last_y?: number
|
||||||
|
width?: number
|
||||||
|
disabled?: boolean
|
||||||
|
hidden?: boolean
|
||||||
|
advanced?: boolean
|
||||||
|
tooltip?: string
|
||||||
|
element?: HTMLElement
|
||||||
|
callback?(
|
||||||
|
value: any,
|
||||||
|
canvas?: LGraphCanvas,
|
||||||
|
node?: LGraphNode,
|
||||||
|
pos?: Point,
|
||||||
|
e?: CanvasMouseEvent,
|
||||||
|
): void
|
||||||
|
mouse?(event: CanvasPointerEvent, pointerOffset: Point, node: LGraphNode): boolean
|
||||||
|
draw?(
|
||||||
|
ctx: CanvasRenderingContext2D,
|
||||||
|
node: LGraphNode,
|
||||||
|
widget_width: number,
|
||||||
|
y: number,
|
||||||
|
H: number,
|
||||||
|
): void
|
||||||
|
computeSize?(width?: number): Size
|
||||||
|
onPointerDown?(pointer: CanvasPointer, node: LGraphNode, canvas: LGraphCanvas): boolean
|
||||||
|
|
||||||
|
constructor(widget: IBaseWidget) {
|
||||||
|
Object.assign(this, widget)
|
||||||
|
this.options = widget.options
|
||||||
|
}
|
||||||
|
|
||||||
|
get outline_color() {
|
||||||
|
return this.advanced ? LiteGraph.WIDGET_ADVANCED_OUTLINE_COLOR : LiteGraph.WIDGET_OUTLINE_COLOR
|
||||||
|
}
|
||||||
|
|
||||||
|
get background_color() {
|
||||||
|
return LiteGraph.WIDGET_BGCOLOR
|
||||||
|
}
|
||||||
|
|
||||||
|
get height() {
|
||||||
|
return LiteGraph.NODE_WIDGET_HEIGHT
|
||||||
|
}
|
||||||
|
|
||||||
|
get text_color() {
|
||||||
|
return LiteGraph.WIDGET_TEXT_COLOR
|
||||||
|
}
|
||||||
|
|
||||||
|
get secondary_text_color() {
|
||||||
|
return LiteGraph.WIDGET_SECONDARY_TEXT_COLOR
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract drawWidget(ctx: CanvasRenderingContext2D, options: {
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
show_text?: boolean
|
||||||
|
margin?: number
|
||||||
|
}): void
|
||||||
|
}
|
||||||
68
src/widgets/BooleanWidget.ts
Normal file
68
src/widgets/BooleanWidget.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
import { IBooleanWidget } from "@/types/widgets"
|
||||||
|
import { BaseWidget } 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
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Draws the widget
|
||||||
|
* @param ctx - The canvas context
|
||||||
|
* @param options - The options for drawing the widget
|
||||||
|
*
|
||||||
|
* @note Not naming this `draw` as `draw` conflicts with the `draw` method in
|
||||||
|
* custom widgets.
|
||||||
|
*/
|
||||||
|
override drawWidget(ctx: CanvasRenderingContext2D, options: {
|
||||||
|
y: number
|
||||||
|
width: number
|
||||||
|
show_text?: boolean
|
||||||
|
margin?: number
|
||||||
|
}) {
|
||||||
|
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 && !this.disabled) ctx.stroke()
|
||||||
|
ctx.fillStyle = this.value ? "#89A" : "#333"
|
||||||
|
ctx.beginPath()
|
||||||
|
ctx.arc(
|
||||||
|
widget_width - margin * 2,
|
||||||
|
y + H * 0.5,
|
||||||
|
H * 0.36,
|
||||||
|
0,
|
||||||
|
Math.PI * 2,
|
||||||
|
)
|
||||||
|
ctx.fill()
|
||||||
|
if (show_text) {
|
||||||
|
ctx.fillStyle = this.secondary_text_color
|
||||||
|
const label = this.label || this.name
|
||||||
|
if (label != null) {
|
||||||
|
ctx.fillText(label, margin * 2, y + H * 0.7)
|
||||||
|
}
|
||||||
|
ctx.fillStyle = this.value ? this.text_color : this.secondary_text_color
|
||||||
|
ctx.textAlign = "right"
|
||||||
|
ctx.fillText(
|
||||||
|
this.value ? this.options.on || "true" : this.options.off || "false",
|
||||||
|
widget_width - 40,
|
||||||
|
y + H * 0.7,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user