File size: 7,962 Bytes
4450790
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
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 `

      <p>

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

        label to your workflow.

      </p>

      <p>

        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.

      <p>

      <ul>

        <li>

          <p>

            <strong>Pro tip #1:</strong> You can add multiline text from the properties panel

            <i>(because ComfyUI let's you shift + enter there, only)</i>.

          </p>

        </li>

        <li>

          <p>

            <strong>Pro tip #2:</strong> You can use ComfyUI's native "pin" option in the

            right-click menu to make the label stick to the workflow and clicks to "go through".

            You can right-click at any time to unpin.

          </p>

        </li>

        <li>

          <p>

            <strong>Pro tip #3:</strong> Color values are hexidecimal strings, like "#FFFFFF" for

            white, or "#660000" for dark red. You can supply a 7th & 8th value (or 5th if using

            shorthand) to create a transluscent color. For instance, "#FFFFFF88" is semi-transparent

            white.

          </p>

        </li>

      </ul>`;
  }
}

/**

 * 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 <T extends LGraphNode>(
  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();
  },
});