Files
chrisgoringe-cg-use-everywhere/js/use_everywhere_ui.js
T

554 lines
21 KiB
JavaScript

import { Logger, get_real_node, Pausable, node_can_broadcast, is_able_to_broadcast, running_nodes2 } from "./use_everywhere_utilities.js";
import { app } from "../../scripts/app.js";
import { settingsCache } from "./use_everywhere_cache.js";
import { in_visible_graph, visible_graph } from "./use_everywhere_subgraph_utils.js";
import { maybe_show_tooltip } from "./tooltip_window.js";
import { is_connectable } from "./use_everywhere_settings.js";
import { shared } from "./shared.js";
import { titlebar_color } from "./ue_shared_ui.js";
export function nodes_in_my_group(node) {
const nodes_in = new Set();
node.graph._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];
}
export function nodes_not_in_my_group(node) {
const nid = nodes_in_my_group(node);
const nodes_not_in = [];
node.graph._nodes.forEach((nd) => {
if (!nid.includes(nd.id)) nodes_not_in.push(nd.id);
});
return nodes_not_in;
}
export function nodes_in_groups_matching(regex, already_limited_to) {
const nodes_in = new Set();
app.graph._groups.forEach((group) => {
if (regex.regex.test(group.title) != regex.invert) {
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];
}
export function nodes_my_color(node, already_limited_to) {
const nodes_in = new Set();
const color = get_real_node(node.id, node.graph)?.color
if (already_limited_to) {
already_limited_to.forEach((nid) => {
if (get_real_node(nid, node.graph)?.color==color) nodes_in.add(nid)
})
} else {
node.graph._nodes.forEach((nd) => {
if (nd.color==color) nodes_in.add(nd.id)
})
}
return [...nodes_in];
}
export 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)?.color!=color) nodes_in.add(nid)
})
} else {
node.graph._nodes.forEach((nd) => {
if (nd.color!=color) nodes_in.add(nd.id)
})
}
return [...nodes_in];
}
export function title_bar_additions(node, ctx, title_height=30) {
if (node_can_broadcast(node)) {
const color = titlebar_color(node)
const offset_x = (node.subgraph) ? 5 : 5
const offset_y = (node.subgraph) ? 4 : 5
const width = (node.subgraph) ? 24 : 20
const height = (node.subgraph) ? 23 : 20
const radius = (node.subgraph) ? 6 : 6
const line_w = (node.subgraph) ? 1 : 2
ctx.save();
ctx.lineWidth = line_w;
ctx.strokeStyle = color;
ctx.beginPath();
ctx.roundRect(offset_x, offset_y-title_height, width, height, radius);
ctx.stroke();
ctx.restore();
}
}
export class LinkRenderController extends Pausable {
constructor() {
super('LinkRenderController')
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() {
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 = shared.graphAnalyser.analyse_graph(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( ) {
this.widgets_disabled = []
app.canvas.graph.extra['ue_links']?.forEach((uel) => {
const node = app.canvas.graph.getNodeById(uel.downstream)
if (node) {
const name = node.inputs[uel.downstream_slot]?.name;
if (name) {
const widget = node.widgets?.find((w)=>(w.name==name))
if (widget && !widget.disabled) {
try {
widget._disabled = widget.disabled;
widget.disabled = true;
this.widgets_disabled.push(widget)
} catch (e) { Logger.log_error(e, `Error disabling widget ${name} on node ${node.id}`) }
}
}
}
})
}
enable_all_disabled_widgets() {
this.widgets_disabled.forEach((w)=>{
try { w.disabled=w._disabled } catch (e) { Logger.log_error(e, `Error enabling widget ${w}`) }
})
}
highlight_subgraph_node_connections(subgraph, ctx) {
if (!settingsCache.getSettingValue('Use Everywhere.Graphics.highlight')) return;
const node = subgraph?.outputNode
const ue_connections = this.ue_list.all_connected_inputs(subgraph.outputNode)
ue_connections.forEach((ue_connection)=>{
const pos2 = node.slots[ue_connection.input_index]?.pos;
if (pos2) {
drawcircle(ctx, pos2, CONNECTED_1, 5, "first", {strokeStyle:LGraphCanvas.link_type_colors[ue_connection.type]})
drawcircle(ctx, pos2, CONNECTED_2, 4, "last")
}
})
shared.graphAnalyser.ambiguities.filter((ambiguity)=>(ambiguity.id==-20)).forEach((ambiguity)=>{
const index = ambiguity.matches[0]?.node_index
const pos2 = node.slots[index]?.pos
if (pos2) {
drawcross(ctx, pos2, AMBIGUITY, 7)
}
})
}
highlight_ue_connections(node, ctx) {
if (!settingsCache.getSettingValue('Use Everywhere.Graphics.highlight')) return;
if (!node.inputs) return;
try {
this.pause('highlight_ue_connections')
if (!this._list_ready()) return;
// get all the inputs that can be connected and aren't
// connected with a 'real' link
const unconnected_connectable_names = new Set(node.inputs
.filter((input)=>is_connectable(node,input.name))
.filter((input)=>(!input.link))
.map((input)=>input.name) || [])
if (this.ue_list.all_connected_inputs) {
this.ue_list.all_connected_inputs(node).forEach((ue_connection) => {
if (!ue_connection.control_node) return;
if (!node.inputs[ue_connection.input_index]) return
const name_sent_to = node.inputs[ue_connection.input_index].name;
unconnected_connectable_names.delete(name_sent_to); // remove the name from the list of connectables
const pos2 = this._relative_connection_pos(node, true, ue_connection.input_index);
drawcircle(ctx, pos2, CONNECTED_1, 6, "first", {strokeStyle:LGraphCanvas.link_type_colors[ue_connection.type]} )
drawcircle(ctx, pos2, CONNECTED_2, 5, "last" )
});
}
unconnected_connectable_names.forEach((name) => {
const index = node.inputs.findIndex((i) => i.name == name);
const pos2 = this._relative_connection_pos(node, true, index);
drawcircle(ctx, pos2, {...UNCONNECTED_CONNECTABLE}, 3)
})
if (node.properties.ue_convert) {
const sending_slots = this.ue_list.all_sending_slots(node)
node.outputs.forEach((output,i) => {
if (is_able_to_broadcast(node, output.name)) {
const pos2 = this._relative_connection_pos(node, false, i);
if (sending_slots.has(i)) {
drawcircle(ctx, pos2, CONNECTED_1, 6, "first", {strokeStyle:LGraphCanvas.link_type_colors[node.outputs[i].type]})
drawcircle(ctx, pos2, CONNECTED_2, 5, "last")
} else {
drawcircle(ctx, pos2, UNCONNECTED_CONNECTABLE, 3)
}
}
})
}
shared.graphAnalyser.ambiguities.filter((ambiguity)=>(ambiguity.id==node.id)).forEach((ambiguity)=>{
const index = node.inputs.findIndex((input)=>(input.name==ambiguity.input))
if (index>=0) {
const pos2 = this._relative_connection_pos(node, true, index);
drawcross(ctx, pos2, AMBIGUITY, 7)
}
})
} catch (e) {
Logger.log_error(e);
} finally {
this.unpause()
}
}
_relative_connection_pos(node, input, slot) {
var pos2;
if (node.getSlotPosition) {
pos2 = node.getSlotPosition(slot, input)
} else {
pos2 = node.getConnectionPos?.(input, slot, this.slot_pos1) || node.slots[slot].pos;
}
pos2[0] -= node.pos[0];
pos2[1] -= node.pos[1];
return pos2
}
_list_ready() {
if (!shared.graphAnalyser) 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_sending_anywhere(node) {
if (!this._list_ready()) return false;
return this.ue_list.node_sending_anywhere(node);
}
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()
}
}
mouse_over_connected_node(ue_connection) {
if (app.canvas.node_over && this.node_in_ueconnection(ue_connection, app.canvas.node_over.id)) return true;
if (ue_connection.control_node?.mouseOver) return true;
if (ue_connection.sending_to?.mouseOver) return true;
}
_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;
shared.graphAnalyser.ambiguities.forEach((ambiguity)=>{
ambiguity.matches.forEach((m)=>{
if (
m.node.selected
|| app.canvas.graph.getNodeById(m.id)?.selected
|| app.canvas.node_over == m.node
|| m.node.mouseOver
|| ambiguity.id==-20 && ambiguity.graph.outputNode?.isPointerOver
) {
this._render_ue_link(
{
graph : ambiguity.graph,
sending_to : m.node,
input_index : m.node_index,
control_node : ambiguity.graph.getNodeById(m.id),
control_node_input_index : m.index,
type : m.type,
}, ctx, 0, "#F00")
}
})
})
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) && this.mouse_over_connected_node(ue_connection) ) 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, color) {
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;
if (node.getSlotPosition) {
pos2 = node.getSlotPosition(ue_connection.input_index, true)
} else {
pos2 = node.getConnectionPos(true, ue_connection.input_index, this.slot_pos1);
}
const control_node = graph.getNodeById(ue_connection.control_node.id)
if (!control_node) {
Logger.problem(`Couldn't find position for UE link ${ue_connection}.`,null,true)
return;
}
/*
get the position of the *input* that is being echoed except for:
- the Seed Anywhere node: the class records control_node_input_index as -ve (-1 => 0, -2 => 1...)
- subgraph converts
those link to outputs
*/
const input_source = (ue_connection.control_node_input_index >= 0 && !control_node.properties.ue_convert);
const source_index = (ue_connection.control_node_input_index >= 0 ) ? ue_connection.control_node_input_index : -1-ue_connection.control_node_input_index;
var pos1;
if (control_node.getSlotPosition) {
pos1 = control_node.getSlotPosition(source_index, input_source)
} else {
pos1 = control_node.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)) && input_source) ?
((delta_y>0) ? LiteGraph.DOWN : LiteGraph.UP) :
((input_source && delta_x<0) ? LiteGraph.LEFT : LiteGraph.RIGHT)
color = color || LGraphCanvas.link_type_colors[ue_connection.type] || 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;
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_error(e);
} finally {
ctx.restore()
app.canvas.render_connections_border = rcb;
}
} catch (e) {
Logger.log_error(e, `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;
}
}
const UNCONNECTED_CONNECTABLE = {
lineWidth : 1,
strokeStyle : "black",
shadowColor : "green",
shadowBlur : 4,
}
const CONNECTED_1 = {
lineWidth : 1,
shadowColor : "white",
strokeStyle : "white",
shadowBlur : 4,
}
const CONNECTED_2 = {
lineWidth : 1,
shadowBlur : 0,
strokeStyle : "black",
}
const AMBIGUITY = {
lineWidth : 1,
strokeStyle : "red",
}
const DEFAULTS = {
shadowOffsetX : 0,
shadowOffsetY : 0,
}
const PROPS = ["lineWidth", "strokeStyle", "shadowColor", "shadowBlur", "shadowOffsetX", "shadowOffsetY"]
function drawcircle(ctx, position, properties, radius, first_last, additional) {
if (!first_last || first_last=='first') ctx.save()
PROPS.forEach((k)=>{
if (DEFAULTS[k]!==undefined) ctx[k] = DEFAULTS[k]
if (properties[k]!==undefined) ctx[k] = properties[k]
if (additional && additional[k]!==undefined) ctx[k] = additional[k]
})
ctx.beginPath();
ctx.roundRect(position[0]-radius,position[1]-radius,2*radius,2*radius,radius);
ctx.stroke();
if (!first_last || first_last=='last') ctx.restore()
}
function drawcross(ctx, position, properties, radius) {
ctx.save()
PROPS.forEach((k)=>{
if (DEFAULTS[k]) ctx[k] = DEFAULTS[k]
})
ctx.lineWidth = properties.lineWidth
ctx.strokeStyle = properties.strokeStyle
ctx.beginPath();
ctx.moveTo(position[0]-radius,position[1]-radius)
ctx.lineTo(position[0]+radius,position[1]+radius)
ctx.moveTo(position[0]-radius,position[1]+radius)
ctx.lineTo(position[0]+radius,position[1]-radius)
ctx.stroke();
ctx.restore()
}
function modify(c) {
if (c.length==4) return c + "6"
if (c.length==7) return c + "66"
return c
}