Add litegraph input indicator helper class (#842)

Example usage with ComfyUI_frontend, via console / devtools:

```ts
const inputIndicators = new InputIndicators(app.canvas)
// Dispose:
inputIndicators.dispose()
```
This commit is contained in:
filtered
2025-03-24 11:43:36 +11:00
committed by GitHub
parent 361ae2c589
commit c8bd5e43dd
4 changed files with 171 additions and 0 deletions

View File

@@ -1,5 +1,6 @@
import type { Dictionary, ISlotType, Rect, WhenNullish } from "./interfaces"
import { InputIndicators } from "./canvas/InputIndicators"
import { ContextMenu } from "./ContextMenu"
import { CurveEditor } from "./CurveEditor"
import { DragAndScale } from "./DragAndScale"
@@ -270,6 +271,7 @@ export class LiteGraphGlobal {
ContextMenu = ContextMenu
CurveEditor = CurveEditor
Reroute = Reroute
InputIndicators = InputIndicators
onNodeTypeRegistered?(type: string, base_class: typeof LGraphNode): void
onNodeTypeReplaced?(type: string, base_class: typeof LGraphNode, prev: unknown): void

View File

@@ -0,0 +1,167 @@
import type { LGraphCanvas } from "@/LGraphCanvas"
/**
* A class that can be added to the render cycle to show pointer / keyboard status symbols.
*
* Used to create videos of feature changes.
*
* Example usage with ComfyUI_frontend, via console / devtools:
*
* ```ts
* const inputIndicators = new InputIndicators(canvas)
* // Dispose:
* inputIndicators.dispose()
* ```
*/
export class InputIndicators {
// #region config
radius = 8
startAngle = 0
endAngle = Math.PI * 2
inactiveColour = "#ffffff10"
colour1 = "#ff5f00"
colour2 = "#00ff7c"
colour3 = "#dea7ff"
fontString = "bold 12px Arial"
// #endregion
// #region state
enabled: boolean = true
shiftDown: boolean = false
undoDown: boolean = false
redoDown: boolean = false
ctrlDown: boolean = false
altDown: boolean = false
mouse0Down: boolean = false
mouse1Down: boolean = false
mouse2Down: boolean = false
x: number = 0
y: number = 0
// #endregion
controller?: AbortController
constructor(public canvas: LGraphCanvas) {
this.controller = new AbortController()
const { signal } = this.controller
const element = canvas.canvas
const options = { capture: true, signal } satisfies AddEventListenerOptions
element.addEventListener("pointerdown", this.#onPointerDownOrMove, options)
element.addEventListener("pointermove", this.#onPointerDownOrMove, options)
element.addEventListener("pointerup", this.#onPointerUp, options)
element.addEventListener("keydown", this.#onKeyDownOrUp, options)
document.addEventListener("keyup", this.#onKeyDownOrUp, options)
const origDrawFrontCanvas = canvas.drawFrontCanvas.bind(canvas)
signal.addEventListener("abort", () => {
canvas.drawFrontCanvas = origDrawFrontCanvas
})
canvas.drawFrontCanvas = () => {
origDrawFrontCanvas()
this.draw()
}
}
#onPointerDownOrMove = this.onPointerDownOrMove.bind(this)
onPointerDownOrMove(e: MouseEvent): void {
this.mouse0Down = (e.buttons & 1) === 1
this.mouse1Down = (e.buttons & 4) === 4
this.mouse2Down = (e.buttons & 2) === 2
this.x = e.clientX
this.y = e.clientY
this.canvas.setDirty(true)
}
#onPointerUp = this.onPointerUp.bind(this)
onPointerUp(): void {
this.mouse0Down = false
this.mouse1Down = false
this.mouse2Down = false
}
#onKeyDownOrUp = this.onKeyDownOrUp.bind(this)
onKeyDownOrUp(e: KeyboardEvent): void {
this.ctrlDown = e.ctrlKey
this.altDown = e.altKey
this.shiftDown = e.shiftKey
this.undoDown = e.ctrlKey && e.code === "KeyZ" && e.type === "keydown"
this.redoDown = e.ctrlKey && e.code === "KeyY" && e.type === "keydown"
}
draw() {
const {
canvas: { ctx },
radius,
startAngle,
endAngle,
x,
y,
inactiveColour,
colour1,
colour2,
colour3,
fontString,
} = this
const { fillStyle, font } = ctx
const mouseDotX = x
const mouseDotY = y - 80
const textX = mouseDotX
const textY = mouseDotY - 15
ctx.font = fontString
textMarker(textX + 0, textY, "Shift", this.shiftDown ? colour1 : inactiveColour)
textMarker(textX + 45, textY + 20, "Alt", this.altDown ? colour2 : inactiveColour)
textMarker(textX + 30, textY, "Control", this.ctrlDown ? colour3 : inactiveColour)
textMarker(textX - 30, textY, "↩️", this.undoDown ? "#000" : "transparent")
textMarker(textX + 45, textY, "↪️", this.redoDown ? "#000" : "transparent")
ctx.beginPath()
drawDot(mouseDotX, mouseDotY)
drawDot(mouseDotX + 15, mouseDotY)
drawDot(mouseDotX + 30, mouseDotY)
ctx.fillStyle = inactiveColour
ctx.fill()
const leftButtonColour = this.mouse0Down ? colour1 : inactiveColour
const middleButtonColour = this.mouse1Down ? colour2 : inactiveColour
const rightButtonColour = this.mouse2Down ? colour3 : inactiveColour
if (this.mouse0Down) mouseMarker(mouseDotX, mouseDotY, leftButtonColour)
if (this.mouse1Down) mouseMarker(mouseDotX + 15, mouseDotY, middleButtonColour)
if (this.mouse2Down) mouseMarker(mouseDotX + 30, mouseDotY, rightButtonColour)
ctx.fillStyle = fillStyle
ctx.font = font
function textMarker(x: number, y: number, text: string, colour: string) {
ctx.fillStyle = colour
ctx.fillText(text, x, y)
}
function mouseMarker(x: number, y: number, colour: string) {
ctx.beginPath()
ctx.fillStyle = colour
drawDot(x, y)
ctx.fill()
}
function drawDot(x: number, y: number) {
ctx.arc(x, y, radius, startAngle, endAngle)
}
}
dispose() {
this.controller?.abort()
this.controller = undefined
}
}

View File

@@ -87,6 +87,7 @@ export interface LGraphNodeConstructor<T extends LGraphNode = LGraphNode> {
// End backwards compat
export { InputIndicators } from "./canvas/InputIndicators"
export { isOverNodeInput, isOverNodeOutput } from "./canvas/measureSlots"
export { CanvasPointer } from "./CanvasPointer"
export { ContextMenu } from "./ContextMenu"

View File

@@ -29,6 +29,7 @@ LiteGraphGlobal {
"Globals": {},
"HIDDEN_LINK": -1,
"INPUT": 1,
"InputIndicators": [Function],
"LEFT": 3,
"LGraph": [Function],
"LGraphCanvas": [Function],