import { app } from "scripts/app.js"; import { RgthreeBaseVirtualNodeConstructor } from "typings/rgthree.js"; import { RgthreeBaseVirtualNode } from "./base_node.js"; import { NodeTypesString } from "./constants.js"; import type { LGraphCanvas as TLGraphCanvas, LGraphNode, AdjustedMouseEvent, Vector2, } from "typings/litegraph.js"; import { rgthree } from "./rgthree.js"; /** * A label node that allows you to put floating text anywhere on the graph. The text is the `Title` * and the font size, family, color, alignment as well as a background color, padding, and * background border radius can all be adjusted in the properties. Multiline text can be added from * the properties panel (because ComfyUI let's you shift + enter there, only). */ export class Label extends RgthreeBaseVirtualNode { static override type = NodeTypesString.LABEL; static override title = NodeTypesString.LABEL; override comfyClass = NodeTypesString.LABEL; static readonly title_mode = LiteGraph.NO_TITLE; static collapsable = false; static "@fontSize" = { type: "number" }; static "@fontFamily" = { type: "string" }; static "@fontColor" = { type: "string" }; static "@textAlign" = { type: "combo", values: ["left", "center", "right"] }; static "@backgroundColor" = { type: "string" }; static "@padding" = { type: "number" }; static "@borderRadius" = { type: "number" }; override resizable = false; constructor(title = Label.title) { super(title); this.properties["fontSize"] = 12; this.properties["fontFamily"] = "Arial"; this.properties["fontColor"] = "#ffffff"; this.properties["textAlign"] = "left"; this.properties["backgroundColor"] = "transparent"; this.properties["padding"] = 0; this.properties["borderRadius"] = 0; this.color = "#fff0"; this.bgcolor = "#fff0"; this.onConstructed(); } draw(ctx: CanvasRenderingContext2D) { this.flags = this.flags || {}; this.flags.allow_interaction = !this.flags.pinned; ctx.save(); this.color = "#fff0"; this.bgcolor = "#fff0"; const fontColor = this.properties["fontColor"] || "#ffffff"; const backgroundColor = this.properties["backgroundColor"] || ""; ctx.font = `${Math.max(this.properties["fontSize"] || 0, 1)}px ${ this.properties["fontFamily"] ?? "Arial" }`; const padding = Number(this.properties["padding"]) ?? 0; const lines = this.title.replace(/\n*$/, "").split("\n"); const maxWidth = Math.max(...lines.map((s) => ctx.measureText(s).width)); this.size[0] = maxWidth + padding * 2; this.size[1] = this.properties["fontSize"] * lines.length + padding * 2; if (backgroundColor) { ctx.beginPath(); const borderRadius = Number(this.properties["borderRadius"]) || 0; ctx.roundRect(0, 0, this.size[0], this.size[1], [borderRadius]); ctx.fillStyle = backgroundColor; ctx.fill(); } ctx.textAlign = "left"; let textX = padding; if (this.properties["textAlign"] === "center") { ctx.textAlign = "center"; textX = this.size[0] / 2; } else if (this.properties["textAlign"] === "right") { ctx.textAlign = "right"; textX = this.size[0] - padding; } ctx.textBaseline = "top"; ctx.fillStyle = fontColor; let currentY = padding; for (let i = 0; i < lines.length; i++) { ctx.fillText(lines[i] || " ", textX, currentY); currentY += this.properties["fontSize"]; } ctx.restore(); } override onDblClick(event: AdjustedMouseEvent, pos: Vector2, canvas: TLGraphCanvas) { // Since everything we can do here is in the properties, let's pop open the properties panel. LGraphCanvas.active_canvas.showShowNodePanel(this); } override onShowCustomPanelInfo(panel: HTMLElement) { panel.querySelector('div.property[data-property="Mode"]')?.remove(); panel.querySelector('div.property[data-property="Color"]')?.remove(); } override inResizeCorner(x: number, y: number) { // A little ridiculous there's both a resizable property and this method separately to draw the // resize icon... return this.resizable; } override getHelp() { return `

The rgthree-comfy ${this.type!.replace("(rgthree)", "")} node allows you to add a floating label to your workflow.

The text shown is the "Title" of the node and you can adjust the the font size, font family, font color, text alignment as well as a background color, padding, and background border radius from the node's properties. You can double-click the node to open the properties panel.

`; } } /** * We override the drawNode to see if we're drawing our label and, if so, hijack it so we can draw * it like we want. We also do call out to oldDrawNode, which takes care of very minimal things, * like a select box. */ const oldDrawNode = LGraphCanvas.prototype.drawNode; LGraphCanvas.prototype.drawNode = function (node: LGraphNode, ctx: CanvasRenderingContext2D) { if (node.constructor === Label) { // These get set very aggressively; maybe an extension is doing it. We'll just clear them out // each time. (node as Label).bgcolor = "transparent"; (node as Label).color = "transparent"; const v = oldDrawNode.apply(this, arguments as any); (node as Label).draw(ctx); return v; } const v = oldDrawNode.apply(this, arguments as any); return v; }; /** * We override LGraph getNodeOnPos to see if we're being called while also processing a mouse down * and, if so, filter out any label nodes on labels that are pinned. This makes the click go * "through" the label. We still allow right clicking (so you can unpin) and double click for the * properties panel, though that takes two double clicks (one to select, one to actually double * click). */ const oldGetNodeOnPos = LGraph.prototype.getNodeOnPos; LGraph.prototype.getNodeOnPos = function ( x: number, y: number, nodes_list?: LGraphNode[], margin?: number, ) { if ( // processMouseDown always passes in the nodes_list nodes_list && rgthree.processingMouseDown && rgthree.lastAdjustedMouseEvent?.type.includes("down") && rgthree.lastAdjustedMouseEvent?.which === 1 ) { // Using the same logic from LGraphCanvas processMouseDown, let's see if we consider this a // double click. let isDoubleClick = LiteGraph.getTime() - LGraphCanvas.active_canvas.last_mouseclick < 300; if (!isDoubleClick) { nodes_list = [...nodes_list].filter((n) => !(n instanceof Label) || !n.flags?.pinned); } } return oldGetNodeOnPos.apply(this, [x, y, nodes_list, margin]) as T | null; }; // Register the extension. app.registerExtension({ name: "rgthree.Label", registerCustomNodes() { Label.setUp(); }, });