Files
Trung0246-ComfyUI-0246/web/js/nodes.js
T
2023-12-19 20:58:03 -08:00

3556 lines
115 KiB
JavaScript

import { app } from "../../../scripts/app.js";
import { api } from "../../../scripts/api.js";
import { ComfyWidgets } from "../../../scripts/widgets.js";
import { GroupNodeHandler } from "../../../extensions/core/groupNode.js";
import * as lib0246 from "./utils.js";
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>
`.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, ''),
};
let defs, type_defs = new Set();
(async () => {
function rgthree_exec(name, ...args) {
return rgthree_utils?.[name]?.apply(null, args);
}
function help_option(node, content, app) {
// Guess this is good enough
rgthree_exec("addHelpMenuItem", node, content, app);
}
// await lib0246.load_script("https://unpkg.com/interactjs@1.10.23/dist/interact.min.js");
// Shamelessly imported :3
const rgthree_utils = await lib0246.try_import("../../../extensions/rgthree-comfy/utils.js");
// mtb_widgets = await lib0246.try_import("../../../extensions/comfy_mtb/mtb_widgets.js");
lib0246.hijack(app, "registerNodesFromDefs", async function (_defs) {
defs = _defs;
for (let key in defs)
for (let idx in defs[key].output)
if (!Array.isArray(defs[key].output[idx]))
type_defs.add(defs[key].output[idx]);
type_defs = [...type_defs.values()];
}, () => {});
let PROCESS_WIDGET_NODE;
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
function init_type(node) {
node.addCustomWidget({
name: "_type",
computeSize: () => [0, -4],
async serializeValue (node, index_str) {
return serialize_type(node);
}
});
}
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 serialize_type(node) {
let data = {
in: [],
out: []
}
if (node.inputs)
for (let i = 0; i < node.inputs.length; ++ i) {
if (BLACKLIST.includes(node.inputs[i].name))
continue;
data.in.push({
name: node.inputs[i].orig_name,
full_name: node.inputs[i].name,
type: node.inputs[i].type,
});
}
if (node.outputs)
for (let i = 0; i < node.outputs.length; ++ i) {
if (BLACKLIST.includes(node.outputs[i].name))
continue;
data.out.push({
name: node.outputs[i].orig_name,
full_name: node.outputs[i].name,
type: node.outputs[i].type,
});
}
return data;
}
function link_shift_up(node, arr, index, flag, link_callback) {
// 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
app.canvas.setDirty(true, false);
if (flag) {
if (arr[index].links.length === 0) {
node.removeOutput(index);
for (let i = 0, c = 0; i < arr.length; ++ i) {
if (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 (BLACKLIST.includes(arr[i].name))
continue;
arr[i].name = `${c}:${arr[i].type}`;
++ c;
}
}
// 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",
"..."
];
const LEGACY_BLACKLIST = {
prev: ["_pipe_in", "_pipe_out"],
next: ["_way_in", "_way_out"],
};
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
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.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 (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 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.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;
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;
return true;
};
lib0246.hijack(node, "onConnectionsChange", () => {}, function (type, index, connected, link_info) {
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)
callback.apply({ self: this.self, name: name, mode: 2 }, arguments);
});
}
function highway_impl(nodeType, nodeData, app, shape_in, shape_out) {
nodeType.prototype.onNodeMoved = function () {};
nodeType.prototype.onNodeCreated = function () {
init_type(this);
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) {
// 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];
});
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 (curr_pin.orig_type === "*")
curr_pin.type = other_origin_slot_obj.type;
curr_pin.name = `${curr_pin.orig_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 (curr_pin.orig_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}:${curr_pin.orig_name}`;
return true;
};
this.onConnectionsChange = function (type, index, connected, link_info) {
if (link_info === null) {
// Clean up when copy paste or template load
for (let i = 0; i < this.inputs.length; ++ i)
if (!BLACKLIST.includes(this.inputs[i].name)) {
this.inputs[i].name = this.inputs[i].orig_name;
this.inputs[i].type = "*";
}
for (let i = 0; i < this.outputs.length; ++ i)
if (!BLACKLIST.includes(this.outputs[i].name)) {
this.outputs[i].name = this.outputs[i].orig_name;
this.outputs[i].type = "*";
}
this.computeSize();
return;
}
if (!connected) {
switch (type) {
case 1: {
if (BLACKLIST.includes(this.inputs[link_info.target_slot].name) || link_info.replaced)
return;
this.inputs[link_info.target_slot].name = this.inputs[link_info.target_slot].orig_name;
if (this.inputs[link_info.target_slot].orig_type === "*")
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)) {
this.outputs[link_info.origin_slot].name = this.outputs[link_info.origin_slot].orig_name;
if (this.outputs[link_info.origin_slot].orig_type === "*")
this.outputs[link_info.origin_slot].type = "*";
}
} break;
default: {
throw new Error("Unsuported type: " + type);
}
}
}
};
};
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
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 junction_impl(nodeType, nodeData, app, name, shape_in, shape_out) {
nodeType.prototype.onNodeCreated = function () {
init_type(this);
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,
};
setup_expand(this, "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]}`;
// node_fit(this.self, this.self.widgets.filter(_ => _.name === "_offset")[0]);
} break;
case 2: {
if (arguments[0] === 1) {
let link_info = arguments[3];
if (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, (link_index, extra_link_index) => {
return this.self.inputs[link_index].link;
});
-- real.input;
// node_fit(this.self, this.self.widgets.filter(_ => _.name === "_offset")[0]);
}
} break;
}
});
setup_expand(this, "output", real, "...", shape_out, function () {
switch (this.mode) {
case 0: {
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 (BLACKLIST.includes(this.self.outputs[i].name))
continue;
let output_node = this.self.getOutputNodes(i);
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 (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, (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;
}
});
lib0246.hijack(this, "getExtraMenuOptions", function (canvas, options) {
help_option(nodeType, HELP["junction"], options);
}, () => {});
};
rgthree_exec("addConnectionLayoutSupport", nodeType, app);
}
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 single_impl_input(nodeType, nodeData, app, shape_in, pin_list) {
nodeType.prototype.onNodeCreated = function () {
init_type(this);
const real = {
input: 0,
};
setup_loop_update(this);
if (shape_in !== null)
setup_expand(this, "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 (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, (link_index, extra_link_index) => {
return this.self.inputs[link_index].link;
});
-- real.input;
}
} break;
}
});
for (let i = 0; i < pin_list.length; i += 4)
setup_sole_pin(this, pin_list[i], pin_list[i + 1], pin_list[i + 2], pin_list[i + 3]);
}
}
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) => {
if (node.widgets_values)
node.widgets_values[index] = "";
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(nodeType, history = false) {
lib0246.hijack(nodeType.prototype, "onNodeCreated", () => {}, function () {
raw_setup_log(this.self);
if (history) {
this.self.log_history = new lib0246.RingBuffer(30);
this.self.log_count = 0;
}
const node = this.self;
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 () {
api.removeEventListener("ue-message-handler", node.msgSize);
});
}, 0);
});
lib0246.hijack(nodeType.prototype, "onExecuted", () => {}, function (message) {
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;
});
}
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: node.inputs[i].orig_name,
node_id: app.graph.links[node.inputs[i].link].origin_id,
slot_id: app.graph.links[node.inputs[i].link].origin_slot,
});
}
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) {
node.inputs[i].orig_name = node.inputs[i].name;
node.inputs[i].orig_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: node.outputs[i].orig_name,
node_id: app.graph.links[node.outputs[i].links[j]].target_id,
slot_id: app.graph.links[node.outputs[i].links[j]].target_slot,
});
}
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) {
node.outputs[i].orig_name = node.outputs[i].name;
node.outputs[i].orig_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) {
for (let j = 0; j < node.outputs.length; ++ j)
if (node.outputs[j].orig_name.slice(0) === prev[i].name.slice(0)) {
node.connect(
j,
prev[i].node_id,
prev[i].slot_id
);
break;
}
} else {
for (let j = 0; j < node.inputs.length; ++ j)
if (node.inputs[j].orig_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 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],
];
}
const FLEX_DB = new WeakMap();
function calc_flex(node, widget, width, height) {
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;
for (let i = 0; i < node.widgets.length; ++ i)
if (node.widgets[i]?.flex) {
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;
} 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;
}
function widget_flex(node, widget, options = {}) {
widget.flex = {};
lib0246.hijack(widget, "mouse", function (event, pos, evt_node) {
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) {
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 (PROCESS_WIDGET_NODE && PROCESS_WIDGET_NODE !== node && PROCESS_WIDGET_NODE.isPointInside(app.canvas.graph_mouse[0], app.canvas.graph_mouse[1])) {
// [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;
}
// 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.real_min_h ?? this.self.flex.min_h;
let raw_size = node.size[1];
if (app.canvas.resizing_node === node)
raw_size = Math.min(node_mouse_pos(node)[1], node.size[1]);
calc_flex(node, this.self, width, raw_size);
this.self.flex.real_h = node.flex_data.avail_h + node.flex_data.off_h; // (LiteGraph.NODE_WIDGET_HEIGHT + 4) * (node.flex_data.share_count + 1)
this.self.flex.real_max_h = lib0246.calc_spread(
node.flex_data.share_count,
node.flex_data.avail_h,
node.flex_data.share_weight,
node.flex_data.share_max_h
)[this.self.flex.index];
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]; // + 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.res = [width, this.self.flex.temp_h];
this.stop = true;
}, () => {});
if (!FLEX_DB.has(node)) {
lib0246.hijack(node, "onRemoved", () => {}, function () {
for (let i = 0; i < node.widgets.length; ++ i) {
if (node.widgets[i]?.flex)
delete node.widgets[i].flex;
delete node.widgets[i];
}
FLEX_DB.delete(node);
});
}
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;
};
FLEX_DB.set(node, false);
}
function box_range_eval_state(db, 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(widget.box_range.state, db[i][j], true, "*")) {
lib0246.update_dict(widget.box_range.state, db[i + 1](node, widget, event, pos) ?? {});
return;
}
continue;
} else if (lib0246.equal_dict(widget.box_range.state, db[i], true, "*")) {
lib0246.update_dict(widget.box_range.state, db[i + 1](node, widget, event, pos) ?? {});
return;
}
}
}
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_reset_state(widget) {
delete widget.box_range.state.mouse;
delete widget.box_range.state.where;
delete widget.box_range.state.action;
delete widget.box_range.state.select;
delete widget.box_range.state.bound;
}
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) {
box_range_reset_state(widget);
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);
function BOX_RANGE_WIDGET(node, 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.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;
box_range_eval_state(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_flex(node, widget, options);
widget.row_count = options.row_count ?? 20;
widget.col_count = options.col_count ?? 20;
return widget;
}
function draw_chain(
ctx,
fill_c, stroke_c, line_w,
link_w, link_h, spacing,
box_x, box_y, box_w, box_h
) {
// Calculate how many links can fit in the box
let links = Math.floor(box_w / (link_w + spacing));
// Draw chain links
for (let i = 0; i < links; ++ i) {
let center_x = box_x + i * (link_w + spacing) + link_w / 2,
center_y = box_y + box_h / 2;
ctx.beginPath();
ctx.fillStyle = fill_c;
ctx.strokeStyle = stroke_c;
ctx.lineWidth = line_w;
ctx.ellipse(center_x, center_y, link_w / 2, link_h / 2, 0, 0, 2 * Math.PI);
ctx.stroke();
ctx.closePath();
}
}
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, index, 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}: ${lib0246.snap(widget.value.data[index], widget.options.snap)})`, x + left_margin + text_margin + lock_margin + 5, y + widget_height * 0.7);
ctx.fillStyle = LiteGraph.WIDGET_TEXT_COLOR;
ctx.textAlign = "right";
ctx.fillText(
Number(widget.value.data[index]).toFixed(widget.options.precision ?? 3),
x + widget_width - right_margin - text_margin - 20,
y + widget_height * 0.7
);
}
draw_lock(ctx, x + left_margin, y, widget_height, widget_height, widget.value.lock[index]);
}
function ratio_process_calc(widget, mode, old_value) {
switch (Math.abs(mode)) {
case 1: {
if (widget.value.lock.height && !widget.value.lock.ratio) {
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
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;
}
} else if (!widget.value.lock.height && widget.value.lock.ratio) {
widget.value.data.ratio = widget.value.data.width / widget.value.data.height;
if (widget.value.data.ratio < widget.options.min) {
widget.value.data.ratio = widget.options.min;
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
} else if (widget.value.data.ratio > widget.options.max) {
widget.value.data.ratio = widget.options.max;
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
}
} else {
widget.value.lock.height = false;
widget.value.lock.ratio = false;
widget.value.data.width = old_value;
}
} break;
case 2: {
if (widget.value.lock.width && !widget.value.lock.ratio) {
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
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;
}
} else if (!widget.value.lock.width && widget.value.lock.ratio) {
widget.value.data.ratio = widget.value.data.width / widget.value.data.height;
if (widget.value.data.ratio < widget.options.min) {
widget.value.data.ratio = widget.options.min;
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
} else if (widget.value.data.ratio > widget.options.max) {
widget.value.data.ratio = widget.options.max;
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
}
} else {
widget.value.lock.width = false;
widget.value.lock.ratio = false;
widget.value.data.height = old_value;
}
} break;
case 3: {
if (!widget.value.lock.height && widget.value.lock.width) {
widget.value.data.width = widget.value.data.height * widget.value.data.ratio;
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;
}
} else if (!widget.value.lock.width && widget.value.lock.height) {
widget.value.data.height = widget.value.data.width / widget.value.data.ratio;
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;
}
} else {
widget.value.lock.height = false;
widget.value.lock.width = false;
widget.value.data.ratio = old_value;
}
} break;
}
if (!Number.isFinite(widget.value.data.width))
widget.value.data.width = 0;
if (!Number.isFinite(widget.value.data.height))
widget.value.data.height = 0;
if (!Number.isFinite(widget.value.data.ratio))
widget.value.data.ratio = 0;
}
function ratio_notify(node, widget, name, mode, old_value, value, pos, event) {
if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(value))
try {
widget.value.data[name] = Number(eval(value));
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) {}
}
function RATIO_WIDGET(node, 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, "W", "width", show_text);
draw_number_lock(this, ctx, calc_width, y, calc_width, widget_height, 0, 0, text_margin, lock_margin, "R", "ratio", show_text);
draw_number_lock(this, ctx, calc_width * 2, y, calc_width, widget_height, 0, margin, text_margin, lock_margin, "H", "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;
console.log(event.dragging);
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)
app.canvas.prompt("Ratio", this.value.data.ratio, (value) => ratio_notify(node, this, "ratio", mode, old_value, value, pos, event), 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;
}
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];
}
};
}
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.Node",
setup(app) {
{
const reroute_class = lib0246.clone_class(LiteGraph.registered_node_types.Reroute);
reroute_class.prototype.onNodeCreated = function() {
this.onConnectionsChange = () => {};
const type_widget = this.addWidget("combo", "", "*", function(value, widget, node) {
let curr_input_node = node.getInputNode(0),
curr_output_node = node.getOutputNodes(0),
prev_input_slot = app.graph.links?.[node.inputs[0].link]?.origin_slot,
prev_output_slot = [];
if (node.outputs[0].links)
for (let i = 0; i < node.outputs[0].links.length; ++ i)
prev_output_slot.push(app.graph.links[node.outputs[0].links[i]].target_slot);
node.disconnectInput(0);
node.disconnectOutput(0);
process_reroute(node);
if (curr_output_node && curr_output_node.length > 0) {
node.inputs[0].widget = curr_output_node[0].inputs[prev_output_slot[0]].widget;
for (let i = 0; i < curr_output_node.length; ++ i)
node.connect(0, curr_output_node[i], prev_output_slot[i]);
} else
node.inputs[0].widget = {};
if (curr_input_node)
curr_input_node.connect(prev_input_slot, node, 0);
}, {
values: type_defs
});
type_widget.y = 3;
let prev_size = this.computeSize();
prev_size[0] = 100;
// Prevent error
// [TODO] Set widget from output/input pin if exist, else just empty object
this.inputs[0].widget = {};
this.setSize(prev_size);
};
lib0246.hijack(reroute_class.prototype, "onDrawForeground", function () {
process_reroute(this.self);
}, () => {});
lib0246.hijack(reroute_class.prototype, "configure", () => {}, function (data) {
process_reroute(this.self, data.inputs[0].type);
if (this.self?.outputs?.[0]?.links?.length > 0)
this.self.inputs[0].widget = app.graph.links[this.self.outputs[0].links[0]].target_node?.inputs[app.graph.links[this.self.outputs[0].links[0]].target_slot].widget ?? {};
});
LiteGraph.registerNodeType(
"0246.CastReroute",
Object.assign(reroute_class, {
title_mode: LiteGraph.NO_TITLE,
title: "Cast Reroute",
collapsable: false,
})
);
reroute_class.category = "0246";
}
lib0246.hijack(app.canvas, "processNodeWidgets", function (node) {
PROCESS_WIDGET_NODE = node;
}, function (node) {
PROCESS_WIDGET_NODE = null;
});
lib0246.hijack(app.canvas, "drawNodeWidgets", function () {
const node = arguments[0];
FLEX_DB.set(node, true);
if (node.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.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;
}
}
}
}, function () {
const node = arguments[0];
FLEX_DB.set(node, false);
if (node.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 () {
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);
}
}, async function () {
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);
}
});
},
nodeCreated(node) {
switch (node.comfyClass) {
case "0246.Highway": {
node.color = LGraphCanvas.node_colors.brown.color;
node.bgcolor = LGraphCanvas.node_colors.brown.bgcolor;
} break;
case "0246.HighwayBatch": {
node.color = lib0246.mix_color_hue(LGraphCanvas.node_colors.brown.color, "#660029");
node.bgcolor = LGraphCanvas.node_colors.brown.bgcolor;
} break;
case "0246.Junction": {
node.color = LGraphCanvas.node_colors.blue.color;
node.bgcolor = LGraphCanvas.node_colors.blue.bgcolor;
} break;
case "0246.JunctionBatch": {
node.color = lib0246.mix_color_hue(LGraphCanvas.node_colors.blue.color, "#660029");
node.bgcolor = LGraphCanvas.node_colors.blue.bgcolor;
} break;
case "0246.Loop": {
node.color = LGraphCanvas.node_colors.yellow.color;
node.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
} break;
case "0246.Count": {
node.color = LGraphCanvas.node_colors.green.color;
node.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
} break;
case "0246.Hold": {
node.color = "#666600";
node.bgcolor = LGraphCanvas.node_colors.yellow.bgcolor;
} break;
case "0246.Beautify": {
// Pink UwU
node.color = "#652069";
node.bgcolor = "#764378";
} break;
case "0246.RandomInt": {
node.color = LGraphCanvas.node_colors.red.color;
node.bgcolor = LGraphCanvas.node_colors.green.bgcolor;
} break;
case "0246.Stringify": {
node.color = LGraphCanvas.node_colors.red.color;
node.bgcolor = LGraphCanvas.node_colors.green.bgcolor;
} break;
case "0246.Merge": {
node.color = "#660029";
node.bgcolor = "#4d001f";
} break;
case "0246.BoxRange": {
node.color = LGraphCanvas.node_colors.green.color;
node.bgcolor = LGraphCanvas.node_colors.green.bgcolor;
} break;
case "0246.ScriptImbue": {
node.color = lib0246.mix_color_hue(LGraphCanvas.node_colors.green.color, "#660029");
node.bgcolor = "#4d001f";
} break;
case "0246.ScriptPlan": {
node.color = "#660029";
node.bgcolor = LGraphCanvas.node_colors.green.bgcolor;
} break;
case "0246.Script": {
node.color = "#660029";
node.bgcolor = "#4d4d00";
} break;
case "0246.Hub": {
node.color = "#1a1a1a";
node.bgcolor = "#111";
} break;
}
},
async beforeRegisterNodeDef (nodeType, nodeData, app) {
// if (nodeData.category === "group nodes/workflow")
// GROUP_NODE_CATEGORY_LIST.push(nodeData.comfyClass);
if (nodeData.category === "0246") {
switch (nodeData.name) {
case "0246.BoxRange": {
lib0246.hijack(nodeType.prototype, "onNodeCreated", () => {}, function () {
const node = this.self;
const ratio_widget = node.addCustomWidget(RATIO_WIDGET(node, "RATIO_RANGE", "box_ratio"));
const box_widget = node.addCustomWidget(BOX_RANGE_WIDGET(node, "BOX_RANGE", "box_range", {
row_count: 10,
col_count: 10,
ratio: 0,
share: 1,
min_h: 50,
center: true,
}));
lib0246.hijack(node, "getExtraMenuOptions", function (canvas, options) {
help_option(nodeType, HELP["box_range"], options);
}, () => {});
ratio_widget.callback = function (value, widget, node, pos, event) {
if (widget.name === "box_ratio") {
box_widget.flex.ratio = widget.value.data.ratio;
app.canvas.setDirty(true);
}
};
lib0246.hijack(node, "onWidgetChanged", function (name, value, old_value, widget) {
if (name === "box_ratio")
box_widget.flex.ratio = value.data.ratio;
}, () => {});
box_widget.flex.ratio = ratio_widget.value.data.ratio;
node.serialize_widgets = true;
node.setSize(node.computeSize());
});
} break;
case "0246.Highway": {
highway_impl(nodeType, nodeData, app, LiteGraph.CIRCLE_SHAPE, LiteGraph.CIRCLE_SHAPE);
} break;
case "0246.HighwayBatch": {
highway_impl(nodeType, nodeData, app, LiteGraph.GRID_SHAPE, LiteGraph.GRID_SHAPE);
} break;
case "0246.Junction": {
junction_impl(nodeType, nodeData, app, "_offset", LiteGraph.CIRCLE_SHAPE, LiteGraph.CIRCLE_SHAPE);
} break;
case "0246.JunctionBatch": {
// [TODO] Dynamically change shape when _mode is changed
junction_impl(nodeType, nodeData, app, "_offset", LiteGraph.GRID_SHAPE, LiteGraph.GRID_SHAPE);
} break;
case "0246.Loop": {
single_impl_input(nodeType, nodeData, app, LiteGraph.CIRCLE_SHAPE, []);
} break;
case "0246.Count": {
single_impl_input(nodeType, nodeData, app, null, [
"_node", "input", 1, LiteGraph.CIRCLE_SHAPE
]);
setup_log(nodeType);
} break;
case "0246.Hold": {
single_impl_input(nodeType, nodeData, app, null, [
"_data_in", "input", 1, LiteGraph.CIRCLE_SHAPE,
"_data_out", "output", 2, LiteGraph.GRID_SHAPE
]);
setup_log(nodeType);
} break;
case "0246.RandomInt": {
lib0246.hijack(nodeType.prototype, "onNodeCreated", () => {}, function () {
const self = this.self;
self.outputs[0].shape = LiteGraph.GRID_SHAPE;
this.self.addWidget("button", "Random Seed", null, () => {
const seed_widget = self.widgets.find(w => w.name === "seed");
seed_widget.value = Math.floor(Math.random() * (seed_widget.options.max - seed_widget.options.min + 1)) + seed_widget.options.min;
}, {
serialize: false
});
this.self.addWidget("button", "Random Value", null, () => {
const val_widget = self.widgets.find(w => w.name === "val"),
min_widget = self.widgets.find(w => w.name === "min"),
max_widget = self.widgets.find(w => w.name === "max"),
batch_widget = self.widgets.find(w => w.name === "batch_size");
let res = val_widget.value.split(",");
if (res.length > batch_widget.value)
res.length = batch_widget.value;
else if (res.length < batch_widget.value)
for (let i = res.length; i < batch_widget.value; ++ i)
res.push("0");
for (let i = 0; i < batch_widget.value; ++ i) {
if (res[i])
res[i] = res[i].trim();
if (res[i] !== "rand" && res[i] !== "add" && res[i] !== "sub")
res[i] = String(Math.floor(Math.random() * (max_widget.value - min_widget.value + 1)) + min_widget.value);
}
val_widget.value = res.join(", ");
}, {
serialize: false
});
this.self.addWidget("button", "Change Mode", null, () => {
const val_widget = self.widgets.find(w => w.name === "val"),
batch_widget = self.widgets.find(w => w.name === "batch_size");
let res = val_widget.value.split(",");
if (res.length > batch_widget.value)
res.length = batch_widget.value;
else if (res.length < batch_widget.value)
for (let i = res.length; i < batch_widget.value; ++ i)
res.push("sub");
for (let i = 0; i < batch_widget.value; ++ i)
if (res[i]) {
res[i] = res[i].trim();
if (isNaN(res[i])) {
if (res[i] !== "rand" && res[i] !== "add")
res[i] = "rand";
else if (res[i] === "rand")
res[i] = "add";
else
res[i] = "sub";
}
}
val_widget.value = res.join(", ");
}, {
serialize: false
});
});
setup_log(nodeType, true);
} break;
case "0246.Beautify": {
setup_log(nodeType);
} break;
case "0246.Stringify": {
single_impl_input(nodeType, nodeData, app, LiteGraph.CIRCLE_SHAPE, []);
} break;
case "0246.Merge": {
single_impl_input(nodeType, nodeData, app, LiteGraph.CIRCLE_SHAPE, []);
} break;
case "0246.Hub": {
const Hub = nodeType,
HUB_PARENT = Symbol("hub_parent"),
HUB_SOLE = 7;
// [TODO] Force tracked node render by hijack app.canvas.computeVisibleNodes and
// change getBounding of each nodes to be within app.canvas.visible_area
Hub.prototype.onNodeCreated = function() {
const node = this;
const prim_widget = node.addWidget("combo", "base:prim", "INT", () => {}, {
serialize: false,
values: ["INT", "FLOAT", "STRING", "BOOLEAN"]
});
node.addWidget("button", "Add Sole Primitive Widget", null, () => {
node.hubPushWidgetPrim(prim_widget.value);
}, {
serialize: false
});
const combo_widget = node.addWidget("combo", "base:combo", ["KSampler", "required", "sampler_name"], () => {}, {
serialize: false,
values: (function () {
let res = [];
for (let key in defs)
for (let input in defs[key].input)
for (let type in defs[key].input[input])
if (Array.isArray(defs[key].input[input][type][0]))
res.push([key, input, type]);
return res;
})()
});
node.addWidget("button", "Add Sole Combo Widget", null, () => {
node.hubPushWidgetCombo(...combo_widget.value);
}, {
serialize: false
});
const node_widget = node.addWidget("combo", "base:node", "KSampler", () => {}, {
serialize: false,
values: Object.keys(defs)
});
node.addWidget("button", "Add Sole Primitive Widgets from Node", null, () => {
node.hubPushWidgetNode(node_widget.value);
}, {
serialize: false
});
const del_widget = node.addWidget("combo", "base:del", "", () => {}, {
serialize: false,
values: function (widget) {
let res = [];
for (let i = 0; i < node.hub.sole_widget.length; ++ i)
res.push(node.hub.sole_widget[i].name);
return res;
}
});
node.addWidget("button", "Delete Sole Widget", null, () => {
node.hubPullWidget(node.hub.sole_widget.find(w => w.name === del_widget.value));
}, {
serialize: false
});
lib0246.hijack(node, "connect", function (slot, target_node, target_slot) {
const output_data = this.self.hub.data.sole_type[this.self.outputs[slot].name];
if (output_data[2] === "combo") {
Hub.nodeData = Hub.nodeData ?? {};
Hub.nodeData.output = Hub.nodeData.output ?? {};
Hub.nodeData.output[slot] = defs[output_data[3]].input[output_data[4]][output_data[5]][0];
}
}, function (slot, target_node, target_slot) {
const output_data = this.self.hub.data.sole_type[this.self.outputs[slot].name];
if (output_data[2] === "combo")
delete Hub.nodeData.output[slot];
});
node.size[0] = 350;
};
lib0246.hijack(Hub.prototype, "onAdded", () => {}, function () {
const node = this.self;
node.hub = node.hub ?? {};
app.graph.extra["0246.HUB_DATA"] = app.graph.extra["0246.HUB_DATA"] ?? {};
app.graph.extra["0246.HUB_DATA"][node.id] = app.graph.extra["0246.HUB_DATA"][node.id] ?? {};
node.hub.data = app.graph.extra["0246.HUB_DATA"][node.id];
node.hub.data.node_list = node.hub.data.node_list ?? [];
node.hub.data.node_img = node.hub.data.node_img ?? [];
node.hub.data.sole_name = node.hub.data.sole_name ?? {};
node.hub.data.sole_type = node.hub.data.sole_type ?? {};
// Temp data
node.hub.node_area = {};
node.hub.node_widget = {};
node.hub.space_widget = {};
node.hub.node_func = {};
node.hub.sole_widget = [];
node.hub.sole_space = null;
});
lib0246.hijack(Hub.prototype, "onConfigure", () => {}, function (data) {
const node = this.self;
for (let node_id of app.graph.extra["0246.HUB_DATA"][node.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 = name.split(":"),
type_data = node.hub.data.sole_type[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": {
widget = node.hubPushWidgetCombo(...type_data, name);
} break;
}
widget.value = data.widgets_values[HUB_SOLE + 2 + (count ++)];
}
}
});
lib0246.hijack(Hub.prototype, "onRemoved", function () {
const node = this.self;
if (node.hub.data)
while (node.hub.data.node_list.length > 0)
node.hubPullNode(node.hub.data.node_list[0]);
delete app.graph.extra["0246.HUB_DATA"][node.id];
}, () => {});
lib0246.hijack(Hub.prototype, "getExtraMenuOptions", function (canvas, options) {
const node = this.self;
options.push(
{
content: "[0246.Hub] Add widgets from \"graph selected nodes\"",
callback: function() {
for (let key in app.canvas.selected_nodes) {
const curr_node = app.canvas.selected_nodes[key];
if (curr_node === node || node.isVirtualNode || curr_node.type === "0246.Hub")
continue;
node.hubPushNode(curr_node);
}
app.canvas.setDirty(true);
}
}, {
content: "[0246.Hub] Add sole widgets from \"graph selected nodes\"",
callback: function() {
for (let key in app.canvas.selected_nodes) {
const curr_node = app.canvas.selected_nodes[key];
if (curr_node === node || node.isVirtualNode || curr_node.type === "0246.Hub")
continue;
node.hubPushNode(curr_node.type);
}
app.canvas.setDirty(true);
}
}, {
content: "[0246.Hub] Remove widgets from \"hub selected nodes\"",
callback: function() {
for (let node_id in node.hub.space_widget) {
if (node.hub.space_widget[node_id].select)
node.hubPullNode(node.hub.space_widget[node_id].value);
}
app.canvas.setDirty(true);
}
}
);
help_option(Hub, HELP["hub"], options);
options.push(null);
}, () => {});
Hub.prototype.hubSize = function () {
const curr_size = this.computeSize();
curr_size[0] = this.size[0] < 350 ? 350 : this.size[0];
this.setSize(curr_size);
};
Hub.prototype.hubPushWidgetPrim = function (type, ...args) {
const node = this;
switch (type) {
case "INT": {
return node.hubPushWidget({
type: "number",
value: 0,
options: {
min: Number.MIN_SAFE_INTEGER,
max: Number.MAX_SAFE_INTEGER,
step: 10, // litegraph.js internally multiply by 0.1
precision: 0
},
}, "int", "INT", ...args);
} break;
case "FLOAT": {
return node.hubPushWidget({
type: "number",
value: 0,
options: {
min: -Infinity,
max: Infinity,
step: 1,
precision: 5
},
}, "float", "FLOAT", ...args);
} break;
case "STRING": {
return node.hubPushWidget({
type: "string",
value: "",
options: {
multiline: true
}
}, "string", "STRING", ...args);
} break;
case "BOOLEAN": {
return node.hubPushWidget({
type: "toggle",
value: false,
options: {}
}, "boolean", "BOOLEAN", ...args);
} break;
}
};
Hub.prototype.hubPushWidgetCombo = function (node_name, part_name, pin_name, full_name) {
const combo_data = defs[node_name].input[part_name][pin_name][0];
return this.hubPushWidget({
type: "combo",
value: combo_data[0],
options: {
values: combo_data
},
}, "combo", "COMBO", node_name, part_name, pin_name, full_name);
};
Hub.prototype.hubPushWidgetNode = function (node_name, full_name) {
for (let part_name in defs[node_name].input)
for (let pin_name in defs[node_name].input[part_name]) {
const curr_type = defs[node_name].input[part_name][pin_name][0];
if (Array.isArray(curr_type))
this.hubPushWidgetCombo(node_name, part_name, pin_name, full_name);
else
this.hubPushWidgetPrim(curr_type, node_name, part_name, pin_name, full_name);
}
};
Hub.prototype.hubPushWidget = function (widget, id_type, pin_type, node_name, part_name, pin_name, full_name) {
if (!this.hub.sole_space) {
this.hub.sole_space = SPACE_TITLE_WIDGET();
this.widgets.splice(HUB_SOLE + 1, 0, this.hub.sole_space);
}
this.widgets.splice(HUB_SOLE + 2 + this.hub.sole_widget.length, 0, widget);
this.hub.sole_widget.push(widget);
if (typeof full_name === "string")
widget.name = full_name;
else {
this.hub.data.sole_name[id_type] = this.hub.data.sole_name[id_type] ?? 0;
let list_name = [
"sole", String(this.hub.data.sole_name[id_type] ++),
id_type, node_name, part_name, pin_name, pin_type
];
full_name = list_name.reduce((a, b) =>
typeof a !== "string" ? (b ?? "") :
typeof b !== "string" ? (a ?? "") :
a + ":" + b
);
this.addOutput(widget.name = full_name, pin_type);
this.hub.data.sole_type[full_name] = list_name;
}
this.hubSize();
return widget;
};
Hub.prototype.hubPullWidget = function (widget) {
const widget_index = this.widgets.indexOf(widget);
if (widget_index === -1)
return;
this.widgets.splice(widget_index, 1);
delete this.hub.data.sole_type[widget.name];
const output_index = this.outputs.indexOf(this.outputs.find(o => o.name === widget.name));
if (output_index > -1) {
delete Hub.nodeData.output[output_index];
this.removeOutput(output_index);
}
const sole_widget_index = this.hub.sole_widget.indexOf(widget);
if (sole_widget_index > -1)
this.hub.sole_widget.splice(sole_widget_index, 1);
if (this.hub.sole_widget.length === 0) {
this.widgets.splice(HUB_SOLE + 1, 1);
this.hub.sole_space = null;
}
this.hubSize();
};
Hub.prototype.hubPushNode = function (curr_node, flag = false) {
const node = this;
if (curr_node[HUB_PARENT] === node || !curr_node.widgets || curr_node.widgets.length === 0)
return;
curr_node[HUB_PARENT] = node;
if (!flag || (flag && node.hub.data.node_list.indexOf(curr_node.id) === -1))
node.hub.data.node_list.push(curr_node.id);
node.hub.node_func[curr_node.id] = {};
node.hub.node_func[curr_node.id].bound = lib0246.hijack(curr_node, "onBounding", () => {}, function (area) {
node.hub.node_area[curr_node.id] = area;
});
node.hub.node_func[curr_node.id].remove = lib0246.hijack(curr_node, "onRemoved", function () {
node.hubPullNode(curr_node.id);
}, () => {});
node.hub.node_widget[curr_node.id] = curr_node.widgets;
node.hub.space_widget[curr_node.id] = node.addCustomWidget(SPACE_TITLE_WIDGET());
node.hub.space_widget[curr_node.id].value = curr_node.id;
node.hub.node_func[curr_node.id].draw_bg = lib0246.hijack(curr_node, "onDrawBackground", () => {}, function (ctx, visible_area) {
const curr_widget = node.hub.space_widget[curr_node.id];
if (node.hub.space_widget[curr_node.id].select) {
ctx.beginPath();
ctx.strokeStyle = curr_widget.select_color;
ctx.lineWidth = 2;
ctx.setLineDash([]);
ctx.roundRect(
-12, -12 - LiteGraph.NODE_TITLE_HEIGHT,
this.self.size[0] + 24, this.self.size[1] + 24 + LiteGraph.NODE_TITLE_HEIGHT,
5, 5
);
ctx.stroke();
ctx.closePath();
}
});
for (let i = 0; i < curr_node.widgets.length; ++ i) {
const widget = curr_node.widgets[i];
node.addCustomWidget(widget);
widget[NODE_PARENT] = curr_node;
}
if (curr_node.imgs && curr_node.imgs.length > 0)
node.hub.data.node_img.push(curr_node.id);
node.hubSize();
};
Hub.prototype.hubPullNode = function (curr_id) {
const node_index = this.hub.data.node_list.indexOf(curr_id);
if (node_index === -1)
return;
this.hub.data.node_list.splice(node_index, 1);
const node_img_index = this.hub.data.node_img.indexOf(curr_id);
if (node_img_index > -1)
this.hub.data.node_img.splice(node_img_index, 1);
delete this.hub.node_area[curr_id];
if (this.hub.node_widget[curr_id]) {
for (let widget of this.hub.node_widget[curr_id]) {
let widget_index = this.widgets.indexOf(widget);
if (widget_index > -1)
this.widgets.splice(widget_index, 1);
}
delete this.hub.node_widget[curr_id];
}
const space_widget_index = this.widgets.indexOf(this.hub.space_widget[curr_id]);
if (space_widget_index > -1)
this.widgets.splice(space_widget_index, 1);
delete this.hub.space_widget[curr_id];
const curr_node = app.graph.getNodeById(curr_id);
if (curr_node && this.hub.node_func[curr_id]) {
delete curr_node[HUB_PARENT];
curr_node.onBounding = this.hub.node_func[curr_id].bound;
curr_node.onRemoved = this.hub.node_func[curr_id].remove;
curr_node.onDrawBackground = this.hub.node_func[curr_id].draw_bg;
}
delete this.hub.node_func[curr_id];
this.hubSize();
};
let temp_parent = [];
lib0246.hijack(GroupNodeHandler, "fromNodes", function (nodes) {
for (let i = 0; i < nodes.length; ++ i)
if (nodes[i][HUB_PARENT] && temp_parent.indexOf(nodes[i].id) === -1) {
temp_parent.push(nodes[i][HUB_PARENT].id);
nodes[i][HUB_PARENT].hubPullNode(nodes[i].id);
}
}, function () {
if (this.res) {
const node_list = this.res.getInnerNodes();
let count = 0;
while (temp_parent.length > 0) {
const curr_parent = app.graph.getNodeById(temp_parent.pop());
curr_parent.hubPushNode(this.res);
node_list[count ++][HUB_PARENT] = curr_parent;
}
lib0246.hijack(this.res, "convertToNodes", function () {
for (let i = 0; i < node_list.length; ++ i)
if (node_list[i][HUB_PARENT]) {
node_list[i][HUB_PARENT].hubPullNode(node_list[i].id);
temp_parent.push(node_list[i][HUB_PARENT].id, node_list[i].id);
}
}, function () {
while (temp_parent.length > 0) {
const curr_node = app.graph.getNodeById(temp_parent.pop()),
curr_parent = app.graph.getNodeById(temp_parent.pop());
for (let i = 0; i < this.res.length; ++ i)
if (this.res[i].id === curr_node.id) {
curr_parent.hubPushNode(this.res[i]);
break;
}
}
});
}
});
rgthree_exec("addConnectionLayoutSupport", Hub, app);
} break;
case "0246.Script": {
junction_impl(nodeType, nodeData, app, null, LiteGraph.GRID_SHAPE, LiteGraph.GRID_SHAPE);
} break;
case "0246.ScriptPlan": {
lib0246.hijack(nodeType.prototype, "onNodeCreated", () => {}, function () {
const chain_widget = this.self.widgets.find(w => w.name === "script_chain_type");
chain_widget.options = chain_widget.options ?? {};
chain_widget.options.multiline = true;
});
} break;
}
}
},
});
// var curr_node = LiteGraph.createNode(LiteGraph.getNodeTypesInCategory(LiteGraph.getNodeTypesCategories()[2])[2].type);
})();