import { Logger, get_real_node, Pausable } from "./use_everywhere_utilities.js"; import { app } from "../../scripts/app.js"; import { settingsCache } from "./use_everywhere_cache.js"; import { in_visible_graph, node_graph } from "./use_everywhere_subgraph_utils.js"; import { maybe_show_tooltip } from "./tooltip_window.js"; function nodes_in_my_group(node) { const nodes_in = new Set(); node_graph(node)._groups.forEach((group) => { if (!app.canvas.selected_group_moving) group.recomputeInsideNodes(); if (group._nodes?.find((nd) => { return (nd.id===node.id) } )) { group._nodes.forEach((nd) => { nodes_in.add(nd.id) } ) } }); return [...nodes_in]; } function nodes_not_in_my_group(node) { const nid = nodes_in_my_group(node); const nodes_not_in = []; node_graph(node)._nodes.forEach((nd) => { if (!nid.includes(nd.id)) nodes_not_in.push(nd.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, already_limited_to) { const nodes_in = new Set(); const color = node.color; if (already_limited_to) { already_limited_to.forEach((nid) => { if (get_real_node(nid, node_graph(node)).color==color) nodes_in.add(nid) }) } else { node_graph(node)._nodes.forEach((nd) => { if (nd.color==color) nodes_in.add(nd.id) }) } return [...nodes_in]; } function nodes_not_my_color(node, 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, node_graph(node)).color!=color) nodes_in.add(nid) }) } else { node_graph(node)._nodes.forEach((nd) => { if (nd.color!=color) nodes_in.add(nd.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 update_input_label(node, slot, app) { const type = node.inputs[slot].type if (type) { node.inputs[slot].label = type; node.inputs[slot].color_on = app.canvas.default_connection_color_byType[type]; } else { node.inputs[slot].label = "anything"; node.inputs[slot].color_on = undefined; } } class LinkRenderController extends Pausable { 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() { super('LinkRenderController') this.the_graph_analyser = null; this.ue_list = undefined; // the most current ue list - set to undefined if we know it is out of date this.last_used_ue_list = undefined; // the last ue list we actually used to generate graphics this.link_list_outdated = false; this.widgets_disabled = [] setInterval(this.try_to_update_link_list.bind(this), 100); setInterval(this.mark_link_list_outdated.bind(this), 2000); } queue_size = null; note_queue_size(x) { this.queue_size = x; } //on_unpause() {app.graph.change();} node_over_changed(v) { const mode = settingsCache.getSettingValue('Use Everywhere.Graphics.showlinks'); if (mode==2 || mode==3) app.canvas.setDirty(true,true) } // memory reuse slot_pos1 = new Float32Array(2); //to reuse slot_pos2 = new Float32Array(2); //to reuse /* Outdating. Convention is that methods starting _ should only be called inside a pause()/unpause() */ mark_link_list_outdated() { this.link_list_outdated = true } try_to_update_link_list() { if (!this.link_list_outdated) return; if (this.paused()) return; try { this.pause('try_to_update_link_list') this.ue_list = undefined; if (this._request_link_list_update()) this.link_list_outdated = false; } finally { this.unpause() } } _request_link_list_update() { try { const ues = this.the_graph_analyser.analyse_visible_graph() if (ues==null) return false // graph analyser was paused this.ue_list = ues; if (this.ue_list.differs_from(this.last_used_ue_list)) app.graph.change(); return true } catch (e) { Logger.log_error(e); return false } } disable_all_connected_widgets( disable ) { if (disable) { app.graph.extra['ue_links']?.forEach((uel) => { const node = app.graph._nodes_by_id[uel.downstream] if (node) { const name = node.inputs[uel.downstream_slot].name; const widget = node._getWidgetByName(name) if (widget && !widget.disabled) { this.widgets_disabled.push(widget) widget.disabled = true; } widget?.linkedWidgets?.filter((w)=>!w.disabled).forEach((w)=>{ this.widgets_disabled.push(w) w.disabled = true; }) } else { Logger.log_info(`Couldn't find node ${uel.downstream}`) } }) } else { this.widgets_disabled.forEach((w)=>w.disabled=false) this.widgets_disabled = [] } } highlight_ue_connections(node, ctx) { if (!settingsCache.getSettingValue('Use Everywhere.Graphics.highlight')) return; try { this.pause('highlight_ue_connections') if (!this._list_ready()) return; const unconnected_connectables = node.properties?.ue_properties?.widget_ue_connectable ? new Set(Object.keys(node.properties.ue_properties.widget_ue_connectable).filter((name) => (node.properties.ue_properties.widget_ue_connectable[name]))) : new Set() node.inputs.filter((input)=>(input.link)).forEach((input) => { unconnected_connectables.delete(input.name) }); 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; } if (!node.inputs[ue_connection.input_index]) { return } const name_sent_to = node.inputs[ue_connection.input_index].name; unconnected_connectables.delete(name_sent_to); // remove the name from the list of connectables 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.shadowBlur = 0; 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(); }); } unconnected_connectables.forEach((name) => { const index = node.inputs.findIndex((i) => i.name == name); var pos2 = node.getConnectionPos(true, index, this.slot_pos1); pos2[0] -= node.pos[0]; pos2[1] -= node.pos[1]; ctx.save(); ctx.strokeStyle = "black"; ctx.shadowColor = "green"; ctx.shadowBlur = 10; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; var radius = 3; ctx.beginPath(); ctx.roundRect(pos2[0]-radius,pos2[1]-radius,2*radius,2*radius,radius); ctx.stroke(); ctx.restore(); }) } catch (e) { Logger.log_error(e); } finally { this.unpause() } } _list_ready() { if (!this.the_graph_analyser) return false; // we don't have the analyser yet (still loading) if (!this.ue_list) { this.mark_link_list_outdated(); return false; } return true; } node_in_ueconnection(ue_connection, id) { if (ue_connection.control_node?.id == id) return true if (ue_connection.sending_to?.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) { maybe_show_tooltip() if (this.paused()) return; try { this.pause('render_all_ue_links') this._render_all_ue_links(ctx); } catch (e) { console.error(e); } finally { this.unpause() } } _render_all_ue_links(ctx) { if (!this._list_ready()) return; this.last_used_ue_list = this.ue_list; ctx.save(); const orig_hqr = app.canvas.highquality_render; app.canvas.highquality_render = false; const mode = settingsCache.getSettingValue('Use Everywhere.Graphics.showlinks'); var animate = settingsCache.getSettingValue('Use Everywhere.Graphics.animate'); if (settingsCache.getSettingValue('Use Everywhere.Graphics.stop_animation_when_running') && 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; show = show && in_visible_graph(ue_connection.control_node) && in_visible_graph(ue_connection.sending_to); 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(); } _render_ue_link(ue_connection, ctx, animate) { try { const graph = ue_connection.graph const node = get_real_node(ue_connection.sending_to.id, graph); /* 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 = graph._nodes_by_id[ue_connection.control_node.id]?.getConnectionPos(input_source, source_index, this.slot_pos2); if (!pos1) { Logger.problem(`Couldn't find position for UE link ${ue_connection}.`,null,true) return; } /* 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.save() const rcb = app.canvas.render_connections_border; try { var skip_border = false if (settingsCache.getSettingValue( "Use Everywhere.Graphics.fuzzlinks" )) { app.canvas.render_connections_border = false ctx.shadowColor = color; ctx.shadowBlur = 6; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; skip_border = true } app.canvas.renderLink(ctx, pos1, pos2, undefined, skip_border, animate%2, modify(color), sta_direction, end_direction, undefined); } catch(e) { Logger.log_problem(`Issue with UE link ${ue_connection}.`); } finally { ctx.restore() app.canvas.render_connections_border = rcb; } } catch (e) { Logger.log_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