Spaces:
Running
Running
import { Logger, get_real_node, get_group_node, get_all_nodes_within } from "./use_everywhere_utilities.js"; | |
import { ComfyWidgets } from "../../scripts/widgets.js"; | |
import { app } from "../../scripts/app.js"; | |
export class UpdateBlocker { | |
static depth = 0 | |
static push() { UpdateBlocker.depth += 1 } | |
static pop() { UpdateBlocker.depth -= 1 } | |
static blocking() { return UpdateBlocker.depth>0 } | |
} | |
function nodes_in_my_group(node_id) { | |
const nodes_in = new Set(); | |
app.graph._groups.forEach((group) => { | |
if (!app.canvas.selected_group_moving) group.recomputeInsideNodes(); | |
if (group._nodes?.find((node) => { return (node.id===node_id) } )) { | |
group._nodes.forEach((node) => { nodes_in.add(node.id) } ) | |
} | |
}); | |
return [...nodes_in]; | |
} | |
function nodes_not_in_my_group(node_id) { | |
const nid = nodes_in_my_group(node_id); | |
const nodes_not_in = []; | |
app.graph._nodes.forEach((node) => { | |
if (!nid.includes(node.id)) nodes_not_in.push(node.id); | |
}); | |
return nodes_not_in; | |
} | |
function nodes_in_groups_matching(regex, already_limited_to) { | |
const nodes_in = new Set(); | |
app.graph._groups.forEach((group) => { | |
if (regex.test(group.title)) { | |
if (!app.canvas.selected_group_moving) group.recomputeInsideNodes(); | |
/* | |
Note for optimisation - it would be more efficient to calculate what nodes are in what groups | |
once at the start of analyse_graph() rather than for every group for every UE? with a group regex. | |
*/ | |
group._nodes.forEach((node) => { | |
if (!already_limited_to || already_limited_to.includes(node.id)) { | |
nodes_in.add(node.id) | |
} | |
} ); | |
} | |
}); | |
return [...nodes_in]; | |
} | |
function nodes_my_color(node_id, already_limited_to) { | |
const nodes_in = new Set(); | |
const color = get_real_node(node_id).color; | |
if (already_limited_to) { | |
already_limited_to.forEach((nid) => { | |
if (get_real_node(nid).color==color) nodes_in.add(nid) | |
}) | |
} else { | |
app.graph._nodes.forEach((node) => { | |
if (node.color==color) nodes_in.add(node.id) | |
}) | |
} | |
return [...nodes_in]; | |
} | |
function nodes_not_my_color(node_id, already_limited_to) { | |
const nodes_in = new Set(); | |
const color = get_real_node(node_id).color; | |
if (already_limited_to) { | |
already_limited_to.forEach((nid) => { | |
if (get_real_node(nid).color!=color) nodes_in.add(nid) | |
}) | |
} else { | |
app.graph._nodes.forEach((node) => { | |
if (node.color!=color) nodes_in.add(node.id) | |
}) | |
} | |
return [...nodes_in]; | |
} | |
function indicate_restriction(ctx, title_height) { | |
ctx.save(); | |
ctx.lineWidth = 2; | |
ctx.strokeStyle = "#6F6"; | |
ctx.beginPath(); | |
ctx.roundRect(5,5-title_height,20,20,8); | |
ctx.stroke(); | |
ctx.restore(); | |
} | |
function displayMessage(id, message) { | |
const node = get_real_node(id); | |
if (!node) return; | |
var w = node.widgets?.find((w) => w.name === "display_text_widget"); | |
if (app.ui.settings.getSettingValue('AE.details', false) || w) { | |
if (!w) { | |
w = ComfyWidgets["STRING"](this, "display_text_widget", ["STRING", { multiline: true }], app).widget; | |
w.inputEl.readOnly = true; | |
w.inputEl.style.opacity = 0.6; | |
w.inputEl.style.fontSize = "9pt"; | |
} | |
w.value = message; | |
this.onResize?.(this.size); | |
} | |
} | |
function update_input_label(node, slot, app) { | |
if (node.input_type[slot]) { | |
node.inputs[slot].name = node.input_type[slot]; | |
node.inputs[slot].color_on = app.canvas.default_connection_color_byType[node.input_type[slot]]; | |
} else { | |
node.inputs[slot].name = "anything"; | |
node.inputs[slot].color_on = undefined; | |
} | |
} | |
class LinkRenderController { | |
static _instance; | |
static instance(tga) { | |
if (!this._instance) this._instance = new LinkRenderController(); | |
if (tga && !this._instance.the_graph_analyser) this._instance.the_graph_analyser = tga; | |
return this._instance | |
} | |
constructor() { | |
this.the_graph_analyser = null; | |
this.periodically_mark_link_list_outdated(); | |
} | |
ue_list = undefined; // the most current ue list - set to undefined if we know it is out of date | |
ue_list_reloading = false; // true when a reload has been requested but not completed | |
last_used_ue_list = undefined; // the last ue list we actually used to generate graphics | |
paused = false; | |
reading_list = false; // don't outdate the list while we read it (because reading it can trigger outdates!) | |
queue_size = null; | |
note_queue_size(x) { this.queue_size = x; } | |
pause(ms) { | |
this.paused = true; | |
if (!ms) ms = 100; | |
setTimeout( this.unpause.bind(this), ms ); | |
} | |
unpause() { | |
this.paused = false; | |
app.graph.change(); | |
} | |
// memory reuse | |
slot_pos1 = new Float32Array(2); //to reuse | |
slot_pos2 = new Float32Array(2); //to reuse | |
mark_link_list_outdated() { | |
if (UpdateBlocker.blocking()) return; | |
if (this.reading_list) return; | |
if (this.ue_list) { | |
this.ue_list = undefined; | |
this.request_link_list_update(); | |
Logger.log(Logger.INFORMATION, "link_list marked outdated"); | |
} else { | |
Logger.log(Logger.INFORMATION, "link_list was already outdated"); | |
} | |
} | |
periodically_mark_link_list_outdated() { | |
this.mark_link_list_outdated(); | |
setTimeout(this.periodically_mark_link_list_outdated.bind(this), 1000); | |
} | |
// callback when the_graph_analyser finishes - store the result and note reloading is false | |
reload_resolve = function (value) { | |
this.ue_list = value; | |
this.ue_list_reloading = false; | |
if (this.ue_list.differs_from(this.last_used_ue_list)) app.graph.change(); | |
Logger.log(Logger.INFORMATION, "link list update completed"); | |
Logger.log_call(Logger.DETAIL, this.ue_list.print_all); | |
}.bind(this) | |
// callback for when the_graph_analyser fails - note reloading is false and log | |
reload_reject = function(reason) { | |
this.ue_list_reloading=false; | |
Logger.log(Logger.ERROR, "link list update failed"); | |
Logger.log_error(Logger.ERROR, reason); | |
}.bind(this) | |
// request an update to the ue_list. | |
request_link_list_update() { | |
if (this.ue_list_reloading) return; // already doing it | |
this.ue_list_reloading = true; // stop any more requests | |
this.the_graph_analyser.analyse_graph().then(this.reload_resolve, this.reload_reject); // an async call is a promise; pass it two callbacks | |
Logger.log(Logger.INFORMATION, "link list update started"); | |
} | |
highlight_ue_connections(node, ctx) { | |
try { | |
this._highlight_ue_connections(node, ctx); | |
} catch (e) { | |
console.error(e); | |
} | |
} | |
_highlight_ue_connections(node, ctx) { | |
this.reading_list = true; | |
if (!app.ui.settings.getSettingValue('AE.highlight', true)) return; | |
//if (this._ue_links_visible) return; | |
if (!this.list_ready()) return; | |
if (this.ue_list.all_connected_inputs) { | |
this.ue_list.all_connected_inputs(node).forEach((ue_connection) => { | |
if (!ue_connection.control_node) { // control node deleted... | |
this.mark_link_list_outdated(); | |
return; | |
} | |
var pos2 = node.getConnectionPos(true, ue_connection.input_index, this.slot_pos1); | |
pos2[0] -= node.pos[0]; | |
pos2[1] -= node.pos[1]; | |
ctx.save(); | |
ctx.lineWidth = 1; | |
var radius=5 | |
ctx.strokeStyle = LGraphCanvas.link_type_colors[ue_connection.type]; | |
ctx.shadowColor = "white"; | |
ctx.shadowBlur = 10; | |
ctx.shadowOffsetX = 0; | |
ctx.shadowOffsetY = 0; | |
ctx.beginPath(); | |
ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius); | |
ctx.stroke(); | |
ctx.beginPath(); | |
ctx.strokeStyle = "black"; | |
ctx.shadowBlur = 0; | |
radius = radius - 1; | |
ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius); | |
ctx.stroke(); | |
ctx.restore(); | |
}); | |
} | |
this.reading_list = false; | |
} | |
list_ready(make_latest) { | |
if (this.paused) return false; | |
if (!this.the_graph_analyser) return false; // we don't have the analyser yet (still loading) | |
if (!this.ue_list) this.request_link_list_update(); | |
if (!this.ue_list) return false; | |
if (make_latest) this.last_used_ue_list = this.ue_list; | |
return true; | |
} | |
node_in_ueconnection(ue_connection, id) { | |
if (ue_connection.control_node && get_group_node(ue_connection.control_node.id)?.id == id) return true | |
if (ue_connection.sending_to && get_group_node(ue_connection.sending_to.id)?.id == id) return true | |
} | |
any_node_in_ueconnection(ue_connection, list_of_nodes) { | |
return (Object.values(list_of_nodes).find((node) => (this.node_in_ueconnection(ue_connection, node.id))))?true:false; | |
} | |
render_all_ue_links(ctx) { | |
try { | |
this._render_all_ue_links(ctx); | |
} catch (e) { | |
console.error(e); | |
} | |
} | |
_render_all_ue_links(ctx) { | |
if (!this.list_ready(true)) return; | |
this.reading_list = true; | |
ctx.save(); | |
const orig_hqr = app.canvas.highquality_render; | |
app.canvas.highquality_render = false; | |
const mode = app.ui.settings.getSettingValue('AE.showlinks', 0); | |
var animate = app.ui.settings.getSettingValue('AE.animate', 3); | |
if (app.ui.settings.getSettingValue('AE.stop.animation.running', true) && this.queue_size>0) animate = 0; | |
if (animate==2 || animate==3) this.animate_step(ctx); | |
var any_links_shown = false; | |
var any_links = false; | |
this.ue_list.all_ue_connections().forEach((ue_connection) => { | |
any_links = true; | |
var show = false; | |
if (mode==4) show = true; | |
if ( (mode==2 || mode==3) && app.canvas.node_over && this.node_in_ueconnection(ue_connection, app.canvas.node_over.id) ) show = true; | |
if ( (mode==1 || mode==3) && this.any_node_in_ueconnection(ue_connection, app.canvas.selected_nodes)) show = true; | |
if ( show ) { | |
this._render_ue_link(ue_connection, ctx, animate); | |
any_links_shown = true; | |
} | |
}); | |
if (animate>0) { | |
/* | |
If animating, we want to mark the visuals as changed so the animation updates - but not often! | |
If links shown: | |
- If showing dots, wait 30ms | |
- Otherwise, wait 100ms | |
If no links are shown | |
- If there are links, and our mode is mouseover, wait 200ms | |
- Otherwise don't request an update (there are no links that could be shown without something else requesting a redraw) | |
*/ | |
const timeout = (any_links_shown) ? ((animate%2 == 1) ? 30 : 100) : ((mode==2 || mode==3) && any_links) ? 200 : -1; | |
if (timeout>0) setTimeout( app.graph.change.bind(app.graph), timeout ); | |
} | |
app.canvas.highquality_render = orig_hqr; | |
ctx.restore(); | |
this.reading_list = false; | |
} | |
_render_ue_link(ue_connection, ctx, animate) { | |
try { | |
const node = get_real_node(ue_connection.sending_to.id); | |
/* this is the end node; get the position of the input */ | |
var pos2 = node.getConnectionPos(true, ue_connection.input_index, this.slot_pos1); | |
/* get the position of the *input* that is being echoed - except for the Seed Anywhere node, | |
which is displayed with an output: the class records control_node_input_index as -ve (-1 => 0, -2 => 1...) */ | |
const input_source = (ue_connection.control_node_input_index >= 0); | |
const source_index = input_source ? ue_connection.control_node_input_index : -1-ue_connection.control_node_input_index; | |
const pos1 = get_group_node(ue_connection.control_node.id).getConnectionPos(input_source, source_index, this.slot_pos2); | |
/* get the direction that we start and end */ | |
const delta_x = pos2[0] - pos1[0]; | |
const delta_y = pos2[1] - pos1[1]; | |
const end_direction = LiteGraph.LEFT; // always end going into an input | |
const sta_direction = ((Math.abs(delta_y) > Math.abs(delta_x))) ? | |
((delta_y>0) ? LiteGraph.DOWN : LiteGraph.UP) : | |
((input_source && delta_x<0) ? LiteGraph.LEFT : LiteGraph.RIGHT) | |
var color = LGraphCanvas.link_type_colors[ue_connection.type]; | |
if (color=="") color = app.canvas.default_link_color; | |
ctx.shadowColor = color; | |
app.canvas.renderLink(ctx, pos1, pos2, undefined, true, animate%2, color, sta_direction, end_direction, undefined); | |
} catch (e) { | |
Logger.log(Logger.PROBLEM, `Couldn't render UE link ${ue_connection}. That's ok if something just got deleted.`); | |
} | |
} | |
animate_step(ctx) { | |
const max_blur = 8; | |
const speed = 0.75; | |
var f = (LiteGraph.getTime()*0.001*speed) % 1; | |
const step = Math.ceil(f*2*max_blur) % (2*max_blur); | |
ctx.shadowBlur = (step<max_blur) ? step + 4 : 3 + 2*max_blur - step; | |
} | |
} | |
export {displayMessage, update_input_label, nodes_in_my_group, nodes_not_in_my_group, nodes_in_groups_matching, nodes_my_color, nodes_not_my_color, indicate_restriction} | |
export{ LinkRenderController} |