Files
Trung0246-ComfyUI-0246/web/js/widgets.js
T
2024-01-09 18:43:30 -08:00

4228 lines
139 KiB
JavaScript

export const HELP = {
"highway": `
<span>
The _query syntax goes as follow:
</span>
<ul>
<li>
<code>&gt;name</code>
<br>
- Input variable.
</li>
<li>
<code>&lt;name</code>
<br>
- Output variable.
</li>
<li>
<code>&gt;\`n!ce n@me\`</code>
<br>
- Input variable but with special character and spaces (except \`, obviously).
</li>
<li>
<code>!name</code>
<br>
- Output variable, but also delete itself, preventing from being referenced further.
<br>
- CURRENTLY BROKEN DUE TO HOW COMFYUI UPDATE THE NODES.
</li>
<li>
<code>&lt;name1; &gt;name2; !name3</code>
<br>
- Multiple input and outputs together.
</li>
</ul>
`.replace(/[\t\n]+/g, ''),
"junction": `
<span>
_offset is used to skip data ahead for specific type (since internally it's a sequence of data).
</span>
<br><br>
<span>
_offset is persistent and will retains information across linked Junction and JunctionBatch.
</span>
<br><br>
<span>
The _offset syntax goes as follow:
</span>
<ul>
<li>
<code>type,1</code>
<br>
- type is the type (usually LATENT, MODEL, VAE, etc.) and 1 is the index being set.
</li>
<li>
<code>type,+2</code>
<br>
- Same as above but instead of set offset, it increase the offset instead.
</li>
<li>
<code>type,-2</code>
<br>
- Decrease offset.
</li>
<li>
<code>type1, -1; type2, +2; type3, 4</code>
<br>
- Multiple offset.
</li>
</ul>
`.replace(/[\t\n]+/g, ''),
"box_range": `
<span>
Drag from an empty space to anywhere to create a new box.
</span>
<br><br>
<span>
Click on a box to select that box.
</span>
<ul>
<li>
Clicking the same position again will cycle through each boxes that are contains that mouse position.
</li>
</ul>
<span>
Changing z-index of a box may also change z-index of other boxes.
</span>
<br><br>
<span>
If during MOVE or RESIZE state, mouse the mouse out of boundary to cancel the action.
</span>
<br><br>
<span>
If holding Shift during drag from empty space, then it will DELETE any box that are within that area.
</span>
<br><br>
<span>
When a box is selected:
</span>
<ul>
<li>
Drag the box around to MOVE it.
</li>
<li>
Double click in top right to DELETE.
</li>
<li>
Double click in bottom right to RESIZE.
</li>
<li>
Double click in bottom left to INCREASE Z-INDEX.
</li>
<li>
Double click in top left to DECREASE Z-INDEX.
</li>
<li>
Hold SHIFT while clicking on the box again will prompt for specific range <code>[x, y, width, height]</code> or JS code string.
<ul>
<li>
If any is <code>null</code> then it will be filled with the current boundary data for that index.
</li>
<li>
If any is <code>"string"</code> then it will be implicitly assumed to be math expression and will be evaluated.
</li>
<li>
If entire thing is JS then it must return an array. There's some utilities function available.
<ul>
<li>
You can Ctrl-F to search for "safe_eval" within "nodes.js" and "utils.js" for more info on what's available.
</li>
</ul>
</li>
</ul>
</li>
</ul>
<span>
There'res a "secret" mode such that when all 3 are unlocked or locked and change the "ratio", it will actually
changes the "area" of the box instead. All 3 locked means "width" are pinned, all 3 unlocked means "height" are pinned.
</span>
`.replace(/[\t\n]+/g, ''),
"hub": `
<span>
This node can create a NEW widget (or create a bunch of NEW widgets using an exist node as template)
</span>
<br><br>
<span>
It also can group and sync widgets data from other nodes.
</span>
<br><br>
<span>
When clicking on node title within the hub, it will select/unselect that node.
</span>
<ul>
<li>
A node is being selected when a border color is shown around the node.
</li>
</ul>
<span>
You don't have to worry about when converting tracked nodes to group since it can be detected and can auto-add the group node.
</span>
<ul>
<li>
However when manually add group node to Hub, Hub will not automatically add widgets from the expanded nodes (probably will allow this in future).
</li>
</ul>
<span>
Only built-in widgets (and Box Range) are supported. Other custom widget are probably not going to works.
</span>
<ul>
<li>
Of course you can try to group custom widgets as I tried to design Hub to be as "reactive" as possible internally.
</li>
</ul>
<span>
Unfortunately due to inherent limitation you cannot resize Hub node.
</span>
<br><br>
<span>
Also for DOM/HTML-based widgets (such as multiline text), the element will probably "flick" between the Hub and the actual node.
</span>
<br><br>
<span>
Warning:
</span>
<ul>
<li>
For any native image upload widgets, the tracked nodes will have preview image may render out of node bound. This is due to inherent limitation and I cannot fix it.
</li>
<li>
Do not convert widget to input from within Hub node. Do it from the tracked node instead. I may handle this case later in future.
</li>
<li>
Image preview also not rendered properly. Therefore it will need a custom widget that I will probably implement.
</ul>
`.replace(/[\t\n]+/g, ''),
};
import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
import * as comfy_widget from "../../../extensions/core/widgetInputs.js";
import * as lib0246 from "./utils.js";
export let defs, node_defs = [], combo_defs = [], type_defs = new Set();
let rgthree_utils;
export function rgthree_exec(name, ...args) {
return rgthree_utils?.[name]?.apply(null, args);
}
export function help_option(node, content, app) {
// Guess this is good enough
rgthree_exec("addHelpMenuItem", node, content, app);
}
lib0246.hijack(app, "registerNodesFromDefs", async function (_defs) {
if (!this.mark && !Array.isArray(type_defs)) {
defs = _defs;
for (let key in defs) {
node_defs.push(key);
for (let idx in defs[key].output)
if (!Array.isArray(defs[key].output[idx]))
type_defs.add(defs[key].output[idx]);
for (let idx in defs[key].input)
for (let type in defs[key].input[idx])
if (Array.isArray(defs[key].input[idx][type][0]))
combo_defs.push([key, idx, type]);
}
type_defs = [...type_defs.values()];
}
});
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
async function init_update_raw(node, widget, callback) {
if (node.__update || !node.__hash_update)
node.__hash_update = lib0246.random_id();
node.__update = false;
const temp = {
data: widget.value,
update: node.__hash_update,
};
if (callback)
await callback(node, widget, temp);
return temp;
}
function init_update(node, name) {
node.__update = false;
for (let i = 0; i < node.widgets.length; ++ i) {
if (node.widgets[i].name === name) {
node.widgets[i].serializeValue = async function (inner_node, index_str) {
return await init_update_raw(node, node.widgets[i]);
};
return;
}
}
}
function init_update_direct(node, name, callback) {
node.__update = false;
node.addCustomWidget({
name: name,
computeSize: () => [0, -4],
async serializeValue (inner_node, index_str) {
return await init_update_raw(node, this, callback);
}
});
}
function link_shift_up(node, arr, index, flag, func) {
// Disconnect event handler
const old_func = node.onConnectionsChange;
node.onConnectionsChange = null;
const old_in_func = node.onConnectInput;
node.onConnectInput = null;
const old_out_func = node.onConnectOutput;
node.onConnectOutput = null;
// Shift up all links
if (flag) {
if (arr[index].links.length === 0) {
node.removeOutput(index);
for (let i = 0, c = 0; i < arr.length; ++ i) {
if (func(i) || BLACKLIST.includes(arr[i].name))
continue;
arr[i].name = `${arr[i].type}:${c}`;
++ c;
}
}
} else {
node.removeInput(index);
for (let i = 0, c = 0; i < arr.length; ++ i) {
if (func(i) || BLACKLIST.includes(arr[i].name))
continue;
arr[i].name = `${c}:${arr[i].type}`;
++ c;
}
}
app.canvas.setDirty(true);
// Revert things back
node.onConnectionsChange = old_func;
node.onConnectInput = old_in_func;
node.onConnectOutput = old_out_func;
return;
}
const BLACKLIST = [
"_way_in",
"_way_out",
"_junc_in",
"_junc_out",
"_pipe_in",
"_pipe_out",
"_query",
"_offset",
"_event",
"_delimiter",
"_script_in",
"_script_out",
"_exec_mode",
"_sort_mode",
"_mode",
"_pad",
"_data",
"_cloud_in",
"_cloud_out",
"..."
];
const LEGACY_BLACKLIST = {
prev: ["_pipe_in", "_pipe_out"],
next: ["_way_in", "_way_out"],
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const RESIZE_OBSERVER = Symbol("resize_observer");
export const SOFT_REMOVE = Symbol("soft_remove");
function dom_resize_calc(node, widget) {
// Very bruh
widget.computedHeight = null;
node.flags = node.flags ?? {};
const old_collapsed = node.flags.collapsed,
old_h = node.size[1],
old_hide = widget.options?.onHide,
old_elem_hide = widget.element.hidden,
old_elem_display = widget.element.style.display;
widget.options.onHide = undefined;
node.flags.collapsed = true;
widget.draw(null, node, null, null);
node.flags.collapsed = old_collapsed;
node.size[1] = old_h;
widget.element.hidden = old_elem_hide;
widget.element.style.display = old_elem_display;
if (old_hide)
widget.options.onHide = old_hide;
app.canvas.setDirty(true);
}
function setup_dom_resize(node, widget) {
widget.element.style.resize = "vertical";
(widget[RESIZE_OBSERVER] = new ResizeObserver(() => {
widget.element.style.setProperty("--comfy-widget-height", String(Math.max(50, widget.element.offsetHeight + 20)));
dom_resize_calc(node, widget);
})).observe(widget.element);
// [TODO] Prevent resize out of bound and preserve --comfy-widget-height when cloning or configure
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function setup_sole_pin(node, name, side_name, side_mode, shape) {
const upper_name = side_name.charAt(0).toUpperCase() + side_name.slice(1),
more_name = side_name + "s";
lib0246.hijack(node, "onConnect" + upper_name, function () {
if (this.mark) {
if (this.self[more_name][arguments[0]].name !== name)
return true;
this.self[more_name][arguments[0]].shape = shape;
this.self[more_name][arguments[0]].type = arguments[2].type;
}
});
lib0246.hijack(node, "onConnectionsChange", function (type, index, connected, link_info) {
if (this.mark) {
if (link_info === null) {
// Clean up when copy paste or template load
if (this.self[more_name])
for (let i = 0; i < this.self[more_name].length; ++ i)
if (this.self[more_name][i].name === name) {
this.self[more_name][i].type = "*";
break;
}
return;
}
if (!connected && index < this.self[more_name].length && this.self[more_name][index].name === name && type === side_mode)
this.self[more_name][index].type = "*";
}
});
}
function expand_y_calc(node, off) {
if (Number.isFinite(node.widgets[0]?.y))
for (let i = 0; i < node.widgets.length; ++ i)
if (node.widgets[i].element) {
node.widgets[0].last_y = node.widgets[0].y =
(Math.max(node.inputs?.length ?? 0, node.outputs?.length ?? 0) + off) * (LiteGraph.NODE_SLOT_HEIGHT) + 6;
break;
}
}
function expand_y_save(node, off, func) {
const old_w = node.size[0],
calc_h = node.size[1] + LiteGraph.NODE_SLOT_HEIGHT * off;
func();
node.size[0] = old_w;
if (node.size[1] - calc_h !== 20) // For Junction
node.size[1] = calc_h;
}
function setup_expand(node, name, real, pin, shape, callback) {
const upper_name = name.charAt(0).toUpperCase() + name.slice(1),
more_name = name + "s";
node["add" + upper_name](pin, "*");
if (node[more_name])
for (let i = 0; i < node[more_name].length; ++ i)
if (node[more_name][i].name === pin) {
node[more_name][i].shape = shape;
break;
}
lib0246.hijack(node, "configure", function (data) {
if (this.mark && this.self[more_name])
for (let i = 0; i < this.self[more_name].length; ++ i) {
if (!BLACKLIST.includes(this.self[more_name][i].name))
++ real[name];
}
});
node["onConnect" + upper_name] = function (
this_slot_index,
other_slot_type,
other_slot_obj,
other_node,
other_slot_index
) {
this.__update = true;
if (
BLACKLIST.includes(this[more_name][this_slot_index].name) &&
this[more_name][this_slot_index].name !== pin
)
return true;
const res = callback.apply({ self: this, name: name, mode: 0 }, arguments);
if (res === true)
return true;
if (res === false)
return false;
expand_y_calc(this, 1);
expand_y_save(this, 1, () => {
callback.call({ self: this, name: name, mode: 1 }, real[name] ++, this[more_name][this_slot_index].type, this_slot_index);
this["add" + upper_name](pin, "*");
});
this[more_name][this[more_name].length - 1].shape = shape;
app.canvas.setDirty(true, false);
return true;
};
lib0246.hijack(node, "onConnectionsChange", function (type, index, connected, link_info) {
if (this.mark) {
if (link_info === null) {
// Clean up when copy paste or template load
if (this.self[more_name])
lib0246.remove_elem_arr(this.self[more_name], (e) => !BLACKLIST.includes(e.name));
this.self.computeSize();
app.canvas.setDirty(true, false);
return;
}
if (!connected) {
expand_y_calc(this.self, -1);
expand_y_save(this.self, -1, () => {
callback.apply({ self: this.self, name: name, mode: 2 }, arguments);
});
}
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const INIT_FLAG = Symbol("init_flag");
export function highway_impl(nodeType, nodeData, app, shape_in, shape_out) {
nodeType.prototype.onNodeMoved = function () {};
nodeType.prototype.onNodeCreated = function () {
init_update(this, "_query");
const query = this.widgets.find(w => w.name === "_query");
query.options = query.options ?? {};
query.options.multiline = true;
let last_query = "";
lib0246.hijack(this, "configure", function (data) {
if (this.mark) {
// Patch legacy nodes
for (let i = 0; i < this.self.inputs.length; ++ i) {
if (LEGACY_BLACKLIST.prev.includes(this.self.inputs[i].name))
this.self.inputs[i].name = LEGACY_BLACKLIST.next[i];
}
for (let i = 0; i < this.self.outputs.length; ++ i) {
if (LEGACY_BLACKLIST.prev.includes(this.self.outputs[i].name))
this.self.outputs[i].name = LEGACY_BLACKLIST.next[i];
}
last_query = data.widgets_values[0];
}
});
lib0246.hijack(this, "clone", function () {
if (this.mark) {
const node = this.res;
// Clean up when copy paste or template load
for (let i = 0; i < node.inputs.length; ++ i)
if (!BLACKLIST.includes(node.inputs[i].name)) {
node.inputs[i].name = app.graph.extra["0246.__NAME__"][this.self.id]["inputs"][i]["name"];
node.inputs[i].type = "*";
}
for (let i = 0; i < node.outputs.length; ++ i)
if (!BLACKLIST.includes(node.outputs[i].name)) {
node.outputs[i].name = app.graph.extra["0246.__NAME__"][this.self.id]["outputs"][i]["name"];
node.outputs[i].type = "*";
}
node.computeSize();
}
});
this.addWidget("button", "Update", null, () => {
const self = this;
(async function () {
let data = await (await fetch(
window.location.origin + "/0246-parse",
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
"input": query.value,
}),
}
)).json();
if (!data) {
lib0246.error_popup("Server or Network error");
return;
}
if (data.error.length > 0) {
lib0246.error_popup(data.error.join("\n"));
query.value = last_query;
return;
}
last_query = query.value;
save_parse_load_pin(self, shape_in, shape_out, (node, prev, mode) => {
if (mode) {
for (let i = 0; i < data.order.length; ++ i) {
switch (data.order[i][0]) {
case "get":{
node.addOutput(`-${data.order[i][1]}`, "*");
} break;
case "eat": {
node.addOutput(`!${data.order[i][1]}`, "*");
} break;
}
}
} else {
for (let i = 0; i < data.order.length; ++ i) {
switch (data.order[i][0]) {
case "set": {
node.addInput(`+${data.order[i][1]}`, "*");
} break;
}
}
}
});
// node_fit(self, query, self.widgets.filter(_ => _.name === "Update")[0]);
})();
}, {
serialize: false
});
this.onConnectInput = function (
this_target_slot_index,
other_origin_slot_type,
other_origin_slot_obj,
other_origin_node,
other_origin_slot_index
) {
this.__update = true;
if (BLACKLIST.includes(this.inputs[this_target_slot_index].name))
return true;
if (this.inputs[this_target_slot_index].link !== null) {
// Prevent premature link kill
app.graph.links[this.inputs[this_target_slot_index].link].replaced = true;
return true;
}
let curr_pin = this.inputs[this_target_slot_index];
if (app.graph.extra["0246.__NAME__"][this.id]["inputs"][this_target_slot_index]["type"] === "*")
curr_pin.type = other_origin_slot_obj.type;
curr_pin.name = `${app.graph.extra["0246.__NAME__"][this.id]["inputs"][this_target_slot_index]["name"]}:${curr_pin.type}`;
return true;
};
this.onConnectOutput = function (
this_origin_slot_index,
other_target_slot_type,
other_target_slot_obj,
other_target_node,
other_target_slot_index
) {
// We detect if we're connecting to Reroute here by checking other_target_node.type === "Reroute"
// return false for not allowing connection
this.__update = true;
if (BLACKLIST.includes(this.outputs[this_origin_slot_index].name))
return true;
let curr_pin = this.outputs[this_origin_slot_index];
if (app.graph.extra["0246.__NAME__"][this.id]["outputs"][this_origin_slot_index]["type"] === "*") {
if (other_target_node.__outputType) // Reroute
curr_pin.type = other_target_node.__outputType;
else if (other_target_node.defaultConnectionsLayout) // Reroute (rgthree)
// rgthree accept this anyways so whatever since too lazy to properly do graph traversal
// EDIT: I was wrong, I have to do it, but not here :(
curr_pin.type = other_target_slot_obj.type;
else
curr_pin.type = other_target_slot_obj.type;
}
curr_pin.name = `${curr_pin.type}:${app.graph.extra["0246.__NAME__"][this.id]["outputs"][this_origin_slot_index]["name"]}`;
return true;
};
this.onConnectionsChange = function (type, index, connected, link_info) {
if (link_info === null) {
this[INIT_FLAG] = true;
return;
}
if (!connected) {
switch (type) {
case 1: {
if (BLACKLIST.includes(this.inputs[link_info.target_slot].name) || link_info.replaced)
return;
const curr_data = app.graph.extra?.["0246.__NAME__"]?.[this.id]?.["inputs"]?.[link_info.target_slot];
this.inputs[link_info.target_slot].name = curr_data?.["name"] ?? this.inputs[link_info.target_slot].name;
if (curr_data?.["type"] === "*" || !app.graph.extra["0246.__NAME__"])
this.inputs[link_info.target_slot].type = "*";
} break;
case 2: {
if (this.outputs[link_info.origin_slot].links.length === 0 && !BLACKLIST.includes(this.outputs[link_info.origin_slot].name)) {
const curr_data = app.graph.extra?.["0246.__NAME__"]?.[this.id]?.["outputs"]?.[link_info.origin_slot];
this.outputs[link_info.origin_slot].name = curr_data?.["name"] ?? this.outputs[link_info.origin_slot].name;
if (curr_data?.["type"] === "*" || !app.graph.extra["0246.__NAME__"])
this.outputs[link_info.origin_slot].type = "*";
}
} break;
default: {
throw new Error("Unsuported type: " + type);
}
}
}
};
lib0246.hijack(this, "onAdded", function () {
if (this.mark && this.self[INIT_FLAG]) {
delete this.self[INIT_FLAG];
this.self.widgets.find(w => w.name === "Update").callback();
return;
}
});
lib0246.hijack(this, "onRemoved", function () {
if (!this.mark)
delete app.graph.extra["0246.__NAME__"][this.self.id];
});
};
lib0246.hijack(nodeType.prototype, "getExtraMenuOptions", function (canvas, options) {
// canvas === app.canvas
// value: parent submenu obj
// options: this.extra == node, scroll_speed, event: litegraph event
// evt: native event object
// menu
// node
if (!this.mark) {
options.push(
{
content: "[0246.Highway] Selected node pins -> highway pins",
callback: (value, options, evt, menu, node) => {
for (let node_id in app.canvas.selected_nodes) {
if (node.id === Number(node_id))
continue;
save_parse_load_pin(node, shape_in, shape_out, (node, prev, mode) => {
const from = app.graph.getNodeById(Number(node_id));
if (mode) {
copy_output_pin(node, from, "output", "<");
} else {
if (defs[from.comfyClass]?.input?.required)
copy_input_pin(node, from, "input", "input", "required", ">");
if (defs[from.comfyClass]?.input?.optional)
copy_input_pin(node, from, "input", "input", "optional", ">");
}
});
}
}
},
{
content: "[0246.Highway] Selected node pins -> highway pins (inverse)",
callback: (value, options, evt, menu, node) => {
for (let node_id in app.canvas.selected_nodes) {
if (node.id === Number(node_id))
continue;
save_parse_load_pin(node, shape_in, shape_out, (node, prev, mode) => {
const from = app.graph.getNodeById(Number(node_id));
if (!mode) {
copy_output_pin(node, from, "input", ">");
} else {
if (defs[from.comfyClass]?.input?.required)
copy_input_pin(node, from, "input", "output", "required", "<");
if (defs[from.comfyClass]?.input?.optional)
copy_input_pin(node, from, "input", "output", "optional", "<");
}
});
}
}
},
{
content: "[0246.Highway] Selected node pins -> highway _query",
callback: (value, options, evt, menu, node) => {
for (let node_id in app.canvas.selected_nodes) {
if (node.id === Number(node_id))
continue;
const query = node.widgets.find(w => w.name === "_query"),
from = app.graph.getNodeById(Number(node_id));
query.value = "";
if (defs[from.comfyClass]?.input?.required)
querify_input_pin(query, from, "required", ">");
if (defs[from.comfyClass]?.input?.optional)
querify_input_pin(query, from, "optional", ">");
querify_output_pin(query, from, "<");
}
}
},
{
content: "[0246.Highway] Selected node pins -> highway _query (inverse)",
callback: (value, options, evt, menu, node) => {
for (let node_id in app.canvas.selected_nodes) {
if (node.id === Number(node_id))
continue;
const query = node.widgets.find(w => w.name === "_query"),
from = app.graph.getNodeById(Number(node_id));
query.value = "";
if (defs[from.comfyClass]?.input?.required)
querify_input_pin(query, from, "required", "<");
if (defs[from.comfyClass]?.input?.optional)
querify_input_pin(query, from, "optional", "<");
querify_output_pin(query, from, ">");
}
}
},
);
// HTML format of help
help_option(nodeType, HELP["highway"], options);
options.push(null);
}
});
rgthree_exec("addConnectionLayoutSupport", nodeType, app);
}
function save_parse_load_pin(node, shape_in, shape_out, callback) {
node.__update = true;
let prev = [];
// Save previous inputs and outputs
if (node.inputs) {
for (let i = 0; i < node.inputs.length; ++ i) {
if (!BLACKLIST.includes(node.inputs[i].name) && node.inputs[i].link !== null)
prev.push({
flag: false,
name: app.graph.extra?.["0246.__NAME__"]?.[node.id]?.["inputs"]?.[i]?.["name"] ?? null,
node_id: app.graph.links[node.inputs[i].link].origin_id,
slot_id: app.graph.links[node.inputs[i].link].origin_slot,
this_id: i
});
}
for (let i = node.inputs.length; i -- > 0;) {
if (!BLACKLIST.includes(node.inputs[i].name))
node.removeInput(i);
}
callback(node, prev, false);
for (let i = 0; i < node.inputs.length; ++ i) {
app.graph.extra["0246.__NAME__"] = app.graph.extra["0246.__NAME__"] ?? {};
app.graph.extra["0246.__NAME__"][node.id] = app.graph.extra["0246.__NAME__"][node.id] ?? {
inputs: {},
outputs: {},
};
app.graph.extra["0246.__NAME__"][node.id].inputs[i] = app.graph.extra["0246.__NAME__"][node.id].inputs[i] ?? {};
app.graph.extra["0246.__NAME__"][node.id].inputs[i].name = node.inputs[i].name;
app.graph.extra["0246.__NAME__"][node.id].inputs[i].type = node.inputs[i].type;
if (!BLACKLIST.includes(node.inputs[i].name))
node.inputs[i].shape = shape_in;
}
}
if (node.outputs) {
for (let i = 0; i < node.outputs.length; ++ i) {
if (!BLACKLIST.includes(node.outputs[i].name) && node.outputs[i].links !== null)
for (let j = 0; j < node.outputs[i].links.length; ++ j)
prev.push({
flag: true,
name: app.graph.extra?.["0246.__NAME__"]?.[node.id]?.["outputs"]?.[i]?.["name"] ?? null,
node_id: app.graph.links[node.outputs[i].links[j]].target_id,
slot_id: app.graph.links[node.outputs[i].links[j]].target_slot,
this_id: i
});
}
for (let i = node.outputs.length; i -- > 0;) {
if (!BLACKLIST.includes(node.outputs[i].name))
node.removeOutput(i);
}
callback(node, prev, true);
for (let i = 0; i < node.outputs.length; ++ i) {
app.graph.extra["0246.__NAME__"] = app.graph.extra["0246.__NAME__"] ?? {};
app.graph.extra["0246.__NAME__"][node.id] = app.graph.extra["0246.__NAME__"][node.id] ?? {
inputs: {},
outputs: {},
};
app.graph.extra["0246.__NAME__"][node.id].outputs[i] = app.graph.extra["0246.__NAME__"][node.id].outputs[i] ?? {};
app.graph.extra["0246.__NAME__"][node.id].outputs[i].name = node.outputs[i].name;
app.graph.extra["0246.__NAME__"][node.id].outputs[i].type = node.outputs[i].type;
if (!BLACKLIST.includes(node.outputs[i].name))
node.outputs[i].shape = shape_out;
}
}
// Restore previous inputs and outputs
for (let i = 0; i < prev.length; ++ i) {
// Check if input/output still exists
if (prev[i].flag) {
if (prev[i].name === null)
node.connect(
prev[i].this_id,
prev[i].node_id,
prev[i].slot_id
);
else for (let j = 0; j < node.outputs.length; ++ j) {
if (app.graph.extra["0246.__NAME__"][node.id]["outputs"][j]["name"].slice(0) === prev[i].name.slice(0)) {
node.connect(
j,
prev[i].node_id,
prev[i].slot_id
);
break;
}
}
} else {
if (prev[i].name === null)
app.graph.getNodeById(prev[i].node_id).connect(
prev[i].slot_id,
node,
prev[i].this_id
);
else for (let j = 0; j < node.inputs.length; ++ j) {
if (app.graph.extra["0246.__NAME__"][node.id]["inputs"][j]["name"].slice(1) === prev[i].name.slice(1)) {
app.graph.getNodeById(prev[i].node_id).connect(
prev[i].slot_id,
node,
j
);
break;
}
}
}
}
}
function copy_input_pin(node, from, kind, to_kind, path, ops) {
const kind_upper = to_kind.charAt(0).toUpperCase() + to_kind.slice(1);
for (let name in defs[from.comfyClass][kind][path])
node["add" + kind_upper](
`${ops}${name}`,
Array.isArray(defs[from.comfyClass][kind][path][name][0]) ?
"STRING" : // COMBO is STRING internally anyways
defs[from.comfyClass][kind][path][name][0]
);
}
function querify_input_pin(widget, from, path, ops) {
for (let name in defs[from.comfyClass].input[path])
widget.value += `${ops}${name};`;
}
function copy_output_pin(node, from, kind, ops) {
const kind_upper = kind.charAt(0).toUpperCase() + kind.slice(1);
for (let i = 0; i < defs[from.comfyClass].output_name.length; ++ i)
node["add" + kind_upper](
`${ops}${defs[from.comfyClass].output_name[i]}`,
Array.isArray(defs[from.comfyClass].output[i]) ?
"STRING" :
defs[from.comfyClass].output[i]
);
}
function querify_output_pin(widget, from, ops) {
for (let i = 0; i < defs[from.comfyClass].output_name.length; ++ i)
widget.value += `${ops}${defs[from.comfyClass].output_name[i]};`;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function single_impl_input_raw(inst, app, real, shape_in, func = () => {}) {
setup_expand(inst, "input", real, "...", shape_in, function () {
switch (this.mode) {
case 0: {
if (this.self.inputs[arguments[0]].link !== null) {
app.graph.links[this.self.inputs[arguments[0]].link].replaced = true;
return true;
}
this.self.inputs[arguments[0]].type = arguments[2].type;
} break;
case 1: {
this.self.inputs[arguments[2]].name = `${arguments[0]}:${arguments[1]}`;
} break;
case 2: {
if (arguments[0] === 1) {
let link_info = arguments[3];
if (func(this.self, 1, arguments) || BLACKLIST.includes(this.self.inputs[link_info.target_slot].name)) // || link_info.replaced)
return;
link_shift_up(this.self, this.self.inputs, link_info.target_slot, false, (i) => func(this.self, 3, arguments, i));
// (link_index, extra_link_index) => {
// return this.self.inputs[link_index].link;
// }
-- real.input;
}
} break;
}
});
}
function single_impl_output_raw(inst, app, real, shape_out, func = () => {}) {
setup_expand(inst, "output", real, "...", shape_out, function () {
switch (this.mode) {
case 0: {
// Prevent the case of connecting "..." to "..." to form "0:* 0:*" then connect "..." to "0:*"
// With the cost of minor inconvenience
// [TODO] Maybe properly detect if pin belongs to "..."?
if (arguments[3].inputs[arguments[4]].link)
return false;
if (this.self.outputs[arguments[0]].links && this.self.outputs[arguments[0]].links.length > 0)
return true;
// Avoid node to connect to multiple output while allowing different pins
for (let i = 0; i < this.self.outputs.length; ++ i) {
if (func(this.self, -2, arguments, i) || BLACKLIST.includes(this.self.outputs[i].name))
continue;
let output_node = this.self.getOutputNodes(i);
if (output_node)
for (let j = 0; j < output_node.length; ++ j) {
if (output_node[j] === arguments[3] && i === arguments[0])
return false;
}
}
if (arguments[2].__outputType) // Reroute
this.self.outputs[arguments[0]].type = arguments[2].__outputType;
// else if (arguments[2].defaultConnectionsLayout) // Reroute (rgthree)
// this.self.outputs[arguments[0]].type = arguments[2].type;
else
this.self.outputs[arguments[0]].type = arguments[2].type;
} break;
case 1: {
this.self.outputs[arguments[2]].name = `${arguments[1]}:${arguments[0]}`;
// node_fit(this.self, this.self.widgets.filter(_ => _.name === "_offset")[0]);
} break;
case 2: {
if (arguments[0] === 2) {
let link_info = arguments[3];
if (func(this.self, -1, arguments) || BLACKLIST.includes(this.self.outputs[link_info.origin_slot].name))
return;
if (!this.self.outputs[link_info.origin_slot].links || this.self.outputs[link_info.origin_slot].links.length === 0) {
link_shift_up(this.self, this.self.outputs, link_info.origin_slot, true, (i) => func(this.self, -3, arguments, i));
// (link_index, extra_link_index) => {
// return this.self.outputs[link_index].links[extra_link_index];
// }
-- real.output;
// node_fit(this.self, this.self.widgets.filter(_ => _.name === "_offset")[0]);
}
}
} break;
}
});
}
export function junction_impl(nodeType, nodeData, app, name, shape_in, shape_out, func = () => {}) {
nodeType.prototype.onNodeCreated = function () {
if (typeof name === "string")
init_update(this, name);
const offset = this.widgets.find(w => w.name === "_offset");
if (offset) {
offset.options = offset.options ?? {};
offset.options.multiline = true;
}
const real = {
input: 0,
output: 0,
};
single_impl_input_raw(this, app, real, shape_in, func);
single_impl_output_raw(this, app, real, shape_out, func);
lib0246.hijack(this, "getExtraMenuOptions", function (canvas, options) {
if (!this.mark)
help_option(nodeType, HELP["junction"], options);
});
};
rgthree_exec("addConnectionLayoutSupport", nodeType, app);
}
export function single_impl_input(nodeType, nodeData, app, shape_in, pin_list, func = () => {}) {
lib0246.hijack(nodeType.prototype, "onNodeCreated", function () {
if (this.mark) {
setup_loop_update(this.self);
if (shape_in !== null)
single_impl_input_raw(this.self, app, {
input: 0,
}, shape_in, func);
for (let i = 0; i < pin_list.length; i += 4)
setup_sole_pin(this.self, pin_list[i], pin_list[i + 1], pin_list[i + 2], pin_list[i + 3]);
}
});
}
export function single_impl_output(nodeType, nodeData, app, shape_out, pin_list, func = () => {}) {
lib0246.hijack(nodeType.prototype, "onNodeCreated", function () {
if (this.mark) {
setup_loop_update(this.self);
if (shape_out !== null)
single_impl_output_raw(this.self, app, {
output: 0,
}, shape_out, func);
for (let i = 0; i < pin_list.length; i += 4)
setup_sole_pin(this.self, pin_list[i], pin_list[i + 1], pin_list[i + 2], pin_list[i + 3]);
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function setup_loop_update(node) {
for (let i = 0; i < node.widgets.length; ++ i) {
if (node.widgets[i].name === "_update") {
node.widgets.splice(i, 1);
init_update_direct(node, "_update");
break;
}
}
}
function raw_setup_log(self) {
self.log_widget = ComfyWidgets["STRING"](self, "output", ["STRING", { multiline: true }], app).widget;
self.log_widget.inputEl.readOnly = true;
self.log_widget.serializeValue = async (node, index_str) => {
if (node.widgets_values)
node.widgets_values[Number(index_str)] = "";
return "";
};
}
function force_size(node, widget, mode) {
let temp_size = node.computeSize();
if (!node.size)
node.size = temp_size;
temp_size[0] = node.size[0];
if (widget)
temp_size[1] += widget.computedHeight - 32;
if (mode)
node.setSize(temp_size);
else
node.size = temp_size;
}
function setup_log_raw(node, history = false) {
raw_setup_log(node);
if (history) {
node.log_history = new lib0246.RingBuffer(30);
node.log_count = 0;
}
node.msgSize = function (event) {
force_size(node, node.log_widget, false);
};
window.setTimeout(() => {
// https://github.com/failfa-st/failfast-comfyui-extensions/issues/16
api.addEventListener("ue-message-handler", node.msgSize);
lib0246.hijack(node, "onRemoved", function () {
if (this.mark)
api.removeEventListener("ue-message-handler", node.msgSize);
});
}, 0);
}
export function setup_log(node, history = false, force = false) {
if (force)
raw_setup_log(node);
else
lib0246.hijack(node, "onNodeCreated", function () {
if (this.mark)
setup_log_raw(this.self, history);
});
lib0246.hijack(node, "onExecuted", function (message) {
if (this.mark) {
if (this.self.log_history) {
this.self.log_history.push({
track: this.self.log_count ++,
msg: message.text
});
this.self.log_widget.value = "";
for (let item of this.self.log_history)
this.self.log_widget.value += `${item.track}: ` + item.msg + "\n\n";
} else
this.self.log_widget.value = message.text;
}
});
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
export function process_reroute(node, type) {
type = type ?? node.widgets[0].value;
node.size[0] = 100 + type.length * 8;
node.inputs[0].type = type;
node.outputs[0].type = type;
node.__outputType = type;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function node_mouse_pos(node) {
return [
app.canvas.graph_mouse[0] - node.pos[0],
app.canvas.graph_mouse[1] - node.pos[1],
];
}
function calc_flex(node, widget, width, height, dom_flag) {
node.flex_data = node.flex_data ?? {};
node.flex_data.share_count = 0;
node.flex_data.share_weight = [];
node.flex_data.share_max_h = [];
node.flex_data.share_real_h = 0;
node.flex_data.off_h = 0;
node.flex_data.left_h = 54;
node.flex_data.dom_h = 0; //-8;
for (let i = 0; i < node.widgets.length; ++ i)
if (node.widgets[i]?.flex) {
if (node.widgets[i]?.type === "converted-widget")
continue;
node.flex_data.share_weight.push(node.widgets[i]?.flex?.share);
node.flex_data.share_max_h.push(Number.isFinite(node.widgets[i]?.flex?.max_h) && node.widgets[i]?.flex?.max_h > 0 ? node.widgets[i]?.flex?.max_h : null);
node.flex_data.share_real_h += node.widgets[i]?.flex?.real_h ?? 0;
++ node.flex_data.share_count;
if (node.widgets[i] === widget)
node.widgets[i].flex.index = node.flex_data.share_count - 1;
node.flex_data.left_h += 4;
} else
node.flex_data.off_h += (node.widgets[i]?.computedHeight ?? node.widgets[i]?.computeSize?.(width)?.[1] ?? LiteGraph.NODE_WIDGET_HEIGHT) + 4;
node.flex_data.avail_h = height - node.flex_data.off_h - node.flex_data.left_h - (dom_flag ? node.flex_data.dom_h : 0);
}
let PROCESS_WIDGET_NODE;
export function widget_flex(node, widget, options = {}) {
widget.flex = {};
lib0246.hijack(widget, "mouse", function (event, pos, evt_node) {
if (!this.mark) {
if (evt_node !== node) {
// [TODO] Figure out why this does not work
// this.self.flex.hold_mouse[0] = this.self.flex.margin_x;
// this.self.flex.hold_mouse[1] = this.self.flex.real_y - 2;
// this.self.flex.hold_mouse[2] = this.self.flex.real_w - this.self.flex.margin_x * 2;
// this.self.flex.hold_mouse[3] = this.self.flex.temp_h - 2;
this.self.flex.hold_mouse = this.self.flex.hold_draw;
} else
lib0246.calc_area(
this.self.flex.margin_x, this.self.flex.margin_head_y, this.self.flex.margin_tail_real_y,
this.self.flex.real_w, this.self.flex.real_h, this.self.flex.real_max_h,
this.self.flex.ratio, this.self.flex.center, (this.self.flex.real_y ?? 0),
true, this.self.flex.hold_mouse
);
}
});
lib0246.hijack(widget, "draw", function (ctx, draw_node, widget_width, y, widget_height) {
if (!this.mark) {
this.self.flex.real_y = y;
this.self.flex.real_w = widget_width;
if (draw_node !== node) {
this.self.flex.hold_draw[0] = this.self.flex.margin_x;
this.self.flex.hold_draw[1] = this.self.flex.real_y - 2;
this.self.flex.hold_draw[2] = widget_width - this.self.flex.margin_x * 2;
this.self.flex.hold_draw[3] = this.self.flex.temp_h - 2;
} else {
lib0246.calc_area(
this.self.flex.margin_x, this.self.flex.margin_head_y, this.self.flex.margin_tail_real_y,
widget_width, this.self.flex.real_h,
this.self.flex.real_max_h,
this.self.flex.ratio, this.self.flex.center, this.self.flex.real_y,
true, this.self.flex.hold_draw
);
}
}
});
lib0246.hijack(widget, "computeSize", function (width) {
if (!this.mark) {
const slot_count = Math.max(node.inputs?.length ?? 0, node.outputs?.length ?? 0),
pad_h = Math.max(24 - slot_count * (LiteGraph.NODE_SLOT_HEIGHT + 4), 0),
pad_h2 = Math.max(64 - slot_count * (LiteGraph.NODE_SLOT_HEIGHT + 4), 0);
this.self.flex.temp_h = 0;
if (PROCESS_WIDGET_NODE && PROCESS_WIDGET_NODE.isPointInside(app.canvas.graph_mouse[0], app.canvas.graph_mouse[1])) {
// Intentional double-ifs
if (PROCESS_WIDGET_NODE !== node) {
// [TODO] Maybe somehow find a way to use hold_size since it technically more correct
this.res = [width, this.self.flex.hold_draw[3]];
this.self.last_y = this.self.flex.real_y + this.self.flex.margin_head_y;
this.stop = true;
return;
}
this.self.flex.temp_h += pad_h;
}
// Don't ask why how I came up with this. This took a week of brain power.
this.self.flex.real_y = this.self.flex.real_y ?? 0;
this.self.flex.margin_tail_real_y = this.self.flex.margin_tail_y + this.self.flex.margin_over_y;
this.self.flex.real_max_h = Infinity;
this.self.flex.real_min_h = this.self.flex.min_h ?? 0;
let raw_size = node.size?.[1] ?? this.self.flex.real_min_h;
if (app.canvas.resizing_node === node) {
let sum_h = 6 + slot_count * (LiteGraph.NODE_SLOT_HEIGHT + 4);
for (let i = 0; i < node.widgets.length; ++ i) {
if (node.widgets[i]?.flex)
sum_h += (node.widgets[i]?.flex?.real_min_h ?? 0) + 4;
else
sum_h += (node.widgets[i]?.computedHeight ?? node.widgets[i]?.computeSize?.(width)[1] ?? LiteGraph.NODE_WIDGET_HEIGHT) + 4;
}
raw_size = Math.max(app.canvas.graph_mouse[1] - node.pos[1], sum_h);
}
const dom_flag = width === undefined; // [UNUSED]
calc_flex(node, this.self, width, raw_size, dom_flag);
this.self.flex.real_h = node.flex_data.avail_h + node.flex_data.off_h + node.flex_data.left_h + (dom_flag ? node.flex_data.dom_h : 0);
this.self.flex.real_max_h = lib0246.calc_spread(
node.flex_data.share_count,
node.flex_data.avail_h - 20 + pad_h2,
node.flex_data.share_weight,
node.flex_data.share_max_h
)[this.self.flex.index] + pad_h;
if (this.self.flex.real_max_h < this.self.flex.real_min_h)
this.self.flex.real_max_h = this.self.flex.real_min_h;
lib0246.calc_area(
this.self.flex.margin_x, this.self.flex.margin_head_y, this.self.flex.margin_tail_real_y,
width, this.self.flex.real_h, this.self.flex.real_max_h,
this.self.flex.ratio, this.self.flex.center, this.self.flex.real_y,
true, this.self.flex.hold_size
);
this.self.flex.temp_h += this.self.flex.hold_size[3] - pad_h; // + this.self.flex.margin_head_y + this.self.flex.margin_tail_y;
if (this.self.flex.real_h < this.self.flex.real_min_h)
this.self.flex.real_h = this.self.flex.real_min_h;
if (this.self.flex.temp_h < this.self.flex.real_min_h)
this.self.flex.temp_h = this.self.flex.real_min_h;
this.self.computedHeight = this.self.flex.temp_h;
this.res = [width, this.self.flex.temp_h]; // + (dom_flag ? 12 : 0)];
this.stop = true;
}
});
widget.flex.hold_draw = [];
widget.flex.hold_mouse = [];
widget.flex.hold_size = [];
widget.flex.margin_x = options.margin_x ?? 20;
widget.flex.margin_head_y = options.margin_head_y ?? 0;
widget.flex.margin_tail_y = options.margin_tail_y ?? 0;
widget.flex.margin_over_y = options.margin_over_y ?? 12
widget.flex.min_h = options.min_h ?? 0;
widget.flex.max_h = options.max_h ?? Infinity;
widget.flex.compat = options.compat ?? false;
widget.flex.ratio = options.ratio ?? 0;
widget.flex.share = options.share ?? false;
widget.flex.center = options.center ?? true;
widget.options = widget.options ?? {};
widget.options.getHeight = function () {
return this.self.flex.real_h;
};
}
const DOM_NODE_DB = new Set(), DOM_RESIZE = Symbol("dom_resize");
export function DOM_WIDGET(data_type, data_name, element, options = {}) {
Object.assign(options, {
hideOnZoom: true,
selectOn: ["focus", "click"],
});
const widget = {
name: data_name,
type: data_type,
get value() {
return this.options.getValue?.();
},
set value(value) {
this.options.setValue?.(value);
this.callback?.(value);
},
draw: function (ctx, node, widget_width, y, widget_height) {
const hidden =
node.flags?.collapsed ||
(!!options.hideOnZoom && app.canvas.ds.scale < 0.5) ||
widget.flex.hold_draw[3] <= 0 ||
widget.type === "converted-widget";
this.element.hidden = hidden;
this.element.style.display = hidden ? "none" : null;
if (hidden) {
widget.options.onHide?.(widget);
return;
}
const elem_rect = ctx.canvas.getBoundingClientRect(),
transform = new DOMMatrix()
.scaleSelf(elem_rect.width / ctx.canvas.width, elem_rect.height / ctx.canvas.height)
.multiplySelf(ctx.getTransform())
.translateSelf(widget.flex.hold_draw[0], widget.flex.hold_draw[1]);
Object.assign(this.element.style, {
transformOrigin: "0 0",
transform: new DOMMatrix().scaleSelf(transform.a, transform.d),
left: `${transform.a + transform.e}px`,
top: `${transform.d + transform.f}px`,
width: `${widget.flex.hold_draw[2]}px`,
height: `${widget.flex.hold_draw[3]}px`,
position: "absolute",
zIndex: app.graph._nodes.indexOf(node),
});
// [TODO]
// if (app.ui.settings.getSettingValue("Comfy.DOMClippingEnabled", false)) {
// this.element.style.clipPath = lib0246.clip_path(node, element, elRect);
// this.element.style.willChange = "clip-path";
// }
},
onAdd(node, flag = false) {
const widget = this;
if (!widget.element.parentElement)
document.body.append(widget.element);
if (!flag) {
DOM_NODE_DB.add(node);
if (widget.element.blur) {
widget.mouse_dom = function (event) {
if (!widget.element.contains(event.target))
widget.element.blur();
};
document.addEventListener("mousedown", widget.mouse_dom);
}
for (const evt of widget.options.selectOn) {
widget.element.addEventListener(evt, () => {
app.canvas.selectNode(node);
app.canvas.bringToFront(node);
});
}
lib0246.hijack(node, "collapse", function () {
if (this.mark && this.self.flags?.collapsed) {
widget.element.hidden = true;
widget.element.style.display = "none";
}
});
lib0246.hijack(node, "onRemoved", function () {
// widget.element.remove();
if (!this.mark) {
widget.onRemove();
DOM_NODE_DB.delete(this.self);
}
});
if (!node[DOM_RESIZE]) {
node[DOM_RESIZE] = true;
lib0246.hijack(node, "onResize", function (size) {
if (this.mark) {
widget.options.beforeResize?.call(widget, this);
widget.computeSize(size[0]);
widget.options.afterResize?.call(widget, this);
}
});
}
}
},
onRemove(flag = false) {
if (this.mouse_dom && !flag)
document.removeEventListener("mousedown", this.mouse_dom);
this.element.remove();
},
element,
options,
};
return widget;
}
Object.defineProperty(LGraphNode.prototype, "addDOMWidget", {
// Secret A/B testing ;)
// If no one complaining then it works I guess
writable: false,
configurable: false,
value: function (name, type, element, options = {}) {
options.flex = options.flex ?? {};
options.flex.ratio = options?.flex?.ratio ?? 0;
options.flex.share = options?.flex?.share ?? 1;
options.flex.min_h = options?.flex?.min_h ?? 30;
options.flex.center = options?.flex?.center ?? true;
options.flex.margin_x = options?.flex?.margin_x ?? 10;
const widget = DOM_WIDGET(type, name, element, options);
this.addCustomWidget(widget);
widget_flex(this, widget, options.flex);
widget.onAdd(this, false);
return widget;
}
});
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function eval_state(db, state, node, widget, event, pos) {
for (let i = 0; i < db.length; i += 2) {
if (Array.isArray(db[i])) {
for (let j = 0; j < db[i].length; ++ j)
if (lib0246.equal_dict(state, db[i][j], true, "*")) {
lib0246.update_dict(state, db[i + 1](node, widget, event, pos) ?? {});
return;
}
continue;
} else if (lib0246.equal_dict(state, db[i], true, "*")) {
lib0246.update_dict(state, db[i + 1](node, widget, event, pos) ?? {});
return;
}
}
}
function reset_state(state) {
delete state.mouse;
delete state.where;
delete state.action;
delete state.select;
delete state.bound;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function box_range_eval_corner(pos, curr_box, widget) {
if (lib0246.is_inside_circ(
pos[0], pos[1],
curr_box[8] + curr_box[10], curr_box[9], widget.box_range.radius
)) {
// Intentionally prioritize top right
widget.box_range.state.where = "tr";
return true;
} else if (lib0246.is_inside_circ(
pos[0], pos[1],
curr_box[8], curr_box[9], widget.box_range.radius
)) {
widget.box_range.state.where = "tl";
return true;
} else if (lib0246.is_inside_circ(
pos[0], pos[1],
curr_box[8], curr_box[9] + curr_box[11], widget.box_range.radius
)) {
widget.box_range.state.where = "bl";
return true;
} else if (lib0246.is_inside_circ(
pos[0], pos[1],
curr_box[8] + curr_box[10], curr_box[9] + curr_box[11], widget.box_range.radius
)) {
widget.box_range.state.where = "br";
return true;
}
return false;
}
function box_range_process_del(widget) {
let index = widget.box_range.boxes.indexOf(widget.box_range.select[widget.box_range.select.length - 1]);
if (index !== -1)
widget.box_range.boxes.splice(index, 1);
widget.box_range.select.length = 0;
widget.box_range.delay_state = null;
}
function box_range_grid_snap(pos, widget) {
pos[0] = lib0246.lerp(
lib0246.snap(
lib0246.norm(pos[0], widget.flex.hold_mouse[0], widget.flex.hold_mouse[0] + widget.flex.hold_mouse[2]) + 1 / (widget.row_count * 2),
1 / widget.row_count
),
widget.flex.hold_mouse[0], widget.flex.hold_mouse[0] + widget.flex.hold_mouse[2]
);
pos[1] = lib0246.lerp(
lib0246.snap(
lib0246.norm(pos[1], widget.flex.hold_mouse[1], widget.flex.hold_mouse[1] + widget.flex.hold_mouse[3]) + 1 / (widget.col_count * 2),
1 / widget.col_count
),
widget.flex.hold_mouse[1], widget.flex.hold_mouse[1] + widget.flex.hold_mouse[3]
);
}
const BOX_RANGE_STATE = [
// Selection
...[
[
{
mouse: "pointerdown",
where: "box",
action: "",
bound: "in"
}, {
mouse: "pointerdown",
where: "box",
action: "select",
bound: "in"
},
], function (node, widget, event, pos) {
widget.box_range.select_during = pos;
widget.box_range.delay_state = window.performance.now();
return {
action: "select"
};
},
[
{
mouse: "pointerup",
where: "box",
action: "select",
bound: "in"
}
], function (node, widget, event, pos) {
// [TODO] Maybe also perform delete for this state?
let res;
if (lib0246.equal_array(widget.box_range.select_during, pos, false)) {
if (event.shiftKey && widget.box_range.select.length > 0) {
let curr_box = widget.box_range.select[widget.box_range.select.length - 1];
app.canvas.prompt("[x, y, width, height]", JSON.stringify(curr_box.slice(0, 4)), (value) => {
try {
const res = JSON.parse(value);
if (res.length !== 4)
return;
for (let i = 0; i < 4; ++ i)
if (typeof res[i] === "string") {
try {
const BOUND_X = widget.flex.hold_mouse[0],
BOUND_Y = widget.flex.hold_mouse[1],
BOUND_W = widget.flex.hold_mouse[2],
BOUND_H = widget.flex.hold_mouse[3];
res[i] = Number(eval(res[i]));
} catch (e) {
lib0246.error_popup("Invalid box range math expression format.");
return;
}
} else if (!Number.isFinite(res[i]))
res[i] = widget.flex.hold_mouse[i];
if (lib0246.is_inside_rect_rect(
res[0], res[1], res[2], res[3],
widget.flex.hold_draw[0], widget.flex.hold_draw[1],
widget.flex.hold_draw[2], widget.flex.hold_draw[3]
)) {
curr_box[0] = res[0];
curr_box[1] = res[1];
curr_box[2] = res[2];
curr_box[3] = res[3];
} else
lib0246.error_popup("Provided range is outside of the boundary.");
} catch (e) {
const ratio_widget = node.widgets.find(w => w.name === "box_ratio");
let size_box = curr_box.slice(4, 8);
if (ratio_widget)
size_box = [0, 0, ratio_widget.value.data.width, ratio_widget.value.data.height];
try {
let old_onmessage = window.onmessage;
window.onmessage = () => {};
lib0246.safe_eval(`
function _ (x, y, w, h) {
return calc_flex_norm(
x, y, w, h,
${size_box[0]}, ${size_box[1]}, ${size_box[2]}, ${size_box[3]},
${widget.flex.hold_draw[0]}, ${widget.flex.hold_draw[1]},
${widget.flex.hold_draw[2]}, ${widget.flex.hold_draw[3]}
);
}
const CURR_X = ${size_box[0]},
CURR_Y = ${size_box[1]},
CURR_W = ${size_box[2]},
CURR_H = ${size_box[3]},
CODE = ${"`" + value + "`"};
return ${value};
`).then((res) => {
if (!Array.isArray(res) || res.length !== 4) {
lib0246.error_popup("Invalid box range data format. Expected [x, y, width, height].");
return;
}
if (lib0246.is_inside_rect_rect(
res[0], res[1], res[2], res[3],
widget.flex.hold_draw[0], widget.flex.hold_draw[1],
widget.flex.hold_draw[2], widget.flex.hold_draw[3]
)) {
curr_box[0] = res[0];
curr_box[1] = res[1];
curr_box[2] = res[2];
curr_box[3] = res[3];
curr_box[12] = res[0];
curr_box[13] = res[1];
curr_box[14] = res[2];
curr_box[15] = res[3];
curr_box[16] = size_box[0];
curr_box[17] = size_box[1];
curr_box[18] = size_box[2];
curr_box[19] = size_box[3];
} else
lib0246.error_popup("Provided range is outside of the boundary.");
window.onmessage = old_onmessage;
app.canvas.setDirty(true);
});
} catch (e) {
lib0246.error_popup(`Invalid box range expression format: ${e.message}`);
return;
}
}
}, event, true);
res = {
action: ""
};
} else {
let select_list = [];
for (let i = 0; i < widget.box_range.boxes.length; ++ i)
if (lib0246.is_inside_rect(
pos[0], pos[1],
widget.box_range.boxes[i][8], widget.box_range.boxes[i][9],
widget.box_range.boxes[i][10], widget.box_range.boxes[i][11]
))
select_list.push(widget.box_range.boxes[i]);
if (lib0246.equal_array(widget.box_range.select, select_list, true))
widget.box_range.select.push(widget.box_range.select.shift());
else
widget.box_range.select = select_list;
}
}
widget.box_range.select_during = null;
widget.box_range.delay_state = null;
return res;
}
],
// Box create
...[
[
{
mouse: "pointerdown",
where: "",
action: "",
bound: "in"
}, {
mouse: "pointerdown",
where: "",
action: "select",
bound: "in"
}
], function (node, widget, event, pos) {
widget.box_range.begin_state = pos;
widget.box_range.during_state = pos;
widget.box_range.select.length = 0;
widget.box_range.delay_state = null;
return {
action: "create"
};
},
{
mouse: "pointerup",
where: "",
action: "",
bound: "in"
}, function (node, widget, event, pos) {
widget.box_range.select.length = 0;
return {
action: ""
};
},
{
mouse: "pointermove",
action: "create",
bound: "in"
}, function (node, widget, event, pos) {
widget.box_range.during_state = pos;
},
{
mouse: "pointerup",
action: "create",
bound: "in"
}, function (node, widget, event, pos) {
widget.box_range.during_state = pos;
// Check if equal then terminate early
if (!lib0246.equal_array(widget.box_range.begin_state, widget.box_range.during_state, false)) {
if (widget.box_range.begin_state[0] > widget.box_range.during_state[0]) {
let temp = widget.box_range.begin_state[0];
widget.box_range.begin_state[0] = widget.box_range.during_state[0];
widget.box_range.during_state[0] = temp;
}
if (widget.box_range.begin_state[1] > widget.box_range.during_state[1]) {
let temp = widget.box_range.begin_state[1];
widget.box_range.begin_state[1] = widget.box_range.during_state[1];
widget.box_range.during_state[1] = temp;
}
const width = Math.abs(widget.box_range.during_state[0] - widget.box_range.begin_state[0]),
height = Math.abs(widget.box_range.during_state[1] - widget.box_range.begin_state[1]);
if (event.shiftKey) {
let old_length = widget.box_range.boxes.length;
for (let i = 0; i < old_length; ++ i)
if (lib0246.is_inside_rect_rect(
widget.box_range.boxes[i][0], widget.box_range.boxes[i][1],
widget.box_range.boxes[i][2], widget.box_range.boxes[i][3],
widget.box_range.begin_state[0], widget.box_range.begin_state[1],
width, height
)) {
widget.box_range.select.push(widget.box_range.boxes[i]);
widget.box_range.boxes.splice(i --, 1);
-- old_length;
}
widget.box_range.select.length = 0;
} else
widget.box_range.boxes.push([
widget.box_range.begin_state[0],
widget.box_range.begin_state[1],
width, height,
...widget.flex.hold_mouse,
widget.box_range.begin_state[0],
widget.box_range.begin_state[1],
width, height,
]);
}
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
return {
action: ""
};
},
],
// Box move
...[
[
{
mouse: "pointermove",
where: "box",
action: "select",
bound: "in"
}
], function (node, widget, event, pos) {
if (event.shiftKey)
box_range_grid_snap(pos, widget);
widget.box_range.begin_state = pos;
widget.box_range.during_state = pos;
widget.box_range.delay_state = null;
if (widget.box_range.select.length === 0)
return {
action: "select"
};
return {
action: "move"
};
},
{
mouse: "pointermove",
action: "move",
bound: "in"
}, function (node, widget, event, pos) {
widget.box_range.during_state = pos;
if (event.shiftKey) {
box_range_grid_snap(widget.box_range.begin_state, widget);
box_range_grid_snap(widget.box_range.during_state, widget);
}
},
{
mouse: "pointerup",
action: "move",
bound: "in"
}, function (node, widget, event, pos) {
const curr_box = widget.box_range.select[widget.box_range.select.length - 1],
res = lib0246.calc_flex_norm(
curr_box[0], curr_box[1], curr_box[2], curr_box[3],
curr_box[4], curr_box[5], curr_box[6], curr_box[7],
widget.flex.hold_mouse[0], widget.flex.hold_mouse[1], widget.flex.hold_mouse[2], widget.flex.hold_mouse[3],
);
let new_x = res[0] + widget.box_range.during_state[0] - widget.box_range.begin_state[0],
new_y = res[1] + widget.box_range.during_state[1] - widget.box_range.begin_state[1];
if (!lib0246.is_inside_rect_rect(
new_x, new_y, res[2], res[3],
widget.flex.hold_mouse[0], widget.flex.hold_mouse[1],
widget.flex.hold_mouse[2], widget.flex.hold_mouse[3]
)) {
// Champ back to range with a combination of min and max
new_x = Math.max(
Math.min(new_x, widget.flex.hold_mouse[0] + widget.flex.hold_mouse[2] - res[2]),
widget.flex.hold_mouse[0]
);
new_y = Math.max(
Math.min(new_y, widget.flex.hold_mouse[1] + widget.flex.hold_mouse[3] - res[3]),
widget.flex.hold_mouse[1]
);
}
curr_box[0] = res[0];
curr_box[1] = res[1];
curr_box[2] = res[2];
curr_box[3] = res[3];
curr_box[4] = widget.flex.hold_mouse[0];
curr_box[5] = widget.flex.hold_mouse[1];
curr_box[6] = widget.flex.hold_mouse[2];
curr_box[7] = widget.flex.hold_mouse[3];
curr_box[8] = curr_box[0];
curr_box[9] = curr_box[1];
curr_box[10] = curr_box[2];
curr_box[11] = curr_box[3];
curr_box[0] = new_x;
curr_box[1] = new_y;
curr_box[8] = new_x
curr_box[9] = new_y;
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
return {
action: "select"
};
},
{
mouse: "pointerup",
action: "move",
bound: "out"
}, function (node, widget, event, pos) {
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
widget.box_range.select.length = 0;
return {
action: ""
};
}
],
// Box resize
...[
{
mouse: "pointerdown",
where: "br",
action: "select",
bound: "in"
}, function (node, widget, event, pos) {
if (!widget.box_range.delay_state)
widget.box_range.delay_state = window.performance.now();
else if (window.performance.now() - widget.box_range.delay_state < widget.box_range.delay_dbl) {
if (event.shiftKey)
box_range_grid_snap(pos, widget);
widget.box_range.begin_state = pos;
widget.box_range.during_state = pos;
widget.box_range.delay_state = null;
return {
action: "resize"
};
}
},
[
{
mouse: "pointermove",
action: "resize",
bound: "in"
},
], function (node, widget, event, pos) {
if (!pos) return;
widget.box_range.during_state = pos;
if (event.shiftKey) {
box_range_grid_snap(widget.box_range.begin_state, widget);
box_range_grid_snap(widget.box_range.during_state, widget);
}
},
{
mouse: "pointerup",
action: "resize",
bound: "in"
}, function (node, widget, event, pos) {
if (!lib0246.equal_array(widget.box_range.during_state, widget.box_range.begin_state, false)) {
const curr_box = widget.box_range.select[widget.box_range.select.length - 1];
let res = lib0246.calc_flex_norm(
curr_box[0], curr_box[1], curr_box[2], curr_box[3],
curr_box[4], curr_box[5], curr_box[6], curr_box[7],
widget.flex.hold_mouse[0], widget.flex.hold_mouse[1], widget.flex.hold_mouse[2], widget.flex.hold_mouse[3],
);
res = lib0246.calc_resize(
res[0], res[1], res[2], res[3],
widget.box_range.during_state[0], widget.box_range.during_state[1]
);
curr_box[0] = res[0];
curr_box[1] = res[1];
curr_box[2] = res[2];
curr_box[3] = res[3];
curr_box[4] = widget.flex.hold_mouse[0];
curr_box[5] = widget.flex.hold_mouse[1];
curr_box[6] = widget.flex.hold_mouse[2];
curr_box[7] = widget.flex.hold_mouse[3];
curr_box[8] = curr_box[0];
curr_box[9] = curr_box[1];
curr_box[10] = curr_box[2];
curr_box[11] = curr_box[3];
// Remove index 12 to index 19
curr_box.splice(12, 8);
}
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
return {
action: "select"
};
},
{
mouse: "pointerup",
bound: "in",
where: "br",
action: "select"
}, () => {},
{
mouse: "pointerup",
action: "resize",
bound: "out"
}, function (node, widget, event, pos) {
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
widget.box_range.select.length = 0;
return {
action: ""
};
}
],
// Box z-index
...[
[
{
mouse: "pointerdown",
where: "tl",
action: "select",
bound: "in"
}, {
mouse: "pointerdown",
where: "bl",
action: "select",
bound: "in"
}
], function (node, widget, event, pos) {
if (!widget.box_range.delay_state)
widget.box_range.delay_state = window.performance.now();
else if (window.performance.now() - widget.box_range.delay_state < widget.box_range.delay_dbl) {
const curr_index = widget.box_range.boxes.indexOf(widget.box_range.select[widget.box_range.select.length - 1]);
// Insert box to curr_index + 1 if state.where is bottom left, else curr_index - 1
widget.box_range.boxes.splice(
lib0246.rem(widget.box_range.state.where === "bl" ? curr_index + 1 : curr_index - 1, widget.box_range.boxes.length),
0,
widget.box_range.boxes.splice(curr_index, 1)[0]
);
widget.box_range.delay_state = null;
return {
action: "select"
};
}
},
[
{
mouse: "pointerup",
where: "tl",
action: "select",
bound: "in"
}, {
mouse: "pointerup",
where: "bl",
action: "select",
bound: "in"
}
], () => {}
],
// Box delete
...[
{
mouse: "pointerdown",
where: "tr",
action: "select",
bound: "in"
}, function (node, widget, event, pos) {
if (!widget.box_range.delay_state)
widget.box_range.delay_state = window.performance.now();
else if (window.performance.now() - widget.box_range.delay_state < widget.box_range.delay_dbl) {
box_range_process_del(widget);
return {
action: ""
};
}
},
{
mouse: "pointerup",
where: "tr",
action: "select",
bound: "in"
}, () => {},
],
// Reset state if invalid
{}, function (node, widget, event, pos) {
reset_state(widget.box_range.state);
widget.box_range.select.length = 0;
widget.box_range.begin_state = null;
widget.box_range.during_state = null;
widget.box_range.select_during = null;
widget.box_range.delay_state = null;
}
];
const NODE_COLOR_LIST = Object.keys(LGraphCanvas.node_colors);
export function BOX_RANGE_WIDGET(data_type, data_name, options = {}) {
const widget = {
type: data_type,
name: data_name,
get value() {
let data = [];
for (let i = 0; i < this.box_range.boxes.length; ++ i)
data.push(lib0246.calc_flex_norm(
this.box_range.boxes[i][0], this.box_range.boxes[i][1], this.box_range.boxes[i][2], this.box_range.boxes[i][3],
this.box_range.boxes[i][4], this.box_range.boxes[i][5], this.box_range.boxes[i][6], this.box_range.boxes[i][7],
this.flex.hold_draw[0], this.flex.hold_draw[1], this.flex.hold_draw[2], this.flex.hold_draw[3]
));
return {
type: "box_range",
data: data,
area: [
this.flex.hold_draw[0], this.flex.hold_draw[1],
this.flex.hold_draw[2], this.flex.hold_draw[3]
],
flag: this.flex.ratio === 0
};
},
set value(v) {
if (v.flag)
this.flex.ratio = 0;
else
this.flex.ratio = v.area[2] / v.area[3];
this.box_range.boxes.length = 0;
for (let i = 0; i < v.data.length; ++ i)
this.box_range.boxes.push([
...v.data[i],
...v.area,
...v.data[i],
]);
},
draw: function (ctx, node, widget_width, y, widget_height) {
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.fillStyle = app.canvas.clear_background_color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.rect(this.flex.hold_draw[0], this.flex.hold_draw[1], this.flex.hold_draw[2], this.flex.hold_draw[3]);
ctx.clip();
ctx.stroke();
ctx.fill();
ctx.closePath();
// Draw evenly spaced grid of both row and column of specified count
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.lineWidth = 1;
ctx.lineWidth = 0.5;
ctx.setLineDash([10, 5]);
for (let i = 0; i < this.row_count; ++ i) {
ctx.moveTo(this.flex.hold_draw[0], i * this.flex.hold_draw[3] / this.row_count + this.flex.hold_draw[1]);
ctx.lineTo(this.flex.hold_draw[2] + this.flex.hold_draw[0], i * this.flex.hold_draw[3] / this.row_count + this.flex.hold_draw[1]);
ctx.stroke();
}
for (let i = 0; i < this.col_count; ++ i) {
ctx.moveTo(i * this.flex.hold_draw[2] / this.col_count + this.flex.hold_draw[0], this.flex.hold_draw[1]);
ctx.lineTo(i * this.flex.hold_draw[2] / this.col_count + this.flex.hold_draw[0], this.flex.hold_draw[3] + this.flex.hold_draw[1]);
ctx.stroke();
}
ctx.closePath();
// Map each point to fit the grid by percentage based on previous size to current size
if (this?.box_range?.boxes)
for (let i = 0; i < this.box_range.boxes.length; ++ i) {
let curr = this.box_range.boxes[i];
const res = lib0246.calc_flex_norm(
curr[0], curr[1], curr[2], curr[3],
curr[4], curr[5], curr[6], curr[7],
this.flex.hold_draw[0], this.flex.hold_draw[1], this.flex.hold_draw[2], this.flex.hold_draw[3]
);
curr[8] = res[0];
curr[9] = res[1];
curr[10] = res[2];
curr[11] = res[3];
}
// Draw each box
if (this?.box_range?.boxes) {
for (let i = 0; i < this.box_range.boxes.length; ++ i) {
let curr = this.box_range.boxes[i];
if (this.box_range.boxes[i] === this.box_range.select[this.box_range.select.length - 1]) {
// Draw text metadata bottom left of the entire grid
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.font = "12px Consolas";
ctx.fillText(
// `%XY2: (${lib0246.floor(lib0246.norm(curr[8] + curr[10], this.flex.hold_draw[0], this.flex.hold_draw[2]), 2)}, ${lib0246.floor(lib0246.norm(curr[9] + curr[11], this.flex.hold_draw[1], this.flex.hold_draw[3]), 2)})`,
`%WH: (${lib0246.floor(curr[10] / this.flex.hold_draw[2], 2)}, ${lib0246.floor(curr[11] / this.flex.hold_draw[3], 2)})`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[3] + this.flex.hold_draw[1] - 5
);
ctx.fillText(
`%XY: (${lib0246.floor(lib0246.norm(curr[0], this.flex.hold_draw[0], this.flex.hold_draw[2]), 2)}, ${lib0246.floor(lib0246.norm(curr[1], this.flex.hold_draw[1], this.flex.hold_draw[3]), 2)})`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[3] + this.flex.hold_draw[1] - 20
);
ctx.fillText(
`Z: ${i}`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[3] + this.flex.hold_draw[1] - 35
);
ctx.fillText(
`WH: (${lib0246.floor(curr[10], 2)}, ${lib0246.floor(curr[11], 2)})`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[3] + this.flex.hold_draw[1] - 50
);
ctx.fillText(
`XY: (${lib0246.floor(curr[8], 2)}, ${lib0246.floor(curr[9], 2)})`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[3] + this.flex.hold_draw[1] - 65
);
ctx.closePath();
// Draw 4 circles for each corner
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#ff7ac1";
ctx.setLineDash([]);
ctx.arc(curr[8], curr[9], this.box_range.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "#800044";
ctx.arc(curr[8], curr[9] + curr[11], this.box_range.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
// Delete
ctx.beginPath();
ctx.lineWidth = 1;
ctx.strokeStyle = "#ff0000";
ctx.arc(curr[8] + curr[10], curr[9], this.box_range.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
// Resize
ctx.beginPath();
ctx.strokeStyle = "#ffff00";
ctx.arc(curr[8] + curr[10], curr[9] + curr[11], this.box_range.radius, 0, Math.PI * 2);
ctx.stroke();
ctx.closePath();
}
ctx.beginPath();
ctx.fillStyle = "rgba(127, 127, 127, 0.1)";
if (this.box_range.select.length > 0 && this.box_range.select.indexOf(this.box_range.boxes[i]) === this.box_range.select.length - 1) {
ctx.lineWidth = 1.5;
ctx.strokeStyle = "#ff0000";
ctx.setLineDash([5, 5]);
} else {
ctx.lineWidth = 1;
ctx.strokeStyle = "#ffffff";
ctx.setLineDash([]);
}
ctx.rect(curr[8], curr[9], curr[10], curr[11]);
ctx.stroke();
ctx.fill();
ctx.closePath();
}
for (let i = 0; i < this.box_range.boxes.length; ++ i) {
let curr = this.box_range.boxes[i];
// Draw index text starting from the box top left
ctx.beginPath();
ctx.fillStyle = LGraphCanvas.node_colors[NODE_COLOR_LIST[i % NODE_COLOR_LIST.length]].groupcolor;
ctx.font = "15px Consolas";
ctx.fillText(
`${i}`,
curr[8] + 5, curr[9] + 15
);
ctx.closePath();
}
}
// Draw ghost movement
if (this?.box_range?.begin_state && this?.box_range?.during_state) {
ctx.beginPath();
ctx.lineWidth = 3;
ctx.setLineDash([5, 5]);
ctx.moveTo(this.box_range.begin_state[0], this.box_range.begin_state[1]);
let last_select = this.box_range.select[this.box_range.select.length - 1] ?? [];
switch (this.box_range.state.action) {
case "create": {
ctx.strokeStyle = "rgba(0, 255, 0, 0.75)";
ctx.rect(
this.box_range.begin_state[0], this.box_range.begin_state[1],
this.box_range.during_state[0] - this.box_range.begin_state[0], this.box_range.during_state[1] - this.box_range.begin_state[1],
);
} break;
case "resize": {
ctx.strokeStyle = "rgba(255, 255, 0, 0.75)";
const res = lib0246.calc_resize(
last_select[8], last_select[9], last_select[10], last_select[11],
this.box_range.during_state[0], this.box_range.during_state[1]
);
ctx.rect(res[0], res[1], res[2], res[3]);
} break;
case "move": {
ctx.strokeStyle = "rgba(0, 0, 255, 0.75)";
ctx.rect(
last_select[8] + this.box_range.during_state[0] - this.box_range.begin_state[0],
last_select[9] + this.box_range.during_state[1] - this.box_range.begin_state[1],
last_select[10], last_select[11]
);
}
}
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = "#ffffff";
ctx.font = "12px Consolas";
ctx.fillText(
`!XY: (${lib0246.floor(this.box_range.during_state[0], 2)}, ${lib0246.floor(this.box_range.during_state[1], 2)})`,
this.flex.hold_draw[0] + 5, this.flex.hold_draw[1] + 15
);
ctx.closePath();
}
ctx.restore();
},
mouse: function (event, pos, node) {
// if (pos[0] < this.flex.hold_mouse[0])
// pos[0] = this.flex.hold_mouse[0];
// if (pos[1] < this.flex.hold_mouse[1])
// pos[1] = this.flex.hold_mouse[1];
// if (pos[0] > this.flex.hold_mouse[2] + this.flex.hold_mouse[0])
// pos[0] = this.flex.hold_mouse[2] + this.flex.hold_mouse[0];
// if (pos[1] > this.flex.hold_mouse[3] + this.flex.hold_mouse[1])
// pos[1] = this.flex.hold_mouse[3] + this.flex.hold_mouse[1];
widget.box_range.state = widget.box_range.state ?? {};
widget.box_range.delay_state = widget.box_range.delay_state ?? null;
widget.box_range.state.mouse = event.type;
let box_flag = false;
if (widget.box_range.select.length > 0)
if (box_range_eval_corner(pos, widget.box_range.select[widget.box_range.select.length - 1], widget))
box_flag = true;
else if (lib0246.is_inside_rect(
pos[0], pos[1],
widget.box_range.select[widget.box_range.select.length - 1][8], widget.box_range.select[widget.box_range.select.length - 1][9],
widget.box_range.select[widget.box_range.select.length - 1][10], widget.box_range.select[widget.box_range.select.length - 1][11]
)) {
widget.box_range.state.where = "box";
box_flag = true;
}
if (!box_flag)
for (let i = 0; i < widget.box_range.boxes.length; ++ i)
if (lib0246.is_inside_rect(
pos[0], pos[1],
widget.box_range.boxes[i][8], widget.box_range.boxes[i][9],
widget.box_range.boxes[i][10], widget.box_range.boxes[i][11]
)) {
widget.box_range.state.where = "box";
box_flag = true;
break;
} else if (box_range_eval_corner(pos, widget.box_range.boxes[i], widget)) {
box_flag = true;
break;
}
widget.box_range.state.bound = lib0246.is_inside_rect(
pos[0], pos[1],
this.flex.hold_mouse[0], this.flex.hold_mouse[1], this.flex.hold_mouse[2], this.flex.hold_mouse[3]
) ? "in" : "out";
if (!widget.box_range.state.where || !box_flag)
widget.box_range.state.where = "";
if (!widget.box_range.state.action)
widget.box_range.state.action = "";
if (window.performance.now() - (widget.box_range.delay_state ?? 0) > widget.box_range.delay_dbl)
widget.box_range.delay_state = null;
eval_state(BOX_RANGE_STATE, widget.box_range.state, node, widget, event, pos);
},
};
widget.box_range = widget.box_range ?? {};
widget.box_range.boxes = widget.box_range.boxes ?? [];
widget.box_range.select = widget.box_range.select ?? [];
widget.box_range.delay_dbl = widget.box_range.delay_dbl ?? options.delay_dbl ?? 200;
widget.box_range.radius = widget.box_range.radius ?? options.radius ?? 15;
widget.row_count = options.row_count ?? 20;
widget.col_count = options.col_count ?? 20;
return widget;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function draw_lock(ctx, x, y, width, height, flag) {
const halve_w = 8,
off_x = halve_w / 2,
off_y = height / 3 + 0.5,
calc_w = (width - halve_w) / 2,
lift_y = flag ? 1.5 : 0,
hole_color = flag ? "#4d4" : "#d44",
thick_w = 1;
// Draw entire red rect of x y width height
// ctx.beginPath();
// ctx.strokeStyle = "#ff0000";
// ctx.rect(x, y, width, height);
// ctx.stroke();
// ctx.closePath();
// Draw half arc
ctx.beginPath();
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.lineWidth = 1;
ctx.arc(x + calc_w + off_x, y + off_y - lift_y, calc_w + thick_w / 2, Math.PI, 0, false);
ctx.stroke();
ctx.closePath();
if (flag) {
// Draw vertical line of the left side
ctx.beginPath();
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.lineWidth = 1;
ctx.moveTo(x - thick_w / 2 + off_x, y + off_y - lift_y);
ctx.lineTo(x - thick_w / 2 + off_x, y + off_y + lift_y);
ctx.stroke();
ctx.closePath();
}
// Draw round rect
ctx.beginPath();
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillStyle = LiteGraph.WIDGET_BGCOLOR;
ctx.lineWidth = 1 + thick_w;
ctx.roundRect(x + off_x, y + off_y + lift_y, width - halve_w, height - halve_w, [0, 0, 5, 5]);
ctx.stroke();
ctx.fill();
ctx.closePath();
// Draw circle at center of the rect
ctx.beginPath();
ctx.fillStyle = hole_color;
ctx.lineWidth = 1;
ctx.arc(x + calc_w + off_x, y + off_y / 2 + off_y + lift_y, 2, 0, Math.PI * 2);
ctx.fill();
ctx.closePath();
// Draw triangle just below the circle
ctx.beginPath();
ctx.fillStyle = hole_color;
ctx.lineWidth = 1;
const tri_of_y = -1;
ctx.moveTo(x + calc_w + off_x, y + off_y / 2 + off_y + lift_y + tri_of_y);
ctx.lineTo(x + calc_w + off_x - 2, y + off_y / 2 + off_y + 6 + lift_y + tri_of_y);
ctx.lineTo(x + calc_w + off_x + 2, y + off_y / 2 + off_y + 6 + lift_y + tri_of_y);
ctx.fill();
ctx.closePath();
}
function draw_number_lock(widget, ctx, x, y, widget_width, widget_height, left_margin, right_margin, text_margin, lock_margin, text_raw, text_real, lock, text_flag) {
ctx.textAlign = "left";
ctx.strokeStyle = LiteGraph.WIDGET_OUTLINE_COLOR;
ctx.fillStyle = LiteGraph.WIDGET_BGCOLOR;
ctx.beginPath();
if (text_flag)
ctx.roundRect(x + left_margin + lock_margin, y, widget_width - left_margin - right_margin - lock_margin, widget_height, [widget_height * 0.5]);
else
ctx.rect(x + left_margin + lock_margin, y, widget_width - left_margin - right_margin - lock_margin, widget_height);
ctx.fill();
if (text_flag) {
if (!widget.disabled)
ctx.stroke();
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
if (!widget.disabled) {
ctx.beginPath();
ctx.moveTo(x + left_margin + lock_margin + 16, y + 5);
ctx.lineTo(x + left_margin + lock_margin + 6, y + widget_height * 0.5);
ctx.lineTo(x + left_margin + lock_margin + 16, y + widget_height - 5);
ctx.fill();
ctx.beginPath();
ctx.moveTo(x + widget_width - right_margin - 16, y + 5);
ctx.lineTo(x + widget_width - right_margin - 6, y + widget_height * 0.5);
ctx.lineTo(x + widget_width - right_margin - 16, y + widget_height - 5);
ctx.fill();
}
ctx.fillStyle = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR;
ctx.fillText(`${widget.label ?? widget.name} (${text_real})`, x + left_margin + text_margin + lock_margin + 5, y + widget_height * 0.7);
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textAlign = "right";
ctx.fillText(
text_raw,
x + widget_width - right_margin - text_margin - 20,
y + widget_height * 0.7
);
}
draw_lock(ctx, x + left_margin, y, widget_height, widget_height, lock);
}
function ratio_process_calc(widget, mode, old_value) {
const data = widget.value.data,
lock = widget.value.lock,
opt = widget.options;
switch (Math.abs(mode)) {
case 1: {
if (lock.height && lock.width && lock.ratio) {
data.height = old_value * data.height / data.width;
if (data.height < opt.min) {
data.height = opt.min;
data.width = data.height * data.ratio;
} else if (data.height > opt.max) {
data.height = opt.max;
data.width = data.height * data.ratio;
}
data.ratio = data.width / data.height;
} else if (lock.height && !lock.ratio) {
data.height = data.width / data.ratio;
if (data.height < opt.min) {
data.height = opt.min;
data.width = data.height * data.ratio;
} else if (data.height > opt.max) {
data.height = opt.max;
data.width = data.height * data.ratio;
}
} else if (!lock.height && lock.ratio) {
data.ratio = data.width / data.height;
if (data.ratio < opt.min) {
data.ratio = opt.min;
data.width = data.height * data.ratio;
} else if (data.ratio > opt.max) {
data.ratio = opt.max;
data.width = data.height * data.ratio;
}
} else {
lock.height = false;
lock.ratio = false;
data.width = old_value;
}
} break;
case 2: {
if (lock.height && lock.width && lock.ratio) {
data.width = old_value * data.width / data.height;
if (data.width < opt.min) {
data.width = opt.min;
data.height = data.width / data.ratio;
} else if (data.width > opt.max) {
data.width = opt.max;
data.height = data.width / data.ratio;
}
data.ratio = data.width / data.height;
} else if (lock.width && !lock.ratio) {
data.width = data.height * data.ratio;
if (data.width < opt.min) {
data.width = opt.min;
data.height = data.width / data.ratio;
} else if (data.width > opt.max) {
data.width = opt.max;
data.height = data.width / data.ratio;
}
} else if (!lock.width && lock.ratio) {
data.ratio = data.width / data.height;
if (data.ratio < opt.min) {
data.ratio = opt.min;
data.height = data.width / data.ratio;
} else if (data.ratio > opt.max) {
data.ratio = opt.max;
data.height = data.width / data.ratio;
}
} else {
lock.width = false;
lock.ratio = false;
data.height = old_value;
}
} break;
case 3: {
if (
(lock.height && lock.width && lock.ratio) ||
(!lock.height && !lock.width && !lock.ratio)
) {
data.ratio = old_value;
} else if (!lock.height && lock.width) {
data.width = data.height * data.ratio;
if (data.width < opt.min) {
data.width = opt.min;
data.height = data.width / data.ratio;
} else if (data.width > opt.max) {
data.width = opt.max;
data.height = data.width / data.ratio;
}
} else if (!lock.width && lock.height) {
data.height = data.width / data.ratio;
if (data.height < opt.min) {
data.height = opt.min;
data.width = data.height * data.ratio;
} else if (data.height > opt.max) {
data.height = opt.max;
data.width = data.height * data.ratio;
}
} else {
lock.height = false;
lock.width = false;
data.ratio = old_value;
}
} break;
}
if (!Number.isFinite(data.width))
data.width = 0;
if (!Number.isFinite(data.height))
data.height = 0;
if (!Number.isFinite(data.ratio))
data.ratio = 0;
}
function ratio_notify(node, widget, name, mode, old_value, value, pos, event, w_f, h_f) {
if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(value))
try {
const res = Number(eval(value));
if (w_f) {
widget.value.data.width = res / widget.value.data.height;
if (widget.value.data.width < widget.options.min) {
widget.value.data.width = widget.options.min;
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
} else if (widget.value.data.width > widget.options.max) {
widget.value.data.width = widget.options.max;
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
}
widget.value.data.ratio = widget.value.data.width / widget.value.data.height;
app.canvas.setDirty(true);
} else if (h_f) {
widget.value.data.height = res / widget.value.data.width;
if (widget.value.data.height < widget.options.min) {
widget.value.data.height = widget.options.min;
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
} else if (widget.value.data.height > widget.options.max) {
widget.value.data.height = widget.options.max;
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
}
widget.value.data.ratio = widget.value.data.width / widget.value.data.height;
app.canvas.setDirty(true);
} else {
widget.value.data[name] = res;
ratio_process_calc(widget, mode, old_value);
}
if (widget.options && widget.options.property && node.properties[widget.options.property] !== undefined)
node.setProperty(widget.options.property, value);
widget?.callback?.(widget.value, widget, node, pos, event);
} catch (e) {}
}
export function RATIO_WIDGET(data_type, data_name, options = {}) {
const widget = {
type: data_type,
name: data_name,
value: {
lock: {
ratio: false,
width: false,
height: false,
},
data: {
ratio: 1,
width: 512,
height: 512,
}
},
options: options,
draw: function (ctx, node, widget_width, y, widget_height) {
ctx.save();
const show_text = app.canvas.ds.scale > 0.5,
margin = 15,
lock_margin = 20,
text_margin = 15,
calc_width = widget_width / 3;
this.temp_w = widget_width;
this.temp_h = widget_height;
this.temp_y = y;
draw_number_lock(
this, ctx, 0, y, calc_width, widget_height, margin, 0, text_margin, lock_margin,
lib0246.floor(widget.value.data.width, widget.options.precision ?? 3),
`W: ${lib0246.snap(widget.value.data.width, widget.options.snap)}`,
widget.value.lock.width, show_text
);
draw_number_lock(
this, ctx, calc_width, y, calc_width, widget_height, 0, 0, text_margin, lock_margin,
lib0246.floor(widget.value.data.ratio, widget.options.precision ?? 3),
`A: ${lib0246.floor(widget.value.data.width * widget.value.data.height, widget.options.precision ?? 3)}`,
widget.value.lock.ratio, show_text
);
draw_number_lock(
this, ctx, calc_width * 2, y, calc_width, widget_height, 0, margin, text_margin, lock_margin,
lib0246.floor(widget.value.data.height, widget.options.precision ?? 3),
`H: ${lib0246.snap(widget.value.data.height, widget.options.snap)}`,
widget.value.lock.height, show_text
);
ctx.restore();
},
mouse: function (event, pos, node) {
const margin = 15,
lock_margin = 20,
text_margin = 15,
calc_width = this.temp_w / 3,
hit_width = 20;
let mode = 0, old_value;
if (event.type === "pointerdown") {
if (lib0246.is_inside_rect(
pos[0], pos[1],
margin, this.temp_y, this.temp_h, this.temp_h
))
this.value.lock.width = !this.value.lock.width;
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width, this.temp_y, this.temp_h, this.temp_h
))
this.value.lock.ratio = !this.value.lock.ratio;
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width * 2, this.temp_y, this.temp_h, this.temp_h
))
this.value.lock.height = !this.value.lock.height;
else if (lib0246.is_inside_rect(
pos[0], pos[1],
margin + lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 1;
old_value = this.value.data.width;
this.value.data.width = Math.max(this.options.min, this.value.data.width - this.options.step * 0.1);
}
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width - lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 1;
old_value = this.value.data.width;
this.value.data.width = Math.max(this.options.min, this.value.data.width + this.options.step * 0.1);
}
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width + lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 3;
old_value = this.value.data.ratio;
this.value.data.ratio = Math.max(this.options.min, this.value.data.ratio - this.options.step * 0.001);
}
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width * 2 - lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 3;
old_value = this.value.data.ratio;
this.value.data.ratio = Math.max(this.options.min, this.value.data.ratio + this.options.step * 0.001);
}
else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width * 2 + lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 2;
old_value = this.value.data.height;
this.value.data.height = Math.max(this.options.min, this.value.data.height - this.options.step * 0.1);
}
else if (lib0246.is_inside_rect(
pos[0], pos[1],
this.temp_w - margin - lock_margin, this.temp_y, hit_width, this.temp_h
)) {
mode = 2;
old_value = this.value.data.height;
this.value.data.height = Math.max(this.options.min, this.value.data.height + this.options.step * 0.1);
}
} else {
if (lib0246.is_inside_rect(
pos[0], pos[1],
margin + lock_margin + hit_width, this.temp_y, (calc_width - lock_margin) - (margin + lock_margin + hit_width), this.temp_h
)) {
mode = 1;
old_value = this.value.data.width;
if (event.dragging) {
this.value.data.width = Math.max(this.options.min, Math.min(this.options.max, this.value.data.width + event.deltaX * 0.1 * this.options.step));
this.drag_flag = true;
} else if (event.type === "pointerup")
if (!this.drag_flag)
app.canvas.prompt("Width", this.value.data.width, (value) => ratio_notify(node, this, "width", mode, old_value, value, pos, event), event);
else
this.drag_flag = false;
} else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width + lock_margin + hit_width, this.temp_y, (calc_width * 2 - lock_margin) - (calc_width + lock_margin + hit_width), this.temp_h
)) {
mode = 3;
old_value = this.value.data.ratio;
if (event.dragging) {
this.value.data.ratio = Math.max(this.options.min, Math.min(this.options.max, this.value.data.ratio + event.deltaX * 0.001 * this.options.step));
this.drag_flag = true;
} else if (event.type === "pointerup")
if (!this.drag_flag) {
const w_f = this.value.lock.ratio && this.value.lock.width && this.value.lock.height,
h_f = !this.value.lock.ratio && !this.value.lock.width && !this.value.lock.height;
app.canvas.prompt(w_f ? "Area (width)" : h_f ? "Area (height)" : "Ratio", this.value.data.ratio, (value) => ratio_notify(
node, this, "ratio", mode, old_value, value, pos, event, w_f, h_f
), event);
} else
this.drag_flag = false;
} else if (lib0246.is_inside_rect(
pos[0], pos[1],
calc_width * 2 + lock_margin + hit_width, this.temp_y, (this.temp_w - margin - lock_margin) - (calc_width * 2 + lock_margin + hit_width), this.temp_h
)) {
mode = 2;
old_value = this.value.data.height;
if (event.dragging) {
this.value.data.height = Math.max(this.options.min, Math.min(this.options.max, this.value.data.height + event.deltaX * 0.1 * this.options.step));
this.drag_flag = true;
} else if (event.type === "pointerup")
if (!this.drag_flag)
app.canvas.prompt("Height", this.value.data.height, (value) => ratio_notify(node, this, "height", mode, old_value, value, pos, event), event);
else
this.drag_flag = false;
}
}
if (mode !== 0) {
ratio_process_calc(this, mode, old_value);
node?.onWidgetChanged?.(this.name, this.value, old_value, this);
}
},
};
widget.options.min = widget.options.min ?? 0;
widget.options.max = widget.options.max ?? 2048;
widget.options.step = widget.options.step ?? 10;
widget.options.precision = widget.options.precision ?? 2;
widget.options.snap = widget.options.snap ?? 8;
return widget;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
export function SPACE_TITLE_WIDGET() {
return {
value: -1,
type: "space_title",
options: {
serialize: false,
},
select: true,
select_color: lib0246.rgb_to_hex(
lib0246.rand_int(128, 255),
lib0246.rand_int(128, 255),
lib0246.rand_int(128, 255)
),
draw: function(ctx, node, widget_width, y, widget_height) {
ctx.save();
if (this.value > -1) {
let widget_text = app.graph.getNodeById(this.value)?.title;
if (widget_text)
widget_text += ` (${this.value})`;
else
widget_text = this.value;
const side_margin = widget_text.length + 5,
text_measure = ctx.measureText(widget_text),
text_center_y = (text_measure.actualBoundingBoxAscent + text_measure.actualBoundingBoxDescent) / 2,
off_y = y + widget_height / 2;
ctx.beginPath();
ctx.fillStyle = this.select ? this.select_color : "#aaaaaa";
ctx.font = "15px Consolas";
ctx.textAlign = "center";
ctx.fillText(widget_text, widget_width / 2, text_center_y + off_y);
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "#aaaaaa";
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.moveTo(0, off_y);
ctx.lineTo(widget_width / 2 - text_measure.width / 2 - side_margin, off_y);
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.strokeStyle = "#aaaaaa";
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.moveTo(widget_width / 2 + text_measure.width / 2 + side_margin, off_y);
ctx.lineTo(widget_width, off_y);
ctx.stroke();
ctx.closePath();
} else {
ctx.beginPath();
ctx.strokeStyle = "#aaaaaa";
ctx.lineWidth = 2;
ctx.setLineDash([5, 5]);
ctx.moveTo(0, y + widget_height / 2);
ctx.lineTo(widget_width, y + widget_height / 2);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
},
mouse: function(event, pos, node) {
if (event.type === "pointerdown")
this.select = !this.select;
},
computeSize: function() {
return [0, 20];
}
};
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
export const WIDGET_PIN = Symbol("widget_pin");
export function hub_combo_pin_type_func(value, canvas, node, pos, event) {
if (this[WIDGET_PIN].type !== "COMBO")
this[WIDGET_PIN].type = value;
for (let i = 0; this[WIDGET_PIN].links && i < this[WIDGET_PIN].links.length; ++ i) {
const link = app.graph.links[this[WIDGET_PIN].links[i]],
target_node = app.graph.getNodeById(link.target_id),
pin = target_node.inputs[link.target_slot];
if (!(pin.type === value || pin.type === "*"))
target_node.disconnectInput(link.target_slot);
}
}
export const HUB_SOLE = 7;
async function hub_serialize_batch_combo(node, index_str) {
return node.widgets[Number(index_str)].value.join(":");
}
export const PIPE_COMBO = ["HIGHWAY_PIPE", "JUNCTION_PIPE"]
export function hub_setup_widget(node, data, id) {
for (let node_id of app.graph.extra["0246.HUB_DATA"][id].node_list)
node.hubPushNode(app.graph.getNodeById(node_id), true);
if (node.outputs && node.outputs.length > 0) {
let count = 0, widget;
for (let name in node.hub.data.sole_type) {
const proc_name = node.hub.data.sole_type[name], // name.split(":"),
type_data = proc_name.slice(3, 6);
switch (proc_name[2]) {
case "int": {
widget = node.hubPushWidgetPrim("INT", type_data, name);
} break;
case "float": {
widget = node.hubPushWidgetPrim("FLOAT", type_data, name);
} break;
case "string": {
widget = node.hubPushWidgetPrim("STRING", type_data, name);
} break;
case "boolean": {
widget = node.hubPushWidgetPrim("BOOLEAN", type_data, name);
} break;
case "combo": {
switch (proc_name[3]) {
case "__BATCH_PRIM__": {
widget = node.hubPushWidgetComboRaw(type_defs, proc_name[3], hub_combo_pin_type_func, type_data, name);
} break;
case "__BATCH_COMBO__": {
widget = node.hubPushWidgetComboRaw(combo_defs, proc_name[3], hub_combo_pin_type_func, type_data, name, hub_serialize_batch_combo);
} break;
case "__PIPE__": {
widget = node.hubPushWidgetComboRaw(PIPE_COMBO, proc_name[3], hub_combo_pin_type_func, type_data, name);
} break;
default: {
widget = node.hubPushWidgetCombo(...type_data, name);
} break;
}
} break;
}
widget.value = data.widgets_values[HUB_SOLE + 2 + (count ++)];
}
}
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function cloud_group_query_inst(group_curr, group_dict, group_list, inst_id) {
let stack = [];
for (let ii = 0; ii < group_list.length; ++ ii)
if (group_dict[group_list[ii]]?.inst?.includes?.(inst_id)) {
group_curr.push(group_list[ii]);
stack.push(group_list[ii]);
}
cloud_group_query_group(group_curr, group_dict, group_list, stack);
}
function cloud_group_query_group(group_curr, group_dict, group_list, group_stack) {
const seen = new Set();
if (!Array.isArray(group_stack))
group_stack = [group_stack];
while (group_stack.length > 0) {
const temp = group_stack.pop();
for (let ii = 0; ii < group_list.length; ++ ii)
if (group_dict[group_list[ii]]?.group?.includes?.(temp) && !seen.has(group_list[ii])) {
group_curr.push(group_list[ii]);
group_stack.push(group_list[ii]);
seen.add(group_list[ii]);
}
}
}
function shelf_layout(
inst_arr, group_dict, mouse, pos_dict, view_dict, hit_dict, row_arr, curr_dict,
ctx, x, y, width, height,
padding_top, padding_bottom, padding_left, padding_right,
group_padding_top, group_padding_bottom, group_padding_left, group_padding_right,
margin_top, margin_bottom, margin_left, margin_right,
width_spacing, height_spacing
) {
let
acc_i = 0, acc_w = 0, acc_h = 0, max_h = 0, total_h = 0,
group_y = 0, text_max_h = 0,
hold = [], flag = false, mouse_flag = true, old_id;
const key_list = [],
group_list = Object.keys(group_dict),
pos_list = Object.keys(pos_dict);
for (let i = 0; i < inst_arr.length; ++ i)
key_list.push(inst_arr[i].id);
for (let i = 0; i < pos_list.length; ++ i) {
delete pos_dict[pos_list[i]];
delete hit_dict[pos_list[i]];
}
curr_dict.x = null;
curr_dict.y = null;
curr_dict.h = null;
curr_dict.i = null;
curr_dict.k = null;
row_arr.length = 0;
for (let i = 0; i < key_list.length; ++ i) {
const text_data = ctx.measureText(view_dict[key_list[i]]);
text_max_h = Math.max(text_max_h, text_data.actualBoundingBoxAscent + text_data.actualBoundingBoxDescent);
}
for (let i = 0; i < key_list.length; ++ i) {
acc_i = i;
acc_w = 0;
max_h = 0;
group_y = 0;
for (; i < key_list.length; ++ i) {
let group_curr = [];
cloud_group_query_inst(group_curr, group_dict, group_list, key_list[i]);
const text_data = ctx.measureText(view_dict[key_list[i]]);
group_y = Math.max(group_y, group_padding_top * group_curr.length);
hit_dict[key_list[i]] = [
x + acc_w, y + acc_h,
margin_left + group_padding_left * group_curr.length + padding_left +
text_data.width +
padding_right + group_padding_right * group_curr.length + margin_right
];
const temp_h = margin_top + group_y + padding_top +
text_max_h +
padding_bottom + group_padding_bottom * group_curr.length + margin_bottom;
max_h = Math.max(max_h, temp_h);
if (flag || lib0246.is_inside_rect_rect(
x + acc_w, y + acc_h, hit_dict[key_list[i]][2], temp_h,
x, y, width, height
)) {
hold.push(
key_list[i],
acc_w + margin_left, acc_h + margin_top,
text_data, group_curr
);
acc_w += hit_dict[key_list[i]][2] + width_spacing;
flag = false;
} else {
if (hold.length === 0) {
-- i;
flag = true;
continue;
}
acc_h += max_h + height_spacing;
if (old_id !== key_list[i]) {
old_id = key_list[i];
-- i;
}
break;
}
}
// hold, text_max_h, group_y,
// mouse, pos_dict,
// padding_top, padding_bottom, padding_left, padding_right,
// group_padding_top, group_padding_bottom, group_padding_left, group_padding_right,
row_arr.push(max_h);
for (let ii = 0; ii < hold.length; ii += 5) {
let temp_text_x = hold[ii + 1] + group_padding_left * hold[ii + 4].length,
temp_text_y = hold[ii + 2] + group_y;
pos_dict[hold[ii]] = [
temp_text_x + hold[ii + 3].width / 2 + padding_left,
temp_text_y + text_max_h / 2 + padding_top,
temp_text_x + hold[ii + 3].width / 2 - hold[ii + 3].actualBoundingBoxLeft,
temp_text_y + text_max_h / 2 - hold[ii + 3].actualBoundingBoxAscent,
hold[ii + 3].width + padding_left + padding_right,
text_max_h + padding_top + padding_bottom,
];
hit_dict[hold[ii]][3] = row_arr[row_arr.length - 1];
let temp_group_x = pos_dict[hold[ii]][2] - group_padding_left,
temp_group_y = pos_dict[hold[ii]][3] - group_padding_top,
temp_group_w = pos_dict[hold[ii]][4] + group_padding_left + group_padding_right,
temp_group_h = pos_dict[hold[ii]][5] + group_padding_top + group_padding_bottom;
for (let iii = 0; iii < hold[ii + 4].length; ++ iii) {
pos_dict[hold[ii]].push(
hold[ii + 4][iii],
temp_group_x, temp_group_y, temp_group_w, temp_group_h
);
temp_group_x -= group_padding_left;
temp_group_y -= group_padding_top;
temp_group_w += group_padding_left + group_padding_right;
temp_group_h += group_padding_top + group_padding_bottom;
}
}
if (Array.isArray(mouse) && mouse[1] - y < row_arr.reduce((a, b) => a + b + height_spacing, 0) && mouse_flag) {
let snap_index = lib0246.multi_snap(mouse[0], hold.length / 5 + 1, function (index) {
if (index === hold.length / 5)
return hit_dict[hold[(index - 1) * 5]][0] + hit_dict[hold[(index - 1) * 5]][2] + width_spacing;
return hit_dict[hold[index * 5]][0];
});
if (snap_index !== null) {
if (snap_index === hold.length / 5) {
curr_dict.x = hit_dict[hold[(snap_index - 1) * 5]][0] + hit_dict[hold[(snap_index - 1) * 5]][2] + width_spacing;
curr_dict.y = hit_dict[hold[(snap_index - 1) * 5]][1];
} else {
curr_dict.x = hit_dict[hold[snap_index * 5]][0];
curr_dict.y = hit_dict[hold[snap_index * 5]][1];
curr_dict.k = hold[snap_index * 5];
}
curr_dict.h = row_arr[row_arr.length - 1];
curr_dict.i = acc_i + snap_index;
curr_dict.r = row_arr.length - 1;
}
mouse_flag = false;
}
hold.length = 0;
total_h = Math.max(max_h, total_h);
}
return total_h;
}
const CLOUD_MARK = Symbol("cloud_mark"),
CLOUD_FILL = ["#292929", "#303030"];
const CLOUD_STATE = [
...[
[
{
mouse: "pointerdown",
where: "box",
action: "",
bound: "in"
}, {
mouse: "pointerdown",
where: "box",
action: "select",
bound: "in"
}, {
mouse: "pointermove",
action: "select",
bound: "in"
}
], function (node, widget, event, pos) {
if (widget.cloud.state.mouse === "pointermove")
widget.cloud.mouse_move = pos;
if (event.shiftKey && widget.cloud.select.size > 0) {
if (widget.cloud.mouse_shift !== null)
widget.cloud.mouse_shift = null;
else
return {
action: "shift"
};
} else for (let inst_id in widget.cloud.data.hit)
if (lib0246.is_inside_rect(
pos[0], pos[1],
widget.cloud.data.hit[inst_id][0], widget.cloud.data.hit[inst_id][1],
widget.cloud.data.hit[inst_id][2], widget.cloud.data.hit[inst_id][3]
)) {
if (inst_id === widget.cloud.select_active && widget.cloud.state.where === "box" && widget.cloud.state.mouse === "pointermove")
continue;
if (widget.cloud.select.has(inst_id)) {
widget?.select(node, inst_id, false);
widget.cloud.select.delete(inst_id);
} else {
widget?.select(node, inst_id, true);
widget.cloud.select.add(inst_id);
}
widget.cloud.select_active = inst_id;
}
return {
action: "select"
};
},
{
mouse: "pointermove",
action: "shift",
bound: "in"
}, function (node, widget, event, pos) {
if (widget.cloud.state.mouse === "pointermove")
widget.cloud.mouse_move = pos;
if (event.shiftKey)
widget.cloud.mouse_shift = pos;
else {
return {
action: "select"
};
}
},
{
mouse: "pointerup",
action: "shift",
bound: "in"
}, function (node, widget, event, pos) {
if (widget.cloud.data.curr.i !== null) {
widget.cloud.data.inst.splice(widget.cloud.data.curr.i, 0, CLOUD_MARK);
let temp = [], indices = [];
for (let i = 0; i < widget.cloud.data.inst.length; ++ i)
if (widget.cloud.select.has(widget.cloud.data.inst[i].id)) {
temp.push(widget.cloud.data.inst[i]);
indices.push(i);
}
for (let i = indices.length - 1; i >= 0; i --)
widget.cloud.data.inst.splice(indices[i], 1);
widget.cloud.data.inst.splice(widget.cloud.data.inst.indexOf(CLOUD_MARK), 1, ...temp);
}
return {
action: "select"
};
},
{
mouse: "pointerdown",
where: "box",
action: "move",
bound: "in"
}, function (node, widget, event, pos) {
return {
action: ""
};
},
{
mouse: "pointerdown",
where: "",
action: "select",
bound: "in"
}, function (node, widget, event, pos) {
for (let inst_id of widget.cloud.select)
widget?.select(node, inst_id, false);
widget.cloud.select.clear();
widget.cloud.select_active = null;
return {
action: ""
};
},
{
mouse: "pointerup",
// action: "select",
bound: "in"
}, function (node, widget, event, pos) {
widget.cloud.mouse_move = null;
},
{
mouse: "pointermove",
bound: "in"
}, function (node, widget, event, pos) {
widget.cloud.mouse_move = pos;
},
],
{}, function (node, widget, event, pos) {
reset_state(widget.cloud.state);
for (let inst_id of widget.cloud.select)
widget?.select(node, inst_id, false);
widget.cloud.select.clear();
widget.cloud.select_active = null;
widget.cloud.delay_state = null;
widget.cloud.mouse_shift = null;
widget.cloud.mouse_move = null;
}
];
function cloud_build(node, kind, data = null, input = null, full = null) {
const inst_id = data?.id ?? String(this.cloud.data.id ++);
switch (kind) {
case "text": {
this.cloud.widgets[inst_id] = [
ComfyWidgets["STRING"](node, `cloud:${inst_id}:text:text_input`, ["STRING", {
multiline: true,
}], app).widget
];
if (input !== null && input.find(_ => _.name === `cloud:${inst_id}:text:text_input`))
window.setTimeout(() => {
node.widgets.splice(node.widgets.indexOf(this.cloud.widgets[inst_id][0]), 1);
}, 0);
else
node.widgets.splice(node.widgets.length - 1, 1);
if (data !== null)
this.cloud.widgets[inst_id][0].options.setValue(data.widgets_values[0]);
} break;
case "text_list": {
} break;
case "text_file_list": {
// [TODO] For this during viewing, there should be a cache system to maybe open a websocket to return file content
// Can have a string combo directly to each line
} break;
case "text_file_json": {
// Same cache thing here
// Use path to get specific string, then the string combo will be available just like above
} break;
case "weight": {
this.cloud.widgets[inst_id] = [
{
name: `cloud:${inst_id}:weight:weight_input`,
type: "number",
value: 0,
options: {
min: -Infinity,
max: Infinity,
step: 0.1,
precision: 2
},
}
];
if (input !== null && input.find(_ => _.name === `cloud:${inst_id}:weight:weight_input`)) {
node.widgets.push(this.cloud.widgets[inst_id][0]);
window.setTimeout(() => {
node.widgets.splice(node.widgets.indexOf(this.cloud.widgets[inst_id][0]), 1);
}, 0);
}
if (data !== null)
this.cloud.widgets[inst_id][0].value = data.widgets_values[0];
} break;
case "rand": {
} break;
case "cycle": {
} break;
default: {
this.options?.build?.(node, kind, data);
} break;
}
if (data === null)
this.cloud.data.inst.push({
id: inst_id,
kind: kind,
widgets_values: [],
widgets_names: [],
});
}
function cloud_remove(node, inst_id) {
this.select(node, inst_id, false);
this.cloud.data.inst.splice(this.cloud.data.inst.findIndex(_ => _.id === inst_id), 1);
delete this.cloud.widgets[inst_id];
delete this.cloud.data.view[inst_id];
delete this.cloud.data.pos[inst_id];
delete this.cloud.data.hit[inst_id];
for (let group_id in this.cloud.data.group) {
const idx = this.cloud.data.group[group_id]?.inst?.indexOf?.(inst_id);
if (idx !== -1)
this.cloud.data.group[group_id]?.inst?.splice?.(idx, 1);
}
cloud_clean_group(this.cloud.data.group);
}
function cloud_select(node, inst_id, flag) {
const inst = this.cloud.data.inst.find(_ => _.id === inst_id);
switch (inst.kind) {
case "text": {
if (flag) {
if (node.widgets.findIndex(_ => _.name === `cloud:${inst_id}:text:text_input`) === -1)
node.widgets.push(this.cloud.widgets[inst_id][0]);
this.cloud.widgets[inst_id][0].onAdd(node, true);
} else {
node.widgets.splice(node.widgets.indexOf(this.cloud.widgets[inst_id][0]), 1);
this.cloud.widgets[inst_id][0].onRemove(node, true);
}
} break;
case "text_list": {
} break;
case "text_json": {
} break;
case "weight": {
if (flag) {
if (node.widgets.findIndex(_ => _.name === `cloud:${inst_id}:weight:weight_input`) === -1)
node.widgets.push(this.cloud.widgets[inst_id][0]);
} else
node.widgets.splice(node.widgets.indexOf(this.cloud.widgets[inst_id][0]), 1);
} break;
case "rand": {
} break;
case "cycle": {
} break;
default: {
this.options?.select?.(node, inst_id, flag);
} break;
}
const curr_size = node.computeSize();
curr_size[0] = Math.max(curr_size[0], node.size[0]);
curr_size[1] = Math.max(curr_size[1], node.size[1]);
node.setSize(curr_size);
app.canvas.setDirty(true);
}
const CLOUD_KIND = ["text", "text_list", "text_file_list", "text_file_json", "weight", "rand", "cycle"];
function cloud_view(node, inst) {
let text = "", old_text = text; // [${inst.kind},${inst.id}]
switch (inst.kind) {
case "text": {
text += this.cloud.widgets[inst.id][0].type === "converted-widget" ?
`##${inst.id}##` :
this.cloud.widgets[inst.id][0].options.getValue();
} break;
case "text_list": {
} break;
case "text_json": {
} break;
case "weight": {
text += this.cloud.widgets[inst.id][0].type === "converted-widget" ?
`##${inst.id}##` :
String(lib0246.round(this.cloud.widgets[inst.id][0].value, 2));
} break;
case "rand": {
} break;
case "cycle": {
} break;
default: {
text += this.options?.view?.(node, inst);
} break;
}
// [TODO] If converted to pin then display as such
if (text === old_text)
text += "#####";
return text;
}
function cloud_build_group(node, mode, second = null, color = undefined) {
if (second === null) {
switch (mode) {
case "_": {
const curr_id = String(this.cloud.data.id ++);
this.cloud.data.group[curr_id] = {
color: color ?? lib0246.rgb_to_hex(
lib0246.rand_int(108, 230),
lib0246.rand_int(108, 230),
lib0246.rand_int(108, 230)
),
inst: [...this.cloud.select],
};
} break;
case "global": {
this.cloud.data.group["global"] = this.cloud.data.group["global"] ?? {};
this.cloud.data.group["global"].inst = this.cloud.data.group["global"].inst ?? [];
this.cloud.data.group["global"].inst.push(...this.cloud.select);
this.cloud.data.group["global"].color = "#000";
cloud_clean_dupe(this.cloud.data.group);
} break;
case `local:${node.id}`: {
this.cloud.data.group[`local:${node.id}`] = this.cloud.data.group[`local:${node.id}`] ?? {};
this.cloud.data.group[`local:${node.id}`].inst = this.cloud.data.group[`local:${node.id}`].inst ?? [];
this.cloud.data.group[`local:${node.id}`].inst.push(...this.cloud.select);
this.cloud.data.group[`local:${node.id}`].color = "#fff";
cloud_clean_dupe(this.cloud.data.group);
} break;
default: {
if (this.cloud.data.group[mode]) {
this.cloud.data.group[mode].inst = this.cloud.data.group[mode].inst ?? [];
for (let inst_id of this.cloud.select)
if (this.cloud.data.group[mode].inst.indexOf(inst_id) === -1)
this.cloud.data.group[mode].inst.push(inst_id);
}
} break;
}
} else if (this.cloud.data.group[second]) {
switch (mode) {
case "_": {
const curr_id = String(this.cloud.data.id ++);
this.cloud.data.group[curr_id] = {
color: color ?? lib0246.rgb_to_hex(
lib0246.rand_int(108, 230),
lib0246.rand_int(108, 230),
lib0246.rand_int(108, 230)
),
group: [second],
};
} break;
case "global": {
this.cloud.data.group["global"] = this.cloud.data.group["global"] ?? {};
this.cloud.data.group["global"].group = this.cloud.data.group["global"].group ?? [];
this.cloud.data.group["global"].group.push(second);
this.cloud.data.group["global"].color = "#000";
cloud_clean_dupe(this.cloud.data.group);
} break;
case `local:${node.id}`: {
this.cloud.data.group[`local:${node.id}`] = this.cloud.data.group[`local:${node.id}`] ?? {};
this.cloud.data.group[`local:${node.id}`].group = this.cloud.data.group[`local:${node.id}`].group ?? [];
this.cloud.data.group[`local:${node.id}`].group.push(second);
this.cloud.data.group[`local:${node.id}`].color = "#fff";
cloud_clean_dupe(this.cloud.data.group);
} break;
default: {
if (this.cloud.data.group[mode]) {
this.cloud.data.group[mode].group = this.cloud.data.group[mode].group ?? [];
const idx = this.cloud.data.group[mode].group.indexOf(second);
if (idx !== -1)
this.cloud.data.group[mode].group.splice(idx, 1);
this.cloud.data.group[mode].group.push(second);
}
} break;
}
cloud_clean_group(this.cloud.data.group);
}
}
function cloud_remove_group(node, group_id, mode) {
// If second_group_id is "_", delete first_group_id from the entire thing
switch (mode) {
case "_": {
delete this.cloud.data.group[group_id];
} break;
case null: {
const curr_group = this.cloud.data.group[group_id];
if (curr_group.inst)
for (let inst_id of this.cloud.select)
if (curr_group.inst.includes(inst_id))
curr_group.inst.splice(curr_group.inst.indexOf(inst_id), 1);
} break;
default: {
if (this.cloud.data.group?.[group_id]?.group?.includes?.(mode))
this.cloud.data.group[group_id].group.splice(this.cloud.data.group[group_id].group.indexOf(mode), 1);
} break;
}
cloud_clean_group(this.cloud.data.group);
}
function cloud_clean_group(group_dict) {
let valid_group = new Set();
for (let key in group_dict) {
let visited = new Set(), stack = [];
if (group_dict[key]?.inst?.length > 0) {
valid_group.add(key);
for (let parent in group_dict)
if (group_dict[parent].group?.includes?.(key))
stack.push([group_dict[parent], group_dict[key], key]);
} else {
delete group_dict[key].inst;
for (let parent in group_dict)
if (group_dict[parent].group?.includes?.(key))
stack.push([group_dict[parent], group_dict[key], key]);
}
while (stack.length > 0) {
const [parent, group, name] = stack.pop();
if (visited.has(name)) {
if (parent)
parent.group.splice(parent.group.indexOf(name), 1);
continue;
}
visited.add(name);
if (group?.group) {
if (group.group.length === 0)
delete group.group;
else
for (let child of group.group)
stack.push([group, group_dict[child], child]);
}
}
}
for (let key in group_dict) {
if (group_dict[key].group) {
for (let i = 0; i < group_dict[key].group.length; ++ i)
if (!valid_group.has(group_dict[key].group[i]) || !group_dict[group_dict[key].group[i]])
group_dict[key].group.splice(i --, 1);
if (group_dict[key].group.length === 0)
delete group_dict[key];
else
valid_group.add(key);
} else if (!valid_group.has(key))
delete group_dict[key];
}
}
function cloud_clean_dupe(group_dict) {
for (let group_id in group_dict) {
if (group_dict[group_id].inst)
for (let i = 0; i < group_dict[group_id].inst.length; ++ i)
for (let j = i + 1; j < group_dict[group_id].inst.length; ++ j)
if (group_dict[group_id].inst[i] === group_dict[group_id].inst[j])
group_dict[group_id].inst.splice(j --, 1);
if (group_dict[group_id].group) {
const idx = group_dict[group_id].group.indexOf(group_id);
if (idx !== -1)
group_dict[group_id].group.splice(idx, 1);
for (let i = 0; i < group_dict[group_id].group.length; ++ i)
for (let j = i + 1; j < group_dict[group_id].group.length; ++ j)
if (group_dict[group_id].group[i] === group_dict[group_id].group[j])
group_dict[group_id].group.splice(j --, 1);
}
}
}
function cloud_draw_msg(ctx, widget, inst_id, text, prev_p) {
const text_measure = ctx.measureText(text),
idx = widget.cloud.data.pos[inst_id].length - 5,
// curr_p = prev_p + text_measure.actualBoundingBoxAscent + text_measure.actualBoundingBoxDescent + 2;
curr_p = prev_p + text_measure.width + 2;
ctx.strokeStyle = "#000";
ctx.setLineDash([]);
ctx.strokeText(
text,
widget.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][idx + 1] + prev_p,
widget.flex.hold_draw[1] +
widget.cloud.data.pos[inst_id][idx + 2] + widget.cloud.data.pos[inst_id][idx + 4] +
(text_measure.actualBoundingBoxAscent + text_measure.actualBoundingBoxDescent) / 2 + 6
);
ctx.fillText(
text,
widget.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][idx + 1] + prev_p,
widget.flex.hold_draw[1] +
widget.cloud.data.pos[inst_id][idx + 2] + widget.cloud.data.pos[inst_id][idx + 4] +
(text_measure.actualBoundingBoxAscent + text_measure.actualBoundingBoxDescent) / 2 + 6
);
return curr_p;
// ctx.strokeText(
// text,
// widget.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][idx + 1],
// widget.flex.hold_draw[1] +
// widget.cloud.data.pos[inst_id][idx + 2] + widget.cloud.data.pos[inst_id][idx + 4] + curr_p
// );
// ctx.fillText(
// text,
// widget.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][idx + 1],
// widget.flex.hold_draw[1] +
// widget.cloud.data.pos[inst_id][idx + 2] + widget.cloud.data.pos[inst_id][idx + 4] + curr_p
// );
// return curr_p;
}
export function cloud_junction_evt(node, mode, args, i) {
switch (mode) {
case 1: {
if (node.inputs[args[3].target_slot].name.startsWith("cloud"))
return true;
} break;
case -1: {
if (node.outputs[args[3].origin_slot].name.startsWith("cloud"))
return true;
} break;
case 3: {
if (node.inputs[i].name.startsWith("cloud"))
return true;
} break;
case -3: {
if (node.outputs[i].name.startsWith("cloud"))
return true;
} break;
}
return false;
}
export function CLOUD_WIDGET(data_type, data_name, options = {}) {
const widget = {
type: data_type,
name: data_name,
get value() {
for (let i = 0; i < this.cloud.data.inst.length; ++ i)
if (this.cloud.widgets[this.cloud.data.inst[i].id])
for (let j = 0; j < this.cloud.widgets[this.cloud.data.inst[i].id].length; ++ j) {
this.cloud.data.inst[i].widgets_values[j] = this.cloud.widgets[this.cloud.data.inst[i].id][j].value;
this.cloud.data.inst[i].widgets_names[j] = this.cloud.widgets[this.cloud.data.inst[i].id][j].name;
}
return {
inst: this.cloud.data.inst,
group: this.cloud.data.group,
id: this.cloud.data.id
};
},
set value(v) {
this.cloud.data.inst = v.inst;
this.cloud.data.group = v.group;
this.cloud.data.id = v.id;
if (this.cloud.node) {
const node = app.graph.getNodeById(this.cloud.node);
for (let inst_id of this.cloud.select)
this?.select(node, inst_id, false);
this.cloud.select.clear();
const stub_obj = {};
stub_obj.widgets_values = [];
stub_obj.widgets_values[node.widgets.findIndex((widget) => widget.name === "base:data")] = v;
node.onConfigure(stub_obj);
}
},
draw: function (ctx, node, widget_width, y, widget_height) {
// [TODO] Use https://github.com/geongeorge/Canvas-Txt to draw text instead?
ctx.save();
ctx.beginPath();
ctx.strokeStyle = "#000000";
ctx.fillStyle = app.canvas.clear_background_color;
ctx.lineWidth = 2;
ctx.beginPath();
ctx.rect(this.flex.hold_draw[0], this.flex.hold_draw[1], this.flex.hold_draw[2], this.flex.hold_draw[3]);
ctx.clip();
ctx.stroke();
ctx.fill();
ctx.closePath();
if (!Number.isFinite(this.flex.hold_draw[2]) || !Number.isFinite(this.flex.hold_draw[3]))
return;
ctx.beginPath();
ctx.font = "9px Consolas";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
for (let i = 0; i < widget.cloud.data.inst.length; ++ i)
widget.cloud.data.view[widget.cloud.data.inst[i].id] = widget.view(node, widget.cloud.data.inst[i]);
widget.cloud.data.h = shelf_layout(
widget.cloud.data.inst, widget.cloud.data.group, widget.cloud.mouse_shift,
widget.cloud.data.pos, widget.cloud.data.view, widget.cloud.data.hit, widget.cloud.data.row, widget.cloud.data.curr,
ctx, this.flex.hold_draw[0], this.flex.hold_draw[1], this.flex.hold_draw[2], this.flex.hold_draw[3],
2, 2, 5, 5,
4, 4, 4, 4,
4, 4, 2, 2,
2, 2
// 0, 0, 0, 0,
// 0, 0, 0, 0,
// 0, 0, 0, 0,
// 0, 0
);
ctx.closePath();
for (let i = 0, sum_y = 0; i < widget.cloud.data.row.length; ++ i) {
ctx.beginPath();
ctx.fillStyle = CLOUD_FILL[i % 2];
ctx.rect(
this.flex.hold_draw[0], this.flex.hold_draw[1] + sum_y,
this.flex.hold_draw[2], widget.cloud.data.row[i]
);
ctx.fill();
ctx.closePath();
sum_y += widget.cloud.data.row[i] + 2;
}
for (let inst_id in widget.cloud.data.pos) {
ctx.beginPath();
ctx.fillStyle = "#333";
if (widget.cloud.select.has(inst_id)) {
ctx.strokeStyle = "#f00";
ctx.setLineDash([3, 3]);
} else {
ctx.strokeStyle = "#666";
ctx.setLineDash([]);
}
ctx.lineWidth = 1;
ctx.roundRect(
this.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][2], this.flex.hold_draw[1] + widget.cloud.data.pos[inst_id][3] + 2,
widget.cloud.data.pos[inst_id][4], widget.cloud.data.pos[inst_id][5],
5
);
ctx.fill();
ctx.stroke();
ctx.closePath();
ctx.beginPath();
ctx.fillStyle = "#ccc";
ctx.font = "9px Consolas";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText(
widget.cloud.data.view[inst_id],
this.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][0],
this.flex.hold_draw[1] + widget.cloud.data.pos[inst_id][1] + 2
);
ctx.closePath();
let msg_flag = widget.cloud.mouse_move && lib0246.is_inside_rect(
widget.cloud.mouse_move[0], widget.cloud.mouse_move[1],
widget.cloud.data.hit[inst_id][0], widget.cloud.data.hit[inst_id][1],
widget.cloud.data.hit[inst_id][2], widget.cloud.data.hit[inst_id][3]
), curr_p = 0;
if (msg_flag) {
ctx.beginPath();
ctx.fillStyle = "#fff";
ctx.font = "7px Consolas";
ctx.textAlign = "left";
ctx.textBaseline = "middle";
const curr_inst = widget.cloud.data.inst.find(_ => _.id === inst_id);
curr_p = cloud_draw_msg(ctx, widget, inst_id, `[${curr_inst.id},${curr_inst.kind}]`, curr_p);
ctx.closePath();
}
const draw_group = new Set();
for (let ii = 6, jj = 0; ii < widget.cloud.data.pos[inst_id].length; ii += 5, ++ jj) {
ctx.beginPath();
ctx.strokeStyle = widget.cloud.data.group[widget.cloud.data.pos[inst_id][ii]].color;
ctx.setLineDash([2, 2]);
ctx.lineWidth = 0.75;
ctx.roundRect(
this.flex.hold_draw[0] + widget.cloud.data.pos[inst_id][ii + 1], this.flex.hold_draw[1] + widget.cloud.data.pos[inst_id][ii + 2] + 2,
widget.cloud.data.pos[inst_id][ii + 3], widget.cloud.data.pos[inst_id][ii + 4],
2
);
ctx.stroke();
ctx.closePath();
if (msg_flag && !draw_group.has(widget.cloud.data.pos[inst_id][ii])) {
draw_group.add(widget.cloud.data.pos[inst_id][ii]);
ctx.beginPath();
ctx.fillStyle = widget.cloud.data.group[widget.cloud.data.pos[inst_id][ii]].color;
ctx.font = "7px Consolas";
ctx.textAlign = "left";
ctx.textBaseline = "middle";
let text_msg = "", group_res = [];
cloud_group_query_group(group_res, widget.cloud.data.group, Object.keys(widget.cloud.data.group), widget.cloud.data.pos[inst_id][ii]);
if (group_res.length > 0)
text_msg += `(${group_res[0]}|${group_res.slice(1).join(",")}>${widget.cloud.data.pos[inst_id][ii]})`;
else
text_msg += `(${widget.cloud.data.pos[inst_id][ii]})`;
curr_p = cloud_draw_msg(ctx, widget, inst_id, text_msg, curr_p);
ctx.closePath();
}
}
}
if (widget.cloud.data.curr.i !== null && widget.cloud.state.action === "shift") {
ctx.beginPath();
ctx.strokeStyle = "#f00";
ctx.setLineDash([4, 2]);
ctx.lineWidth = 1;
ctx.moveTo(
widget.cloud.data.curr.x,
widget.cloud.data.curr.y
);
ctx.lineTo(
widget.cloud.data.curr.x,
widget.cloud.data.curr.y + widget.cloud.data.curr.h
);
ctx.stroke();
ctx.closePath();
}
ctx.restore();
},
mouse: function (event, pos, node) {
widget.cloud.state = widget.cloud.state ?? {};
widget.cloud.delay_state = widget.cloud.delay_state ?? null;
widget.cloud.state.mouse = event.type;
let box_flag = false;
for (let inst_id in widget.cloud.data.hit)
if (lib0246.is_inside_rect(
pos[0], pos[1],
widget.cloud.data.hit[inst_id][0], widget.cloud.data.hit[inst_id][1],
widget.cloud.data.hit[inst_id][2], widget.cloud.data.hit[inst_id][3]
)) {
widget.cloud.state.where = "box";
box_flag = true;
break;
}
widget.cloud.state.bound = lib0246.is_inside_rect(
pos[0], pos[1],
this.flex.hold_mouse[0], this.flex.hold_mouse[1], this.flex.hold_mouse[2], this.flex.hold_mouse[3]
) ? "in" : "out";
if (!widget.cloud.state.where || !box_flag)
widget.cloud.state.where = "";
if (!widget.cloud.state.action)
widget.cloud.state.action = "";
if (window.performance.now() - (widget.cloud.delay_state ?? 0) > widget.cloud.delay_dbl)
widget.cloud.delay_state = null;
eval_state(CLOUD_STATE, widget.cloud.state, node, widget, event, pos);
},
build: cloud_build,
remove: cloud_remove,
build_group: cloud_build_group,
remove_group: cloud_remove_group,
select: cloud_select,
view: cloud_view
};
widget.cloud = widget.cloud ?? {};
widget.cloud.select = widget.cloud.select ?? new Set();
widget.cloud.data = widget.cloud.data ?? {
inst: [],
view: {},
group: {},
row: [],
pos: {},
hit: {},
curr: {},
id: 0
};
widget.cloud.delay_dbl = widget.cloud.delay_dbl ?? options.delay_dbl ?? 200;
widget.cloud.widgets = widget.cloud.widgets ?? {};
return widget;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
const WIDGETS_MAP = new WeakMap();
function setup_hijack_widget(node, name_fn) {
const original_widgets = node.widgets;
if (!original_widgets) return;
// Store the original widgets before applying the proxy
WIDGETS_MAP.set(node, original_widgets);
node.widgets = new Proxy(original_widgets, {
get(target, prop, receiver) {
const original_widget = Reflect.get(target, prop, receiver);
if (original_widget && typeof original_widget === 'object') {
return new Proxy(original_widget, {
get(widget_target, widget_prop) {
if (widget_prop === 'name')
return name_fn(node, widget_target);
return Reflect.get(widget_target, widget_prop);
}
});
}
return original_widget;
}
});
}
function reset_hijack_widget(node) {
if (WIDGETS_MAP.has(node)) {
node.widgets = WIDGETS_MAP.get(node);
WIDGETS_MAP.delete(node);
}
}
const NODE_PARENT = Symbol("node_parent");
function hijack_widget_name(node, widget) {
if (node.comfyClass === "0246.Hub" && widget[NODE_PARENT])
return `node:${widget[NODE_PARENT].id}:${widget.name}`;
return widget.name;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
app.registerExtension({
name: "0246.Widget",
async init() {
// Shamelessly imported :3
rgthree_utils = await lib0246.try_import("../../../extensions/rgthree-comfy/utils.js");
// await lib0246.load_script("https://unpkg.com/interactjs@1.10.23/dist/interact.min.js");
// mtb_widgets = await lib0246.try_import("../../../extensions/comfy_mtb/mtb_widgets.js");
// import { JSONEditor } from "https://cdn.jsdelivr.net/npm/vanilla-jsoneditor/standalone.js";
},
async setup(app) {
lib0246.hijack(app.canvas, "processNodeWidgets", function (node) {
if (!this.mark)
PROCESS_WIDGET_NODE = node;
else
PROCESS_WIDGET_NODE = null;
});
lib0246.hijack(app.canvas, "drawNodeWidgets", function () {
if (!this.mark) {
const node = arguments[0];
if (node.comfyClass === "0246.Hub") {
node.hub.temp_y = node.hub.temp_y ?? {};
const io_height = Math.max(node?.inputs?.length ?? 0, node?.outputs?.length ?? 0) * 24;
node.hub.curr_y = io_height;
let compute_height = node.size[1],
widget_count = 0;
for (; widget_count < node.widgets.length; ++ widget_count) {
if (
node.widgets[widget_count] === app.graph.getNodeById(node.hub?.data?.node_list?.[0])?.widgets?.[0] ||
node.widgets[widget_count].type === "space_title"
)
break;
node.hub.curr_y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
if (node.hub.sole_space) {
node.hub.curr_y += LiteGraph.NODE_WIDGET_HEIGHT + 8 - node.hub.sole_widget.length * 4;
++ widget_count;
}
for (let i = 0; i < node.hub.sole_widget.length; ++ i, ++ widget_count) {
const curr_widget = node.hub.sole_widget[i];
if (curr_widget.y !== undefined)
node.hub.temp_y[widget_count] = curr_widget.y;
curr_widget.y = node.hub.curr_y;
node.hub.curr_y += LiteGraph.NODE_WIDGET_HEIGHT + 4; // [TODO] Maybe add checks like below
}
if (node.hub.data)
for (let i = 0; i < node.hub.data.node_list.length; ++ i) {
const curr_node = app.graph.getNodeById(node.hub.data.node_list[i]);
if (node.widgets.length <= widget_count || !curr_node)
break;
node.widgets[widget_count ++].y = node.hub.curr_y;
node.hub.curr_y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
for (let j = 0; j < curr_node.widgets.length; ++ j, ++ widget_count) {
const curr_widget = curr_node.widgets[j];
if (curr_widget.y !== undefined)
node.hub.temp_y[widget_count] = curr_widget.y;
curr_widget.y = node.hub.curr_y;
if (curr_widget.flex) {
curr_widget.flex.real_w = node.size[0];
curr_widget.flex.real_y = node.hub.curr_y;
node.hub.curr_y += curr_widget.flex.temp_h;
} else if (curr_widget.computedHeight) {
node.hub.curr_y += curr_widget.computedHeight + 4;
compute_height += curr_widget.computedHeight;
} else if (curr_widget.openpose) {
node.hub.curr_y += node.size[0];
} else if (curr_widget.painter_wrap) {
node.hub.curr_y += Math.max((
curr_widget.painter_toolbox = curr_widget.painter_toolbox ?? document.querySelector("div.painter_drawning_box")
).clientHeight / app.canvas.ds.scale + 4, node.size[0]);
app.canvas.setDirty(true);
} else if (curr_widget.computeSize) {
const curr_height = curr_widget.computeSize(node.size[0], node)[1]; // compute_height, raw_height - io_height + flex_height
node.hub.curr_y += curr_height + 4;
} else
node.hub.curr_y += LiteGraph.NODE_WIDGET_HEIGHT + 4;
}
}
}
} else {
const node = arguments[0];
if (node.comfyClass === "0246.Hub") {
let curr_size = node.computeSize();
for (let i = 0; i < node.widgets.length; ++ i) {
if (node.hub.temp_y[i] !== undefined)
node.widgets[i].y = node.hub.temp_y[i];
else
node.widgets[i].y = undefined;
delete node.hub.temp_y[i];
}
curr_size[0] = node.size[0];
curr_size[1] = node.hub.curr_y + 2;
node.setSize(curr_size);
}
}
});
lib0246.hijack(app, "graphToPrompt", async function () {
if (!this.mark)
for (let i = 0; i < this.self.graph._nodes.length; ++ i) {
const node = this.self.graph._nodes[i];
if (node.comfyClass === "0246.Hub")
setup_hijack_widget(node, hijack_widget_name);
}
else
for (let i = 0; i < this.self.graph._nodes.length; ++ i) {
const node = this.self.graph._nodes[i];
if (node.comfyClass === "0246.Hub")
reset_hijack_widget(node);
}
});
lib0246.hijack(app.graph, "remove", function (node) {
if (!this.mark && node.type === "0246.Junction") {
for (let i = node.inputs.length - 1; i >= 0; -- i)
node.removeInput(i);
for (let i = node.outputs.length - 1; i >= 0; -- i)
node.removeOutput(i);
}
});
},
});