diff --git a/build/litegraph.core.js b/build/litegraph.core.js index d8bbe6b84..7c57179b4 100644 --- a/build/litegraph.core.js +++ b/build/litegraph.core.js @@ -1,5 +1,3 @@ -//packer version - (function(global) { // ************************************************************* @@ -2435,7 +2433,9 @@ + unsafe_execution: not allowed for safe execution + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally + + horizontal: slots are distributed horizontally (applies to both inputs and outputs) + + inputs_horizontal: input slots are distributed horizontally (overrides horizontal for inputs) + + outputs_horizontal: output slots are distributed horizontally (overrides horizontal for outputs) + widgets_start_y: widgets start at y distance from the top of the node flags object: @@ -4769,9 +4769,17 @@ var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; + // Check for separate input/output orientation, fallback to general horizontal property + var is_horizontal = false; + if (is_input) { + is_horizontal = this.inputs_horizontal !== undefined && this.inputs_horizontal !== null ? this.inputs_horizontal : this.horizontal; + } else { + is_horizontal = this.outputs_horizontal !== undefined && this.outputs_horizontal !== null ? this.outputs_horizontal : this.horizontal; + } + if (this.flags.collapsed) { var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { + if (is_horizontal) { out[0] = this.pos[0] + w * 0.5; if (is_input) { out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; @@ -4816,7 +4824,7 @@ } //horizontal distributed slots - if (this.horizontal) { + if (is_horizontal) { out[0] = this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); if (is_input) { @@ -7059,7 +7067,8 @@ LGraphNode.prototype.executeAction = function(action) var input = node.inputs[i]; var link_pos = node.getConnectionPos(true, i); var is_inside = false; - if (node.horizontal) { + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : node.horizontal; + if (inputs_horizontal) { is_inside = isInsideRectangle( canvasx, canvasy, @@ -7105,7 +7114,8 @@ LGraphNode.prototype.executeAction = function(action) var output = node.outputs[i]; var link_pos = node.getConnectionPos(false, i); var is_inside = false; - if (node.horizontal) { + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : node.horizontal; + if (outputs_horizontal) { is_inside = isInsideRectangle( canvasx, canvasy, @@ -7954,10 +7964,15 @@ LGraphNode.prototype.executeAction = function(action) var connDir = connInOrOut.dir; if(connDir == null) { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; + if (this.connecting_output) { + var outputs_horizontal = this.connecting_node.outputs_horizontal !== undefined && this.connecting_node.outputs_horizontal !== null ? + this.connecting_node.outputs_horizontal : this.connecting_node.horizontal; + connDir = outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; + } else { + var inputs_horizontal = this.connecting_node.inputs_horizontal !== undefined && this.connecting_node.inputs_horizontal !== null ? + this.connecting_node.inputs_horizontal : this.connecting_node.horizontal; + connDir = inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT; + } } var connShape = connInOrOut.shape; @@ -8589,6 +8604,8 @@ LGraphNode.prototype.executeAction = function(action) var size = temp_vec2; temp_vec2.set(node.size); var horizontal = node.horizontal; // || node.flags.horizontal; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; if (node.flags.collapsed) { ctx.font = this.inner_text_font; @@ -8645,7 +8662,7 @@ LGraphNode.prototype.executeAction = function(action) } //connection slots - ctx.textAlign = horizontal ? "center" : "left"; + ctx.textAlign = inputs_horizontal ? "center" : "left"; ctx.font = this.inner_text_font; var render_text = !low_quality; @@ -8702,7 +8719,7 @@ LGraphNode.prototype.executeAction = function(action) slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE ) { - if (horizontal) { + if (inputs_horizontal) { ctx.rect( pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, @@ -8746,7 +8763,7 @@ LGraphNode.prototype.executeAction = function(action) var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { + if (inputs_horizontal || slot.dir == LiteGraph.UP) { ctx.fillText(text, pos[0], pos[1] - 10); } else { ctx.fillText(text, pos[0] + 10, pos[1] + 5); @@ -8758,7 +8775,7 @@ LGraphNode.prototype.executeAction = function(action) //output connection slots - ctx.textAlign = horizontal ? "center" : "right"; + ctx.textAlign = outputs_horizontal ? "center" : "right"; ctx.strokeStyle = "black"; if (node.outputs) { for (var i = 0; i < node.outputs.length; i++) { @@ -8801,7 +8818,7 @@ LGraphNode.prototype.executeAction = function(action) slot_type === LiteGraph.EVENT || slot_shape === LiteGraph.BOX_SHAPE ) { - if (horizontal) { + if (outputs_horizontal) { ctx.rect( pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, @@ -8853,7 +8870,7 @@ LGraphNode.prototype.executeAction = function(action) var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { + if (outputs_horizontal || slot.dir == LiteGraph.DOWN) { ctx.fillText(text, pos[0], pos[1] - 8); } else { ctx.fillText(text, pos[0] - 10, pos[1] + 5); @@ -8868,7 +8885,7 @@ LGraphNode.prototype.executeAction = function(action) if (node.widgets) { var widgets_y = max_y; - if (horizontal || node.widgets_up) { + if (inputs_horizontal || outputs_horizontal || node.widgets_up) { widgets_y = 2; } if( node.widgets_start_y != null ) @@ -8911,7 +8928,9 @@ LGraphNode.prototype.executeAction = function(action) if (input_slot) { var x = 0; var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { + // For collapsed nodes, use inputs_horizontal if available, otherwise fall back to general horizontal + var input_is_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + if (input_is_horizontal) { x = node._collapsed_width * 0.5; y = -LiteGraph.NODE_TITLE_HEIGHT; } @@ -8936,7 +8955,9 @@ LGraphNode.prototype.executeAction = function(action) if (output_slot) { var x = node._collapsed_width; var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { + // For collapsed nodes, use outputs_horizontal if available, otherwise fall back to general horizontal + var output_is_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + if (output_is_horizontal) { x = node._collapsed_width * 0.5; y = 0; } @@ -9440,10 +9461,14 @@ LGraphNode.prototype.executeAction = function(action) } var start_dir = start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); + (start_node.outputs_horizontal !== undefined && start_node.outputs_horizontal !== null ? + (start_node.outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT) : + (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT)); var end_dir = end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); + (node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? + (node.inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT) : + (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT)); this.renderLink( ctx, @@ -9525,6 +9550,31 @@ LGraphNode.prototype.executeAction = function(action) var dist = distance(a, b); + // Auto-detect when to use straight lines for better visual appearance + var should_use_straight = false; + var dx = Math.abs(a[0] - b[0]); + var dy = Math.abs(a[1] - b[1]); + + // Use straight lines when connecting opposite directions and nodes are reasonably aligned + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + // Vertical to vertical connections (like mixed nodes with vertical outputs to vertical inputs) + if ((start_dir == LiteGraph.RIGHT && end_dir == LiteGraph.LEFT) || + (start_dir == LiteGraph.LEFT && end_dir == LiteGraph.RIGHT)) { + // If nodes are roughly vertically aligned (small horizontal distance relative to vertical) + if (dx < 50 && dy > 20) { + should_use_straight = true; + } + } + // Horizontal to horizontal connections + else if ((start_dir == LiteGraph.DOWN && end_dir == LiteGraph.UP) || + (start_dir == LiteGraph.UP && end_dir == LiteGraph.DOWN)) { + // If nodes are roughly horizontally aligned (small vertical distance relative to horizontal) + if (dy < 50 && dx > 20) { + should_use_straight = true; + } + } + } + if (this.render_connections_border && this.ds.scale > 0.6) { ctx.lineWidth = this.connections_width + 4; } @@ -9541,46 +9591,82 @@ LGraphNode.prototype.executeAction = function(action) if (this.links_render_mode == LiteGraph.SPLINE_LINK) { ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; + + if (should_use_straight) { + // Use straight line logic for better visual alignment + var start_x = a[0]; + var start_y = a[1] + offsety; + var end_x = b[0]; + var end_y = b[1] + offsety; + + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else if (start_dir == LiteGraph.LEFT) { + start_x -= 10; + } else if (start_dir == LiteGraph.DOWN) { + start_y += 10; + } else if (start_dir == LiteGraph.UP) { + start_y -= 10; + } + + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else if (end_dir == LiteGraph.RIGHT) { + end_x += 10; + } else if (end_dir == LiteGraph.UP) { + end_y -= 10; + } else if (end_dir == LiteGraph.DOWN) { + end_y += 10; + } + + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1] + offsety); + } else { + // Original spline logic + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + start_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + start_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + start_offset_y = dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + end_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + end_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + end_offset_y = dist * 0.25; + break; + } + ctx.bezierCurveTo( + a[0] + start_offset_x, + a[1] + start_offset_y + offsety, + b[0] + end_offset_x, + b[1] + end_offset_y + offsety, + b[0], + b[1] + offsety + ); } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { ctx.moveTo(a[0], a[1] + offsety); var start_offset_x = 0; @@ -14424,4 +14510,3 @@ if (typeof exports != "undefined") { exports.ContextMenu = this.ContextMenu; } - diff --git a/build/litegraph.core.min.js b/build/litegraph.core.min.js index 2c3db303f..b53fe446e 100644 --- a/build/litegraph.core.min.js +++ b/build/litegraph.core.min.js @@ -1,342 +1 @@ -(function(X){function w(a){h.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear();a&&this.configure(a)}function Y(a,b,c,d,e,f){this.id=a;this.type=b;this.origin_id=c;this.origin_slot=d;this.target_id=e;this.target_slot=f;this._data=null;this._pos=new Float32Array(2)}function v(a){this._ctor(a)}function P(a){this._ctor(a)}function L(a,b){this.offset=new Float32Array([0,0]);this.scale=1;this.max_scale=10;this.min_scale=.1;this.onredraw=null;this.enabled=!0;this.last_mouse= -[0,0];this.element=null;this.visible_area=new Float32Array(4);a&&(this.element=a,b||this.bindEvents(a))}function q(a,b,c){this.options=c=c||{};this.background_image=q.DEFAULT_BACKGROUND_IMAGE;a&&a.constructor===String&&(a=document.querySelector(a));this.ds=new L;this.zoom_modify_alpha=!0;this.title_text_font=""+h.NODE_TEXT_SIZE+"px Arial";this.inner_text_font="normal "+h.NODE_SUBTEXT_SIZE+"px Arial";this.node_title_color=h.NODE_TITLE_COLOR;this.default_link_color=h.LINK_COLOR;this.default_connection_color= -{input_off:"#778",input_on:"#7F7",output_off:"#778",output_on:"#7F7"};this.default_connection_color_byType={};this.default_connection_color_byTypeOff={};this.highquality_render=!0;this.use_gradients=!1;this.editor_alpha=1;this.pause_rendering=!1;this.clear_background=!0;this.clear_background_color="#222";this.read_only=!1;this.render_only_selected=!0;this.live_mode=!1;this.allow_interaction=this.allow_dragnodes=this.allow_dragcanvas=this.show_info=!0;this.multi_select=!1;this.allow_reconnect_links= -this.allow_searchbox=!0;this.drag_mode=this.align_to_grid=!1;this.filter=this.dragging_rectangle=null;this.set_canvas_dirty_on_mouse_event=!0;this.always_render_background=!1;this.render_canvas_border=this.render_shadows=!0;this.render_connections_shadows=!1;this.render_connections_border=!0;this.render_connection_arrows=this.render_curved_connections=!1;this.render_collapsed_slots=!0;this.render_execution_order=!1;this.render_link_tooltip=this.render_title_colored=!0;this.links_render_mode=h.SPLINE_LINK; -this.mouse=[0,0];this.canvas_mouse=this.graph_mouse=[0,0];this.onAfterChange=this.onBeforeChange=this.onConnectingChange=this.onSelectionChange=this.onNodeMoved=this.onDrawLinkTooltip=this.onDrawOverlay=this.onDrawForeground=this.onDrawBackground=this.onMouse=this.onSearchBoxSelection=this.onSearchBox=null;this.connections_width=3;this.round_radius=8;this.over_link_center=this.node_widget=this.current_node=null;this.last_mouse_position=[0,0];this.visible_area=this.ds.visible_area;this.visible_links= -[];this.viewport=c.viewport||null;b&&b.attachCanvas(this);this.setCanvas(a,c.skip_events);this.clear();c.skip_render||this.startRendering();this.autoresize=c.autoresize}function ba(a,b){return Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1]))}function K(a,b,c,d,e,f){return ca&&db?!0:!1}function Z(a,b){var c=a[0]+a[2],d=a[1]+a[3],e=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>e||cg.width-k.width-10&&(e=g.width-k.width-10),g.height&&a>g.height-k.height-10&&(a=g.height-k.height-10));f.style.left=e+"px";f.style.top=a+"px";b.scale&&(f.style.transform="scale("+b.scale+")")}function T(a){this.points=a;this.nearest=this.selected=-1;this.size=null;this.must_update=!0;this.margin=5}function ca(a,b,c){return b>a?b:c(a^16*Math.random()>>a/4).toString(16))},isValidConnection:function(a,b){if(""==a||"*"===a)a=0;if(""==b||"*"===b)b=0;if(!a||!b||a==b||a==h.EVENT&& -b==h.ACTION)return!0;a=String(a);b=String(b);a=a.toLowerCase();b=b.toLowerCase();if(-1==a.indexOf(",")&&-1==b.indexOf(","))return a==b;a=a.split(",");b=b.split(",");for(var c=0;cg&&(g=l.size[n]);k+=l.size[b==h.VERTICAL_LAYOUT?0:1]+a+h.NODE_TITLE_HEIGHT}c+=g+a}this.setDirtyCanvas(!0,!0)};w.prototype.getTime=function(){return this.globaltime};w.prototype.getFixedTime=function(){return this.fixedtime};w.prototype.getElapsedTime=function(){return this.elapsed_time};w.prototype.sendEventToAllNodes= -function(a,b,c){c=c||h.ALWAYS;var d=this._nodes_in_order?this._nodes_in_order:this._nodes;if(d)for(var e=0,f=d.length;e=h.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(h.use_uuids){if(null==a.id||-1==a.id)a.id=h.uuidv4()}else null==a.id||-1== -a.id?a.id=++this.last_node_id:this.last_node_idb.length||(this._pos[0]=b[0],this._pos[1]=b[1])},get:function(){return this._pos},enumerable:!0});this.id=h.use_uuids?h.uuidv4():-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.flags={}};v.prototype.configure=function(a){this.graph&& -this.graph._version++;for(var b in a)if("properties"==b)for(var c in a.properties){if(this.properties[c]=a.properties[c],this.onPropertyChanged)this.onPropertyChanged(c,a.properties[c])}else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=h.cloneObject(a[b],this[b]):this[b]=a[b]);a.title||(this.title=this.constructor.title);if(this.inputs)for(c=0;c=this.outputs.length)){var c=this.outputs[a];if(c&&(c._data=b,this.outputs[a].links))for(c=0;c=this.outputs.length)){var c=this.outputs[a];if(c&&(c.type=b,this.outputs[a].links))for(c=0;c=this.inputs.length||null==this.inputs[a].link)){a=this.graph.links[this.inputs[a].link];if(!a)return null;if(!b)return a.data;b=this.graph.getNodeById(a.origin_id);if(!b)return a.data;if(b.updateOutputData)b.updateOutputData(a.origin_slot);else if(b.onExecute)b.onExecute();return a.data}};v.prototype.getInputDataType=function(a){if(!this.inputs||a>=this.inputs.length||null==this.inputs[a].link)return null;a=this.graph.links[this.inputs[a].link];if(!a)return null; -var b=this.graph.getNodeById(a.origin_id);return b?(a=b.outputs[a.origin_slot])?a.type:null:a.type};v.prototype.getInputDataByName=function(a,b){a=this.findInputSlot(a);return-1==a?null:this.getInputData(a,b)};v.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};v.prototype.getInputOrProperty=function(a){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[a]:null;for(var b=0,c=this.inputs.length;b=this.outputs.length?null:this.outputs[a]._data};v.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null;for(var b=[],c=0;ca&&this.pos[1]-e-cb)return!0;return!1};v.prototype.getSlotInPosition=function(a,b){var c=new Float32Array(2);if(this.inputs)for(var d=0,e=this.inputs.length;d=this.outputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),null;b&&b.constructor===Number&&(b=this.graph.getNodeById(b)); -if(!b)throw"target node is null";if(b==this)return null;if(c.constructor===String){if(c=b.findInputSlot(c),-1==c)return h.debug&&console.log("Connect: Error, no slot of name "+c),null}else if(c===h.EVENT)if(h.do_add_triggers_slots)b.changeMode(h.ON_TRIGGER),c=b.findInputSlot("onTrigger");else return null;else if(!b.inputs||c>=b.inputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),null;var d=b.inputs[c],e=this.outputs[a];if(!this.outputs[a])return null;b.onBeforeConnectInput&& -(c=b.onBeforeConnectInput(c));if(!1===c||null===c||!h.isValidConnection(e.type,d.type))return this.setDirtyCanvas(!1,!0),null;if(b.onConnectInput&&!1===b.onConnectInput(c,e.type,e,this,a)||this.onConnectOutput&&!1===this.onConnectOutput(a,d.type,d,b,c))return null;b.inputs[c]&&null!=b.inputs[c].link&&(this.graph.beforeChange(),b.disconnectInput(c,{doProcessChange:!1}));if(null!==e.links&&e.links.length)switch(e.type){case h.EVENT:h.allow_multi_output_for_events||(this.graph.beforeChange(),this.disconnectOutput(a, -!1,{doProcessChange:!1}))}var f=h.use_uuids?h.uuidv4():++this.graph.last_link_id;f=new Y(f,d.type||e.type,this.id,a,b.id,c);this.graph.links[f.id]=f;null==e.links&&(e.links=[]);e.links.push(f.id);b.inputs[c].link=f.id;this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(h.OUTPUT,a,!0,f,e);if(b.onConnectionsChange)b.onConnectionsChange(h.INPUT,c,!0,f,d);this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(h.INPUT,b,c,this,a),this.graph.onNodeConnectionChange(h.OUTPUT, -this,a,b,c));this.setDirtyCanvas(!1,!0);this.graph.afterChange();this.graph.connectionChange(this,f);return f};v.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return h.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),!1;var c=this.outputs[a];if(!c||!c.links||0==c.links.length)return!1;if(b){b.constructor===Number&&(b= -this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var d=0,e=c.links.length;d=this.inputs.length)return h.debug&&console.log("Connect: Error, slot number not found"),!1;var b=this.inputs[a];if(!b)return!1;var c=this.inputs[a].link;if(null!=c){this.inputs[a].link=null;var d=this.graph.links[c];if(d){var e=this.graph.getNodeById(d.origin_id);if(!e)return!1;var f=e.outputs[d.origin_slot];if(!f||!f.links||0==f.links.length)return!1;for(var g=0,k=f.links.length;gb&&this.inputs[b].pos)return c[0]=this.pos[0]+this.inputs[b].pos[0],c[1]=this.pos[1]+ -this.inputs[b].pos[1],c;if(!a&&d>b&&this.outputs[b].pos)return c[0]=this.pos[0]+this.outputs[b].pos[0],c[1]=this.pos[1]+this.outputs[b].pos[1],c;if(this.horizontal)return c[0]=this.pos[0]+this.size[0]/d*(b+.5),c[1]=a?this.pos[1]-h.NODE_TITLE_HEIGHT:this.pos[1]+this.size[1],c;c[0]=a?this.pos[0]+e:this.pos[0]+this.size[0]+1-e;c[1]=this.pos[1]+(b+.7)*h.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0);return c};v.prototype.alignToGrid=function(){this.pos[0]=h.CANVAS_GRID_SIZE*Math.round(this.pos[0]/ -h.CANVAS_GRID_SIZE);this.pos[1]=h.CANVAS_GRID_SIZE*Math.round(this.pos[1]/h.CANVAS_GRID_SIZE)};v.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>v.MAX_CONSOLE&&this.console.shift();if(this.graph.onNodeTrace)this.graph.onNodeTrace(this,a)};v.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};v.prototype.loadImage=function(a){var b=new Image;b.src=h.node_images_path+a;b.ready=!1;var c=this;b.onload= -function(){this.ready=!0;c.setDirtyCanvas(!0)};return b};v.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,c=0;cb.length||(this._pos[0]=b[0],this._pos[1]=b[1])},get:function(){return this._pos},enumerable:!0});Object.defineProperty(this,"size",{set:function(b){!b||2>b.length||(this._size[0]=Math.max(140,b[0]),this._size[1]=Math.max(80,b[1]))},get:function(){return this._size},enumerable:!0})};P.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding);this.color=a.color;this.font_size=a.font_size};P.prototype.serialize=function(){var a= -this._bounding;return{title:this.title,bounding:[Math.round(a[0]),Math.round(a[1]),Math.round(a[2]),Math.round(a[3])],color:this.color,font_size:this.font_size}};P.prototype.move=function(a,b,c){this._pos[0]+=a;this._pos[1]+=b;if(!c)for(c=0;c=this.viewport[0]&&d=this.viewport[1]&&cthis.max_scale&&(a=this.max_scale);if(a!=this.scale&&this.element){var c=this.element.getBoundingClientRect();if(c&&(b= -b||[.5*c.width,.5*c.height],c=this.convertCanvasToOffset(b),this.scale=a,.01>Math.abs(this.scale-1)&&(this.scale=1),a=this.convertCanvasToOffset(b),a=[a[0]-c[0],a[1]-c[1]],this.offset[0]+=a[0],this.offset[1]+=a[1],this.onredraw))this.onredraw(this)}};L.prototype.changeDeltaScale=function(a,b){this.changeScale(this.scale*a,b)};L.prototype.reset=function(){this.scale=1;this.offset[0]=0;this.offset[1]=0};X.LGraphCanvas=h.LGraphCanvas=q;q.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; -q.link_type_colors={"-1":h.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"};q.gradients={};q.prototype.clear=function(){this.fps=this.render_time=this.last_draw_time=this.frame=0;this.dragging_rectangle=null;this.selected_nodes={};this.selected_group=null;this.visible_nodes=[];this.connecting_node=this.node_capturing_input=this.node_over=this.node_dragged=null;this.highlighted_links={};this.dragging_canvas=!1;this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_widget=this.node_in_panel=this.dirty_area= -null;this.last_mouse=[0,0];this.last_mouseclick=0;this.pointer_is_double=this.pointer_is_down=!1;this.visible_area.set([0,0,0,0]);if(this.onClear)this.onClear()};q.prototype.setGraph=function(a,b){this.graph!=a&&(b||this.clear(),!a&&this.graph?this.graph.detachCanvas(this):(a.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))};q.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph};q.prototype.openSubgraph=function(a){if(!a)throw"graph cannot be null"; -if(this.graph==a)throw"graph cannot be the same";this.clear();this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph));a.attachCanvas(this);this.checkPanels();this.setDirty(!0,!0)};q.prototype.closeSubgraph=function(){if(this._graph_stack&&0!=this._graph_stack.length){var a=this.graph._subgraph_node,b=this._graph_stack.pop();this.selected_nodes={};this.highlighted_links={};b.attachCanvas(this);this.setDirty(!0,!0);a&&(this.centerOnNode(a),this.selectNodes([a]));this.ds.offset= -[0,0];this.ds.scale=1}};q.prototype.getCurrentGraph=function(){return this.graph};q.prototype.setCanvas=function(a,b){if(a&&a.constructor===String&&(a=document.getElementById(a),!a))throw"Error creating LiteGraph canvas: Canvas not found";if(a!==this.canvas&&(!a&&this.canvas&&(b||this.unbindEvents()),this.canvas=a,this.ds.element=a)){a.className+=" lgraphcanvas";a.data=this;a.tabindex="1";this.bgcanvas=null;this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width, -this.bgcanvas.height=this.canvas.height);if(null==a.getContext){if("canvas"!=a.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+a.localName;throw"This browser doesn't support Canvas";}null==(this.ctx=a.getContext("2d"))&&(a.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL());b||this.bindEvents()}};q.prototype._doNothing=function(a){a.preventDefault();return!1};q.prototype._doReturnTrue=function(a){a.preventDefault(); -return!0};q.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas,b=this.getCanvasWindow().document;this._mousedown_callback=this.processMouseDown.bind(this);this._mousewheel_callback=this.processMouseWheel.bind(this);this._mousemove_callback=this.processMouseMove.bind(this);this._mouseup_callback=this.processMouseUp.bind(this);h.pointerListenerAdd(a,"down",this._mousedown_callback,!0);a.addEventListener("mousewheel",this._mousewheel_callback, -!1);h.pointerListenerAdd(a,"up",this._mouseup_callback,!0);h.pointerListenerAdd(a,"move",this._mousemove_callback);a.addEventListener("contextmenu",this._doNothing);a.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1);this._key_callback=this.processKey.bind(this);a.setAttribute("tabindex",1);a.addEventListener("keydown",this._key_callback,!0);b.addEventListener("keyup",this._key_callback,!0);this._ondrop_callback=this.processDrop.bind(this);a.addEventListener("dragover",this._doNothing, -!1);a.addEventListener("dragend",this._doNothing,!1);a.addEventListener("drop",this._ondrop_callback,!1);a.addEventListener("dragenter",this._doReturnTrue,!1);this._events_binded=!0}};q.prototype.unbindEvents=function(){if(this._events_binded){var a=this.getCanvasWindow().document;h.pointerListenerRemove(this.canvas,"move",this._mousedown_callback);h.pointerListenerRemove(this.canvas,"up",this._mousedown_callback);h.pointerListenerRemove(this.canvas,"down",this._mousedown_callback);this.canvas.removeEventListener("mousewheel", -this._mousewheel_callback);this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback);this.canvas.removeEventListener("keydown",this._key_callback);a.removeEventListener("keyup",this._key_callback);this.canvas.removeEventListener("contextmenu",this._doNothing);this.canvas.removeEventListener("drop",this._ondrop_callback);this.canvas.removeEventListener("dragenter",this._doReturnTrue);this._ondrop_callback=this._key_callback=this._mousewheel_callback=this._mousedown_callback=null; -this._events_binded=!1}else console.warn("LGraphCanvas: no events binded")};q.getFileExtension=function(a){var b=a.indexOf("?");-1!=b&&(a=a.substr(0,b));b=a.lastIndexOf(".");return-1==b?"":a.substr(b+1).toLowerCase()};q.prototype.enableWebGL=function(){if("undefined"===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0; -this.bgcanvas=this.canvas;this.bgctx=this.gl;this.canvas.webgl_enabled=!0};q.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};q.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};q.prototype.startRendering=function(){function a(){this.pause_rendering||this.draw();var b=this.getCanvasWindow();this.is_rendering&&b.requestAnimationFrame(a.bind(this))}this.is_rendering||(this.is_rendering= -!0,a.call(this))};q.prototype.stopRendering=function(){this.is_rendering=!1};q.prototype.blockClick=function(){this.block_click=!0;this.last_mouseclick=0};q.prototype.processMouseDown=function(a){this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();q.active_canvas=this;var c=this,d=a.clientX,e=a.clientY;this.ds.viewport=this.viewport;d=!this.viewport||this.viewport&&d>=this.viewport[0]&&d=this.viewport[1]&&ee-this.last_mouseclick&&g;this.mouse[0]=a.clientX;this.mouse[1]=a.clientY; -this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;this.last_click_position=[this.mouse[0],this.mouse[1]];this.pointer_is_double=this.pointer_is_down&&g?!0:!1;this.pointer_is_down=!0;this.canvas.focus();h.closeAllContextMenus(b);if(!this.onMouse||1!=this.onMouse(a)){if(1!=a.which||this.pointer_is_double)if(2==a.which)if(h.middle_click_slot_add_default_node){if(f&&this.allow_interaction&&!d&&!this.read_only&&!this.connecting_node&&!f.flags.collapsed&&!this.live_mode){e=d=g=!1;if(f.outputs)for(n= -0,k=f.outputs.length;nf.size[0]-h.NODE_TITLE_HEIGHT&&0>k[1]&&(c=this,setTimeout(function(){c.openSubgraph(f.subgraph)},10)),this.live_mode&&(n=g=!0));n?f.is_selected||this.processNodeSelected(f,a):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=f),this.processNodeSelected(f,a));this.dirty_canvas=!0}}else if(!d){if(!this.read_only)for(n=0;nk[0]+4||a.canvasY -k[1]+4)){this.showLinkMenu(g,a);this.over_link_center=null;break}this.selected_group=this.graph.getGroupOnPos(a.canvasX,a.canvasY);this.selected_group_resizing=!1;this.selected_group&&!this.read_only&&(a.ctrlKey&&(this.dragging_rectangle=null),10>ba([a.canvasX,a.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes());e&&!this.read_only&&this.allow_searchbox&& -(this.showSearchBox(a),a.preventDefault(),a.stopPropagation());g=!0}!d&&g&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}this.last_mouse[0]=a.clientX;this.last_mouse[1]=a.clientY;this.last_mouseclick=h.getTime();this.last_mouse_dragging=!0;this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&&a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}}}}; -q.prototype.processMouseMove=function(a){this.autoresize&&this.resize();this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){q.active_canvas=this;this.adjustMouseEvent(a);var b=[a.clientX,a.clientY];this.mouse[0]=b[0];this.mouse[1]=b[1];var c=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;if(this.block_click)return a.preventDefault(),!1;a.dragging=this.last_mouse_dragging;this.node_widget&&(this.processNodeWidgets(this.node_widget[0], -this.graph_mouse,a,this.node_widget[1]),this.dirty_canvas=!0);var d=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=a.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=a.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[a.canvasX-this.selected_group.pos[0],a.canvasY-this.selected_group.pos[1]]:(this.selected_group.move(c[0]/ -this.ds.scale,c[1]/this.ds.scale,a.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=c[0]/this.ds.scale,this.ds.offset[1]+=c[1]/this.ds.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else if((this.allow_interaction||d&&d.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);b=0;for(var e=this.graph._nodes.length;bg[0]+4||a.canvasYg[1]+4)){e=f;break}e!=this.over_link_center&&(this.over_link_center=e,this.dirty_canvas=!0);this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&& -this.node_capturing_input!=d&&this.node_capturing_input.onMouseMove)this.node_capturing_input.onMouseMove(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]],this);if(this.node_dragged&&!this.live_mode){for(b in this.selected_nodes)d=this.selected_nodes[b],d.pos[0]+=c[0]/this.ds.scale,d.pos[1]+=c[1]/this.ds.scale,d.is_selected||this.processNodeSelected(d,a);this.dirty_bgcanvas=this.dirty_canvas=!0}this.resizing_node&&!this.live_mode&&(c=[a.canvasX-this.resizing_node.pos[0], -a.canvasY-this.resizing_node.pos[1]],b=this.resizing_node.computeSize(),c[0]=Math.max(b[0],c[0]),c[1]=Math.max(b[1],c[1]),this.resizing_node.setSize(c),this.canvas.style.cursor="se-resize",this.dirty_bgcanvas=this.dirty_canvas=!0)}a.preventDefault();return!1}};q.prototype.processMouseUp=function(a){var b=void 0===a.isPrimary||a.isPrimary;if(!b)return!1;this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){var c=this.getCanvasWindow().document;q.active_canvas=this;this.options.skip_events|| -(h.pointerListenerRemove(c,"move",this._mousemove_callback,!0),h.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),h.pointerListenerRemove(c,"up",this._mouseup_callback,!0));this.adjustMouseEvent(a);c=h.getTime();a.click_time=c-this.last_mouseclick;this.last_mouse_dragging=!1;this.last_click_position=null;this.block_click&&(this.block_click=!1);if(1==a.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,a);this.node_widget=null;this.selected_group&& -(this.selected_group.move(this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),a.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null);this.selected_group_resizing=!1;var d=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){c= -this.graph._nodes;var e=new Float32Array(4),f=Math.abs(this.dragging_rectangle[2]),g=Math.abs(this.dragging_rectangle[3]),k=0>this.dragging_rectangle[3]?this.dragging_rectangle[1]-g:this.dragging_rectangle[1];this.dragging_rectangle[0]=0>this.dragging_rectangle[2]?this.dragging_rectangle[0]-f:this.dragging_rectangle[0];this.dragging_rectangle[1]=k;this.dragging_rectangle[2]=f;this.dragging_rectangle[3]=g;if(!d||10a.click_time&&K(a.canvasX,a.canvasY,d.pos[0],d.pos[1]-h.NODE_TITLE_HEIGHT,h.NODE_TITLE_HEIGHT,h.NODE_TITLE_HEIGHT)&&d.collapse();this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_dragged.pos[0]=Math.round(this.node_dragged.pos[0]);this.node_dragged.pos[1]=Math.round(this.node_dragged.pos[1]);(this.graph.config.align_to_grid||this.align_to_grid)&&this.node_dragged.alignToGrid();if(this.onNodeMoved)this.onNodeMoved(this.node_dragged);this.graph.afterChange(this.node_dragged); -this.node_dragged=null}else{d=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);!d&&300>a.click_time&&this.deselectAllNodes();this.dirty_canvas=!0;this.dragging_canvas=!1;if(this.node_over&&this.node_over.onMouseUp)this.node_over.onMouseUp(a,[a.canvasX-this.node_over.pos[0],a.canvasY-this.node_over.pos[1]],this);if(this.node_capturing_input&&this.node_capturing_input.onMouseUp)this.node_capturing_input.onMouseUp(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]])}}else 2== -a.which?(this.dirty_canvas=!0,this.dragging_canvas=!1):3==a.which&&(this.dirty_canvas=!0,this.dragging_canvas=!1);b&&(this.pointer_is_double=this.pointer_is_down=!1);this.graph.change();a.stopPropagation();a.preventDefault();return!1}};q.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var c=a.clientX,d=a.clientY;if(!this.viewport||this.viewport&&c>=this.viewport[0]&&c=this.viewport[1]&&db&&(c*=1/1.1),this.ds.changeScale(c,[a.clientX,a.clientY]),this.graph.change(),a.preventDefault(),!1}};q.prototype.isOverNodeBox=function(a,b,c){var d=h.NODE_TITLE_HEIGHT;return K(b,c,a.pos[0]+2,a.pos[1]+2-d,d-4,d-4)?!0:!1};q.prototype.isOverNodeInput=function(a,b,c,d){if(a.inputs)for(var e=0,f=a.inputs.length;eb.nodes[d].pos[0]&&(c[0]=b.nodes[d].pos[0]),c[1]>b.nodes[d].pos[1]&&(c[1]=b.nodes[d].pos[1])):c=[b.nodes[d].pos[0],b.nodes[d].pos[1]];var e=[];for(d=0;d=this.viewport[0]&&b=this.viewport[1]&&cc-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};q.prototype.drawFrontCanvas=function(){this.dirty_canvas=!1;this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){var b=this.canvas;a.start2D&& -!this.viewport&&(a.start2D(),a.restore(),a.setTransform(1,0,0,1,0,0));var c=this.viewport||this.dirty_area;c&&(a.save(),a.beginPath(),a.rect(c[0],c[1],c[2],c[3]),a.clip());this.clear_background&&(c?a.clearRect(c[0],c[1],c[2],c[3]):a.clearRect(0,0,b.width,b.height));this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b,a);this.show_info&&this.renderInfo(a,c?c[0]:0,c?c[1]:0);if(this.graph){a.save();this.ds.toCanvasContext(a);b=this.computeVisibleNodes(null, -this.visible_nodes);for(var d=0;d> ";b.fillText(d+c.getTitle(),.5*a.width,40);b.restore()}c=!1;this.onRenderBackground&&(c=this.onRenderBackground(a,b));this.viewport||(b.restore(),b.setTransform(1,0,0,1,0,0));this.visible_links.length=0;if(this.graph){b.save();this.ds.toCanvasContext(b);1.5>this.ds.scale&&!c&&this.clear_background_color&&(b.fillStyle=this.clear_background_color,b.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]));if(this.background_image&&.5this.ds.scale;if(this.live_mode){if(!a.flags.collapsed&&(b.shadowColor="transparent",a.onDrawForeground))a.onDrawForeground(b,this,this.canvas)}else{var f=this.editor_alpha;b.globalAlpha=f;this.render_shadows&&!e?(b.shadowColor=h.DEFAULT_SHADOW_COLOR,b.shadowOffsetX=2*this.ds.scale,b.shadowOffsetY=2*this.ds.scale,b.shadowBlur=3*this.ds.scale):b.shadowColor="transparent";if(!a.flags.collapsed||!a.onDrawCollapsed||1!=a.onDrawCollapsed(b,this)){var g=a._shape||h.BOX_SHAPE; -N.set(a.size);var k=a.horizontal;if(a.flags.collapsed){b.font=this.inner_text_font;var m=a.getTitle?a.getTitle():a.title;null!=m&&(a._collapsed_width=Math.min(a.size[0],b.measureText(m).width+2*h.NODE_TITLE_HEIGHT),N[0]=a._collapsed_width,N[1]=0)}a.clip_area&&(b.save(),b.beginPath(),g==h.BOX_SHAPE?b.rect(0,0,N[0],N[1]):g==h.ROUND_SHAPE?b.roundRect(0,0,N[0],N[1],[10]):g==h.CIRCLE_SHAPE&&b.arc(.5*N[0],.5*N[1],.5*N[0],0,2*Math.PI),b.clip());a.has_errors&&(d="red");this.drawNodeShape(a,b,N,c,d,a.is_selected, -a.mouseOver);b.shadowColor="transparent";if(a.onDrawForeground)a.onDrawForeground(b,this,this.canvas);b.textAlign=k?"center":"left";b.font=this.inner_text_font;d=!e;var l=this.connecting_output;g=this.connecting_input;b.lineWidth=1;m=0;var n=new Float32Array(2);if(!a.flags.collapsed){if(a.inputs)for(c=0;cthis.ds.scale,m=a._shape||a.constructor.shape||h.ROUND_SHAPE,l=a.constructor.title_mode,n=!0;l==h.TRANSPARENT_TITLE||l==h.NO_TITLE?n=!1:l==h.AUTOHIDE_TITLE&& -g&&(n=!0);C[0]=0;C[1]=n?-e:0;C[2]=c[0]+1;C[3]=n?c[1]+e:c[1];g=b.globalAlpha;b.beginPath();m==h.BOX_SHAPE||k?b.fillRect(C[0],C[1],C[2],C[3]):m==h.ROUND_SHAPE||m==h.CARD_SHAPE?b.roundRect(C[0],C[1],C[2],C[3],m==h.CARD_SHAPE?[this.round_radius,this.round_radius,0,0]:[this.round_radius]):m==h.CIRCLE_SHAPE&&b.arc(.5*c[0],.5*c[1],.5*c[0],0,2*Math.PI);b.fill();!a.flags.collapsed&&n&&(b.shadowColor="transparent",b.fillStyle="rgba(0,0,0,0.2)",b.fillRect(0,-1,C[2],2));b.shadowColor="transparent";if(a.onDrawBackground)a.onDrawBackground(b, -this,this.canvas,this.graph_mouse);if(n||l==h.TRANSPARENT_TITLE){if(a.onDrawTitleBar)a.onDrawTitleBar(b,e,c,this.ds.scale,d);else if(l!=h.TRANSPARENT_TITLE&&(a.constructor.title_color||this.render_title_colored)){n=a.constructor.title_color||d;a.flags.collapsed&&(b.shadowColor=h.DEFAULT_SHADOW_COLOR);if(this.use_gradients){var p=q.gradients[n];p||(p=q.gradients[n]=b.createLinearGradient(0,0,400,0),p.addColorStop(0,n),p.addColorStop(1,"#000"));b.fillStyle=p}else b.fillStyle=n;b.beginPath();m==h.BOX_SHAPE|| -k?b.rect(0,-e,c[0]+1,e):(m==h.ROUND_SHAPE||m==h.CARD_SHAPE)&&b.roundRect(0,-e,c[0]+1,e,a.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]);b.fill();b.shadowColor="transparent"}n=!1;h.node_box_coloured_by_mode&&h.NODE_MODES_COLORS[a.mode]&&(n=h.NODE_MODES_COLORS[a.mode]);h.node_box_coloured_when_on&&(n=a.action_triggered?"#FFF":a.execute_triggered?"#AAA":n);if(a.onDrawTitleBox)a.onDrawTitleBox(b,e,c,this.ds.scale);else m==h.ROUND_SHAPE||m==h.CIRCLE_SHAPE||m==h.CARD_SHAPE? -(k&&(b.fillStyle="black",b.beginPath(),b.arc(.5*e,-.5*e,6,0,2*Math.PI),b.fill()),b.fillStyle=a.boxcolor||n||h.NODE_DEFAULT_BOXCOLOR,k?b.fillRect(.5*e-5,-.5*e-5,10,10):(b.beginPath(),b.arc(.5*e,-.5*e,5,0,2*Math.PI),b.fill())):(k&&(b.fillStyle="black",b.fillRect(.5*(e-10)-1,-.5*(e+10)-1,12,12)),b.fillStyle=a.boxcolor||n||h.NODE_DEFAULT_BOXCOLOR,b.fillRect(.5*(e-10),-.5*(e+10),10,10));b.globalAlpha=g;if(a.onDrawTitleText)a.onDrawTitleText(b,e,c,this.ds.scale,this.title_text_font,f);!k&&(b.font=this.title_text_font, -g=String(a.getTitle()))&&(b.fillStyle=f?h.NODE_SELECTED_TITLE_COLOR:a.constructor.title_text_color||this.node_title_color,a.flags.collapsed?(b.textAlign="left",b.measureText(g),b.fillText(g.substr(0,20),e,h.NODE_TITLE_TEXT_Y-e),b.textAlign="left"):(b.textAlign="left",b.fillText(g,e,h.NODE_TITLE_TEXT_Y-e)));a.flags.collapsed||!a.subgraph||a.skip_subgraph_button||(g=h.NODE_TITLE_HEIGHT,n=a.size[0]-g,p=h.isInsideRectangle(this.graph_mouse[0]-a.pos[0],this.graph_mouse[1]-a.pos[1],n+2,-g+2,g-4,g-4),b.fillStyle= -p?"#888":"#555",m==h.BOX_SHAPE||k?b.fillRect(n+2,-g+2,g-4,g-4):(b.beginPath(),b.roundRect(n+2,-g+2,g-4,g-4,[4]),b.fill()),b.fillStyle="#333",b.beginPath(),b.moveTo(n+.2*g,.6*-g),b.lineTo(n+.8*g,.6*-g),b.lineTo(n+.5*g,.3*-g),b.fill());if(a.onDrawTitle)a.onDrawTitle(b)}if(f){if(a.onBounding)a.onBounding(C);l==h.TRANSPARENT_TITLE&&(C[1]-=e,C[3]+=e);b.lineWidth=1;b.globalAlpha=.8;b.beginPath();m==h.BOX_SHAPE?b.rect(-6+C[0],-6+C[1],12+C[2],12+C[3]):m==h.ROUND_SHAPE||m==h.CARD_SHAPE&&a.flags.collapsed? -b.roundRect(-6+C[0],-6+C[1],12+C[2],12+C[3],[2*this.round_radius]):m==h.CARD_SHAPE?b.roundRect(-6+C[0],-6+C[1],12+C[2],12+C[3],[2*this.round_radius,2,2*this.round_radius,2]):m==h.CIRCLE_SHAPE&&b.arc(.5*c[0],.5*c[1],.5*c[0]+6,0,2*Math.PI);b.strokeStyle=h.NODE_BOX_OUTLINE_COLOR;b.stroke();b.strokeStyle=d;b.globalAlpha=1}0H[2]&&(H[0]+=H[2],H[2]=Math.abs(H[2]));0>H[3]&&(H[1]+=H[3],H[3]=Math.abs(H[3]));if(Z(H,aa)){var r=m.outputs[l];l=f.inputs[g];if(r&&l&&(m=r.dir||(m.horizontal?h.DOWN:h.RIGHT),l=l.dir||(f.horizontal?h.UP:h.LEFT),this.renderLink(a,n,p,k,!1,0,null,m,l),k&&k._last_time&&1E3>b-k._last_time)){r=2-.002*(b-k._last_time);var u=a.globalAlpha;a.globalAlpha=u* -r;this.renderLink(a,n,p,k,!0,r,"white",m,l);a.globalAlpha=u}}}}}}a.globalAlpha=1};q.prototype.renderLink=function(a,b,c,d,e,f,g,k,m,l){d&&this.visible_links.push(d);!g&&d&&(g=d.color||q.link_type_colors[d.type]);g||(g=this.default_link_color);null!=d&&this.highlighted_links[d.id]&&(g="#FFF");k=k||h.RIGHT;m=m||h.LEFT;var n=ba(b,c);this.render_connections_border&&.6b[1]?0:Math.PI,a.save(),a.translate(p[0],p[1]),a.rotate(n),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore(),a.save(),a.translate(d[0],d[1]),a.rotate(l),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore()),a.beginPath(),a.arc(e[0],e[1],5,0,2*Math.PI),a.fill());if(f)for(a.fillStyle=g,p=0;5>p;++p)f=(.001*h.getTime()+.2*p)%1,e=this.computeConnectionPoint(b,c,f,k, -m),a.beginPath(),a.arc(e[0],e[1],5,0,2*Math.PI),a.fill()};q.prototype.computeConnectionPoint=function(a,b,c,d,e){d=d||h.RIGHT;e=e||h.LEFT;var f=ba(a,b),g=[a[0],a[1]],k=[b[0],b[1]];switch(d){case h.LEFT:g[0]+=-.25*f;break;case h.RIGHT:g[0]+=.25*f;break;case h.UP:g[1]+=-.25*f;break;case h.DOWN:g[1]+=.25*f}switch(e){case h.LEFT:k[0]+=-.25*f;break;case h.RIGHT:k[0]+=.25*f;break;case h.UP:k[1]+=-.25*f;break;case h.DOWN:k[1]+=.25*f}d=(1-c)*(1-c)*(1-c);e=3*(1-c)*(1-c)*c;f=3*(1-c)*c*c;c*=c*c;return[d*a[0]+ -e*g[0]+f*k[0]+c*b[0],d*a[1]+e*g[1]+f*k[1]+c*b[1]]};q.prototype.drawExecutionOrder=function(a){a.shadowColor="transparent";a.globalAlpha=.25;a.textAlign="center";a.strokeStyle="white";a.globalAlpha=.75;for(var b=this.visible_nodes,c=0;cB&&(B=0);1z&&(z=0),1f||f>t-12||gr.last_y+u||void 0===r.last_y)){d=r.value;switch(r.type){case "button":c.type===h.pointerevents_method+"down"&&(r.callback&&setTimeout(function(){r.callback(r,l,a,b,c)},20),this.dirty_canvas=r.clicked=!0);break;case "slider":d= -r.value;n=ca((f-15)/(t-30),0,1);if(r.options.read_only)break;r.value=r.options.min+(r.options.max-r.options.min)*n;d!=r.value&&setTimeout(function(){e(r,r.value)},20);this.dirty_canvas=!0;break;case "number":case "combo":d=r.value;if(c.type==h.pointerevents_method+"move"&&"number"==r.type)m&&(r.value+=.1*m*(r.options.step||1)),null!=r.options.min&&r.valuer.options.max&&(r.value=r.options.max);else if(c.type==h.pointerevents_method+ -"down"){var x=r.options.values;x&&x.constructor===Function&&(x=r.options.values(r,a));var z=null;"number"!=r.type&&(z=x.constructor===Array?x:Object.keys(x));f=40>f?-1:f>t-40?1:0;if("number"==r.type)r.value+=.1*f*(r.options.step||1),null!=r.options.min&&r.valuer.options.max&&(r.value=r.options.max);else if(f)n=-1,this.last_mouseclick=0,n=x.constructor===Object?z.indexOf(String(r.value))+f:z.indexOf(r.value)+f,n>=z.length&&(n=z.length- -1),0>n&&(n=0),r.value=x.constructor===Array?x[n]:n;else{var B=x!=z?Object.values(x):x;new h.ContextMenu(B,{scale:Math.max(1,this.ds.scale),event:c,className:"dark",callback:E.bind(r)},n);function E(F,da,M){x!=z&&(F=B.indexOf(F));this.value=F;e(this,F);l.dirty_canvas=!0;return!1}}}else c.type==h.pointerevents_method+"up"&&"number"==r.type&&(f=40>f?-1:f>t-40?1:0,200>c.click_time&&0==f&&this.prompt("Value",r.value,function(E){if(/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(E))try{E=eval(E)}catch(F){}this.value= -Number(E);e(this,this.value)}.bind(r),c));d!=r.value&&setTimeout(function(){e(this,this.value)}.bind(r),20);this.dirty_canvas=!0;break;case "toggle":c.type==h.pointerevents_method+"down"&&(r.value=!r.value,setTimeout(function(){e(r,r.value)},20));break;case "string":case "text":c.type==h.pointerevents_method+"down"&&this.prompt("Value",r.value,function(E){e(this,E)}.bind(r),c,r.options?r.options.multiline:!1);break;default:r.mouse&&(this.dirty_canvas=r.mouse(c,[f,g],a))}if(d!=r.value){if(a.onWidgetChanged)a.onWidgetChanged(r.name, -r.value,d,r);a.graph._version++}return r}}}return null};q.prototype.drawGroups=function(a,b){if(this.graph){a=this.graph._groups;b.save();b.globalAlpha=.5*this.editor_alpha;for(var c=0;cc&&.01>b.editor_alpha&&(clearInterval(d),1>c&&(b.live_mode=!0));1c.pos[0]+c.size[0])c=g;if(null===d||m+n>d.pos[1]+d.size[1])d=g;if(null===e||k"+(m.label?m.label:k)+""+a+"",value:k})}if(g.length)return new h.ContextMenu(g,{event:c,callback:function(l,n,p,r){e&&(n=this.getBoundingClientRect(),f.showEditPropertyValue(e,l.value,{position:[n.left,n.top]}))},parentMenu:d,allow_html:!0, -node:e},b),!1}};q.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};q.onMenuResizeNode=function(a,b,c,d,e){if(e){a=function(g){g.size=g.computeSize();if(g.onResize)g.onResize(g.size)};b=q.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(e);else for(var f in b.selected_nodes)a(b.selected_nodes[f]);e.setDirtyCanvas(!0,!0)}};q.prototype.showLinkMenu=function(a,b){var c=this,d=c.graph.getNodeById(a.origin_id),e=c.graph.getNodeById(a.target_id), -f=!1;d&&d.outputs&&d.outputs[a.origin_slot]&&(f=d.outputs[a.origin_slot].type);var g=!1;e&&e.outputs&&e.outputs[a.target_slot]&&(g=e.inputs[a.target_slot].type);var k=new h.ContextMenu(["Add Node",null,"Delete",null],{event:b,title:null!=a.data?a.data.constructor.name:null,callback:function(m,l,n){switch(m){case "Add Node":q.onMenuAdd(null,null,n,k,function(p){p.inputs&&p.inputs.length&&p.outputs&&p.outputs.length&&d.connectByType(a.origin_slot,p,f)&&(p.connectByType(a.target_slot,e,g),p.pos[0]-= -.5*p.size[0])});break;case "Delete":c.graph.removeLink(a.id)}}});return!1};q.prototype.createDefaultNodeForSlot=function(a){a=a||{};a=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},a);var b=a.nodeFrom&&null!==a.slotFrom,c=!b&&a.nodeTo&&null!==a.slotTo;if(!b&&!c)return console.warn("No data passed to createDefaultNodeForSlot "+a.nodeFrom+" "+a.slotFrom+" "+a.nodeTo+" "+a.slotTo),!1;if(!a.nodeType)return console.warn("No type to createDefaultNodeForSlot"), -!1;var d=b?a.nodeFrom:a.nodeTo,e=b?a.slotFrom:a.slotTo;switch(typeof e){case "string":c=b?d.findOutputSlot(e,!1):d.findInputSlot(e,!1);e=b?d.outputs[e]:d.inputs[e];break;case "object":c=b?d.findOutputSlot(e.name):d.findInputSlot(e.name);break;case "number":c=e;e=b?d.outputs[e]:d.inputs[e];break;default:return console.warn("Cant get slot information "+e),!1}!1!==e&&!1!==c||console.warn("createDefaultNodeForSlot bad slotX "+e+" "+c);d=e.type==h.EVENT?"_event_":e.type;if((e=b?h.slot_types_default_out: -h.slot_types_default_in)&&e[d]){nodeNewType=!1;if("object"==typeof e[d]||"array"==typeof e[d])for(var f in e[d]){if(a.nodeType==e[d][f]||"AUTO"==a.nodeType){nodeNewType=e[d][f];break}}else if(a.nodeType==e[d]||"AUTO"==a.nodeType)nodeNewType=e[d];if(nodeNewType){f=!1;"object"==typeof nodeNewType&&nodeNewType.node&&(f=nodeNewType,nodeNewType=nodeNewType.node);if(e=h.createNode(nodeNewType)){if(f){if(f.properties)for(var g in f.properties)e.addProperty(g,f.properties[g]);if(f.inputs)for(g in e.inputs= -[],f.inputs)e.addOutput(f.inputs[g][0],f.inputs[g][1]);if(f.outputs)for(g in e.outputs=[],f.outputs)e.addOutput(f.outputs[g][0],f.outputs[g][1]);f.title&&(e.title=f.title);f.json&&e.configure(f.json)}this.graph.add(e);e.pos=[a.position[0]+a.posAdd[0]+(a.posSizeFix[0]?a.posSizeFix[0]*e.size[0]:0),a.position[1]+a.posAdd[1]+(a.posSizeFix[1]?a.posSizeFix[1]*e.size[1]:0)];b?a.nodeFrom.connectByType(c,e,d):a.nodeTo.connectByTypeOutput(c,e,d);return!0}console.log("failed creating "+nodeNewType)}}return!1}; -q.prototype.showConnectionMenu=function(a){a=a||{};var b=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},a),c=this,d=b.nodeFrom&&b.slotFrom;a=!d&&b.nodeTo&&b.slotTo;if(!d&&!a)return console.warn("No data passed to showConnectionMenu"),!1;a=d?b.nodeFrom:b.nodeTo;var e=d?b.slotFrom:b.slotTo,f=!1;switch(typeof e){case "string":f=d?a.findOutputSlot(e,!1):a.findInputSlot(e,!1);e=d?a.outputs[e]:a.inputs[e];break;case "object":f=d?a.findOutputSlot(e.name):a.findInputSlot(e.name); -break;case "number":f=e;e=d?a.outputs[e]:a.inputs[e];break;default:return console.warn("Cant get slot information "+e),!1}a=["Add Node",null];c.allow_searchbox&&(a.push("Search"),a.push(null));var g=e.type==h.EVENT?"_event_":e.type,k=d?h.slot_types_default_out:h.slot_types_default_in;if(k&&k[g])if("object"==typeof k[g]||"array"==typeof k[g])for(var m in k[g])a.push(k[g][m]);else a.push(k[g]);var l=new h.ContextMenu(a,{event:b.e,title:(e&&""!=e.name?e.name+(g?" | ":""):"")+(e&&g?g:""),callback:function(n, -p,r){switch(n){case "Add Node":q.onMenuAdd(null,null,r,l,function(u){d?b.nodeFrom.connectByType(f,u,g):b.nodeTo.connectByTypeOutput(f,u,g)});break;case "Search":d?c.showSearchBox(r,{node_from:b.nodeFrom,slot_from:e,type_filter_in:g}):c.showSearchBox(r,{node_to:b.nodeTo,slot_from:e,type_filter_out:g});break;default:c.createDefaultNodeForSlot(Object.assign(b,{position:[b.e.canvasX,b.e.canvasY],nodeType:n}))}}});return!1};q.onShowPropertyEditor=function(a,b,c,d,e){function f(){if(m){var p=m.value;"Number"== -a.type?p=Number(p):"Boolean"==a.type&&(p=!!p);e[g]=p;k.parentNode&&k.parentNode.removeChild(k);e.setDirtyCanvas(!0,!0)}}var g=a.property||"title";b=e[g];var k=document.createElement("div");k.is_modified=!1;k.className="graphdialog";k.innerHTML="";k.close=function(){k.parentNode&&k.parentNode.removeChild(k)};k.querySelector(".name").innerText=g;var m=k.querySelector(".value");m&&(m.value=b,m.addEventListener("blur", -function(p){this.focus()}),m.addEventListener("keydown",function(p){k.is_modified=!0;if(27==p.keyCode)k.close();else if(13==p.keyCode)f();else if(13!=p.keyCode&&"textarea"!=p.target.localName)return;p.preventDefault();p.stopPropagation()}));b=q.active_canvas.canvas;c=b.getBoundingClientRect();var l=d=-20;c&&(d-=c.left,l-=c.top);event?(k.style.left=event.clientX+d+"px",k.style.top=event.clientY+l+"px"):(k.style.left=.5*b.width+d+"px",k.style.top=.5*b.height+l+"px");k.querySelector("button").addEventListener("click", -f);b.parentNode.appendChild(k);m&&m.focus();var n=null;k.addEventListener("mouseleave",function(p){h.dialog_close_on_mouse_leave&&!k.is_modified&&h.dialog_close_on_mouse_leave&&(n=setTimeout(k.close,h.dialog_close_on_mouse_leave_delay))});k.addEventListener("mouseenter",function(p){h.dialog_close_on_mouse_leave&&n&&clearTimeout(n)})};q.prototype.prompt=function(a,b,c,d,e){var f=this;a=a||"";var g=document.createElement("div");g.is_modified=!1;g.className="graphdialog rounded";g.innerHTML=e?" ": -" ";g.close=function(){f.prompt_box=null;g.parentNode&&g.parentNode.removeChild(g)};e=q.active_canvas.canvas;e.parentNode.appendChild(g);1q.search_limit))break}}O=null;if(Array.prototype.filter)O=Object.keys(h.registered_node_types).filter(R);else for(D in O=[],h.registered_node_types)R(D)&&O.push(D);for(D=0;Dq.search_limit);D++);if(b.show_general_after_typefiltered&&(V.value||W.value)){filtered_extra=[];for(D in h.registered_node_types)R(D,{inTypeOverride:V&&V.value?"*":!1,outTypeOverride:W&&W.value? -"*":!1})&&filtered_extra.push(D);for(D=0;Dq.search_limit);D++);}if((V.value||W.value)&&0==t.childNodes.length&&b.show_general_if_none_on_typefilter){filtered_extra=[];for(D in h.registered_node_types)R(D,{skipFilter:!0})&&filtered_extra.push(D);for(D=0;Dq.search_limit);D++);}function R(U,J){J=J||{};J=Object.assign({skipFilter:!1, -inTypeOverride:!1,outTypeOverride:!1},J);var G=h.registered_node_types[U];if(ea&&G.filter!=ea||(!b.show_all_if_empty||y)&&-1===U.toLowerCase().indexOf(y))return!1;if(b.do_type_filter&&!J.skipFilter){G=V.value;!1!==J.inTypeOverride&&(G=J.inTypeOverride);if(V&&G&&h.registered_slot_in_types[G]&&h.registered_slot_in_types[G].nodes&&(G=h.registered_slot_in_types[G].nodes.includes(U),!1===G))return!1;G=W.value;!1!==J.outTypeOverride&&(G=J.outTypeOverride);if(W&&G&&h.registered_slot_out_types[G]&&h.registered_slot_out_types[G].nodes&& -(G=h.registered_slot_out_types[G].nodes.includes(U),!1===G))return!1}return!0}}}b=Object.assign({slot_from:null,node_from:null,node_to:null,do_type_filter:h.search_filter_enabled,type_filter_in:!1,type_filter_out:!1,show_general_if_none_on_typefilter:!0,show_general_after_typefiltered:!0,hide_on_mouse_leave:h.search_hide_on_mouse_leave,show_all_if_empty:!0,show_all_on_open:h.search_show_all_on_open},b||{});var f=this,g=q.active_canvas,k=g.canvas,m=k.ownerDocument||document,l=document.createElement("div"); -l.className="litegraph litesearchbox graphdialog rounded";l.innerHTML="Search ";b.do_type_filter&&(l.innerHTML+="",l.innerHTML+="");l.innerHTML+="
";m.fullscreenElement?m.fullscreenElement.appendChild(l):(m.body.appendChild(l),m.body.style.overflow="hidden"); -if(b.do_type_filter)var n=l.querySelector(".slot_in_type_filter"),p=l.querySelector(".slot_out_type_filter");l.close=function(){f.search_box=null;this.blur();k.focus();m.body.style.overflow="";setTimeout(function(){f.canvas.focus()},20);l.parentNode&&l.parentNode.removeChild(l)};1n.height-200&&(t.style.maxHeight=n.height-a.layerY-20+"px");E.focus();b.show_all_on_open&&e();return l};q.prototype.showEditPropertyValue=function(a,b,c){function d(){e(p.value)}function e(r){f&&f.values&&f.values.constructor===Object&&void 0!=f.values[r]&&(r=f.values[r]); -"number"==typeof a.properties[b]&&(r=Number(r));if("array"==g||"object"==g)r=JSON.parse(r);a.properties[b]=r;a.graph&&a.graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b,r);if(c.onclose)c.onclose();n.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var f=a.getPropertyInfo(b),g=f.type,k="";if("string"==g||"number"==g||"array"==g||"object"==g)k="";else if("enum"!=g&&"combo"!=g||!f.values)if("boolean"==g||"toggle"==g)k="";else{console.warn("unknown type: "+g);return}else{k=""}var n=this.createDialog(""+(f.label?f.label:b)+""+k+"",c),p=!1;if("enum"!=g&&"combo"!=g||!f.values)if("boolean"==g||"toggle"==g)(p=n.querySelector("input"))&& -p.addEventListener("click",function(r){n.modified();e(!!p.checked)});else{if(p=n.querySelector("input"))p.addEventListener("blur",function(r){this.focus()}),l=void 0!==a.properties[b]?a.properties[b]:"","string"!==g&&(l=JSON.stringify(l)),p.value=l,p.addEventListener("keydown",function(r){if(27==r.keyCode)n.close();else if(13==r.keyCode)d();else if(13!=r.keyCode){n.modified();return}r.preventDefault();r.stopPropagation()})}else p=n.querySelector("select"),p.addEventListener("change",function(r){n.modified(); -e(r.target.value)});p&&p.focus();n.querySelector("button").addEventListener("click",d);return n}};q.prototype.createDialog=function(a,b){b=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},b||{});var c=document.createElement("div");c.className="graphdialog";c.innerHTML=a;c.is_modified=!1;a=this.canvas.getBoundingClientRect();var d=-20,e=-20;a&&(d-=a.left,e-=a.top);b.position?(d+=b.position[0],e+=b.position[1]):b.event?(d+=b.event.clientX,e+=b.event.clientY):(d+=.5*this.canvas.width, -e+=.5*this.canvas.height);c.style.left=d+"px";c.style.top=e+"px";this.canvas.parentNode.appendChild(c);b.checkForInput&&(a=[],(a=c.querySelectorAll("input"))&&a.forEach(function(k){k.addEventListener("keydown",function(m){c.modified();if(27==m.keyCode)c.close();else if(13!=m.keyCode)return;m.preventDefault();m.stopPropagation()});k.focus()}));c.modified=function(){c.is_modified=!0};c.close=function(){c.parentNode&&c.parentNode.removeChild(c)};var f=null,g=!1;c.addEventListener("mouseleave",function(k){g|| -(b.closeOnLeave||h.dialog_close_on_mouse_leave)&&!c.is_modified&&h.dialog_close_on_mouse_leave&&(f=setTimeout(c.close,h.dialog_close_on_mouse_leave_delay))});c.addEventListener("mouseenter",function(k){(b.closeOnLeave||h.dialog_close_on_mouse_leave)&&f&&clearTimeout(f)});(a=c.querySelectorAll("select"))&&a.forEach(function(k){k.addEventListener("click",function(m){g++});k.addEventListener("blur",function(m){g=0});k.addEventListener("change",function(m){g=-1})});return c};q.prototype.createPanel=function(a, -b){b=b||{};var c=b.window||window,d=document.createElement("div");d.className="litegraph dialog";d.innerHTML="
";d.header=d.querySelector(".dialog-header");b.width&&(d.style.width=b.width+(b.width.constructor===Number?"px":""));b.height&&(d.style.height=b.height+(b.height.constructor===Number?"px":""));b.closable&& -(b=document.createElement("span"),b.innerHTML="✕",b.classList.add("close"),b.addEventListener("click",function(){d.close()}),d.header.appendChild(b));d.title_element=d.querySelector(".dialog-title");d.title_element.innerText=a;d.content=d.querySelector(".dialog-content");d.alt_content=d.querySelector(".dialog-alt-content");d.footer=d.querySelector(".dialog-footer");d.close=function(){if(d.onClose&&"function"==typeof d.onClose)d.onClose();d.parentNode&&d.parentNode.removeChild(d);this.parentNode&& -this.parentNode.removeChild(this)};d.toggleAltContent=function(e){if("undefined"!=typeof e){var f=e?"block":"none";e=e?"none":"block"}else f="block"!=d.alt_content.style.display?"block":"none",e="block"!=d.alt_content.style.display?"none":"block";d.alt_content.style.display=f;d.content.style.display=e};d.toggleFooterVisibility=function(e){d.footer.style.display="undefined"!=typeof e?e?"block":"none":"block"!=d.footer.style.display?"block":"none"};d.clear=function(){this.content.innerHTML=""};d.addHTML= -function(e,f,g){var k=document.createElement("div");f&&(k.className=f);k.innerHTML=e;g?d.footer.appendChild(k):d.content.appendChild(k);return k};d.addButton=function(e,f,g){var k=document.createElement("button");k.innerText=e;k.options=g;k.classList.add("btn");k.addEventListener("click",f);d.footer.appendChild(k);return k};d.addSeparator=function(){var e=document.createElement("div");e.className="separator";d.content.appendChild(e)};d.addWidget=function(e,f,g,k,m){function l(u,t){k.callback&&k.callback(u, -t,k);m&&m(u,t,k)}k=k||{};var n=String(g);e=e.toLowerCase();"number"==e&&(n=g.toFixed(3));var p=document.createElement("div");p.className="property";p.innerHTML="";p.querySelector(".property_name").innerText=k.label||f;var r=p.querySelector(".property_value");r.innerText=n;p.dataset.property=f;p.dataset.type=k.type||e;p.options=k;p.value=g;if("code"==e)p.addEventListener("click",function(u){d.inner_showCodePad(this.dataset.property)}); -else if("boolean"==e)p.classList.add("boolean"),g&&p.classList.add("bool-on"),p.addEventListener("click",function(){var u=this.dataset.property;this.value=!this.value;this.classList.toggle("bool-on");this.querySelector(".property_value").innerText=this.value?"true":"false";l(u,this.value)});else if("string"==e||"number"==e)r.setAttribute("contenteditable",!0),r.addEventListener("keydown",function(u){"Enter"!=u.code||"string"==e&&u.shiftKey||(u.preventDefault(),this.blur())}),r.addEventListener("blur", -function(){var u=this.innerText,t=this.parentNode.dataset.property;"number"==this.parentNode.dataset.type&&(u=Number(u));l(t,u)});else if("enum"==e||"combo"==e)n=q.getPropertyPrintableValue(g,k.values),r.innerText=n,r.addEventListener("click",function(u){var t=this.parentNode.dataset.property,x=this;new h.ContextMenu(k.values||[],{event:u,className:"dark",callback:function(z,B,E){x.innerText=z;l(t,z);return!1}},c)});d.content.appendChild(p);return p};if(d.onOpen&&"function"==typeof d.onOpen)d.onOpen(); -return d};q.getPropertyPrintableValue=function(a,b){if(!b||b.constructor===Array)return String(a);if(b.constructor===Object){var c="",d;for(d in b)if(b[d]==a){c=d;break}return String(a)+" ("+c+")"}};q.prototype.closePanels=function(){var a=document.querySelector("#node-panel");a&&a.close();(a=document.querySelector("#option-panel"))&&a.close()};q.prototype.showShowGraphOptionsPanel=function(a,b,c,d){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(b&&b.event&&b.event.target&&b.event.target.lgraphcanvas)){console.warn("Canvas not found"); -return}var e=b.event.target.lgraphcanvas}else e=this;e.closePanels();a=e.getCanvasWindow();panel=e.createPanel("Options",{closable:!0,window:a,onOpen:function(){e.OPTIONPANEL_IS_OPEN=!0},onClose:function(){e.OPTIONPANEL_IS_OPEN=!1;e.options_panel=null}});e.options_panel=panel;panel.id="option-panel";panel.classList.add("settings");(function(){panel.content.innerHTML="";var f=function(l,n,p){p&&p.key&&(l=p.key);p.values&&(n=Object.values(p.values).indexOf(n));e[l]=n},g=h.availableCanvasOptions;g.sort(); -for(var k in g){var m=g[k];panel.addWidget("boolean",m,e[m],{key:m,on:"True",off:"False"},f)}panel.addWidget("combo","Render mode",h.LINK_RENDER_MODES[e.links_render_mode],{key:"links_render_mode",values:h.LINK_RENDER_MODES},f);panel.addSeparator();panel.footer.innerHTML=""})();e.canvas.parentNode.appendChild(panel)};q.prototype.showShowNodePanel=function(a){function b(){e.content.innerHTML="";e.addHTML(""+a.type+""+(a.constructor.desc||"")+""); -e.addHTML("

Properties

");var f=function(l,n){d.graph.beforeChange(a);switch(l){case "Title":a.title=n;break;case "Mode":l=Object.values(h.NODE_MODES).indexOf(n);0<=l&&h.NODE_MODES[l]?a.changeMode(l):console.warn("unexpected mode: "+n);break;case "Color":q.node_colors[n]?(a.color=q.node_colors[n].color,a.bgcolor=q.node_colors[n].bgcolor):console.warn("unexpected color: "+n);break;default:a.setProperty(l,n)}d.graph.afterChange();d.dirty_canvas=!0};e.addWidget("string","Title",a.title,{},f); -e.addWidget("combo","Mode",h.NODE_MODES[a.mode],{values:h.NODE_MODES},f);var g="";void 0!==a.color&&(g=Object.keys(q.node_colors).filter(function(l){return q.node_colors[l].color==a.color}));e.addWidget("combo","Color",g,{values:Object.keys(q.node_colors)},f);for(var k in a.properties){g=a.properties[k];var m=a.getPropertyInfo(k);a.onAddPropertyToPanel&&a.onAddPropertyToPanel(k,e)||e.addWidget(m.widget||m.type,k,g,m,f)}e.addSeparator();if(a.onShowCustomPanelInfo)a.onShowCustomPanelInfo(e);e.footer.innerHTML= -"";e.addButton("Delete",function(){a.block_delete||(a.graph.remove(a),e.close())}).classList.add("delete")}this.SELECTED_NODE=a;this.closePanels();var c=this.getCanvasWindow(),d=this,e=this.createPanel(a.title||"",{closable:!0,window:c,onOpen:function(){d.NODEPANEL_IS_OPEN=!0},onClose:function(){d.NODEPANEL_IS_OPEN=!1;d.node_panel=null}});d.node_panel=e;e.id="node-panel";e.node=a;e.classList.add("settings");e.inner_showCodePad=function(f){e.classList.remove("settings");e.classList.add("centered"); -e.alt_content.innerHTML="";var g=e.alt_content.querySelector("textarea"),k=function(){e.toggleAltContent(!1);e.toggleFooterVisibility(!0);g.parentNode.removeChild(g);e.classList.add("settings");e.classList.remove("centered");b()};g.value=a.properties[f];g.addEventListener("keydown",function(l){"Enter"==l.code&&l.ctrlKey&&(a.setProperty(f,g.value),k())});e.toggleAltContent(!0);e.toggleFooterVisibility(!1);g.style.height="calc(100% - 40px)";var m=e.addButton("Assign", -function(){a.setProperty(f,g.value);k()});e.alt_content.appendChild(m);m=e.addButton("Close",k);m.style.float="right";e.alt_content.appendChild(m)};b();this.canvas.parentNode.appendChild(e)};q.prototype.showSubgraphPropertiesDialog=function(a){function b(){d.clear();if(a.inputs)for(var e=0;e","subgraph_property"); -g.dataset.name=f.name;g.dataset.slot=e;g.querySelector(".name").innerText=f.name;g.querySelector(".type").innerText=f.type;g.querySelector("button").addEventListener("click",function(k){a.removeInput(Number(this.parentNode.dataset.slot));b()})}}}console.log("showing subgraph properties dialog");var c=this.canvas.parentNode.querySelector(".subgraph_dialog");c&&c.close();var d=this.createPanel("Subgraph Inputs",{closable:!0,width:500});d.node=a;d.classList.add("subgraph_dialog");d.addHTML(" + NameType", -"subgraph_property extra",!0).querySelector("button").addEventListener("click",function(e){e=this.parentNode;var f=e.querySelector(".name").value,g=e.querySelector(".type").value;f&&-1==a.findInputSlot(f)&&(a.addInput(f,g),e.querySelector(".name").value="",e.querySelector(".type").value="",b())});b();this.canvas.parentNode.appendChild(d);return d};q.prototype.showSubgraphPropertiesDialogRight=function(a){function b(){e.clear();if(a.outputs)for(var f=0;f","subgraph_property");k.dataset.name=g.name;k.dataset.slot=f;k.querySelector(".name").innerText=g.name;k.querySelector(".type").innerText=g.type;k.querySelector("button").addEventListener("click",function(m){a.removeOutput(Number(this.parentNode.dataset.slot));b()})}}}function c(){var f=this.parentNode,g=f.querySelector(".name").value,k=f.querySelector(".type").value;g&&-1==a.findOutputSlot(g)&& -(a.addOutput(g,k),f.querySelector(".name").value="",f.querySelector(".type").value="",b())}var d=this.canvas.parentNode.querySelector(".subgraph_dialog");d&&d.close();var e=this.createPanel("Subgraph Outputs",{closable:!0,width:500});e.node=a;e.classList.add("subgraph_dialog");d=e.addHTML(" + NameType","subgraph_property extra",!0);d.querySelector(".name").addEventListener("keydown", -function(f){13==f.keyCode&&c.apply(this)});d.querySelector("button").addEventListener("click",function(f){c.apply(this)});b();this.canvas.parentNode.appendChild(e);return e};q.prototype.checkPanels=function(){if(this.canvas)for(var a=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),b=0;b=Object.keys(a.selected_nodes).length)e.collapse(); -else for(var f in a.selected_nodes)a.selected_nodes[f].collapse();e.graph.afterChange()};q.onMenuNodePin=function(a,b,c,d,e){e.pin()};q.onMenuNodeMode=function(a,b,c,d,e){new h.ContextMenu(h.NODE_MODES,{event:c,callback:function(f){if(e){var g=Object.values(h.NODE_MODES).indexOf(f),k=function(n){0<=g&&h.NODE_MODES[g]?n.changeMode(g):(console.warn("unexpected mode: "+f),n.changeMode(h.ALWAYS))},m=q.active_canvas;if(!m.selected_nodes||1>=Object.keys(m.selected_nodes).length)k(e);else for(var l in m.selected_nodes)k(m.selected_nodes[l])}}, -parentMenu:d,node:e});return!1};q.onMenuNodeColors=function(a,b,c,d,e){if(!e)throw"no node for color";b=[];b.push({value:null,content:"No color"});for(var f in q.node_colors)a=q.node_colors[f],a={value:f,content:""+f+""},b.push(a);new h.ContextMenu(b,{event:c,callback:function(g){if(e){var k=g.value?q.node_colors[g.value]: -null;g=function(n){k?n.constructor===h.LGraphGroup?n.color=k.groupcolor:(n.color=k.color,n.bgcolor=k.bgcolor):(delete n.color,delete n.bgcolor)};var m=q.active_canvas;if(!m.selected_nodes||1>=Object.keys(m.selected_nodes).length)g(e);else for(var l in m.selected_nodes)g(m.selected_nodes[l]);e.setDirtyCanvas(!0,!0)}},parentMenu:d,node:e});return!1};q.onMenuNodeShapes=function(a,b,c,d,e){if(!e)throw"no node passed";new h.ContextMenu(h.VALID_SHAPES,{event:c,callback:function(f){if(e){e.graph.beforeChange(); -var g=q.active_canvas;if(!g.selected_nodes||1>=Object.keys(g.selected_nodes).length)e.shape=f;else for(var k in g.selected_nodes)g.selected_nodes[k].shape=f;e.graph.afterChange();e.setDirtyCanvas(!0)}},parentMenu:d,node:e});return!1};q.onMenuNodeRemove=function(a,b,c,d,e){if(!e)throw"no node passed";a=e.graph;a.beforeChange();b=q.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)!1!==e.removable&&a.remove(e);else for(var f in b.selected_nodes)c=b.selected_nodes[f],!1!==c.removable&& -a.remove(c);a.afterChange();e.setDirtyCanvas(!0,!0)};q.onMenuNodeToSubgraph=function(a,b,c,d,e){a=e.graph;if(b=q.active_canvas)c=Object.values(b.selected_nodes||{}),c.length||(c=[e]),d=h.createNode("graph/subgraph"),d.pos=e.pos.concat(),a.add(d),d.buildFromNodes(c),b.deselectAllNodes(),e.setDirtyCanvas(!0,!0)};q.onMenuNodeClone=function(a,b,c,d,e){e.graph.beforeChange();var f={};a=function(k){if(!1!==k.clonable){var m=k.clone();m&&(m.pos=[k.pos[0]+5,k.pos[1]+5],k.graph.add(m),f[m.id]=m)}};b=q.active_canvas; -if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(e);else for(var g in b.selected_nodes)a(b.selected_nodes[g]);Object.keys(f).length&&b.selectNodes(f);e.graph.afterChange();e.setDirtyCanvas(!0,!0)};q.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"}, -cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}};q.prototype.getCanvasMenuOptions=function(){if(this.getMenuOptions)var a=this.getMenuOptions();else a=[{content:"Add Node",has_submenu:!0,callback:q.onMenuAdd},{content:"Add Group",callback:q.onGroupAdd}],1Name", -m),r=p.querySelector("input");r&&n&&(r.value=n.label||"");var u=function(){a.graph.beforeChange();r.value&&(n&&(n.label=r.value),c.setDirty(!0));p.close();a.graph.afterChange()};p.querySelector("button").addEventListener("click",u);r.addEventListener("keydown",function(t){p.is_modified=!0;if(27==t.keyCode)p.close();else if(13==t.keyCode)u();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault();t.stopPropagation()});r.focus()}},extra:a};a&&(f.title=a.type);var g=null;a&&(g= -a.getSlotInPosition(b.canvasX,b.canvasY),q.active_node=a);g?(e=[],a.getSlotMenuOptions?e=a.getSlotMenuOptions(g):(g&&g.output&&g.output.links&&g.output.links.length&&e.push({content:"Disconnect Links",slot:g}),b=g.input||g.output,b.removable&&e.push(b.locked?"Cannot remove":{content:"Remove Slot",slot:g}),b.nameLocked||e.push({content:"Rename Slot",slot:g})),f.title=(g.input?g.input.type:g.output.type)||"*",g.input&&g.input.type==h.ACTION&&(f.title="Action"),g.output&&g.output.type==h.EVENT&&(f.title= -"Event")):a?e=this.getNodeMenuOptions(a):(e=this.getCanvasMenuOptions(),(g=this.graph.getGroupOnPos(b.canvasX,b.canvasY))&&e.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:g,options:this.getGroupMenuOptions(g)}}));e&&new h.ContextMenu(e,f,d)};h.compareObjects=function(a,b){for(var c in a)if(a[c]!=b[c])return!1;return!0};h.distance=ba;h.colorToString=function(a){return"rgba("+Math.round(255*a[0]).toFixed()+","+Math.round(255*a[1]).toFixed()+","+Math.round(255*a[2]).toFixed()+ -","+(4==a.length?a[3].toFixed(2):"1.0")+")"};h.isInsideRectangle=K;h.growBounding=function(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)};h.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};h.overlapBounding=Z;h.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,d,e,f=0;6>f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b}; -h.num2hex=function(a){for(var b="#",c,d,e=0;3>e;e++)c=a[e]/16,d=a[e]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(d);return b};Q.prototype.addItem=function(a,b,c){function d(m){var l=this.value;l&&l.has_submenu&&e.call(this,m)}function e(m){var l=this.value,n=!0;f.current_submenu&&f.current_submenu.close(m);if(c.callback){var p=c.callback.call(this,l,c,m,f,c.node);!0===p&&(n=!1)}if(l&&(l.callback&&!c.ignore_item_callbacks&&!0!==l.disabled&&(p=l.callback.call(this,l,c,m,f,c.extra), -!0===p&&(n=!1)),l.submenu)){if(!l.submenu.options)throw"ContextMenu submenu needs options";new f.constructor(l.submenu.options,{callback:l.submenu.callback,event:m,parentMenu:f,ignore_item_callbacks:l.submenu.ignore_item_callbacks,title:l.submenu.title,extra:l.submenu.extra,autoopen:c.autoopen});n=!1}n&&!f.lock&&f.close()}var f=this;c=c||{};var g=document.createElement("div");g.className="litemenu-entry submenu";var k=!1;if(null===b)g.classList.add("separator");else{g.innerHTML=b&&b.title?b.title: -a;if(g.value=b)b.disabled&&(k=!0,g.classList.add("disabled")),(b.submenu||b.has_submenu)&&g.classList.add("has_submenu");"function"==typeof b?(g.dataset.value=a,g.onclick_callback=b):g.dataset.value=b;b.className&&(g.className+=" "+b.className)}this.root.appendChild(g);k||g.addEventListener("click",e);!k&&c.autoopen&&h.pointerListenerAdd(g,"enter",d);return g};Q.prototype.close=function(a,b){this.root.parentNode&&this.root.parentNode.removeChild(this.root);this.parentMenu&&!b&&(this.parentMenu.lock= -!1,this.parentMenu.current_submenu=null,void 0===a?this.parentMenu.close():a&&!Q.isCursorOverElement(a,this.parentMenu.root)&&Q.trigger(this.parentMenu.root,h.pointerevents_method+"leave",a));this.current_submenu&&this.current_submenu.close(a,!0);this.root.closing_timer&&clearTimeout(this.root.closing_timer)};Q.trigger=function(a,b,c,d){var e=document.createEvent("CustomEvent");e.initCustomEvent(b,!0,!0,c);e.srcElement=d;a.dispatchEvent?a.dispatchEvent(e):a.__events&&a.__events.dispatchEvent(e);return e}; -Q.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};Q.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};Q.isCursorOverElement=function(a,b){var c=a.clientX;a=a.clientY;return(b=b.getBoundingClientRect())?a>b.top&&ab.left&&cMath.abs(b))return d[1];a=(a-d[0])/b;return d[1]*(1-a)+e[1]*a}}return 0}};T.prototype.draw=function(a,b,c,d,e,f){if(c=this.points){this.size=b;var g=b[0]-2*this.margin;b=b[1]-2*this.margin;e=e||"#666";a.save();a.translate(this.margin,this.margin);d&&(a.fillStyle="#111",a.fillRect(0,0,g,b),a.fillStyle="#222",a.fillRect(.5*g,0,1,b),a.strokeStyle="#333", -a.strokeRect(0,0,g,b));a.strokeStyle=e;f&&(a.globalAlpha=.5);a.beginPath();for(d=0;da[1])){var d=this.size[0]-2*this.margin,e=this.size[1]-2*this.margin,f=a[0]-this.margin;a=a[1]-this.margin; -this.selected=this.getCloserPoint([f,a],30/b.ds.scale);-1==this.selected&&(b=[f/d,1-a/e],c.push(b),c.sort(function(g,k){return g[0]-k[0]}),this.selected=c.indexOf(b),this.must_update=!0);if(-1!=this.selected)return!0}};T.prototype.onMouseMove=function(a,b){var c=this.points;if(c){var d=this.selected;if(!(0>d)){var e=(a[0]-this.margin)/(this.size[0]-2*this.margin),f=(a[1]-this.margin)/(this.size[1]-2*this.margin);this._nearest=this.getCloserPoint([a[0]-this.margin,a[1]-this.margin],30/b.ds.scale); -if(b=c[d]){var g=0==d||d==c.length-1;!g&&(-10>a[0]||a[0]>this.size[0]+10||-10>a[1]||a[1]>this.size[1]+10)?(c.splice(d,1),this.selected=-1):(b[0]=g?0==d?0:1:ca(e,0,1),b[1]=1-ca(f,0,1),c.sort(function(k,m){return k[0]-m[0]}),this.selected=c.indexOf(b),this.must_update=!0)}}}};T.prototype.onMouseUp=function(a,b){this.selected=-1;return!1};T.prototype.getCloserPoint=function(a,b){var c=this.points;if(!c)return-1;b=b||30;for(var d=this.size[0]-2*this.margin,e=this.size[1]-2*this.margin,f=c.length,g=[0, -0],k=1E6,m=-1,l=0;lk||n>b||(m=l,k=n)}return m};h.CurveEditor=T;h.getParameterNames=function(a){return(a+"").replace(/[/][/].*$/gm,"").replace(/\s+/g,"").replace(/[/][*][^/*]*[*][/]/g,"").split("){",1)[0].replace(/^[^(]*[(]/,"").replace(/=[^,]+/g,"").split(",").filter(Boolean)};h.pointerListenerAdd=function(a,b,c,d=!1){if(a&&a.addEventListener&&b&&"function"===typeof c){var e=h.pointerevents_method;if("pointer"==e&&!window.PointerEvent)switch(console.warn("sMethod=='pointer' && !window.PointerEvent"), -console.log("Converting pointer["+b+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."),b){case "down":e="touch";b="start";break;case "move":e="touch";break;case "up":e="touch";b="end";break;case "cancel":e="touch";break;case "enter":console.log("debug: Should I send a move event?");break;default:console.warn("PointerEvent not available in this browser ? The event "+b+" would not be called")}switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":a.addEventListener(e+ -b,c,d);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("mouse"!=e)return a.addEventListener(e+b,c,d);default:return a.addEventListener(b,c,d)}}};h.pointerListenerRemove=function(a,b,c,d=!1){if(a&&a.removeEventListener&&b&&"function"===typeof c)switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":"pointer"!=h.pointerevents_method&&"mouse"!=h.pointerevents_method||a.removeEventListener(h.pointerevents_method+b,c,d);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("pointer"== -h.pointerevents_method)return a.removeEventListener(h.pointerevents_method+b,c,d);default:return a.removeEventListener(b,c,d)}};X.clamp=ca;"undefined"==typeof window||window.requestAnimationFrame||(window.requestAnimationFrame=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)})})(this); -"undefined"!=typeof exports&&(exports.LiteGraph=this.LiteGraph,exports.LGraph=this.LGraph,exports.LLink=this.LLink,exports.LGraphNode=this.LGraphNode,exports.LGraphGroup=this.LGraphGroup,exports.DragAndScale=this.DragAndScale,exports.LGraphCanvas=this.LGraphCanvas,exports.ContextMenu=this.ContextMenu); +!function(global){var LiteGraph=global.LiteGraph={VERSION:.4,CANVAS_GRID_SIZE:10,NODE_TITLE_HEIGHT:30,NODE_TITLE_TEXT_Y:20,NODE_SLOT_HEIGHT:20,NODE_WIDGET_HEIGHT:20,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,NODE_TITLE_COLOR:"#999",NODE_SELECTED_TITLE_COLOR:"#FFF",NODE_TEXT_SIZE:14,NODE_TEXT_COLOR:"#AAA",NODE_SUBTEXT_SIZE:12,NODE_DEFAULT_COLOR:"#333",NODE_DEFAULT_BGCOLOR:"#353535",NODE_DEFAULT_BOXCOLOR:"#666",NODE_DEFAULT_SHAPE:"box",NODE_BOX_OUTLINE_COLOR:"#FFF",DEFAULT_SHADOW_COLOR:"rgba(0,0,0,0.5)",DEFAULT_GROUP_FONT:24,WIDGET_BGCOLOR:"#222",WIDGET_OUTLINE_COLOR:"#666",WIDGET_TEXT_COLOR:"#DDD",WIDGET_SECONDARY_TEXT_COLOR:"#999",LINK_COLOR:"#9A9",EVENT_LINK_COLOR:"#A86",CONNECTING_LINK_COLOR:"#AFA",MAX_NUMBER_OF_NODES:1e3,DEFAULT_POSITION:[100,100],VALID_SHAPES:["default","box","round","card"],BOX_SHAPE:1,ROUND_SHAPE:2,CIRCLE_SHAPE:3,CARD_SHAPE:4,ARROW_SHAPE:5,GRID_SHAPE:6,INPUT:1,OUTPUT:2,EVENT:-1,ACTION:-1,NODE_MODES:["Always","On Event","Never","On Trigger"],NODE_MODES_COLORS:["#666","#422","#333","#224","#626"],ALWAYS:0,ON_EVENT:1,NEVER:2,ON_TRIGGER:3,UP:1,DOWN:2,LEFT:3,RIGHT:4,CENTER:5,LINK_RENDER_MODES:["Straight","Linear","Spline"],STRAIGHT_LINK:0,LINEAR_LINK:1,SPLINE_LINK:2,NORMAL_TITLE:0,NO_TITLE:1,TRANSPARENT_TITLE:2,AUTOHIDE_TITLE:3,VERTICAL_LAYOUT:"vertical",proxy:null,node_images_path:"",debug:!1,catch_exceptions:!0,throw_errors:!0,allow_scripts:!1,use_deferred_actions:!0,registered_node_types:{},node_types_by_file_extension:{},Nodes:{},Globals:{},searchbox_extras:{},auto_sort_node_types:!1,node_box_coloured_when_on:!1,node_box_coloured_by_mode:!1,dialog_close_on_mouse_leave:!0,dialog_close_on_mouse_leave_delay:500,shift_click_do_break_link_from:!1,click_do_break_link_to:!1,search_hide_on_mouse_leave:!0,search_filter_enabled:!1,search_show_all_on_open:!0,auto_load_slot_types:!1,registered_slot_in_types:{},registered_slot_out_types:{},slot_types_in:[],slot_types_out:[],slot_types_default_in:[],slot_types_default_out:[],alt_drag_do_clone_nodes:!1,do_add_triggers_slots:!1,allow_multi_output_for_events:!0,middle_click_slot_add_default_node:!1,release_link_on_empty_shows_menu:!1,pointerevents_method:"mouse",ctrl_shift_v_paste_connect_unselected_outputs:!1,use_uuids:!1,registerNodeType:function(t,e){if(!e.prototype)throw"Cannot register a simple object, it must be a class with a prototype";e.type=t,LiteGraph.debug&&console.log("Node registered: "+t);var n,i=e.name,o=t.lastIndexOf("/");for(n in e.category=t.substring(0,o),e.title||(e.title=i),LGraphNode.prototype)e.prototype[n]||(e.prototype[n]=LGraphNode.prototype[n]);o=this.registered_node_types[t];if(o&&console.log("replacing node type: "+t),!Object.prototype.hasOwnProperty.call(e.prototype,"shape")&&(Object.defineProperty(e.prototype,"shape",{set:function(t){switch(t){case"default":delete this._shape;break;case"box":this._shape=LiteGraph.BOX_SHAPE;break;case"round":this._shape=LiteGraph.ROUND_SHAPE;break;case"circle":this._shape=LiteGraph.CIRCLE_SHAPE;break;case"card":this._shape=LiteGraph.CARD_SHAPE;break;default:this._shape=t}},get:function(){return this._shape},enumerable:!0,configurable:!0}),e.supported_extensions))for(var s in e.supported_extensions){s=e.supported_extensions[s];s&&s.constructor===String&&(this.node_types_by_file_extension[s.toLowerCase()]=e)}(this.registered_node_types[t]=e).constructor.name&&(this.Nodes[i]=e),LiteGraph.onNodeTypeRegistered&&LiteGraph.onNodeTypeRegistered(t,e),o&&LiteGraph.onNodeTypeReplaced&&LiteGraph.onNodeTypeReplaced(t,e,o),e.prototype.onPropertyChange&&console.warn("LiteGraph node class "+t+" has onPropertyChange method, it must be called onPropertyChanged with d at the end"),this.auto_load_slot_types&&new e(e.title||"tmpnode")},unregisterNodeType:function(t){var e=t.constructor===String?this.registered_node_types[t]:t;if(!e)throw"node type not found: "+t;delete this.registered_node_types[e.type],e.constructor.name&&delete this.Nodes[e.constructor.name]},registerNodeAndSlotType:function(t,e,n){n=n||!1;var i=(t.constructor===String&&"anonymous"!==this.registered_node_types[t]?this.registered_node_types[t]:t).constructor.type;let o=[];o="string"==typeof e?e.split(","):e==this.EVENT||e==this.ACTION?["_event_"]:["*"];for(let e=0;e(t^16*Math.random()>>t/4).toString(16))},isValidConnection:function(t,e){if(""!=e&&"*"!==e||(e=0),!(t=""!=t&&"*"!==t?t:0)||!e||t==e||t==LiteGraph.EVENT&&e==LiteGraph.ACTION)return!0;if(t=String(t),e=String(e),t=t.toLowerCase(),e=e.toLowerCase(),-1==t.indexOf(",")&&-1==e.indexOf(","))return t==e;for(var n=t.split(","),i=e.split(","),o=0;oe&&(e=h.size[p]),o==LiteGraph.VERTICAL_LAYOUT?0:1);n+=h.size[p]+i+LiteGraph.NODE_TITLE_HEIGHT}r+=e+i}}this.setDirtyCanvas(!0,!0)},LGraph.prototype.getTime=function(){return this.globaltime},LGraph.prototype.getFixedTime=function(){return this.fixedtime},LGraph.prototype.getElapsedTime=function(){return this.elapsed_time},LGraph.prototype.sendEventToAllNodes=function(t,e,n){n=n||LiteGraph.ALWAYS;var i=this._nodes_in_order||this._nodes;if(i)for(var o=0,s=i.length;o=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";return LiteGraph.use_uuids?null!=t.id&&-1!=t.id||(t.id=LiteGraph.uuidv4()):null==t.id||-1==t.id?t.id=++this.last_node_id:this.last_node_id=this.outputs.length)){var n=this.outputs[t];if(n&&(n._data=e,this.outputs[t].links))for(var i=0;i=this.outputs.length)){var n=this.outputs[t];if(n&&(n.type=e,this.outputs[t].links))for(var i=0;i=this.inputs.length||null==this.inputs[t].link))return t=this.inputs[t].link,(t=this.graph.links[t])?(e&&(e=this.graph.getNodeById(t.origin_id))&&(e.updateOutputData?e.updateOutputData(t.origin_slot):e.onExecute&&e.onExecute()),t.data):null},LGraphNode.prototype.getInputDataType=function(t){var e;return this.inputs&&!(t>=this.inputs.length||null==this.inputs[t].link)&&(t=this.inputs[t].link,t=this.graph.links[t])?(e=this.graph.getNodeById(t.origin_id))?(e=e.outputs[t.origin_slot])?e.type:null:t.type:null},LGraphNode.prototype.getInputDataByName=function(t,e){t=this.findInputSlot(t);return-1==t?null:this.getInputData(t,e)},LGraphNode.prototype.isInputConnected=function(t){return!!this.inputs&&t=this.inputs.length)&&(t=this.inputs[t])&&null!==t.link&&(t=this.graph.links[t.link])?this.graph.getNodeById(t.origin_id):null},LGraphNode.prototype.getInputOrProperty=function(t){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[t]:null;for(var e=0,n=this.inputs.length;e=this.outputs.length?null:this.outputs[t]._data},LGraphNode.prototype.getOutputInfo=function(t){return this.outputs&&t=this.outputs.length)return null;var e=this.outputs[t];if(!e.links||0==e.links.length)return null;for(var n=[],i=0;it&&this.pos[1]-o-ne)return!0;return!1},LGraphNode.prototype.getSlotInPosition=function(t,e){var n=new Float32Array(2);if(this.inputs)for(var i=0,o=this.inputs.length;i=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;if(!(e=e&&e.constructor===Number?this.graph.getNodeById(e):e))throw"target node is null";if(e==this)return null;if(n.constructor===String){if(-1==(n=e.findInputSlot(n)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+n),null}else if(n===LiteGraph.EVENT){if(!LiteGraph.do_add_triggers_slots)return null;e.changeMode(LiteGraph.ON_TRIGGER),n=e.findInputSlot("onTrigger")}else if(!e.inputs||n>=e.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;var i,o=!1,s=e.inputs[n],a=this.outputs[t];return this.outputs[t]?!1!==(n=e.onBeforeConnectInput?e.onBeforeConnectInput(n):n)&&null!==n&&LiteGraph.isValidConnection(a.type,s.type)?e.onConnectInput&&!1===e.onConnectInput(n,a.type,a,this,t)||this.onConnectOutput&&!1===this.onConnectOutput(t,s.type,s,e,n)?null:(e.inputs[n]&&null!=e.inputs[n].link&&(this.graph.beforeChange(),e.disconnectInput(n,{doProcessChange:!1}),o=!0),null!==a.links&&a.links.length&&a.type===LiteGraph.EVENT&&!LiteGraph.allow_multi_output_for_events&&(this.graph.beforeChange(),this.disconnectOutput(t,!1,{doProcessChange:!1}),o=!0),i=new LLink(LiteGraph.use_uuids?LiteGraph.uuidv4():++this.graph.last_link_id,s.type||a.type,this.id,t,e.id,n),this.graph.links[i.id]=i,null==a.links&&(a.links=[]),a.links.push(i.id),e.inputs[n].link=i.id,this.graph&&this.graph._version++,this.onConnectionsChange&&this.onConnectionsChange(LiteGraph.OUTPUT,t,!0,i,a),e.onConnectionsChange&&e.onConnectionsChange(LiteGraph.INPUT,n,!0,i,s),this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(LiteGraph.INPUT,e,n,this,t),this.graph.onNodeConnectionChange(LiteGraph.OUTPUT,this,t,e,n)),this.setDirtyCanvas(!1,!0),this.graph.afterChange(),this.graph.connectionChange(this,i),i):(this.setDirtyCanvas(!1,!0),o&&this.graph.connectionChange(this,null),null):null},LGraphNode.prototype.disconnectOutput=function(t,e){if(t.constructor===String){if(-1==(t=this.findOutputSlot(t)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+t),!1}else if(!this.outputs||t>=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var n=this.outputs[t];if(!n||!n.links||0==n.links.length)return!1;if(e){if(!(e=e.constructor===Number?this.graph.getNodeById(e):e))throw"Target Node not found";for(var i=0,o=n.links.length;i=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var e=this.inputs[t];if(!e)return!1;var n=this.inputs[t].link;if(null!=n){this.inputs[t].link=null;var i=this.graph.links[n];if(i){var o=this.graph.getNodeById(i.origin_id);if(!o)return!1;var s=o.outputs[i.origin_slot];if(!s||!s.links||0==s.links.length)return!1;for(var a=0,r=s.links.length;aLGraphNode.MAX_CONSOLE&&this.console.shift(),this.graph.onNodeTrace&&this.graph.onNodeTrace(this,t)},LGraphNode.prototype.setDirtyCanvas=function(t,e){this.graph&&this.graph.sendActionToCanvas("setDirty",[t,e])},LGraphNode.prototype.loadImage=function(t){var e=new Image,n=(e.src=LiteGraph.node_images_path+t,e.ready=!1,this);return e.onload=function(){this.ready=!0,n.setDirtyCanvas(!0)},e},LGraphNode.prototype.captureInput=function(t){if(this.graph&&this.graph.list_of_graphcanvas)for(var e=this.graph.list_of_graphcanvas,n=0;n=this.viewport[0]&&n=this.viewport[1]&&ithis.max_scale&&(t=this.max_scale),t!=this.scale&&this.element&&(n=this.element.getBoundingClientRect())&&(e=e||[.5*n.width,.5*n.height],n=this.convertCanvasToOffset(e),this.scale=t,Math.abs(this.scale-1)<.01&&(this.scale=1),e=[(t=this.convertCanvasToOffset(e))[0]-n[0],t[1]-n[1]],this.offset[0]+=e[0],this.offset[1]+=e[1],this.onredraw)&&this.onredraw(this)},DragAndScale.prototype.changeDeltaScale=function(t,e){this.changeScale(this.scale*t,e)},DragAndScale.prototype.reset=function(){this.scale=1,this.offset[0]=0,this.offset[1]=0},global.LGraphCanvas=LiteGraph.LGraphCanvas=LGraphCanvas,LGraphCanvas.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",LGraphCanvas.link_type_colors={"-1":LiteGraph.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"},LGraphCanvas.gradients={},LGraphCanvas.prototype.clear=function(){this.frame=0,this.last_draw_time=0,this.render_time=0,this.fps=0,this.dragging_rectangle=null,this.selected_nodes={},this.selected_group=null,this.visible_nodes=[],this.node_dragged=null,this.node_over=null,this.node_capturing_input=null,this.connecting_node=null,this.highlighted_links={},this.dragging_canvas=!1,this.dirty_canvas=!0,this.dirty_bgcanvas=!0,this.dirty_area=null,this.node_in_panel=null,this.node_widget=null,this.last_mouse=[0,0],this.last_mouseclick=0,this.pointer_is_down=!1,this.pointer_is_double=!1,this.visible_area.set([0,0,0,0]),this.onClear&&this.onClear()},LGraphCanvas.prototype.setGraph=function(t,e){this.graph!=t&&(e||this.clear(),!t&&this.graph?this.graph.detachCanvas(this):(t.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))},LGraphCanvas.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph},LGraphCanvas.prototype.openSubgraph=function(t){if(!t)throw"graph cannot be null";if(this.graph==t)throw"graph cannot be the same";this.clear(),this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph)),t.attachCanvas(this),this.checkPanels(),this.setDirty(!0,!0)},LGraphCanvas.prototype.closeSubgraph=function(){var t,e;this._graph_stack&&0!=this._graph_stack.length&&(t=this.graph._subgraph_node,e=this._graph_stack.pop(),this.selected_nodes={},this.highlighted_links={},e.attachCanvas(this),this.setDirty(!0,!0),t&&(this.centerOnNode(t),this.selectNodes([t])),this.ds.offset=[0,0],this.ds.scale=1)},LGraphCanvas.prototype.getCurrentGraph=function(){return this.graph},LGraphCanvas.prototype.setCanvas=function(t,e){if(t&&t.constructor===String&&!(t=document.getElementById(t)))throw"Error creating LiteGraph canvas: Canvas not found";if(t!==this.canvas&&(t||!this.canvas||e||this.unbindEvents(),this.canvas=t,this.ds.element=t)){if(t.className+=" lgraphcanvas",t.data=this,t.tabindex="1",this.bgcanvas=null,this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height),null==t.getContext){if("canvas"!=t.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+t.localName;throw"This browser doesn't support Canvas"}null==(this.ctx=t.getContext("2d"))&&(t.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL()),e||this.bindEvents()}},LGraphCanvas.prototype._doNothing=function(t){return t.preventDefault(),!1},LGraphCanvas.prototype._doReturnTrue=function(t){return t.preventDefault(),!0},LGraphCanvas.prototype.bindEvents=function(){var t,e;this._events_binded?console.warn("LGraphCanvas: events already binded"):(t=this.canvas,e=this.getCanvasWindow().document,this._mousedown_callback=this.processMouseDown.bind(this),this._mousewheel_callback=this.processMouseWheel.bind(this),this._mousemove_callback=this.processMouseMove.bind(this),this._mouseup_callback=this.processMouseUp.bind(this),LiteGraph.pointerListenerAdd(t,"down",this._mousedown_callback,!0),t.addEventListener("mousewheel",this._mousewheel_callback,!1),LiteGraph.pointerListenerAdd(t,"up",this._mouseup_callback,!0),LiteGraph.pointerListenerAdd(t,"move",this._mousemove_callback),t.addEventListener("contextmenu",this._doNothing),t.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1),this._key_callback=this.processKey.bind(this),t.setAttribute("tabindex",1),t.addEventListener("keydown",this._key_callback,!0),e.addEventListener("keyup",this._key_callback,!0),this._ondrop_callback=this.processDrop.bind(this),t.addEventListener("dragover",this._doNothing,!1),t.addEventListener("dragend",this._doNothing,!1),t.addEventListener("drop",this._ondrop_callback,!1),t.addEventListener("dragenter",this._doReturnTrue,!1),this._events_binded=!0)},LGraphCanvas.prototype.unbindEvents=function(){var t;this._events_binded?(t=this.getCanvasWindow().document,LiteGraph.pointerListenerRemove(this.canvas,"move",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"up",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"down",this._mousedown_callback),this.canvas.removeEventListener("mousewheel",this._mousewheel_callback),this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback),this.canvas.removeEventListener("keydown",this._key_callback),t.removeEventListener("keyup",this._key_callback),this.canvas.removeEventListener("contextmenu",this._doNothing),this.canvas.removeEventListener("drop",this._ondrop_callback),this.canvas.removeEventListener("dragenter",this._doReturnTrue),this._mousedown_callback=null,this._mousewheel_callback=null,this._key_callback=null,this._ondrop_callback=null,this._events_binded=!1):console.warn("LGraphCanvas: no events binded")},LGraphCanvas.getFileExtension=function(t){var e=t.indexOf("?"),e=(t=-1!=e?t.substr(0,e):t).lastIndexOf(".");return-1==e?"":t.substr(e+1).toLowerCase()},LGraphCanvas.prototype.enableWebGL=function(){if("undefined"==typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"==typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas),this.ctx.webgl=!0,this.bgcanvas=this.canvas,this.bgctx=this.gl,this.canvas.webgl_enabled=!0},LGraphCanvas.prototype.setDirty=function(t,e){t&&(this.dirty_canvas=!0),e&&(this.dirty_bgcanvas=!0)},LGraphCanvas.prototype.getCanvasWindow=function(){var t;return this.canvas?(t=this.canvas.ownerDocument).defaultView||t.parentWindow:window},LGraphCanvas.prototype.startRendering=function(){function e(){this.pause_rendering||this.draw();var t=this.getCanvasWindow();this.is_rendering&&t.requestAnimationFrame(e.bind(this))}this.is_rendering||(this.is_rendering=!0,e.call(this))},LGraphCanvas.prototype.stopRendering=function(){this.is_rendering=!1},LGraphCanvas.prototype.blockClick=function(){this.block_click=!0,this.last_mouseclick=0},LGraphCanvas.prototype.processMouseDown=function(t){if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){this.adjustMouseEvent(t);var e=this.getCanvasWindow(),n=(e.document,LGraphCanvas.active_canvas=this),i=t.clientX,o=t.clientY,i=(this.ds.viewport=this.viewport,!this.viewport||(this.viewport,i>=this.viewport[0]&&i=this.viewport[1]&&og[0]+4||t.canvasYg[1]+4)){this.showLinkMenu(_,t),this.over_link_center=null;break}}this.selected_group=this.graph.getGroupOnPos(t.canvasX,t.canvasY),this.selected_group_resizing=!1,this.selected_group&&!this.read_only&&(t.ctrlKey&&(this.dragging_rectangle=null),distance([t.canvasX,t.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale<10?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes()),r&&!this.read_only&&this.allow_searchbox&&(this.showSearchBox(t),t.preventDefault(),t.stopPropagation()),i=!0}}else{if(this.live_mode||s.flags.pinned||this.bringToFront(s),this.allow_interaction&&!this.connecting_node&&!s.flags.collapsed&&!this.live_mode)if(!a&&!1!==s.resizable&&isInsideRectangle(t.canvasX,t.canvasY,s.pos[0]+s.size[0]-5,s.pos[1]+s.size[1]-5,10,10))this.graph.beforeChange(),this.resizing_node=s,this.canvas.style.cursor="se-resize",a=!0;else{if(s.outputs)for(var d=0,v=s.outputs.length;ds.size[0]-LiteGraph.NODE_TITLE_HEIGHT&&o[1]<0&&(n=this,setTimeout(function(){n.openSubgraph(s.subgraph)},10)),this.live_mode&&(u=i=!0)),u?s.is_selected||this.processNodeSelected(s,t):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=s),this.processNodeSelected(s,t)),this.dirty_canvas=!0)}!a&&i&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}return this.last_mouse[0]=t.clientX,this.last_mouse[1]=t.clientY,this.last_mouseclick=LiteGraph.getTime(),this.last_mouse_dragging=!0,this.graph.change(),e.document.activeElement&&("input"==e.document.activeElement.nodeName.toLowerCase()||"textarea"==e.document.activeElement.nodeName.toLowerCase())||t.preventDefault(),t.stopPropagation(),this.onMouseDown&&this.onMouseDown(t),!1}}}},LGraphCanvas.prototype.processMouseMove=function(t){if(this.autoresize&&this.resize(),this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){(LGraphCanvas.active_canvas=this).adjustMouseEvent(t);var e=[t.clientX,t.clientY],n=(this.mouse[0]=e[0],this.mouse[1]=e[1],[e[0]-this.last_mouse[0],e[1]-this.last_mouse[1]]);if(this.last_mouse=e,this.graph_mouse[0]=t.canvasX,this.graph_mouse[1]=t.canvasY,!this.block_click){t.dragging=this.last_mouse_dragging,this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t,this.node_widget[1]),this.dirty_canvas=!0);var i=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=t.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=t.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[t.canvasX-this.selected_group.pos[0],t.canvasY-this.selected_group.pos[1]]:(e=n[0]/this.ds.scale,r=n[1]/this.ds.scale,this.selected_group.move(e,r,t.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=n[0]/this.ds.scale,this.ds.offset[1]+=n[1]/this.ds.scale,this.dirty_canvas=!0,this.dirty_bgcanvas=!0;else if((this.allow_interaction||i&&i.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);for(var o,s,a,r,l=0,h=this.graph._nodes.length;lc[0]+4||t.canvasYc[1]+4)){p=u;break}}p!=this.over_link_center&&(this.over_link_center=p,this.dirty_canvas=!0),this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&&this.node_capturing_input!=i&&this.node_capturing_input.onMouseMove&&this.node_capturing_input.onMouseMove(t,[t.canvasX-this.node_capturing_input.pos[0],t.canvasY-this.node_capturing_input.pos[1]],this),this.node_dragged&&!this.live_mode){for(var l in this.selected_nodes){var d=this.selected_nodes[l];d.pos[0]+=n[0]/this.ds.scale,d.pos[1]+=n[1]/this.ds.scale,d.is_selected||this.processNodeSelected(d,t)}this.dirty_canvas=!0,this.dirty_bgcanvas=!0}this.resizing_node&&!this.live_mode&&(e=[t.canvasX-this.resizing_node.pos[0],t.canvasY-this.resizing_node.pos[1]],r=this.resizing_node.computeSize(),e[0]=Math.max(r[0],e[0]),e[1]=Math.max(r[1],e[1]),this.resizing_node.setSize(e),this.canvas.style.cursor="se-resize",this.dirty_canvas=!0,this.dirty_bgcanvas=!0)}}return t.preventDefault(),!1}},LGraphCanvas.prototype.processMouseUp=function(t){var e=void 0===t.isPrimary||t.isPrimary;if(!e)return!1;if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){var n=this.getCanvasWindow().document,n=((LGraphCanvas.active_canvas=this).options.skip_events||(LiteGraph.pointerListenerRemove(n,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerRemove(n,"up",this._mouseup_callback,!0)),this.adjustMouseEvent(t),LiteGraph.getTime());if(t.click_time=n-this.last_mouseclick,this.last_mouse_dragging=!1,this.last_click_position=null,this.block_click&&(this.block_click=!1),1==t.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t),this.node_widget=null,this.selected_group&&(n=this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),a=this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),this.selected_group.move(n,a,t.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null),this.selected_group_resizing=!1;var i,n=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){var o=this.graph._nodes,s=new Float32Array(4),a=Math.abs(this.dragging_rectangle[2]),r=Math.abs(this.dragging_rectangle[3]),l=this.dragging_rectangle[2]<0?this.dragging_rectangle[0]-a:this.dragging_rectangle[0],h=this.dragging_rectangle[3]<0?this.dragging_rectangle[1]-r:this.dragging_rectangle[1];if(this.dragging_rectangle[0]=l,this.dragging_rectangle[1]=h,this.dragging_rectangle[2]=a,this.dragging_rectangle[3]=r,!n||10=this.viewport[0]&&n=this.viewport[1]&&in.nodes[s].pos[0]&&(i[0]=n.nodes[s].pos[0],o[0]=s),i[1]>n.nodes[s].pos[1]&&(i[1]=n.nodes[s].pos[1],o[1]=s)):(i=[n.nodes[s].pos[0],n.nodes[s].pos[1]],o=[s,s]);for(var a=[],s=0;s=this.viewport[0]&&e=this.viewport[1]&&n> ";e.fillText(i+n.getTitle(),.5*t.width,40),e.restore()}var s,n=!1;this.onRenderBackground&&(n=this.onRenderBackground(t,e)),this.viewport||(e.restore(),e.setTransform(1,0,0,1,0,0)),this.visible_links.length=0,this.graph&&(e.save(),this.ds.toCanvasContext(e),this.ds.scale<1.5&&!n&&this.clear_background_color&&(e.fillStyle=this.clear_background_color,e.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3])),this.background_image&&.5t[2]&&(t[2]=e),nt[3]&&(t[3]=n)}function isInsideBounding(t,e){return!(t[0]e[1][0]||t[1]>e[1][1])}function overlapBounding(t,e){var n=t[0]+t[2];return!(e[0]+e[2]e[1]+e[3]||nh.width-p.width-10&&(i=h.width-p.width-10),h.height)&&u>h.height-p.height-10&&(u=h.height-p.height-10),o.style.left=i+"px",o.style.top=u+"px",n.scale&&(o.style.transform="scale("+n.scale+")")}function CurveEditor(t){this.points=t,this.selected=-1,this.nearest=-1,this.size=null,this.must_update=!0,this.margin=5}function clamp(t,e,n){return te[1]?0:Math.PI,t.save(),t.translate(d[0],d[1]),t.rotate(w),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore(),t.save(),t.translate(o[0],o[1]),t.rotate(T),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore()),t.beginPath(),t.arc(N[0],N[1],5,0,2*Math.PI),t.fill()),s){t.fillStyle=a;for(_=0;_<5;++_){var E=(.001*LiteGraph.getTime()+.2*_)%1,N=this.computeConnectionPoint(e,n,E,r,l);t.beginPath(),t.arc(N[0],N[1],5,0,2*Math.PI),t.fill()}}},LGraphCanvas.prototype.computeConnectionPoint=function(t,e,n,i,o){i=i||LiteGraph.RIGHT,o=o||LiteGraph.LEFT;var s=distance(t,e),a=t,r=[t[0],t[1]],l=[e[0],e[1]],t=e;switch(i){case LiteGraph.LEFT:r[0]+=-.25*s;break;case LiteGraph.RIGHT:r[0]+=.25*s;break;case LiteGraph.UP:r[1]+=-.25*s;break;case LiteGraph.DOWN:r[1]+=.25*s}switch(o){case LiteGraph.LEFT:l[0]+=-.25*s;break;case LiteGraph.RIGHT:l[0]+=.25*s;break;case LiteGraph.UP:l[1]+=-.25*s;break;case LiteGraph.DOWN:l[1]+=.25*s}e=(1-n)*(1-n)*(1-n),i=(1-n)*(1-n)*3*n,o=3*(1-n)*(n*n),n*=n*n;return[e*a[0]+i*r[0]+o*l[0]+n*t[0],e*a[1]+i*r[1]+o*l[1]+n*t[1]]},LGraphCanvas.prototype.drawExecutionOrder=function(t){t.shadowColor="transparent",t.globalAlpha=.25,t.textAlign="center",t.strokeStyle="white",t.globalAlpha=.75;for(var e=this.visible_nodes,n=0;nw.last_y+widget_height||void 0===w.last_y)){var old_value=w.value;switch(w.type){case"button":event.type===LiteGraph.pointerevents_method+"down"&&(w.callback&&setTimeout(function(){w.callback(w,that,node,pos,event)},20),w.clicked=!0,this.dirty_canvas=!0);break;case"slider":var old_value=w.value,nvalue=clamp((x-15)/(widget_width-30),0,1);w.options.read_only||(w.value=w.options.min+(w.options.max-w.options.min)*nvalue,old_value!=w.value&&setTimeout(function(){inner_value_change(w,w.value)},20),this.dirty_canvas=!0);break;case"number":case"combo":var old_value=w.value,values,values_list,delta,index,text_values,menu,delta;function inner_clicked(t,e,n){return values!=values_list&&(t=text_values.indexOf(t)),this.value=t,inner_value_change(this,t),!(that.dirty_canvas=!0)}event.type==LiteGraph.pointerevents_method+"move"&&"number"==w.type?(deltaX&&(w.value+=.1*deltaX*(w.options.step||1)),null!=w.options.min&&w.valuew.options.max&&(w.value=w.options.max)):event.type==LiteGraph.pointerevents_method+"down"?(values=w.options.values,values&&values.constructor===Function&&(values=w.options.values(w,node)),values_list=null,"number"!=w.type&&(values_list=values.constructor===Array?values:Object.keys(values)),delta=x<40?-1:widget_width-40w.options.max&&(w.value=w.options.max)):delta?(index=-1,this.last_mouseclick=0,index=values.constructor===Object?values_list.indexOf(String(w.value))+delta:values_list.indexOf(w.value)+delta,index>=values_list.length&&(index=values_list.length-1),index<0&&(index=0),values.constructor===Array?w.value=values[index]:w.value=index):(text_values=values!=values_list?Object.values(values):values,menu=new LiteGraph.ContextMenu(text_values,{scale:Math.max(1,this.ds.scale),event:event,className:"dark",callback:inner_clicked.bind(w)},ref_window))):event.type==LiteGraph.pointerevents_method+"up"&&"number"==w.type&&(delta=x<40?-1:widget_width-40n.pos[0]+n.size[0])&&(n=s),(null===i||r+h>i.pos[1]+i.size[1])&&(i=s),(null===o||a"+(h.label||o)+""+t+"",value:o})}if(l.length)return new LiteGraph.ContextMenu(l,{event:n,callback:function(t,e,n,i){var o;s&&(o=this.getBoundingClientRect(),a.showEditPropertyValue(s,t.value,{position:[o.left,o.top]}))},parentMenu:i,allow_html:!0,node:s},r),!1}},LGraphCanvas.decodeHTML=function(t){var e=document.createElement("div");return e.innerText=t,e.innerHTML},LGraphCanvas.onMenuResizeNode=function(t,e,n,i,o){if(o){var s=function(t){t.size=t.computeSize(),t.onResize&&t.onResize(t.size)},a=LGraphCanvas.active_canvas;if(!a.selected_nodes||Object.keys(a.selected_nodes).length<=1)s(o);else for(var r in a.selected_nodes)s(a.selected_nodes[r]);o.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.showLinkMenu=function(i,t){var o=this,s=o.graph.getNodeById(i.origin_id),a=o.graph.getNodeById(i.target_id),r=!1,l=(s&&s.outputs&&s.outputs[i.origin_slot]&&(r=s.outputs[i.origin_slot].type),!1),h=(a&&a.outputs&&a.outputs[i.target_slot]&&(l=a.inputs[i.target_slot].type),new LiteGraph.ContextMenu(["Add Node",null,"Delete",null],{event:t,title:null!=i.data?i.data.constructor.name:null,callback:function(t,e,n){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,n,h,function(t){t.inputs&&t.inputs.length&&t.outputs&&t.outputs.length&&s.connectByType(i.origin_slot,t,r)&&(t.connectByType(i.target_slot,a,l),t.pos[0]-=.5*t.size[0])});break;case"Delete":o.graph.removeLink(i.id)}}}));return!1},LGraphCanvas.prototype.createDefaultNodeForSlot=function(t){var t=t||{},e=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},t),n=e.nodeFrom&&null!==e.slotFrom,t=!n&&e.nodeTo&&null!==e.slotTo;if(n||t)if(e.nodeType){var i=n?e.nodeFrom:e.nodeTo,o=n?e.slotFrom:e.slotTo,s=!1;switch(typeof o){case"string":s=n?i.findOutputSlot(o,!1):i.findInputSlot(o,!1),o=(n?i.outputs:i.inputs)[o];break;case"object":s=n?i.findOutputSlot(o.name):i.findInputSlot(o.name);break;case"number":s=o,o=(n?i.outputs:i.inputs)[o];break;default:return console.warn("Cant get slot information "+o),!1}!1!==o&&!1!==s||console.warn("createDefaultNodeForSlot bad slotX "+o+" "+s);var a=o.type==LiteGraph.EVENT?"_event_":o.type,r=n?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(r&&r[a]){if(o.link,nodeNewType=!1,"object"==typeof r[a]||"array"==typeof r[a]){for(var l in r[a])if(e.nodeType==r[a][l]||"AUTO"==e.nodeType){nodeNewType=r[a][l];break}}else e.nodeType!=r[a]&&"AUTO"!=e.nodeType||(nodeNewType=r[a]);if(nodeNewType){var h=!1,p=("object"==typeof nodeNewType&&nodeNewType.node&&(nodeNewType=(h=nodeNewType).node),LiteGraph.createNode(nodeNewType));if(p){if(h){if(h.properties)for(var u in h.properties)p.addProperty(u,h.properties[u]);if(h.inputs)for(var u in p.inputs=[],h.inputs)p.addOutput(h.inputs[u][0],h.inputs[u][1]);if(h.outputs)for(var u in p.outputs=[],h.outputs)p.addOutput(h.outputs[u][0],h.outputs[u][1]);h.title&&(p.title=h.title),h.json&&p.configure(h.json)}return this.graph.add(p),p.pos=[e.position[0]+e.posAdd[0]+(e.posSizeFix[0]?e.posSizeFix[0]*p.size[0]:0),e.position[1]+e.posAdd[1]+(e.posSizeFix[1]?e.posSizeFix[1]*p.size[1]:0)],n?e.nodeFrom.connectByType(s,p,a):e.nodeTo.connectByTypeOutput(s,p,a),!0}console.log("failed creating "+nodeNewType)}}}else console.warn("No type to createDefaultNodeForSlot");else console.warn("No data passed to createDefaultNodeForSlot "+e.nodeFrom+" "+e.slotFrom+" "+e.nodeTo+" "+e.slotTo);return!1},LGraphCanvas.prototype.showConnectionMenu=function(t){var t=t||{},i=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},t),o=this,s=i.nodeFrom&&i.slotFrom,t=!s&&i.nodeTo&&i.slotTo;if(s||t){var e=s?i.nodeFrom:i.nodeTo,a=s?i.slotFrom:i.slotTo,r=!1;switch(typeof a){case"string":r=s?e.findOutputSlot(a,!1):e.findInputSlot(a,!1),a=(s?e.outputs:e.inputs)[a];break;case"object":r=s?e.findOutputSlot(a.name):e.findInputSlot(a.name);break;case"number":r=a,a=(s?e.outputs:e.inputs)[a];break;default:return console.warn("Cant get slot information "+a),!1}var n=["Add Node",null],l=(o.allow_searchbox&&(n.push("Search"),n.push(null)),a.type==LiteGraph.EVENT?"_event_":a.type),h=s?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(h&&h[l])if("object"==typeof h[l]||"array"==typeof h[l])for(var p in h[l])n.push(h[l][p]);else n.push(h[l]);var u=new LiteGraph.ContextMenu(n,{event:i.e,title:(a&&""!=a.name?a.name+(l?" | ":""):"")+(a&&l?l:""),callback:function(t,e,n){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,n,u,function(t){s?i.nodeFrom.connectByType(r,t,l):i.nodeTo.connectByTypeOutput(r,t,l)});break;case"Search":s?o.showSearchBox(n,{node_from:i.nodeFrom,slot_from:a,type_filter_in:l}):o.showSearchBox(n,{node_to:i.nodeTo,slot_from:a,type_filter_out:l});break;default:o.createDefaultNodeForSlot(Object.assign(i,{position:[i.e.canvasX,i.e.canvasY],nodeType:t}))}}})}else console.warn("No data passed to showConnectionMenu");return!1},LGraphCanvas.onShowPropertyEditor=function(e,t,n,i,o){var s=e.property||"title",a=o[s],r=document.createElement("div");r.is_modified=!1,r.className="graphdialog",r.innerHTML="",r.close=function(){r.parentNode&&r.parentNode.removeChild(r)};r.querySelector(".name").innerText=s;var l=r.querySelector(".value");l&&(l.value=a,l.addEventListener("blur",function(t){this.focus()}),l.addEventListener("keydown",function(t){if(r.is_modified=!0,27==t.keyCode)r.close();else if(13==t.keyCode)d();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}));var a=LGraphCanvas.active_canvas.canvas,h=a.getBoundingClientRect(),p=-20,u=-20;h&&(p-=h.left,u-=h.top),event?(r.style.left=event.clientX+p+"px",r.style.top=event.clientY+u+"px"):(r.style.left=.5*a.width+p+"px",r.style.top=.5*a.height+u+"px");r.querySelector("button").addEventListener("click",d),a.parentNode.appendChild(r),l&&l.focus();var c=null;function d(){var t;l&&(t=l.value,"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t)),o[s]=t,r.parentNode&&r.parentNode.removeChild(r),o.setDirtyCanvas(!0,!0))}r.addEventListener("mouseleave",function(t){LiteGraph.dialog_close_on_mouse_leave&&!r.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(c=setTimeout(r.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),r.addEventListener("mouseenter",function(t){LiteGraph.dialog_close_on_mouse_leave&&c&&clearTimeout(c)})},LGraphCanvas.prototype.prompt=function(t,e,n,i,o){var s=this,a=(t=t||"",document.createElement("div"));a.is_modified=!1,a.className="graphdialog rounded",a.innerHTML=o?" ":" ",a.close=function(){s.prompt_box=null,a.parentNode&&a.parentNode.removeChild(a)};var o=LGraphCanvas.active_canvas.canvas,r=(o.parentNode.appendChild(a),1LGraphCanvas.search_limit)break}}var h=null;if(Array.prototype.filter)h=Object.keys(LiteGraph.registered_node_types).filter(p);else for(var e in h=[],LiteGraph.registered_node_types)p(e)&&h.push(e);for(e=0;eLGraphCanvas.search_limit));e++);if(c.show_general_after_typefiltered&&(s.value||a.value)){for(var e in filtered_extra=[],LiteGraph.registered_node_types)p(e,{inTypeOverride:!(!s||!s.value)&&"*",outTypeOverride:!(!a||!a.value)&&"*"})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}if((s.value||a.value)&&0==g.childNodes.length&&c.show_general_if_none_on_typefilter){for(var e in filtered_extra=[],LiteGraph.registered_node_types)p(e,{skipFilter:!0})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}function p(t,e){var e=e||{},e=Object.assign({skipFilter:!1,inTypeOverride:!1,outTypeOverride:!1},e),n=LiteGraph.registered_node_types[t];if(r&&n.filter!=r)return!1;if((!c.show_all_if_empty||o)&&-1===t.toLowerCase().indexOf(o))return!1;if(c.do_type_filter&&!e.skipFilter){n=t,t=s.value;if(!1!==e.inTypeOverride&&(t=e.inTypeOverride),s&&t&&LiteGraph.registered_slot_in_types[t]&&LiteGraph.registered_slot_in_types[t].nodes){var i=LiteGraph.registered_slot_in_types[t].nodes.includes(n);if(!1===i)return!1}t=a.value;if(!1!==e.outTypeOverride&&(t=e.outTypeOverride),a&&t&&LiteGraph.registered_slot_out_types[t]&&LiteGraph.registered_slot_out_types[t].nodes){i=LiteGraph.registered_slot_out_types[t].nodes.includes(n);if(!1===i)return!1}}return!0}}function u(t,e){var n=document.createElement("div");v=v||t,n.innerText=t,n.dataset.type=escape(t),n.className="litegraph lite-search-item",e&&(n.className+=" "+e),n.addEventListener("click",function(t){C(unescape(this.dataset.type))}),g.appendChild(n)}}return l.style.left=b+"px",l.style.top=G+"px",s.layerY>o.height-200&&(g.style.maxHeight=o.height-s.layerY-20+"px"),y.focus(),c.show_all_on_open&&T(),l},LGraphCanvas.prototype.showEditPropertyValue=function(e,n,i){if(e&&void 0!==e.properties[n]){i=i||{};var o=e.getPropertyInfo(n),s=o.type,t="";if("string"==s||"number"==s||"array"==s||"object"==s)t="";else if("enum"!=s&&"combo"!=s||!o.values){if("boolean"!=s&&"toggle"!=s)return void console.warn("unknown type: "+s);t=""}else{for(var a in t=""}var l=this.createDialog(""+(o.label||n)+""+t+"",i),h=!1;return"enum"!=s&&"combo"!=s||!o.values?"boolean"==s||"toggle"==s?(h=l.querySelector("input"))&&h.addEventListener("click",function(t){l.modified(),u(!!h.checked)}):(h=l.querySelector("input"))&&(h.addEventListener("blur",function(t){this.focus()}),r=void 0!==e.properties[n]?e.properties[n]:"","string"!==s&&(r=JSON.stringify(r)),h.value=r,h.addEventListener("keydown",function(t){if(27==t.keyCode)l.close();else if(13==t.keyCode)p();else if(13!=t.keyCode)return void l.modified();t.preventDefault(),t.stopPropagation()})):(h=l.querySelector("select")).addEventListener("change",function(t){l.modified(),u(t.target.value)}),h&&h.focus(),l.querySelector("button").addEventListener("click",p),l}function p(){u(h.value)}function u(t){o&&o.values&&o.values.constructor===Object&&null!=o.values[t]&&(t=o.values[t]),"number"==typeof e.properties[n]&&(t=Number(t)),"array"!=s&&"object"!=s||(t=JSON.parse(t)),e.properties[n]=t,e.graph&&e.graph._version++,e.onPropertyChanged&&e.onPropertyChanged(n,t),i.onclose&&i.onclose(),l.close(),e.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.createDialog=function(t,e){e=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},e||{});var n=document.createElement("div"),t=(n.className="graphdialog",n.innerHTML=t,n.is_modified=!1,this.canvas.getBoundingClientRect()),i=-20,o=-20,s=(t&&(i-=t.left,o-=t.top),e.position?(i+=e.position[0],o+=e.position[1]):e.event?(i+=e.event.clientX,o+=e.event.clientY):(i+=.5*this.canvas.width,o+=.5*this.canvas.height),n.style.left=i+"px",n.style.top=o+"px",this.canvas.parentNode.appendChild(n),e.checkForInput&&(t=[],t=n.querySelectorAll("input"))&&t.forEach(function(t){t.addEventListener("keydown",function(t){if(n.modified(),27==t.keyCode)n.close();else if(13!=t.keyCode)return;t.preventDefault(),t.stopPropagation()}),t.focus()}),n.modified=function(){n.is_modified=!0},n.close=function(){n.parentNode&&n.parentNode.removeChild(n)},null),a=!1,i=(n.addEventListener("mouseleave",function(t){a||(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&!n.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(s=setTimeout(n.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),n.addEventListener("mouseenter",function(t){(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&s&&clearTimeout(s)}),n.querySelectorAll("select"));return i&&i.forEach(function(t){t.addEventListener("click",function(t){a++}),t.addEventListener("blur",function(t){a=0}),t.addEventListener("change",function(t){a=-1})}),n},LGraphCanvas.prototype.createPanel=function(t,e){var h=(e=e||{}).window||window,p=document.createElement("div");return p.className="litegraph dialog",p.innerHTML="
",p.header=p.querySelector(".dialog-header"),e.width&&(p.style.width=e.width+(e.width.constructor===Number?"px":"")),e.height&&(p.style.height=e.height+(e.height.constructor===Number?"px":"")),e.closable&&((e=document.createElement("span")).innerHTML="✕",e.classList.add("close"),e.addEventListener("click",function(){p.close()}),p.header.appendChild(e)),p.title_element=p.querySelector(".dialog-title"),p.title_element.innerText=t,p.content=p.querySelector(".dialog-content"),p.alt_content=p.querySelector(".dialog-alt-content"),p.footer=p.querySelector(".dialog-footer"),p.close=function(){p.onClose&&"function"==typeof p.onClose&&p.onClose(),p.parentNode&&p.parentNode.removeChild(p),this.parentNode&&this.parentNode.removeChild(this)},p.toggleAltContent=function(t){var e;t=void 0!==t?(e=t?"block":"none",t?"none":"block"):(e="block"!=p.alt_content.style.display?"block":"none","block"!=p.alt_content.style.display?"none":"block"),p.alt_content.style.display=e,p.content.style.display=t},p.toggleFooterVisibility=function(t){t=void 0!==t?t?"block":"none":"block"!=p.footer.style.display?"block":"none",p.footer.style.display=t},p.clear=function(){this.content.innerHTML=""},p.addHTML=function(t,e,n){var i=document.createElement("div");return e&&(i.className=e),i.innerHTML=t,(n?p.footer:p.content).appendChild(i),i},p.addButton=function(t,e,n){var i=document.createElement("button");return i.innerText=t,i.options=n,i.classList.add("btn"),i.addEventListener("click",e),p.footer.appendChild(i),i},p.addSeparator=function(){var t=document.createElement("div");t.className="separator",p.content.appendChild(t)},p.addWidget=function(e,t,n,s,i){s=s||{};var o=String(n),a=("number"==(e=e.toLowerCase())&&(o=n.toFixed(3)),document.createElement("div")),r=(a.className="property",a.innerHTML="",a.querySelector(".property_name").innerText=s.label||t,a.querySelector(".property_value"));function l(t,e){s.callback&&s.callback(t,e,s),i&&i(t,e,s)}return r.innerText=o,a.dataset.property=t,a.dataset.type=s.type||e,a.options=s,a.value=n,"code"==e?a.addEventListener("click",function(t){p.inner_showCodePad(this.dataset.property)}):"boolean"==e?(a.classList.add("boolean"),n&&a.classList.add("bool-on"),a.addEventListener("click",function(){var t=this.dataset.property;this.value=!this.value,this.classList.toggle("bool-on"),this.querySelector(".property_value").innerText=this.value?"true":"false",l(t,this.value)})):"string"==e||"number"==e?(r.setAttribute("contenteditable",!0),r.addEventListener("keydown",function(t){"Enter"!=t.code||"string"==e&&t.shiftKey||(t.preventDefault(),this.blur())}),r.addEventListener("blur",function(){var t=this.innerText;l(this.parentNode.dataset.property,t="number"==this.parentNode.dataset.type?Number(t):t)})):"enum"!=e&&"combo"!=e||(o=LGraphCanvas.getPropertyPrintableValue(n,s.values),r.innerText=o,r.addEventListener("click",function(t){var e=s.values||[],i=this.parentNode.dataset.property,o=this;new LiteGraph.ContextMenu(e,{event:t,className:"dark",callback:function(t,e,n){return o.innerText=t,l(i,t),!1}},h)})),p.content.appendChild(a),a},p.onOpen&&"function"==typeof p.onOpen&&p.onOpen(),p},LGraphCanvas.getPropertyPrintableValue=function(t,e){if(!e)return String(t);if(e.constructor===Array)return String(t);if(e.constructor===Object){var n,i="";for(n in e)if(e[n]==t){i=n;break}return String(t)+" ("+i+")"}},LGraphCanvas.prototype.closePanels=function(){var t=document.querySelector("#node-panel");t&&t.close(),(t=document.querySelector("#option-panel"))&&t.close()},LGraphCanvas.prototype.showShowGraphOptionsPanel=function(t,e,n,i){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(e&&e.event&&e.event.target&&e.event.target.lgraphcanvas))return void console.warn("Canvas not found");var o=e.event.target.lgraphcanvas}else o=this;o.closePanels();e=o.getCanvasWindow();function s(t,e,n){n&&n.key&&(t=n.key),n.values&&(e=Object.values(n.values).indexOf(e)),o[t]=e}panel=o.createPanel("Options",{closable:!0,window:e,onOpen:function(){o.OPTIONPANEL_IS_OPEN=!0},onClose:function(){o.OPTIONPANEL_IS_OPEN=!1,o.options_panel=null}}),(o.options_panel=panel).id="option-panel",panel.classList.add("settings"),panel.content.innerHTML="";var a,r=LiteGraph.availableCanvasOptions;for(a in r.sort(),r){var l=r[a];panel.addWidget("boolean",l,o[l],{key:l,on:"True",off:"False"},s)}o.links_render_mode,panel.addWidget("combo","Render mode",LiteGraph.LINK_RENDER_MODES[o.links_render_mode],{key:"links_render_mode",values:LiteGraph.LINK_RENDER_MODES},s),panel.addSeparator(),panel.footer.innerHTML="",o.canvas.parentNode.appendChild(panel)},LGraphCanvas.prototype.showShowNodePanel=function(s){this.SELECTED_NODE=s,this.closePanels();var t=this.getCanvasWindow(),a=this,r=this.createPanel(s.title||"",{closable:!0,window:t,onOpen:function(){a.NODEPANEL_IS_OPEN=!0},onClose:function(){a.NODEPANEL_IS_OPEN=!1,a.node_panel=null}});function o(){r.content.innerHTML="",r.addHTML(""+s.type+""+(s.constructor.desc||"")+""),r.addHTML("

Properties

");function t(t,e){switch(a.graph.beforeChange(s),t){case"Title":s.title=e;break;case"Mode":var n=Object.values(LiteGraph.NODE_MODES).indexOf(e);0<=n&&LiteGraph.NODE_MODES[n]?s.changeMode(n):console.warn("unexpected mode: "+e);break;case"Color":LGraphCanvas.node_colors[e]?(s.color=LGraphCanvas.node_colors[e].color,s.bgcolor=LGraphCanvas.node_colors[e].bgcolor):console.warn("unexpected color: "+e);break;default:s.setProperty(t,e)}a.graph.afterChange(),a.dirty_canvas=!0}r.addWidget("string","Title",s.title,{},t),r.addWidget("combo","Mode",LiteGraph.NODE_MODES[s.mode],{values:LiteGraph.NODE_MODES},t);var e,n="";for(e in void 0!==s.color&&(n=Object.keys(LGraphCanvas.node_colors).filter(function(t){return LGraphCanvas.node_colors[t].color==s.color})),r.addWidget("combo","Color",n,{values:Object.keys(LGraphCanvas.node_colors)},t),s.properties){var i=s.properties[e],o=s.getPropertyInfo(e);o.type;s.onAddPropertyToPanel&&s.onAddPropertyToPanel(e,r)||r.addWidget(o.widget||o.type,e,i,o,t)}r.addSeparator(),s.onShowCustomPanelInfo&&s.onShowCustomPanelInfo(r),r.footer.innerHTML="",r.addButton("Delete",function(){s.block_delete||(s.graph.remove(s),r.close())}).classList.add("delete")}(a.node_panel=r).id="node-panel",r.node=s,r.classList.add("settings"),r.inner_showCodePad=function(e){r.classList.remove("settings"),r.classList.add("centered"),r.alt_content.innerHTML="";function n(){r.toggleAltContent(!1),r.toggleFooterVisibility(!0),i.parentNode.removeChild(i),r.classList.add("settings"),r.classList.remove("centered"),o()}var i=r.alt_content.querySelector("textarea"),t=(i.value=s.properties[e],i.addEventListener("keydown",function(t){"Enter"==t.code&&t.ctrlKey&&(s.setProperty(e,i.value),n())}),r.toggleAltContent(!0),r.toggleFooterVisibility(!1),i.style.height="calc(100% - 40px)",r.addButton("Assign",function(){s.setProperty(e,i.value),n()})),t=(r.alt_content.appendChild(t),r.addButton("Close",n));t.style.float="right",r.alt_content.appendChild(t)},o(),this.canvas.parentNode.appendChild(r)},LGraphCanvas.prototype.showSubgraphPropertiesDialog=function(o){console.log("showing subgraph properties dialog");var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),i=(t&&t.close(),this.createPanel("Subgraph Inputs",{closable:!0,width:500}));function s(){if(i.clear(),o.inputs)for(var t=0;t","subgraph_property")).dataset.name=n.name,e.dataset.slot=t,e.querySelector(".name").innerText=n.name,e.querySelector(".type").innerText=n.type,e.querySelector("button").addEventListener("click",function(t){o.removeInput(Number(this.parentNode.dataset.slot)),s()}))}}i.node=o,i.classList.add("subgraph_dialog");return i.addHTML(" + NameType","subgraph_property extra",!0).querySelector("button").addEventListener("click",function(t){var e=this.parentNode,n=e.querySelector(".name").value,i=e.querySelector(".type").value;n&&-1==o.findInputSlot(n)&&(o.addInput(n,i),e.querySelector(".name").value="",e.querySelector(".type").value="",s())}),s(),this.canvas.parentNode.appendChild(i),i},LGraphCanvas.prototype.showSubgraphPropertiesDialogRight=function(i){var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),o=(t&&t.close(),this.createPanel("Subgraph Outputs",{closable:!0,width:500}));function s(){if(o.clear(),i.outputs)for(var t=0;t","subgraph_property")).dataset.name=n.name,e.dataset.slot=t,e.querySelector(".name").innerText=n.name,e.querySelector(".type").innerText=n.type,e.querySelector("button").addEventListener("click",function(t){i.removeOutput(Number(this.parentNode.dataset.slot)),s()}))}}o.node=i,o.classList.add("subgraph_dialog");t=o.addHTML(" + NameType","subgraph_property extra",!0);function e(){var t=this.parentNode,e=t.querySelector(".name").value,n=t.querySelector(".type").value;e&&-1==i.findOutputSlot(e)&&(i.addOutput(e,n),t.querySelector(".name").value="",t.querySelector(".type").value="",s())}return t.querySelector(".name").addEventListener("keydown",function(t){13==t.keyCode&&e.apply(this)}),t.querySelector("button").addEventListener("click",function(t){e.apply(this)}),s(),this.canvas.parentNode.appendChild(o),o},LGraphCanvas.prototype.checkPanels=function(){if(this.canvas)for(var t=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),e=0;eNo color"}),LGraphCanvas.node_colors){var r=LGraphCanvas.node_colors[o],t={value:o,content:""+o+""};a.push(t)}return new LiteGraph.ContextMenu(a,{event:n,callback:function(t){if(s){var e=t.value?LGraphCanvas.node_colors[t.value]:null,n=function(t){e?t.constructor===LiteGraph.LGraphGroup?t.color=e.groupcolor:(t.color=e.color,t.bgcolor=e.bgcolor):(delete t.color,delete t.bgcolor)},i=LGraphCanvas.active_canvas;if(!i.selected_nodes||Object.keys(i.selected_nodes).length<=1)n(s);else for(var o in i.selected_nodes)n(i.selected_nodes[o]);s.setDirtyCanvas(!0,!0)}},parentMenu:i,node:s}),!1},LGraphCanvas.onMenuNodeShapes=function(t,e,n,i,o){if(o)return new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES,{event:n,callback:function(e){if(o){o.graph.beforeChange();var t=function(t){t.shape=e},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)t(o);else for(var i in n.selected_nodes)t(n.selected_nodes[i]);o.graph.afterChange(),o.setDirtyCanvas(!0)}},parentMenu:i,node:o}),!1;throw"no node passed"},LGraphCanvas.onMenuNodeRemove=function(t,e,n,i,o){if(!o)throw"no node passed";function s(t){!1!==t.removable&&a.remove(t)}var a=o.graph,r=(a.beforeChange(),LGraphCanvas.active_canvas);if(!r.selected_nodes||Object.keys(r.selected_nodes).length<=1)s(o);else for(var l in r.selected_nodes)s(r.selected_nodes[l]);a.afterChange(),o.setDirtyCanvas(!0,!0)},LGraphCanvas.onMenuNodeToSubgraph=function(t,e,n,i,o){var s,a,r=o.graph,l=LGraphCanvas.active_canvas;l&&((s=Object.values(l.selected_nodes||{})).length||(s=[o]),(a=LiteGraph.createNode("graph/subgraph")).pos=o.pos.concat(),r.add(a),a.buildFromNodes(s),l.deselectAllNodes(),o.setDirtyCanvas(!0,!0))},LGraphCanvas.onMenuNodeClone=function(t,e,n,i,o){o.graph.beforeChange();function s(t){var e;!1!==t.clonable&&(e=t.clone())&&(e.pos=[t.pos[0]+5,t.pos[1]+5],t.graph.add(e),a[e.id]=e)}var a={},r=LGraphCanvas.active_canvas;if(!r.selected_nodes||Object.keys(r.selected_nodes).length<=1)s(o);else for(var l in r.selected_nodes)s(r.selected_nodes[l]);Object.keys(a).length&&r.selectNodes(a),o.graph.afterChange(),o.setDirtyCanvas(!0,!0)},LGraphCanvas.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"},cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}},LGraphCanvas.prototype.getCanvasMenuOptions=function(){var t,e=null;return this.getMenuOptions?e=this.getMenuOptions():(e=[{content:"Add Node",has_submenu:!0,callback:LGraphCanvas.onMenuAdd},{content:"Add Group",callback:LGraphCanvas.onGroupAdd}],1Name",e),(a=s.querySelector("input"))&&o&&(a.value=o.label||""),r=function(){l.graph.beforeChange(),a.value&&(o&&(o.label=a.value),h.setDirty(!0)),s.close(),l.graph.afterChange()},s.querySelector("button").addEventListener("click",r),a.addEventListener("keydown",function(t){if(s.is_modified=!0,27==t.keyCode)s.close();else if(13==t.keyCode)r();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}),a.focus()))},extra:l},s=(l&&(o.title=l.type),null);l&&(s=l.getSlotInPosition(t.canvasX,t.canvasY),LGraphCanvas.active_node=l),s?(i=[],l.getSlotMenuOptions?i=l.getSlotMenuOptions(s):(s&&s.output&&s.output.links&&s.output.links.length&&i.push({content:"Disconnect Links",slot:s}),(e=s.input||s.output).removable&&i.push(e.locked?"Cannot remove":{content:"Remove Slot",slot:s}),e.nameLocked||i.push({content:"Rename Slot",slot:s})),o.title=(s.input||s.output).type||"*",s.input&&s.input.type==LiteGraph.ACTION&&(o.title="Action"),s.output&&s.output.type==LiteGraph.EVENT&&(o.title="Event")):l?i=this.getNodeMenuOptions(l):(i=this.getCanvasMenuOptions(),(e=this.graph.getGroupOnPos(t.canvasX,t.canvasY))&&i.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:e,options:this.getGroupMenuOptions(e)}})),i&&new LiteGraph.ContextMenu(i,o,n)},LiteGraph.compareObjects=compareObjects,LiteGraph.distance=distance,LiteGraph.colorToString=colorToString,LiteGraph.isInsideRectangle=isInsideRectangle,LiteGraph.growBounding=growBounding,LiteGraph.isInsideBounding=isInsideBounding,LiteGraph.overlapBounding=overlapBounding,LiteGraph.hex2num=hex2num,LiteGraph.num2hex=num2hex,ContextMenu.prototype.addItem=function(t,e,i){var o=this,n=(i=i||{},document.createElement("div")),s=!(n.className="litemenu-entry submenu");function a(t){var e=this.value,n=!0;if((o.current_submenu&&o.current_submenu.close(t),i.callback&&!0===i.callback.call(this,e,i,t,o,i.node)&&(n=!1),e)&&(e.callback&&!i.ignore_item_callbacks&&!0!==e.disabled&&!0===e.callback.call(this,e,i,t,o,i.extra)&&(n=!1),e.submenu)){if(!e.submenu.options)throw"ContextMenu submenu needs options";new o.constructor(e.submenu.options,{callback:e.submenu.callback,event:t,parentMenu:o,ignore_item_callbacks:e.submenu.ignore_item_callbacks,title:e.submenu.title,extra:e.submenu.extra,autoopen:i.autoopen});n=!1}n&&!o.lock&&o.close()}return null===e?n.classList.add("separator"):(n.innerHTML=e&&e.title?e.title:t,(n.value=e)&&(e.disabled&&(s=!0,n.classList.add("disabled")),e.submenu||e.has_submenu)&&n.classList.add("has_submenu"),"function"==typeof e?(n.dataset.value=t,n.onclick_callback=e):n.dataset.value=e,e.className&&(n.className+=" "+e.className)),this.root.appendChild(n),s||n.addEventListener("click",a),!s&&i.autoopen&&LiteGraph.pointerListenerAdd(n,"enter",function(t){var e=this.value;e&&e.has_submenu&&a.call(this,t)}),n},ContextMenu.prototype.close=function(t,e){this.root.parentNode&&this.root.parentNode.removeChild(this.root),this.parentMenu&&!e&&(this.parentMenu.lock=!1,this.parentMenu.current_submenu=null,void 0===t?this.parentMenu.close():t&&!ContextMenu.isCursorOverElement(t,this.parentMenu.root)&&ContextMenu.trigger(this.parentMenu.root,LiteGraph.pointerevents_method+"leave",t)),this.current_submenu&&this.current_submenu.close(t,!0),this.root.closing_timer&&clearTimeout(this.root.closing_timer)},ContextMenu.trigger=function(t,e,n,i){var o=document.createEvent("CustomEvent");return o.initCustomEvent(e,!0,!0,n),o.srcElement=i,t.dispatchEvent?t.dispatchEvent(o):t.__events&&t.__events.dispatchEvent(o),o},ContextMenu.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this},ContextMenu.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event},ContextMenu.isCursorOverElement=function(t,e){var n=t.clientX,t=t.clientY,e=e.getBoundingClientRect();return!!e&&t>e.top&&te.left&&nthis.size[0]+10||t[1]<-10||t[1]>this.size[1]+10)?(a.splice(n,1),this.selected=-1):(s[0]=e?0==n?0:1:clamp(i,0,1),s[1]=1-clamp(o,0,1),a.sort(function(t,e){return t[0]-e[0]}),this.selected=a.indexOf(s),this.must_update=!0)))},CurveEditor.prototype.onMouseUp=function(t,e){return!(this.selected=-1)},CurveEditor.prototype.getCloserPoint=function(t,e){var n=this.points;if(!n)return-1;e=e||30;for(var i=this.size[0]-2*this.margin,o=this.size[1]-2*this.margin,s=n.length,a=[0,0],r=1e6,l=-1,h=0;h(a^Math.random()*16>>a/4).toString(16)); - }, - - /** - * Returns if the types of two slots are compatible (taking into account wildcards, etc) - * @method isValidConnection - * @param {String} type_a - * @param {String} type_b - * @return {Boolean} true if they can be connected - */ - isValidConnection: function(type_a, type_b) { - if (type_a=="" || type_a==="*") type_a = 0; - if (type_b=="" || type_b==="*") type_b = 0; - if ( - !type_a //generic output - || !type_b // generic input - || type_a == type_b //same type (is valid for triggers) - || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) - ) { - return true; - } - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { - return type_a == type_b; - } - - // Check all permutations to see if one is valid - var supported_types_a = type_a.split(","); - var supported_types_b = type_b.split(","); - for (var i = 0; i < supported_types_a.length; ++i) { - for (var j = 0; j < supported_types_b.length; ++j) { - if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ - //if (supported_types_a[i] == supported_types_b[j]) { - return true; - } - } - } - - return false; - }, - - /** - * Register a string in the search box so when the user types it it will recommend this node - * @method registerSearchboxExtra - * @param {String} node_type the node recommended - * @param {String} description text to show next to it - * @param {Object} data it could contain info of how the node should be configured - * @return {Boolean} true if they can be connected - */ - registerSearchboxExtra: function(node_type, description, data) { - this.searchbox_extras[description.toLowerCase()] = { - type: node_type, - desc: description, - data: data - }; - }, - - /** - * Wrapper to load files (from url using fetch or from file using FileReader) - * @method fetchFile - * @param {String|File|Blob} url the url of the file (or the file itself) - * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" - * @param {Function} on_complete callback(data) - * @param {Function} on_error in case of an error - * @return {FileReader|Promise} returns the object used to - */ - fetchFile: function( url, type, on_complete, on_error ) { - var that = this; - if(!url) - return null; - - type = type || "text"; - if( url.constructor === String ) - { - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - return fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); //it will be catch below - if(type == "arraybuffer") - return response.arrayBuffer(); - else if(type == "text" || type == "string") - return response.text(); - else if(type == "json") - return response.json(); - else if(type == "blob") - return response.blob(); - }) - .then(function(data) { - if(on_complete) - on_complete(data); - }) - .catch(function(error) { - console.error("error fetching file:",url); - if(on_error) - on_error(error); - }); - } - else if( url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.onload = function(e) - { - var v = e.target.result; - if( type == "json" ) - v = JSON.parse(v); - if(on_complete) - on_complete(v); - } - if(type == "arraybuffer") - return reader.readAsArrayBuffer(url); - else if(type == "text" || type == "json") - return reader.readAsText(url); - else if(type == "blob") - return reader.readAsBinaryString(url); - } - return null; - } - }); - - //timer that works everywhere - if (typeof performance != "undefined") { - LiteGraph.getTime = performance.now.bind(performance); - } else if (typeof Date != "undefined" && Date.now) { - LiteGraph.getTime = Date.now.bind(Date); - } else if (typeof process != "undefined") { - LiteGraph.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; - } else { - LiteGraph.getTime = function getTime() { - return new Date().getTime(); - }; - } - - //********************************************************************************* - // LGraph CLASS - //********************************************************************************* - - /** - * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. - * supported callbacks: - + onNodeAdded: when a new node is added to the graph - + onNodeRemoved: when a node inside this graph is removed - + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) - * - * @class LGraph - * @constructor - * @param {Object} o data from previous serialization [optional] - */ - - function LGraph(o) { - if (LiteGraph.debug) { - console.log("Graph created"); - } - this.list_of_graphcanvas = null; - this.clear(); - - if (o) { - this.configure(o); - } - } - - global.LGraph = LiteGraph.LGraph = LGraph; - - //default supported types - LGraph.supported_types = ["number", "string", "boolean"]; - - //used to know which types of connections support this graph (some graphs do not allow certain types) - LGraph.prototype.getSupportedTypes = function() { - return this.supported_types || LGraph.supported_types; - }; - - LGraph.STATUS_STOPPED = 1; - LGraph.STATUS_RUNNING = 2; - - /** - * Removes all nodes from this graph - * @method clear - */ - - LGraph.prototype.clear = function() { - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 0; - this.last_link_id = 0; - - this._version = -1; //used to detect changes - - //safe clear - if (this._nodes) { - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - if (node.onRemoved) { - node.onRemoved(); - } - } - } - - //nodes - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute sorted in execution order - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - this.vars = {}; - this.extra = {}; //to store custom data - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - - //subgraph_data - this.inputs = {}; - this.outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); - }; - - /** - * Attach Canvas to this graph - * @method attachCanvas - * @param {GraphCanvas} graph_canvas - */ - - LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - if (graphcanvas.graph && graphcanvas.graph != this) { - graphcanvas.graph.detachCanvas(graphcanvas); - } - - graphcanvas.graph = this; - - if (!this.list_of_graphcanvas) { - this.list_of_graphcanvas = []; - } - this.list_of_graphcanvas.push(graphcanvas); - }; - - /** - * Detach Canvas from this graph - * @method detachCanvas - * @param {GraphCanvas} graph_canvas - */ - LGraph.prototype.detachCanvas = function(graphcanvas) { - if (!this.list_of_graphcanvas) { - return; - } - - var pos = this.list_of_graphcanvas.indexOf(graphcanvas); - if (pos == -1) { - return; - } - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos, 1); - }; - - /** - * Starts running this graph every interval milliseconds. - * @method start - * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - - LGraph.prototype.start = function(interval) { - if (this.status == LGraph.STATUS_RUNNING) { - return; - } - this.status = LGraph.STATUS_RUNNING; - - if (this.onPlayEvent) { - this.onPlayEvent(); - } - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 0; - var that = this; - - //execute once per frame - if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { - function on_frame() { - if (that.execution_timer_id != -1) { - return; - } - window.requestAnimationFrame(on_frame); - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - } - this.execution_timer_id = -1; - on_frame(); - } else { //execute every 'interval' ms - this.execution_timer_id = setInterval(function() { - //execute - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - }, interval); - } - }; - - /** - * Stops the execution loop of the graph - * @method stop execution - */ - - LGraph.prototype.stop = function() { - if (this.status == LGraph.STATUS_STOPPED) { - return; - } - - this.status = LGraph.STATUS_STOPPED; - - if (this.onStopEvent) { - this.onStopEvent(); - } - - if (this.execution_timer_id != null) { - if (this.execution_timer_id != -1) { - clearInterval(this.execution_timer_id); - } - this.execution_timer_id = null; - } - - this.sendEventToAllNodes("onStop"); - }; - - /** - * Run N steps (cycles) of the graph - * @method runStep - * @param {number} num number of steps to run, default is 1 - * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors - * @param {number} limit max number of nodes to execute (used to execute from start to a node) - */ - - LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - //not optimal: executes possible pending actions in node, problem is it is not optimized - //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute - - //from now on it will iterate only on executable nodes which is faster - var nodes = this._nodes_executable - ? this._nodes_executable - : this._nodes; - if (!nodes) { - return; - } - - limit = limit || nodes.length; - - if (do_not_catch_errors) { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) - node.executePendingActions(); - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - //wrap node.onExecute(); - node.doExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { //catch errors - try { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) - node.executePendingActions(); - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = false; - } catch (err) { - this.errors_in_execution = true; - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error during execution: " + err); - } - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) { - elapsed = 1; - } - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - }; - - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - * @method updateExecutionOrder - */ - LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(false); - this._nodes_executable = []; - for (var i = 0; i < this._nodes_in_order.length; ++i) { - if (this._nodes_in_order[i].onExecute) { - this._nodes_executable.push(this._nodes_in_order[i]); - } - } - }; - - //This is more internal, it computes the executable nodes in order and returns it - LGraph.prototype.computeExecutionOrder = function( - only_onExecute, - set_level - ) { - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) { - var node = this._nodes[i]; - if (only_onExecute && !node.onExecute) { - continue; - } - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if (node.inputs) { - for (var j = 0, l2 = node.inputs.length; j < l2; j++) { - if (node.inputs[j] && node.inputs[j].link != null) { - num += 1; - } - } - } - - if (num == 0) { - //is a starting node - S.push(node); - if (set_level) { - node._level = 1; - } - } //num of input links - else { - if (set_level) { - node._level = 0; - } - remaining_links[node.id] = num; - } - } - - while (true) { - if (S.length == 0) { - break; - } - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if (!node.outputs) { - continue; - } - - //for every output - for (var i = 0; i < node.outputs.length; i++) { - var output = node.outputs[i]; - //not connected - if ( - output == null || - output.links == null || - output.links.length == 0 - ) { - continue; - } - - //for every connection - for (var j = 0; j < output.links.length; j++) { - var link_id = output.links[j]; - var link = this.links[link_id]; - if (!link) { - continue; - } - - //already visited link (ignore it) - if (visited_links[link.id]) { - continue; - } - - var target_node = this.getNodeById(link.target_id); - if (target_node == null) { - visited_links[link.id] = true; - continue; - } - - if ( - set_level && - (!target_node._level || - target_node._level <= node._level) - ) { - target_node._level = node._level + 1; - } - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[target_node.id] == 0) { - S.push(target_node); - } //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for (var i in M) { - L.push(M[i]); - } - - if (L.length != this._nodes.length && LiteGraph.debug) { - console.warn("something went wrong, nodes missing"); - } - - var l = L.length; - - //save order number in the node - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - //sort now by priority - L = L.sort(function(A, B) { - var Ap = A.constructor.priority || A.priority || 0; - var Bp = B.constructor.priority || B.priority || 0; - if (Ap == Bp) { - //if same priority, sort by order - return A.order - B.order; - } - return Ap - Bp; //sort by priority - }); - - //save order number in the node, again... - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - return L; - }; - - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @method getAncestors - * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution - */ - LGraph.prototype.getAncestors = function(node) { - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) { - var current = pending.shift(); - if (!current.inputs) { - continue; - } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); - } - - for (var i = 0; i < current.inputs.length; ++i) { - var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); - } - } - } - - ancestors.sort(function(a, b) { - return a.order - b.order; - }); - return ancestors; - }; - - /** - * Positions every node in a more readable manner - * @method arrange - */ - LGraph.prototype.arrange = function (margin, layout) { - margin = margin || 100; - - const nodes = this.computeExecutionOrder(false, true); - const columns = []; - for (let i = 0; i < nodes.length; ++i) { - const node = nodes[i]; - const col = node._level || 1; - if (!columns[col]) { - columns[col] = []; - } - columns[col].push(node); - } - - let x = margin; - - for (let i = 0; i < columns.length; ++i) { - const column = columns[i]; - if (!column) { - continue; - } - let max_size = 100; - let y = margin + LiteGraph.NODE_TITLE_HEIGHT; - for (let j = 0; j < column.length; ++j) { - const node = column[j]; - node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; - node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; - const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; - if (node.size[max_size_index] > max_size) { - max_size = node.size[max_size_index]; - } - const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; - y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true, true); - }; - - /** - * Returns the amount of time the graph has been running in milliseconds - * @method getTime - * @return {number} number of milliseconds the graph has been running - */ - LGraph.prototype.getTime = function() { - return this.globaltime; - }; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @method getFixedTime - * @return {number} number of milliseconds the graph has been running - */ - - LGraph.prototype.getFixedTime = function() { - return this.fixedtime; - }; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @method getElapsedTime - * @return {number} number of milliseconds it took the last cycle - */ - - LGraph.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - - /** - * Sends an event to all the nodes, useful to trigger stuff - * @method sendEventToAllNodes - * @param {String} eventname the name of the event (function to be called) - * @param {Array} params parameters in array format - */ - LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (!nodes) { - return; - } - - for (var j = 0, l = nodes.length; j < l; ++j) { - var node = nodes[j]; - - if ( - node.constructor === LiteGraph.Subgraph && - eventname != "onExecute" - ) { - if (node.mode == mode) { - node.sendEventToAllNodes(eventname, params, mode); - } - continue; - } - - if (!node[eventname] || node.mode != mode) { - continue; - } - if (params === undefined) { - node[eventname](); - } else if (params && params.constructor === Array) { - node[eventname].apply(node, params); - } else { - node[eventname](params); - } - } - }; - - LGraph.prototype.sendActionToCanvas = function(action, params) { - if (!this.list_of_graphcanvas) { - return; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c[action]) { - c[action].apply(c, params); - } - } - }; - - /** - * Adds a new node instance to this graph - * @method add - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.add = function(node, skip_compute_order) { - if (!node) { - return; - } - - //groups - if (node.constructor === LGraphGroup) { - this._groups.push(node); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); - if (LiteGraph.use_uuids) { - node.id = LiteGraph.uuidv4(); - } - else { - node.id = ++this.last_node_id; - } - } - - if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - - //give him an id - if (LiteGraph.use_uuids) { - if (node.id == null || node.id == -1) - node.id = LiteGraph.uuidv4(); - } - else { - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; - } - } - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if (node.onAdded) { - node.onAdded(this); - } - - if (this.config.align_to_grid) { - node.alignToGrid(); - } - - if (!skip_compute_order) { - this.updateExecutionOrder(); - } - - if (this.onNodeAdded) { - this.onNodeAdded(node); - } - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions - }; - - /** - * Removes a node from the graph - * @method remove - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.remove = function(node) { - if (node.constructor === LiteGraph.LGraphGroup) { - var index = this._groups.indexOf(node); - if (index != -1) { - this._groups.splice(index, 1); - } - node.graph = null; - this._version++; - this.setDirtyCanvas(true, true); - this.change(); - return; - } - - if (this._nodes_by_id[node.id] == null) { - return; - } //not found - - if (node.ignore_remove) { - return; - } //cannot be removed - - this.beforeChange(); //sure? - almost sure is wrong - - //disconnect inputs - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link != null) { - node.disconnectInput(i); - } - } - } - - //disconnect outputs - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); - } - } - } - - //node.id = -1; //why? - - //callback - if (node.onRemoved) { - node.onRemoved(); - } - - node.graph = null; - this._version++; - - //remove from canvas render - if (this.list_of_graphcanvas) { - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var canvas = this.list_of_graphcanvas[i]; - if (canvas.selected_nodes[node.id]) { - delete canvas.selected_nodes[node.id]; - } - if (canvas.node_dragged == node) { - canvas.node_dragged = null; - } - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if (pos != -1) { - this._nodes.splice(pos, 1); - } - delete this._nodes_by_id[node.id]; - - if (this.onNodeRemoved) { - this.onNodeRemoved(node); - } - - //close panels - this.sendActionToCanvas("checkPanels"); - - this.setDirtyCanvas(true, true); - this.afterChange(); //sure? - almost sure is wrong - this.change(); - - this.updateExecutionOrder(); - }; - - /** - * Returns a node by its id. - * @method getNodeById - * @param {Number} id - */ - - LGraph.prototype.getNodeById = function(id) { - if (id == null) { - return null; - } - return this._nodes_by_id[id]; - }; - - /** - * Returns a list of nodes that matches a class - * @method findNodesByClass - * @param {Class} classObject the class itself (not an string) - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByClass = function(classObject, result) { - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].constructor === classObject) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns a list of nodes that matches a type - * @method findNodesByType - * @param {String} type the name of the node type - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByType = function(type, result) { - var type = type.toLowerCase(); - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == type) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the first node that matches a name in its title - * @method findNodeByTitle - * @param {String} name the name of the node to search - * @return {Node} the node or null - */ - LGraph.prototype.findNodeByTitle = function(title) { - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - return this._nodes[i]; - } - } - return null; - }; - - /** - * Returns a list of nodes that matches a name - * @method findNodesByTitle - * @param {String} name the name of the node to search - * @return {Array} a list with all the nodes with this name - */ - LGraph.prototype.findNodesByTitle = function(title) { - var result = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the top-most node in this position of the canvas - * @method getNodeOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return {LGraphNode} the node at this position or null - */ - LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { - nodes_list = nodes_list || this._nodes; - var nRet = null; - for (var i = nodes_list.length - 1; i >= 0; i--) { - var n = nodes_list[i]; - if (n.isPointInside(x, y, margin)) { - // check for lesser interest nodes (TODO check for overlapping, use the top) - /*if (typeof n == "LGraphGroup"){ - nRet = n; - }else{*/ - return n; - /*}*/ - } - } - return nRet; - }; - - /** - * Returns the top-most group in that position - * @method getGroupOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @return {LGraphGroup} the group or null - */ - LGraph.prototype.getGroupOnPos = function(x, y) { - for (var i = this._groups.length - 1; i >= 0; i--) { - var g = this._groups[i]; - if (g.isPointInside(x, y, 2, true)) { - return g; - } - } - return null; - }; - - /** - * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution - * this replaces the ones using the old version with the new version - * @method checkNodeTypes - */ - LGraph.prototype.checkNodeTypes = function() { - var changes = false; - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - var ctor = LiteGraph.registered_node_types[node.type]; - if (node.constructor == ctor) { - continue; - } - console.log("node being replaced by newer version: " + node.type); - var newnode = LiteGraph.createNode(node.type); - changes = true; - this._nodes[i] = newnode; - newnode.configure(node.serialize()); - newnode.graph = this; - this._nodes_by_id[newnode.id] = newnode; - if (node.inputs) { - newnode.inputs = node.inputs.concat(); - } - if (node.outputs) { - newnode.outputs = node.outputs.concat(); - } - } - this.updateExecutionOrder(); - }; - - // ********** GLOBALS ***************** - - LGraph.prototype.onAction = function(action, param, options) { - this._input_nodes = this.findNodesByClass( - LiteGraph.GraphInput, - this._input_nodes - ); - for (var i = 0; i < this._input_nodes.length; ++i) { - var node = this._input_nodes[i]; - if (node.properties.name != action) { - continue; - } - //wrap node.onAction(action, param); - node.actionDo(action, param, options); - break; - } - }; - - LGraph.prototype.trigger = function(action, param) { - if (this.onTrigger) { - this.onTrigger(action, param); - } - }; - - /** - * Tell this graph it has a global graph input of this type - * @method addGlobalInput - * @param {String} name - * @param {String} type - * @param {*} value [optional] - */ - LGraph.prototype.addInput = function(name, type, value) { - var input = this.inputs[name]; - if (input) { - //already exist - return; - } - - this.beforeChange(); - this.inputs[name] = { name: name, type: type, value: value }; - this._version++; - this.afterChange(); - - if (this.onInputAdded) { - this.onInputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global graph input - * @method setGlobalInputData - * @param {String} name - * @param {*} data - */ - LGraph.prototype.setInputData = function(name, data) { - var input = this.inputs[name]; - if (!input) { - return; - } - input.value = data; - }; - - /** - * Returns the current value of a global graph input - * @method getInputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getInputData = function(name) { - var input = this.inputs[name]; - if (!input) { - return null; - } - return input.value; - }; - - /** - * Changes the name of a global graph input - * @method renameInput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameInput = function(old_name, name) { - if (name == old_name) { - return; - } - - if (!this.inputs[old_name]) { - return false; - } - - if (this.inputs[name]) { - console.error("there is already one input with that name"); - return false; - } - - this.inputs[name] = this.inputs[old_name]; - delete this.inputs[old_name]; - this._version++; - - if (this.onInputRenamed) { - this.onInputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph input - * @method changeInputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeInputType = function(name, type) { - if (!this.inputs[name]) { - return false; - } - - if ( - this.inputs[name].type && - String(this.inputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.inputs[name].type = type; - this._version++; - if (this.onInputTypeChanged) { - this.onInputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph input - * @method removeInput - * @param {String} name - * @param {String} type - */ - LGraph.prototype.removeInput = function(name) { - if (!this.inputs[name]) { - return false; - } - - delete this.inputs[name]; - this._version++; - - if (this.onInputRemoved) { - this.onInputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - /** - * Creates a global graph output - * @method addOutput - * @param {String} name - * @param {String} type - * @param {*} value - */ - LGraph.prototype.addOutput = function(name, type, value) { - this.outputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onOutputAdded) { - this.onOutputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global output - * @method setOutputData - * @param {String} name - * @param {String} value - */ - LGraph.prototype.setOutputData = function(name, value) { - var output = this.outputs[name]; - if (!output) { - return; - } - output.value = value; - }; - - /** - * Returns the current value of a global graph output - * @method getOutputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getOutputData = function(name) { - var output = this.outputs[name]; - if (!output) { - return null; - } - return output.value; - }; - - /** - * Renames a global graph output - * @method renameOutput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameOutput = function(old_name, name) { - if (!this.outputs[old_name]) { - return false; - } - - if (this.outputs[name]) { - console.error("there is already one output with that name"); - return false; - } - - this.outputs[name] = this.outputs[old_name]; - delete this.outputs[old_name]; - this._version++; - - if (this.onOutputRenamed) { - this.onOutputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph output - * @method changeOutputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeOutputType = function(name, type) { - if (!this.outputs[name]) { - return false; - } - - if ( - this.outputs[name].type && - String(this.outputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.outputs[name].type = type; - this._version++; - if (this.onOutputTypeChanged) { - this.onOutputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph output - * @method removeOutput - * @param {String} name - */ - LGraph.prototype.removeOutput = function(name) { - if (!this.outputs[name]) { - return false; - } - delete this.outputs[name]; - this._version++; - - if (this.onOutputRemoved) { - this.onOutputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - LGraph.prototype.triggerInput = function(name, value) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].onTrigger(value); - } - }; - - LGraph.prototype.setCallback = function(name, func) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].setTrigger(func); - } - }; - - //used for undo, called before any change is made to the graph - LGraph.prototype.beforeChange = function(info) { - if (this.onBeforeChange) { - this.onBeforeChange(this,info); - } - this.sendActionToCanvas("onBeforeChange", this); - }; - - //used to resend actions, called after any change is made to the graph - LGraph.prototype.afterChange = function(info) { - if (this.onAfterChange) { - this.onAfterChange(this,info); - } - this.sendActionToCanvas("onAfterChange", this); - }; - - LGraph.prototype.connectionChange = function(node, link_info) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(node); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - - /** - * returns if the graph is in live mode - * @method isLive - */ - - LGraph.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return false; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c.live_mode) { - return true; - } - } - return false; - }; - - /** - * clears the triggered slot animation in all links (stop visual animation) - * @method clearTriggeredSlots - */ - LGraph.prototype.clearTriggeredSlots = function() { - for (var i in this.links) { - var link_info = this.links[i]; - if (!link_info) { - continue; - } - if (link_info._last_time) { - link_info._last_time = 0; - } - } - }; - - /* Called when something visually changed (not the graph!) */ - LGraph.prototype.change = function() { - if (LiteGraph.debug) { - console.log("Graph changed"); - } - this.sendActionToCanvas("setDirty", [true, true]); - if (this.on_change) { - this.on_change(this); - } - }; - - LGraph.prototype.setDirtyCanvas = function(fg, bg) { - this.sendActionToCanvas("setDirty", [fg, bg]); - }; - - /** - * Destroys a link - * @method removeLink - * @param {Number} link_id - */ - LGraph.prototype.removeLink = function(link_id) { - var link = this.links[link_id]; - if (!link) { - return; - } - var node = this.getNodeById(link.target_id); - if (node) { - node.disconnectInput(link.target_slot); - } - }; - - //save and recover app state *************************************** - /** - * Creates a Object containing all the info about this graph, it can be serialized - * @method serialize - * @return {Object} value of the node - */ - LGraph.prototype.serialize = function() { - var nodes_info = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - nodes_info.push(this._nodes[i].serialize()); - } - - //pack link info into a non-verbose format - var links = []; - for (var i in this.links) { - //links is an OBJECT - var link = this.links[i]; - if (!link.serialize) { - //weird bug I havent solved yet - console.warn( - "weird LLink bug, link info is not a LLink but a regular object" - ); - var link2 = new LLink(); - for (var j in link) { - link2[j] = link[j]; - } - this.links[i] = link2; - link = link2; - } - - links.push(link.serialize()); - } - - var groups_info = []; - for (var i = 0; i < this._groups.length; ++i) { - groups_info.push(this._groups[i].serialize()); - } - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config, - extra: this.extra, - version: LiteGraph.VERSION - }; - - if(this.onSerialize) - this.onSerialize(data); - - return data; - }; - - /** - * Configure a graph from a JSON string - * @method configure - * @param {String} str configure a graph from a JSON string - * @param {Boolean} returns if there was any error parsing - */ - LGraph.prototype.configure = function(data, keep_old) { - if (!data) { - return; - } - - if (!keep_old) { - this.clear(); - } - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if (data.links && data.links.constructor === Array) { - var links = []; - for (var i = 0; i < data.links.length; ++i) { - var link_data = data.links[i]; - if(!link_data) //weird bug - { - console.warn("serialized graph link data contains errors, skipping."); - continue; - } - var link = new LLink(); - link.configure(link_data); - links[link.id] = link; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) { - if(i == "nodes" || i == "groups" ) //links must be accepted - continue; - this[i] = data[i]; - } - - var error = false; - - //create nodes - this._nodes = []; - if (nodes) { - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode(n_info.type, n_info.title); - if (!node) { - if (LiteGraph.debug) { - console.log( - "Node not found or has errors: " + n_info.type - ); - } - - //in case of error we create a replacement node to avoid losing info - node = new LGraphNode(); - node.last_serialization = n_info; - node.has_errors = true; - error = true; - //continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; - var node = this.getNodeById(n_info.id); - if (node) { - node.configure(n_info); - } - } - } - - //groups - this._groups.length = 0; - if (data.groups) { - for (var i = 0; i < data.groups.length; ++i) { - var group = new LiteGraph.LGraphGroup(); - group.configure(data.groups[i]); - this.add(group); - } - } - - this.updateExecutionOrder(); - - this.extra = data.extra || {}; - - if(this.onConfigure) - this.onConfigure(data); - - this._version++; - this.setDirtyCanvas(true, true); - return error; - }; - - LGraph.prototype.load = function(url, callback) { - var that = this; - - //from file - if(url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.addEventListener('load', function(event) { - var data = JSON.parse(event.target.result); - that.configure(data); - if(callback) - callback(); - }); - - reader.readAsText(url); - return; - } - - //is a string, then an URL - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.send(null); - req.onload = function(oEvent) { - if (req.status !== 200) { - console.error("Error loading graph:", req.status, req.response); - return; - } - var data = JSON.parse( req.response ); - that.configure(data); - if(callback) - callback(); - }; - req.onerror = function(err) { - console.error("Error loading graph:", err); - }; - }; - - LGraph.prototype.onNodeTrace = function(node, msg, color) { - //TODO - }; - - //this is the class in charge of storing link information - function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { - this.id = id; - this.type = type; - this.origin_id = origin_id; - this.origin_slot = origin_slot; - this.target_id = target_id; - this.target_slot = target_slot; - - this._data = null; - this._pos = new Float32Array(2); //center - } - - LLink.prototype.configure = function(o) { - if (o.constructor === Array) { - this.id = o[0]; - this.origin_id = o[1]; - this.origin_slot = o[2]; - this.target_id = o[3]; - this.target_slot = o[4]; - this.type = o[5]; - } else { - this.id = o.id; - this.type = o.type; - this.origin_id = o.origin_id; - this.origin_slot = o.origin_slot; - this.target_id = o.target_id; - this.target_slot = o.target_slot; - } - }; - - LLink.prototype.serialize = function() { - return [ - this.id, - this.origin_id, - this.origin_slot, - this.target_id, - this.target_slot, - this.type - ]; - }; - - LiteGraph.LLink = LLink; - - // ************************************************************* - // Node CLASS ******* - // ************************************************************* - - /* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - general properties: - + clip_area: if you render outside the node, it will be clipped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally - + widgets_start_y: widgets start at y distance from the top of the node - - flags object: - + collapsed: if it is collapsed - - supported callbacks: - + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) - + onDblClick: double clicked in the node - + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) - + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) - + onConfigure: called after the node has been configured - + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) - + onAction: action slot triggered - + getExtraMenuOptions: to add option to context menu -*/ - - /** - * Base Class for all the node type classes - * @class LGraphNode - * @param {String} name a name for the node - */ - - function LGraphNode(title) { - this._ctor(title); - } - - global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - - LGraphNode.prototype._ctor = function(title) { - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH, 60]; - this.graph = null; - - this._pos = new Float32Array(10, 10); - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - if (LiteGraph.use_uuids) { - this.id = LiteGraph.uuidv4(); - } - else { - this.id = -1; //not know till not added - } - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.flags = {}; - }; - - /** - * configure a node from an object containing the serialized info - * @method configure - */ - LGraphNode.prototype.configure = function(info) { - if (this.graph) { - this.graph._version++; - } - for (var j in info) { - if (j == "properties") { - //i don't want to clone properties, I want to reuse the old container - for (var k in info.properties) { - this.properties[k] = info.properties[k]; - if (this.onPropertyChanged) { - this.onPropertyChanged( k, info.properties[k] ); - } - } - continue; - } - - if (info[j] == null) { - continue; - } else if (typeof info[j] == "object") { - //object - if (this[j] && this[j].configure) { - this[j].configure(info[j]); - } else { - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - } //value - else { - this[j] = info[j]; - } - } - - if (!info.title) { - this.title = this.constructor.title; - } - - if (this.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - var link_info = this.graph ? this.graph.links[input.link] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - - if( this.onInputAdded ) - this.onInputAdded(input); - - } - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if (!output.links) { - continue; - } - for (var j = 0; j < output.links.length; ++j) { - var link_info = this.graph ? this.graph.links[output.links[j]] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated - } - - if( this.onOutputAdded ) - this.onOutputAdded(output); - } - } - - if( this.widgets ) - { - for (var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options && w.options.property && (this.properties[ w.options.property ] != undefined)) - w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); - } - if (info.widgets_values) { - for (var i = 0; i < info.widgets_values.length; ++i) { - if (this.widgets[i]) { - this.widgets[i].value = info.widgets_values[i]; - } - } - } - } - - if (this.onConfigure) { - this.onConfigure(info); - } - }; - - /** - * serialize the content - * @method serialize - */ - - LGraphNode.prototype.serialize = function() { - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - flags: LiteGraph.cloneObject(this.flags), - order: this.order, - mode: this.mode - }; - - //special case for when there were errors - if (this.constructor === LGraphNode && this.last_serialization) { - return this.last_serialization; - } - - if (this.inputs) { - o.inputs = this.inputs; - } - - if (this.outputs) { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - for (var i = 0; i < this.outputs.length; i++) { - delete this.outputs[i]._data; - } - o.outputs = this.outputs; - } - - if (this.title && this.title != this.constructor.title) { - o.title = this.title; - } - - if (this.properties) { - o.properties = LiteGraph.cloneObject(this.properties); - } - - if (this.widgets && this.serialize_widgets) { - o.widgets_values = []; - for (var i = 0; i < this.widgets.length; ++i) { - if(this.widgets[i]) - o.widgets_values[i] = this.widgets[i].value; - else - o.widgets_values[i] = null; - } - } - - if (!o.type) { - o.type = this.constructor.type; - } - - if (this.color) { - o.color = this.color; - } - if (this.bgcolor) { - o.bgcolor = this.bgcolor; - } - if (this.boxcolor) { - o.boxcolor = this.boxcolor; - } - if (this.shape) { - o.shape = this.shape; - } - - if (this.onSerialize) { - if (this.onSerialize(o)) { - console.warn( - "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" - ); - } - } - - return o; - }; - - /* Creates a clone of this node */ - LGraphNode.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - if (!node) { - return null; - } - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject(this.serialize()); - - //remove links - if (data.inputs) { - for (var i = 0; i < data.inputs.length; ++i) { - data.inputs[i].link = null; - } - } - - if (data.outputs) { - for (var i = 0; i < data.outputs.length; ++i) { - if (data.outputs[i].links) { - data.outputs[i].links.length = 0; - } - } - } - - delete data["id"]; - - if (LiteGraph.use_uuids) { - data["id"] = LiteGraph.uuidv4() - } - - //remove links - node.configure(data); - - return node; - }; - - /** - * serialize and stringify - * @method toString - */ - - LGraphNode.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - /** - * get the title string - * @method getTitle - */ - - LGraphNode.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - - /** - * sets the value of a property - * @method setProperty - * @param {String} name - * @param {*} value - */ - LGraphNode.prototype.setProperty = function(name, value) { - if (!this.properties) { - this.properties = {}; - } - if( value === this.properties[name] ) - return; - var prev_value = this.properties[name]; - this.properties[name] = value; - if (this.onPropertyChanged) { - if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change - this.properties[name] = prev_value; - } - if(this.widgets) //widgets could be linked to properties - for(var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options.property == name) - { - w.value = value; - break; - } - } - }; - - // Execution ************************* - /** - * sets the output data - * @method setOutputData - * @param {number} slot - * @param {*} data - */ - LGraphNode.prototype.setOutputData = function(slot, data) { - if (!this.outputs) { - return; - } - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if (slot == -1 || slot >= this.outputs.length) { - return; - } - - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - var link = this.graph.links[link_id]; - if(link) - link.data = data; - } - } - }; - - /** - * sets the output data type, useful when you want to be able to overwrite the data type - * @method setOutputDataType - * @param {number} slot - * @param {String} datatype - */ - LGraphNode.prototype.setOutputDataType = function(slot, type) { - if (!this.outputs) { - return; - } - if (slot == -1 || slot >= this.outputs.length) { - return; - } - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - //store data in the output itself in case we want to debug - output_info.type = type; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - this.graph.links[link_id].type = type; - } - } - }; - - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @method getInputData - * @param {number} slot - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns undefined - */ - LGraphNode.prototype.getInputData = function(slot, force_update) { - if (!this.inputs) { - return; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return; - } - - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - - if (!force_update) { - return link.data; - } - - //special case: used to extract data from the incoming connection before the graph has been executed - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.data; - } - - if (node.updateOutputData) { - node.updateOutputData(link.origin_slot); - } else if (node.onExecute) { - node.onExecute(); - } - - return link.data; - }; - - /** - * Retrieves the input data type (in case this supports multiple input types) - * @method getInputDataType - * @param {number} slot - * @return {String} datatype in string format - */ - LGraphNode.prototype.getInputDataType = function(slot) { - if (!this.inputs) { - return null; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return null; - } - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.type; - } - var output_info = node.outputs[link.origin_slot]; - if (output_info) { - return output_info.type; - } - return null; - }; - - /** - * Retrieves the input data from one slot using its name instead of slot number - * @method getInputDataByName - * @param {String} slot_name - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns null - */ - LGraphNode.prototype.getInputDataByName = function( - slot_name, - force_update - ) { - var slot = this.findInputSlot(slot_name); - if (slot == -1) { - return null; - } - return this.getInputData(slot, force_update); - }; - - /** - * tells you if there is a connection in one input slot - * @method isInputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isInputConnected = function(slot) { - if (!this.inputs) { - return false; - } - return slot < this.inputs.length && this.inputs[slot].link != null; - }; - - /** - * tells you info about an input connection (which node, type, etc) - * @method getInputInfo - * @param {number} slot - * @return {Object} object or null { link: id, name: string, type: string or 0 } - */ - LGraphNode.prototype.getInputInfo = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - return this.inputs[slot]; - } - return null; - }; - - /** - * Returns the link info in the connection of an input slot - * @method getInputLink - * @param {number} slot - * @return {LLink} object or null - */ - LGraphNode.prototype.getInputLink = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - var slot_info = this.inputs[slot]; - return this.graph.links[ slot_info.link ]; - } - return null; - }; - - /** - * returns the node connected in the input slot - * @method getInputNode - * @param {number} slot - * @return {LGraphNode} node or null - */ - LGraphNode.prototype.getInputNode = function(slot) { - if (!this.inputs) { - return null; - } - if (slot >= this.inputs.length) { - return null; - } - var input = this.inputs[slot]; - if (!input || input.link === null) { - return null; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - return null; - } - return this.graph.getNodeById(link_info.origin_id); - }; - - /** - * returns the value of an input with this name, otherwise checks if there is a property with that name - * @method getInputOrProperty - * @param {string} name - * @return {*} value - */ - LGraphNode.prototype.getInputOrProperty = function(name) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[name] : null; - } - - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input_info = this.inputs[i]; - if (name == input_info.name && input_info.link != null) { - var link = this.graph.links[input_info.link]; - if (link) { - return link.data; - } - } - } - return this.properties[name]; - }; - - /** - * tells you the last output data that went in that slot - * @method getOutputData - * @param {number} slot - * @return {Object} object or null - */ - LGraphNode.prototype.getOutputData = function(slot) { - if (!this.outputs) { - return null; - } - if (slot >= this.outputs.length) { - return null; - } - - var info = this.outputs[slot]; - return info._data; - }; - - /** - * tells you info about an output connection (which node, type, etc) - * @method getOutputInfo - * @param {number} slot - * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } - */ - LGraphNode.prototype.getOutputInfo = function(slot) { - if (!this.outputs) { - return null; - } - if (slot < this.outputs.length) { - return this.outputs[slot]; - } - return null; - }; - - /** - * tells you if there is a connection in one output slot - * @method isOutputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isOutputConnected = function(slot) { - if (!this.outputs) { - return false; - } - return ( - slot < this.outputs.length && - this.outputs[slot].links && - this.outputs[slot].links.length - ); - }; - - /** - * tells you if there is any connection in the output slots - * @method isAnyOutputConnected - * @return {boolean} - */ - LGraphNode.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return false; - } - for (var i = 0; i < this.outputs.length; ++i) { - if (this.outputs[i].links && this.outputs[i].links.length) { - return true; - } - } - return false; - }; - - /** - * retrieves all the nodes connected to this output slot - * @method getOutputNodes - * @param {number} slot - * @return {array} - */ - LGraphNode.prototype.getOutputNodes = function(slot) { - if (!this.outputs || this.outputs.length == 0) { - return null; - } - - if (slot >= this.outputs.length) { - return null; - } - - var output = this.outputs[slot]; - if (!output.links || output.links.length == 0) { - return null; - } - - var r = []; - for (var i = 0; i < output.links.length; i++) { - var link_id = output.links[i]; - var link = this.graph.links[link_id]; - if (link) { - var target_node = this.graph.getNodeById(link.target_id); - if (target_node) { - r.push(target_node); - } - } - } - return r; - }; - - LGraphNode.prototype.addOnTriggerInput = function(){ - var trigS = this.findInputSlot("onTrigger"); - if (trigS == -1){ //!trigS || - var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); - return this.findInputSlot("onTrigger"); - } - return trigS; - } - - LGraphNode.prototype.addOnExecutedOutput = function(){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS == -1){ //!trigS || - var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); - return this.findOutputSlot("onExecuted"); - } - return trigS; - } - - LGraphNode.prototype.onAfterExecuteNode = function(param, options){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS != -1){ - - //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); - //console.debug(param); - //console.debug(options); - this.triggerSlot(trigS, param, null, options); - - } - } - - LGraphNode.prototype.changeMode = function(modeTo){ - switch(modeTo){ - case LiteGraph.ON_EVENT: - // this.addOnExecutedOutput(); - break; - - case LiteGraph.ON_TRIGGER: - this.addOnTriggerInput(); - this.addOnExecutedOutput(); - break; - - case LiteGraph.NEVER: - break; - - case LiteGraph.ALWAYS: - break; - - case LiteGraph.ON_REQUEST: - break; - - default: - return false; - break; - } - this.mode = modeTo; - return true; - }; - - /** - * Triggers the execution of actions that were deferred when the action was triggered - * @method executePendingActions - */ - LGraphNode.prototype.executePendingActions = function() { - if(!this._waiting_actions || !this._waiting_actions.length) - return; - for(var i = 0; i < this._waiting_actions.length;++i) - { - var p = this._waiting_actions[i]; - this.onAction(p[0],p[1],p[2],p[3],p[4]); - } - this._waiting_actions.length = 0; - } - - - /** - * Triggers the node code execution, place a boolean/counter to mark the node as being executed - * @method doExecute - * @param {*} param - * @param {*} options - */ - LGraphNode.prototype.doExecute = function(param, options) { - options = options || {}; - if (this.onExecute){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); - - this.graph.nodes_executing[this.id] = true; //.push(this.id); - - this.onExecute(param, options); - - this.graph.nodes_executing[this.id] = false; //.pop(); - - // save execution/action ref - this.exec_version = this.graph.iteration; - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - else { - } - this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback - }; - - /** - * Triggers an action, wrapped by logics to control execution flow - * @method actionDo - * @param {String} action name - * @param {*} param - */ - LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) { - options = options || {}; - if (this.onAction){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); - - this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); - - this.onAction(action, param, options, action_slot); - - this.graph.nodes_actioning[this.id] = false; //.pop(); - - // save execution/action ref - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); - }; - - /** - * Triggers an event in this node, this will trigger any output with the same name - * @method trigger - * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all - * @param {*} param - */ - LGraphNode.prototype.trigger = function(action, param, options) { - if (!this.outputs || !this.outputs.length) { - return; - } - - if (this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot(i, param, null, options); - } - }; - - /** - * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes - * @method triggerSlot - * @param {Number} slot the index of the output slot - * @param {*} param - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { - options = options || {}; - if (!this.outputs) { - return; - } - - if(slot == null) - { - console.error("slot must be a number"); - return; - } - - if(slot.constructor !== Number) - console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - if (this.graph) { - this.graph._last_trigger_time = LiteGraph.getTime(); - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = LiteGraph.getTime(); - var node = this.graph.getNodeById(link_info.target_id); - if (!node) { - //node not found? - continue; - } - - //used to mark events in graph - var target_connection = node.inputs[link_info.target_slot]; - - if (node.mode === LiteGraph.ON_TRIGGER) - { - // generate unique trigger ID if not present - if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); - if (node.onExecute) { - // -- wrapping node.onExecute(param); -- - node.doExecute(param, options); - } - } - else if (node.onAction) { - // generate unique action ID if not present - if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); - //pass the action name - var target_connection = node.inputs[link_info.target_slot]; - - //instead of executing them now, it will be executed in the next graph loop, to ensure data flow - if(LiteGraph.use_deferred_actions && node.onExecute) - { - if(!node._waiting_actions) - node._waiting_actions = []; - node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]); - } - else - { - // wrap node.onAction(target_connection.name, param); - node.actionDo( target_connection.name, param, options, link_info.target_slot ); - } - } - } - }; - - /** - * clears the trigger slot animation - * @method clearTriggeredSlot - * @param {Number} slot the index of the output slot - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = 0; - } - }; - - /** - * changes node size and triggers callback - * @method setSize - * @param {vec2} size - */ - LGraphNode.prototype.setSize = function(size) - { - this.size = size; - if(this.onResize) - this.onResize(this.size); - } - - /** - * add a new property to this node - * @method addProperty - * @param {string} name - * @param {*} default_value - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) - */ - LGraphNode.prototype.addProperty = function( - name, - default_value, - type, - extra_info - ) { - var o = { name: name, type: type, default_value: default_value }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - if (!this.properties_info) { - this.properties_info = []; - } - this.properties_info.push(o); - if (!this.properties) { - this.properties = {}; - } - this.properties[name] = default_value; - return o; - }; - - //connections - - /** - * add a new output slot to use in this node - * @method addOutput - * @param {string} name - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - LGraphNode.prototype.addOutput = function(name, type, extra_info) { - var output = { name: name, type: type, links: null }; - if (extra_info) { - for (var i in extra_info) { - output[i] = extra_info[i]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(output); - if (this.onOutputAdded) { - this.onOutputAdded(output); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - return output; - }; - - /** - * add a new output slot to use in this node - * @method addOutputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addOutputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); - - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing output slot - * @method removeOutput - * @param {number} slot - */ - LGraphNode.prototype.removeOutput = function(slot) { - this.disconnectOutput(slot); - this.outputs.splice(slot, 1); - for (var i = slot; i < this.outputs.length; ++i) { - if (!this.outputs[i] || !this.outputs[i].links) { - continue; - } - var links = this.outputs[i].links; - for (var j = 0; j < links.length; ++j) { - var link = this.graph.links[links[j]]; - if (!link) { - continue; - } - link.origin_slot -= 1; - } - } - - this.setSize( this.computeSize() ); - if (this.onOutputRemoved) { - this.onOutputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add a new input slot to use in this node - * @method addInput - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - LGraphNode.prototype.addInput = function(name, type, extra_info) { - type = type || 0; - var input = { name: name, type: type, link: null }; - if (extra_info) { - for (var i in extra_info) { - input[i] = extra_info[i]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - - this.inputs.push(input); - this.setSize( this.computeSize() ); - - if (this.onInputAdded) { - this.onInputAdded(input); - } - - LiteGraph.registerNodeAndSlotType(this,type); - - this.setDirtyCanvas(true, true); - return input; - }; - - /** - * add several new input slots in this node - * @method addInputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addInputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - if (this.onInputAdded) { - this.onInputAdded(o); - } - - LiteGraph.registerNodeAndSlotType(this,info[1]); - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing input slot - * @method removeInput - * @param {number} slot - */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); - var slot_info = this.inputs.splice(slot, 1); - for (var i = slot; i < this.inputs.length; ++i) { - if (!this.inputs[i]) { - continue; - } - var link = this.graph.links[this.inputs[i].link]; - if (!link) { - continue; - } - link.target_slot -= 1; - } - this.setSize( this.computeSize() ); - if (this.onInputRemoved) { - this.onInputRemoved(slot, slot_info[0] ); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add an special connection to this node (used for special kinds of graphs) - * @method addConnection - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...) - * @param {[x,y]} pos position of the connection inside the node - * @param {string} direction if is input or output - */ - LGraphNode.prototype.addConnection = function(name, type, pos, direction) { - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push(o); - return o; - }; - - /** - * computes the minimum size of a node according to its inputs and output slots - * @method computeSize - * @param {vec2} minHeight - * @return {vec2} the total size - */ - LGraphNode.prototype.computeSize = function(out) { - if (this.constructor.size) { - return this.constructor.size.concat(); - } - - var rows = Math.max( - this.inputs ? this.inputs.length : 1, - this.outputs ? this.outputs.length : 1 - ); - var size = out || new Float32Array([0, 0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - - var title_width = compute_text_size(this.title); - var input_width = 0; - var output_width = 0; - - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size(text); - if (input_width < text_width) { - input_width = text_width; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size(text); - if (output_width < text_width) { - output_width = text_width; - } - } - } - - size[0] = Math.max(input_width + output_width + 10, title_width); - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); - if (this.widgets && this.widgets.length) { - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); - } - - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - for (var i = 0, l = this.widgets.length; i < l; ++i) { - if (this.widgets[i].computeSize) - widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; - else - widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - widgets_height += 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; - - function compute_text_size(text) { - if (!text) { - return 0; - } - return font_size * text.length * 0.6; - } - - if ( - this.constructor.min_height && - size[1] < this.constructor.min_height - ) { - size[1] = this.constructor.min_height; - } - - size[1] += 6; //margin - - return size; - }; - - /** - * returns all the info available about a property of this node. - * - * @method getPropertyInfo - * @param {String} property name of the property - * @return {Object} the object with all the available info - */ - LGraphNode.prototype.getPropertyInfo = function( property ) - { - var info = null; - - //there are several ways to define info about a property - //legacy mode - if (this.properties_info) { - for (var i = 0; i < this.properties_info.length; ++i) { - if (this.properties_info[i].name == property) { - info = this.properties_info[i]; - break; - } - } - } - //litescene mode using the constructor - if(this.constructor["@" + property]) - info = this.constructor["@" + property]; - - if(this.constructor.widgets_info && this.constructor.widgets_info[property]) - info = this.constructor.widgets_info[property]; - - //litescene mode using the constructor - if (!info && this.onGetPropertyInfo) { - info = this.onGetPropertyInfo(property); - } - - if (!info) - info = {}; - if(!info.type) - info.type = typeof this.properties[property]; - if(info.widget == "combo") - info.type = "enum"; - - return info; - } - - /** - * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties - * - * @method addWidget - * @param {String} type the widget type (could be "number","string","combo" - * @param {String} name the text to show on the widget - * @param {String} value the default value - * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) - * @param {Object} options the object that contains special properties of this widget - * @return {Object} the created widget object - */ - LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) - { - if (!this.widgets) { - this.widgets = []; - } - - if(!options && callback && callback.constructor === Object) - { - options = callback; - callback = null; - } - - if(options && options.constructor === String) //options can be the property name - options = { property: options }; - - if(callback && callback.constructor === String) //callback can be the property name - { - if(!options) - options = {}; - options.property = callback; - callback = null; - } - - if(callback && callback.constructor !== Function) - { - console.warn("addWidget: callback must be a function"); - callback = null; - } - - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if (w.options.y !== undefined) { - w.y = w.options.y; - } - - if (!callback && !w.options.callback && !w.options.property) { - console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - } - if (type == "combo" && !w.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(w); - this.setSize( this.computeSize() ); - return w; - }; - - LGraphNode.prototype.addCustomWidget = function(custom_widget) { - if (!this.widgets) { - this.widgets = []; - } - this.widgets.push(custom_widget); - return custom_widget; - }; - - /** - * returns the bounding of the object, used for rendering purposes - * @method getBounding - * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage - * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation - * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] - */ - LGraphNode.prototype.getBounding = function(out, compute_outer) { - out = out || new Float32Array(4); - const nodePos = this.pos; - const isCollapsed = this.flags.collapsed; - const nodeSize = this.size; - - let left_offset = 0; - // 1 offset due to how nodes are rendered - let right_offset = 1 ; - let top_offset = 0; - let bottom_offset = 0; - - if (compute_outer) { - // 4 offset for collapsed node connection points - left_offset = 4; - // 6 offset for right shadow and collapsed node connection points - right_offset = 6 + left_offset; - // 4 offset for collapsed nodes top connection points - top_offset = 4; - // 5 offset for bottom shadow and collapsed node connection points - bottom_offset = 5 + top_offset; - } - - out[0] = nodePos[0] - left_offset; - out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset; - out[2] = isCollapsed ? - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset : - nodeSize[0] + right_offset; - out[3] = isCollapsed ? - LiteGraph.NODE_TITLE_HEIGHT + bottom_offset : - nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset; - - if (this.onBounding) { - this.onBounding(out); - } - return out; - }; - - /** - * checks if a point is inside the shape of a node - * @method isPointInside - * @param {number} x - * @param {number} y - * @return {boolean} - */ - LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; - if (skip_title) { - margin_top = 0; - } - if (this.flags && this.flags.collapsed) { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if ( - isInsideRectangle( - x, - y, - this.pos[0] - margin, - this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + - 2 * margin, - LiteGraph.NODE_TITLE_HEIGHT + 2 * margin - ) - ) { - return true; - } - } else if ( - this.pos[0] - 4 - margin < x && - this.pos[0] + this.size[0] + 4 + margin > x && - this.pos[1] - margin_top - margin < y && - this.pos[1] + this.size[1] + margin > y - ) { - return true; - } - return false; - }; - - /** - * checks if a point is inside a node slot, and returns info about which slot - * @method getSlotInPosition - * @param {number} x - * @param {number} y - * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } - */ - LGraphNode.prototype.getSlotInPosition = function(x, y) { - //search for inputs - var link_pos = new Float32Array(2); - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - this.getConnectionPos(true, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { input: input, slot: i, link_pos: link_pos }; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - this.getConnectionPos(false, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { output: output, slot: i, link_pos: link_pos }; - } - } - } - - return null; - }; - - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @method findInputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlot = function(name, returnObj) { - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (name == this.inputs[i].name) { - return !returnObj ? i : this.inputs[i]; - } - } - return -1; - }; - - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @method findOutputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlot = function(name, returnObj) { - returnObj = returnObj || false; - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (name == this.outputs[i].name) { - return !returnObj ? i : this.outputs[i]; - } - } - return -1; - }; - - // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options - - /** - * returns the first free input slot - * @method findInputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = {returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (this.inputs[i].link && this.inputs[i].link != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.inputs[i]; - } - return -1; - }; - - /** - * returns the first output slot free - * @method findOutputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = { returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (this.outputs[i].links && this.outputs[i].links != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.outputs[i]; - } - return -1; - }; - - /** - * findSlotByType for INPUTS - */ - LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * findSlotByType for OUTPUTS - */ - LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * returns the output (or input) slot with a given type, -1 if not found - * @method findSlotByType - * @param {boolean} input uise inputs instead of outputs - * @param {string} type the type of the slot - * @param {boolean} returnObj if the obj itself wanted - * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { - input = input || false; - returnObj = returnObj || false; - preferFreeSlot = preferFreeSlot || false; - doNotUseOccupied = doNotUseOccupied || false; - var aSlots = input ? this.inputs : this.outputs; - if (!aSlots) { - return -1; - } - // !! empty string type is considered 0, * !! - if (type == "" || type == "*") type = 0; - for (var i = 0, l = aSlots.length; i < l; ++i) { - var tFound = false; - var aSource = (type+"").toLowerCase().split(","); - var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; - aDest = (aDest+"").toLowerCase().split(","); - for(var sI=0;sI= 0 && target_slot !== null){ - //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) - return this.connect(slot, target_node, target_slot); - }else{ - //console.log("type "+target_slotType+" not found or not free?") - if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onTrigger IN SLOT - //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); - return this.connect(slot, target_node, -1); - } - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var target_slot = target_node.findInputSlotByType(0, false, true, true); - //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - // connect to the first free input slot if not found a specific type and this output is general - if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ - var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - - console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); - //TODO filter - - return null; - } - } - - /** - * connect this node input to the output of another node BY TYPE - * @method connectByType - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {string} target_type the output slot type of the target node - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { - var optsIn = optsIn || {}; - var optsDef = { createEventInCase: true - ,firstFreeIfInputGeneralInCase: true - ,generalTypeInCase: true - }; - var opts = Object.assign(optsDef,optsIn); - if (source_node && source_node.constructor === Number) { - source_node = this.graph.getNodeById(source_node); - } - var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); - if (source_slot >= 0 && source_slot !== null){ - //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) - return source_node.connect(source_slot, this, slot); - }else{ - - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var source_slot = source_node.findOutputSlotByType(0, false, true, true); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onExecuted OUT SLOT - if (LiteGraph.do_add_triggers_slots){ - var source_slot = source_node.addOnExecutedOutput(); - return source_node.connect(source_slot, this, slot); - } - } - // connect to the first free output slot if not found a specific type and this input is general - if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ - var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); - //TODO filter - - //console.log("type OUT! "+source_slotType+" not found or not free?") - return null; - } - } - - /** - * connect this node output to the input of another node - * @method connect - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connect = function(slot, target_node, target_slot) { - target_slot = target_slot || 0; - - if (!this.graph) { - //could be connected before adding it to a graph - console.log( - "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." - ); //due to link ids being associated with graphs - return null; - } - - //seek for the output slot - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return null; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - if (target_node && target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "target node is null"; - } - - //avoid loopback - if (target_node == this) { - return null; - } - - //you can specify the slot by name - if (target_slot.constructor === String) { - target_slot = target_node.findInputSlot(target_slot); - if (target_slot == -1) { - if (LiteGraph.debug) { - console.log( - "Connect: Error, no slot of name " + target_slot - ); - } - return null; - } - } else if (target_slot === LiteGraph.EVENT) { - - if (LiteGraph.do_add_triggers_slots){ - //search for first slot with event? :: NO this is done outside - //console.log("Connect: Creating triggerEvent"); - // force mode - target_node.changeMode(LiteGraph.ON_TRIGGER); - target_slot = target_node.findInputSlot("onTrigger"); - }else{ - return null; // -- break -- - } - } else if ( - !target_node.inputs || - target_slot >= target_node.inputs.length - ) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - var changed = false; - - var input = target_node.inputs[target_slot]; - var link_info = null; - var output = this.outputs[slot]; - - if (!this.outputs[slot]){ - /*console.debug("Invalid slot passed: "+slot); - console.debug(this.outputs);*/ - return null; - } - - // allow target node to change slot - if (target_node.onBeforeConnectInput) { - // This way node can choose another slot (or make a new one?) - target_slot = target_node.onBeforeConnectInput(target_slot); //callback - } - - //check target_slot and check connection types - if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) - { - this.setDirtyCanvas(false, true); - if(changed) - this.graph.connectionChange(this, link_info); - return null; - }else{ - //console.debug("valid connection",output.type, input.type); - } - - //allows nodes to block connection, callback - if (target_node.onConnectInput) { - if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { - return null; - } - } - if (this.onConnectOutput) { // callback - if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { - return null; - } - } - - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { - this.graph.beforeChange(); - target_node.disconnectInput(target_slot, {doProcessChange: false}); - changed = true; - } - if (output.links !== null && output.links.length){ - switch(output.type){ - case LiteGraph.EVENT: - if (!LiteGraph.allow_multi_output_for_events){ - this.graph.beforeChange(); - this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); - changed = true; - } - break; - default: - break; - } - } - - var nextId - if (LiteGraph.use_uuids) - nextId = LiteGraph.uuidv4(); - else - nextId = ++this.graph.last_link_id; - - //create link class - link_info = new LLink( - nextId, - input.type || output.type, - this.id, - slot, - target_node.id, - target_slot - ); - - //add to graph links list - this.graph.links[link_info.id] = link_info; - - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - - this.setDirtyCanvas(false, true); - this.graph.afterChange(); - this.graph.connectionChange(this, link_info); - - return link_info; - }; - - /** - * disconnect one output to an specific node - * @method disconnectOutput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - //get output slot - var output = this.outputs[slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //one of the output links in this slot - if (target_node) { - if (target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "Target Node not found"; - } - - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - - //is the link we are searching for... - if (link_info.target_id == target_node.id) { - output.links.splice(i, 1); //remove here - var input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove there - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.graph) { - this.graph._version++; - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - break; - } - } - } //all the links in this output slot - else { - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - if (!link_info) { - //bug: it happens sometimes - continue; - } - - var target_node = this.graph.getNodeById(link_info.target_id); - var input = null; - if (this.graph) { - this.graph._version++; - } - if (target_node) { - input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove other side link - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - output.links = null; - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * disconnect one input - * @method disconnectInput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectInput = function(slot) { - //seek for the output slot - if (slot.constructor === String) { - slot = this.findInputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.inputs || slot >= this.inputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - var input = this.inputs[slot]; - if (!input) { - return false; - } - - var link_id = this.inputs[slot].link; - if(link_id != null) - { - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[link_id]; - if (link_info) { - var target_node = this.graph.getNodeById(link_info.origin_id); - if (!target_node) { - return false; - } - - var output = target_node.outputs[link_info.origin_slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //search in the inputs list for this link - for (var i = 0, l = output.links.length; i < l; i++) { - if (output.links[i] == link_id) { - output.links.splice(i, 1); - break; - } - } - - delete this.graph.links[link_id]; //remove from the pool - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.INPUT, - slot, - false, - link_info, - input - ); - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.OUTPUT, - i, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - target_node, - i - ); - this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); - } - } - } //link != null - - this.setDirtyCanvas(false, true); - if(this.graph) - this.graph.connectionChange(this); - return true; - }; - - /** - * returns the center of a connection point in canvas coords - * @method getConnectionPos - * @param {boolean} is_input true if if a input slot, false if it is an output - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {vec2} out [optional] a place to store the output, to free garbage - * @return {[x,y]} the position - **/ - LGraphNode.prototype.getConnectionPos = function( - is_input, - slot_number, - out - ) { - out = out || new Float32Array(2); - var num_slots = 0; - if (is_input && this.inputs) { - num_slots = this.inputs.length; - } - if (!is_input && this.outputs) { - num_slots = this.outputs.length; - } - - var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - - if (this.flags.collapsed) { - var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { - out[0] = this.pos[0] + w * 0.5; - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1]; - } - } else { - if (is_input) { - out[0] = this.pos[0]; - } else { - out[0] = this.pos[0] + w; - } - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; - } - return out; - } - - //weird feature that never got finished - if (is_input && slot_number == -1) { - out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - return out; - } - - //hard-coded pos - if ( - is_input && - num_slots > slot_number && - this.inputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; - return out; - } else if ( - !is_input && - num_slots > slot_number && - this.outputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; - return out; - } - - //horizontal distributed slots - if (this.horizontal) { - out[0] = - this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1] + this.size[1]; - } - return out; - } - - //default vertical slots - if (is_input) { - out[0] = this.pos[0] + offset; - } else { - out[0] = this.pos[0] + this.size[0] + 1 - offset; - } - out[1] = - this.pos[1] + - (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + - (this.constructor.slot_start_y || 0); - return out; - }; - - /* Force align to grid */ - LGraphNode.prototype.alignToGrid = function() { - this.pos[0] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); - }; - - /* Console output */ - LGraphNode.prototype.trace = function(msg) { - if (!this.console) { - this.console = []; - } - - this.console.push(msg); - if (this.console.length > LGraphNode.MAX_CONSOLE) { - this.console.shift(); - } - - if(this.graph.onNodeTrace) - this.graph.onNodeTrace(this, msg); - }; - - /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - LGraphNode.prototype.setDirtyCanvas = function( - dirty_foreground, - dirty_background - ) { - if (!this.graph) { - return; - } - this.graph.sendActionToCanvas("setDirty", [ - dirty_foreground, - dirty_background - ]); - }; - - LGraphNode.prototype.loadImage = function(url) { - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - }; - return img; - }; - - //safe LGraphNode action execution (not sure if safe) - /* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; -} -*/ - - /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - LGraphNode.prototype.captureInput = function(v) { - if (!this.graph || !this.graph.list_of_graphcanvas) { - return; - } - - var list = this.graph.list_of_graphcanvas; - - for (var i = 0; i < list.length; ++i) { - var c = list[i]; - //releasing somebody elses capture?! - if (!v && c.node_capturing_input != this) { - continue; - } - - //change - c.node_capturing_input = v ? this : null; - } - }; - - /** - * Collapse the node to make it smaller on the canvas - * @method collapse - **/ - LGraphNode.prototype.collapse = function(force) { - this.graph._version++; - if (this.constructor.collapsable === false && !force) { - return; - } - if (!this.flags.collapsed) { - this.flags.collapsed = true; - } else { - this.flags.collapsed = false; - } - this.setDirtyCanvas(true, true); - }; - - /** - * Forces the node to do not move or realign on Z - * @method pin - **/ - - LGraphNode.prototype.pin = function(v) { - this.graph._version++; - if (v === undefined) { - this.flags.pinned = !this.flags.pinned; - } else { - this.flags.pinned = v; - } - }; - - LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { - return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] - ]; - }; - - function LGraphGroup(title) { - this._ctor(title); - } - - global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - - LGraphGroup.prototype._ctor = function(title) { - this.title = title || "Group"; - this.font_size = 24; - this.color = LGraphCanvas.node_colors.pale_blue - ? LGraphCanvas.node_colors.pale_blue.groupcolor - : "#AAA"; - this._bounding = new Float32Array([10, 10, 140, 80]); - this._pos = this._bounding.subarray(0, 2); - this._size = this._bounding.subarray(2, 4); - this._nodes = []; - this.graph = null; - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty(this, "size", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._size[0] = Math.max(140, v[0]); - this._size[1] = Math.max(80, v[1]); - }, - get: function() { - return this._size; - }, - enumerable: true - }); - }; - - LGraphGroup.prototype.configure = function(o) { - this.title = o.title; - this._bounding.set(o.bounding); - this.color = o.color; - this.font_size = o.font_size; - }; - - LGraphGroup.prototype.serialize = function() { - var b = this._bounding; - return { - title: this.title, - bounding: [ - Math.round(b[0]), - Math.round(b[1]), - Math.round(b[2]), - Math.round(b[3]) - ], - color: this.color, - font_size: this.font_size - }; - }; - - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - this._pos[0] += deltax; - this._pos[1] += deltay; - if (ignore_nodes) { - return; - } - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } - }; - - LGraphGroup.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if (!overlapBounding(this._bounding, node_bounding)) { - continue; - } //out of the visible area - this._nodes.push(node); - } - }; - - LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; - LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - - //**************************************** - - //Scale and Offset - function DragAndScale(element, skip_events) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = true; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - - if (element) { - this.element = element; - if (!skip_events) { - this.bindEvents(element); - } - } - } - - LiteGraph.DragAndScale = DragAndScale; - - DragAndScale.prototype.bindEvents = function(element) { - this.last_mouse = new Float32Array(2); - - this._binded_mouse_callback = this.onMouse.bind(this); - - LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); - - element.addEventListener( - "mousewheel", - this._binded_mouse_callback, - false - ); - element.addEventListener("wheel", this._binded_mouse_callback, false); - }; - - DragAndScale.prototype.computeVisibleArea = function( viewport ) { - if (!this.element) { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - return; - } - var width = this.element.width; - var height = this.element.height; - var startx = -this.offset[0]; - var starty = -this.offset[1]; - if( viewport ) - { - startx += viewport[0] / this.scale; - starty += viewport[1] / this.scale; - width = viewport[2]; - height = viewport[3]; - } - var endx = startx + width / this.scale; - var endy = starty + height / this.scale; - this.visible_area[0] = startx; - this.visible_area[1] = starty; - this.visible_area[2] = endx - startx; - this.visible_area[3] = endy - starty; - }; - - DragAndScale.prototype.onMouse = function(e) { - if (!this.enabled) { - return; - } - - var canvas = this.element; - var rect = canvas.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - e.canvasx = x; - e.canvasy = y; - e.dragging = this.dragging; - - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); - - var ignore = false; - if (this.onmouse) { - ignore = this.onmouse(e); - } - - if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { - this.dragging = true; - LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); - } else if (e.type == LiteGraph.pointerevents_method+"move") { - if (!ignore) { - var deltax = x - this.last_mouse[0]; - var deltay = y - this.last_mouse[1]; - if (this.dragging) { - this.mouseDrag(deltax, deltay); - } - } - } else if (e.type == LiteGraph.pointerevents_method+"up") { - this.dragging = false; - LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); - } else if ( is_inside && - (e.type == "mousewheel" || - e.type == "wheel" || - e.type == "DOMMouseScroll") - ) { - e.eventType = "mousewheel"; - if (e.type == "wheel") { - e.wheel = -e.deltaY; - } else { - e.wheel = - e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - } - - //from stack overflow - e.delta = e.wheelDelta - ? e.wheelDelta / 40 - : e.deltaY - ? -e.deltaY / 3 - : 0; - this.changeDeltaScale(1.0 + e.delta * 0.05); - } - - this.last_mouse[0] = x; - this.last_mouse[1] = y; - - if(is_inside) - { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - DragAndScale.prototype.toCanvasContext = function(ctx) { - ctx.scale(this.scale, this.scale); - ctx.translate(this.offset[0], this.offset[1]); - }; - - DragAndScale.prototype.convertOffsetToCanvas = function(pos) { - //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; - return [ - (pos[0] + this.offset[0]) * this.scale, - (pos[1] + this.offset[1]) * this.scale - ]; - }; - - DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { - out = out || [0, 0]; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; - }; - - DragAndScale.prototype.mouseDrag = function(x, y) { - this.offset[0] += x / this.scale; - this.offset[1] += y / this.scale; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeScale = function(value, zooming_center) { - if (value < this.min_scale) { - value = this.min_scale; - } else if (value > this.max_scale) { - value = this.max_scale; - } - - if (value == this.scale) { - return; - } - - if (!this.element) { - return; - } - - var rect = this.element.getBoundingClientRect(); - if (!rect) { - return; - } - - zooming_center = zooming_center || [ - rect.width * 0.5, - rect.height * 0.5 - ]; - var center = this.convertCanvasToOffset(zooming_center); - this.scale = value; - if (Math.abs(this.scale - 1) < 0.01) { - this.scale = 1; - } - - var new_center = this.convertCanvasToOffset(zooming_center); - var delta_offset = [ - new_center[0] - center[0], - new_center[1] - center[1] - ]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { - this.changeScale(this.scale * value, zooming_center); - }; - - DragAndScale.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - - //********************************************************************************* - // LGraphCanvas: LGraph renderer CLASS - //********************************************************************************* - - /** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @class LGraphCanvas - * @constructor - * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param {LGraph} graph [optional] - * @param {Object} options [optional] { skip_rendering, autoresize, viewport } - */ - function LGraphCanvas(canvas, graph, options) { - this.options = options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; - - if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); - } - - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = - "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = LiteGraph.LINK_COLOR; - this.default_connection_color = { - input_off: "#778", - input_on: "#7F7", //"#BBD" - output_off: "#778", - output_on: "#7F7" //"#BBD" - }; - this.default_connection_color_byType = { - /*number: "#7F7", - string: "#77F", - boolean: "#F77",*/ - } - this.default_connection_color_byTypeOff = { - /*number: "#474", - string: "#447", - boolean: "#744",*/ - }; - - this.highquality_render = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.clear_background = true; - this.clear_background_color = "#222"; - - this.read_only = false; //if set to true users cannot modify the graph - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.multi_select = false; //allow selecting multi nodes without pressing extra keys - this.allow_searchbox = true; - this.allow_reconnect_links = true; //allows to change a connection with having to redo it again - this.align_to_grid = false; //snap to grid - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything - this.always_render_background = false; - this.render_shadows = true; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = false; - this.render_connection_arrows = false; - this.render_collapsed_slots = true; - this.render_execution_order = false; - this.render_title_colored = true; - this.render_link_tooltip = true; - - this.links_render_mode = LiteGraph.SPLINE_LINK; - - this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle - this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle - this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD - - //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; - - //callbacks - this.onMouse = null; - this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform - this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform - this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) - this.onDrawLinkTooltip = null; //called when rendering a tooltip - this.onNodeMoved = null; //called after moving a node - this.onSelectionChange = null; //called if the selection changes - this.onConnectingChange = null; //called before any link changes - this.onBeforeChange = null; //called before modifying the graph - this.onAfterChange = null; //called after modifying the graph - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.over_link_center = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - - this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas - - //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } - - this.setCanvas(canvas,options.skip_events); - this.clear(); - - if (!options.skip_render) { - this.startRendering(); - } - - this.autoresize = options.autoresize; - } - - global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - - LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - - LGraphCanvas.link_type_colors = { - "-1": LiteGraph.EVENT_LINK_COLOR, - number: "#AAA", - node: "#DCA" - }; - LGraphCanvas.gradients = {}; //cache of gradients - - /** - * clears all the data inside - * - * @method clear - */ - LGraphCanvas.prototype.clear = function() { - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; - - //this.scale = 1; - //this.offset = [0,0]; - - this.dragging_rectangle = null; - - this.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dragging_canvas = false; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; - - this.node_in_panel = null; - this.node_widget = null; - - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - this.pointer_is_down = false; - this.pointer_is_double = false; - this.visible_area.set([0, 0, 0, 0]); - - if (this.onClear) { - this.onClear(); - } - }; - - /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { - if (this.graph == graph) { - return; - } - - if (!skip_clear) { - this.clear(); - } - - if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; - } - - graph.attachCanvas(this); - - //remove the graph stack in case a subgraph was open - if (this._graph_stack) - this._graph_stack = null; - - this.setDirty(true, true); - }; - - /** - * returns the top level graph (in case there are subgraphs open on the canvas) - * - * @method getTopGraph - * @return {LGraph} graph - */ - LGraphCanvas.prototype.getTopGraph = function() - { - if(this._graph_stack.length) - return this._graph_stack[0]; - return this.graph; - } - - /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.openSubgraph = function(graph) { - if (!graph) { - throw "graph cannot be null"; - } - - if (this.graph == graph) { - throw "graph cannot be the same"; - } - - this.clear(); - - if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.checkPanels(); - this.setDirty(true, true); - }; - - /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ - LGraphCanvas.prototype.closeSubgraph = function() { - if (!this._graph_stack || this._graph_stack.length == 0) { - return; - } - var subgraph_node = this.graph._subgraph_node; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true, true); - if (subgraph_node) { - this.centerOnNode(subgraph_node); - this.selectNodes([subgraph_node]); - } - // when close sub graph back to offset [0, 0] scale 1 - this.ds.offset = [0, 0] - this.ds.scale = 1 - }; - - /** - * returns the visually active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ - LGraphCanvas.prototype.getCurrentGraph = function() { - return this.graph; - }; - - /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ - LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - } - } - - if (canvas === this.canvas) { - return; - } - - if (!canvas && this.canvas) { - //maybe detach events from old_canvas - if (!skip_events) { - this.unbindEvents(); - } - } - - this.canvas = canvas; - this.ds.element = canvas; - - if (!canvas) { - return; - } - - //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - canvas.tabindex = "1"; //to allow key events - - //bg canvas: used for non changing stuff - this.bgcanvas = null; - if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if (canvas.getContext == null) { - if (canvas.localName != "canvas") { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; - } - throw "This browser doesn't support Canvas"; - } - - var ctx = (this.ctx = canvas.getContext("2d")); - if (ctx == null) { - if (!canvas.webgl_enabled) { - console.warn( - "This canvas seems to be WebGL, enabling WebGL renderer" - ); - } - this.enableWebGL(); - } - - //input: (move and up could be unbinded) - // why here? this._mousemove_callback = this.processMouseMove.bind(this); - // why here? this._mouseup_callback = this.processMouseUp.bind(this); - - if (!skip_events) { - this.bindEvents(); - } - }; - - //used in some events to capture them - LGraphCanvas.prototype._doNothing = function doNothing(e) { - //console.log("pointerevents: _doNothing "+e.type); - e.preventDefault(); - return false; - }; - LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { - e.preventDefault(); - return true; - }; - - /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ - LGraphCanvas.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; - } - - //console.log("pointerevents: bindEvents"); - - var canvas = this.canvas; - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows - - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - // why mousemove and mouseup were not binded here? - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - - //touch events -- TODO IMPLEMENT - //this._touch_callback = this.touchHandler.bind(this); - - LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); - - LiteGraph.pointerListenerAdd(canvas,"up", this._mouseup_callback, true); // CHECK: ??? binded or not - LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); - - canvas.addEventListener("contextmenu", this._doNothing); - canvas.addEventListener( - "DOMMouseScroll", - this._mousewheel_callback, - false - ); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this._touch_callback, true); - canvas.addEventListener("touchmove", this._touch_callback, true); - canvas.addEventListener("touchend", this._touch_callback, true); - canvas.addEventListener("touchcancel", this._touch_callback, true); - }*/ - - //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - canvas.setAttribute("tabindex",1); //otherwise key events are ignored - canvas.addEventListener("keydown", this._key_callback, true); - document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup - - //Dropping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); - - canvas.addEventListener("dragover", this._doNothing, false); - canvas.addEventListener("dragend", this._doNothing, false); - canvas.addEventListener("drop", this._ondrop_callback, false); - canvas.addEventListener("dragenter", this._doReturnTrue, false); - - this._events_binded = true; - }; - - /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ - LGraphCanvas.prototype.unbindEvents = function() { - if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; - } - - //console.log("pointerevents: unbindEvents"); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"down", this._mousedown_callback); - this.canvas.removeEventListener( - "mousewheel", - this._mousewheel_callback - ); - this.canvas.removeEventListener( - "DOMMouseScroll", - this._mousewheel_callback - ); - this.canvas.removeEventListener("keydown", this._key_callback); - document.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*this.canvas.removeEventListener("touchstart", this._touch_callback ); - this.canvas.removeEventListener("touchmove", this._touch_callback ); - this.canvas.removeEventListener("touchend", this._touch_callback ); - this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ - - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; - }; - - LGraphCanvas.getFileExtension = function(url) { - var question = url.indexOf("?"); - if (question != -1) { - url = url.substr(0, question); - } - var point = url.lastIndexOf("."); - if (point == -1) { - return ""; - } - return url.substr(point + 1).toLowerCase(); - }; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - * @method enableWebGL - **/ - LGraphCanvas.prototype.enableWebGL = function() { - if (typeof GL === "undefined") { - throw "litegl.js must be included to use a WebGL canvas"; - } - if (typeof enableWebGLCanvas === "undefined") { - throw "webglCanvas.js must be included to use this feature"; - } - - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ - }; - - /** - * marks as dirty the canvas, this way it will be rendered again - * - * @class LGraphCanvas - * @method setDirty - * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) - * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) - */ - LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { - if (fgcanvas) { - this.dirty_canvas = true; - } - if (bgcanvas) { - this.dirty_bgcanvas = true; - } - }; - - /** - * Used to attach the canvas in a popup - * - * @method getCanvasWindow - * @return {window} returns the window where the canvas is attached (the DOM root node) - */ - LGraphCanvas.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; - }; - - /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ - LGraphCanvas.prototype.startRendering = function() { - if (this.is_rendering) { - return; - } //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() { - if (!this.pause_rendering) { - this.draw(); - } - - var window = this.getCanvasWindow(); - if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); - } - } - }; - - /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ - LGraphCanvas.prototype.stopRendering = function() { - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ - }; - - /* LiteGraphCanvas input */ - - //used to block future mouse events (because of im gui) - LGraphCanvas.prototype.blockClick = function() - { - this.block_click = true; - this.last_mouseclick = 0; - } - - LGraphCanvas.prototype.processMouseDown = function(e) { - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - var x = e.clientX; - var y = e.clientY; - //console.log(y,this.viewport); - //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); - - this.ds.viewport = this.viewport; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //move mouse move event to the window in case it drags outside of the canvas - if(!this.options.skip_events) - { - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousemove_callback); - LiteGraph.pointerListenerAdd(ref_window.document,"move", this._mousemove_callback,true); //catch for the entire window - LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); - } - - if(!is_inside){ - return; - } - - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_primary = (e.isPrimary === undefined || !e.isPrimary); - var is_double_click = (now - this.last_mouseclick < 300) && is_primary; - this.mouse[0] = e.clientX; - this.mouse[1] = e.clientY; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - this.last_click_position = [this.mouse[0],this.mouse[1]]; - - if (this.pointer_is_down && is_primary ){ - this.pointer_is_double = true; - //console.log("pointerevents: pointer_is_double start"); - }else{ - this.pointer_is_double = false; - } - this.pointer_is_down = true; - - - this.canvas.focus(); - - LiteGraph.closeAllContextMenus(ref_window); - - if (this.onMouse) - { - if (this.onMouse(e) == true) - return; - } - - //left button mouse / single finger - if (e.which == 1 && !this.pointer_is_double) - { - if (e.ctrlKey) - { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; - } - - // clone node ALT dragging - if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) - { - if (cloned = node.clone()){ - cloned.pos[0] += 5; - cloned.pos[1] += 5; - this.graph.add(cloned,false,{doCalcSize: false}); - node = cloned; - skip_action = true; - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - if (!this.selected_nodes[node.id]) { - this.processNodeSelected(node, e); - } - } - } - } - - var clicking_canvas_bg = false; - - //when clicked on top of a node - //and it is not interactive - if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { - if (!this.live_mode && !node.flags.pinned) { - this.bringToFront(node); - } //if it wasn't selected? - - //not dragging mouse to connect two slots - if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { - //Search for corner for resize - if ( !skip_action && - node.resizable !== false && - isInsideRectangle( e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 10, - 10 - ) - ) { - this.graph.beforeChange(); - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; - } else { - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_output.slot_index = i; - this.connecting_pos = node.getConnectionPos( false, i ); - this.connecting_slot = i; - - if (LiteGraph.shift_click_do_break_link_from){ - if (e.shiftKey) { - node.disconnectOutput(i); - } - } - - if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } - } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } - } - - skip_action = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } - } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } - } - - if (input.link !== null) { - var link_info = this.graph.links[ - input.link - ]; //before disconnecting - if (LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - this.dirty_bgcanvas = true; - skip_action = true; - }else{ - // do same action as has not node ? - } - - if ( - this.allow_reconnect_links || - //this.move_destination_link_without_shift || - e.shiftKey - ) { - if (!LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - } - this.connecting_node = this.graph._nodes_by_id[ - link_info.origin_id - ]; - this.connecting_slot = - link_info.origin_slot; - this.connecting_output = this.connecting_node.outputs[ - this.connecting_slot - ]; - this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); - - this.dirty_bgcanvas = true; - skip_action = true; - } - - - }else{ - // has not node - } - - if (!skip_action){ - // connect from in to out, from to to from - this.connecting_node = node; - this.connecting_input = input; - this.connecting_input.slot_index = i; - this.connecting_pos = node.getConnectionPos( true, i ); - this.connecting_slot = i; - - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - } - } //not resizing - } - - //it wasn't clicked on the links boxes - if (!skip_action) { - var block_drag_node = false; - var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; - - //widgets - var widget = this.processNodeWidgets( node, this.graph_mouse, e ); - if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { - //double click node - if (node.onDblClick) { - node.onDblClick( e, pos, this ); - } - this.processNodeDblClicked(node); - block_drag_node = true; - } - - //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { - block_drag_node = true; - } else { - //open subgraph button - if(node.subgraph && !node.skip_subgraph_button) - { - if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { - var that = this; - setTimeout(function() { - that.openSubgraph(node.subgraph); - }, 10); - } - } - - if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; - } - } - - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - this.processNodeSelected(node, e); - } else { // double-click - /** - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while its panel is open. - */ - if (!node.is_selected) this.processNodeSelected(node, e); - } - - this.dirty_canvas = true; - } - } //clicked outside of nodes - else { - if (!skip_action){ - //search for link connector - if(!this.read_only) { - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; - } - } - - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if (this.selected_group && !this.read_only ) { - if (e.ctrlKey) { - this.dragging_rectangle = null; - } - - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } - - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - e.preventDefault(); - e.stopPropagation(); - } - - clicking_canvas_bg = true; - } - } - - if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start"); - this.dragging_canvas = true; - } - - } else if (e.which == 2) { - //middle button - - if (LiteGraph.middle_click_slot_add_default_node){ - if (node && this.allow_interaction && !skip_action && !this.read_only){ - //not dragging mouse to connect two slots - if ( - !this.connecting_node && - !node.flags.collapsed && - !this.live_mode - ) { - var mClikSlot = false; - var mClikSlot_index = false; - var mClikSlot_isOut = false; - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = output; - mClikSlot_index = i; - mClikSlot_isOut = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = input; - mClikSlot_index = i; - mClikSlot_isOut = false; - break; - } - } - } - //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); - if (mClikSlot && mClikSlot_index!==false){ - - var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); - var node_bounding = node.getBounding(); - // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes - var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 - ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" - ]; - var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node - ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index - ,nodeTo: !mClikSlot_isOut?node:null - ,slotTo: !mClikSlot_isOut?mClikSlot_index:null - ,position: posRef //,e: e - ,nodeType: "AUTO" //nodeNewType - ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] - ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ - }); - - } - } - } - } else if (!skip_action && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start from middle button"); - this.dragging_canvas = true; - } - - - } else if (e.which == 3 || this.pointer_is_double) { - - //right button - if (this.allow_interaction && !skip_action && !this.read_only){ - - // is it hover a node ? - if (node){ - if(Object.keys(this.selected_nodes).length - && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) - ){ - // is multiselected or using shift to include the now node - if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present - }else{ - // update selection - this.selectNodes([node]); - } - } - - // show menu on this node - this.processContextMenu(node, e); - } - - } - - //TODO - //if(this.node_selected != prev_selected) - // this.onNodeSelectionChange(this.node_selected); - - this.last_mouse[0] = e.clientX; - this.last_mouse[1] = e.clientY; - this.last_mouseclick = LiteGraph.getTime(); - this.last_mouse_dragging = true; - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - //this is to ensure to defocus(blur) if a text input element is on focus - if ( - !ref_window.document.activeElement || - (ref_window.document.activeElement.nodeName.toLowerCase() != - "input" && - ref_window.document.activeElement.nodeName.toLowerCase() != - "textarea") - ) { - e.preventDefault(); - } - e.stopPropagation(); - - if (this.onMouseDown) { - this.onMouseDown(e); - } - - return false; - }; - - /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ - LGraphCanvas.prototype.processMouseMove = function(e) { - if (this.autoresize) { - this.resize(); - } - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.clientX, e.clientY]; - this.mouse[0] = mouse[0]; - this.mouse[1] = mouse[1]; - var delta = [ - mouse[0] - this.last_mouse[0], - mouse[1] - this.last_mouse[1] - ]; - this.last_mouse = mouse; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - - //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); - - if(this.block_click) - { - //console.log("pointerevents: processMouseMove block_click"); - e.preventDefault(); - return false; - } - - e.dragging = this.last_mouse_dragging; - - if (this.node_widget) { - this.processNodeWidgets( - this.node_widget[0], - this.graph_mouse, - e, - this.node_widget[1] - ); - this.dirty_canvas = true; - } - - //get node over - var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); - - if (this.dragging_rectangle) - { - this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; - this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; - this.dirty_canvas = true; - } - else if (this.selected_group && !this.read_only) - { - //moving/resizing a group - if (this.selected_group_resizing) { - this.selected_group.size = [ - e.canvasX - this.selected_group.pos[0], - e.canvasY - this.selected_group.pos[1] - ]; - } else { - var deltax = delta[0] / this.ds.scale; - var deltay = delta[1] / this.ds.scale; - this.selected_group.move(deltax, deltay, e.ctrlKey); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - } - this.dirty_bgcanvas = true; - } else if (this.dragging_canvas) { - ////console.log("pointerevents: processMouseMove is dragging_canvas"); - this.ds.offset[0] += delta[0] / this.ds.scale; - this.ds.offset[1] += delta[1] / this.ds.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { - if (this.connecting_node) { - this.dirty_canvas = true; - } - - //remove mouseover flag - for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { - //mouse leave - this.graph._nodes[i].mouseOver = false; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); - } - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if (node) { - - if(node.redraw_on_mouse) - this.dirty_canvas = true; - - //this.canvas.style.cursor = "move"; - if (!node.mouseOver) { - //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; - - if (node.onMouseEnter) { - node.onMouseEnter(e); - } - } - - //in case the node wants to do something - if (node.onMouseMove) { - node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); - } - - //if dragging a link - if (this.connecting_node) { - - if (this.connecting_output){ - - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput - - //on top of input - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS - } - } else { - this._highlight_input = null; - this._highlight_input_slot = null; // XXX CHECK THIS - } - } - - }else if(this.connecting_input){ - - var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput - - //on top of output - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.outputs[slot]) { - var slot_type = node.outputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { - this._highlight_output = pos; - } - } else { - this._highlight_output = null; - } - } - } - } - - //Search for corner - if (this.canvas) { - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 5, - 5 - ) - ) { - this.canvas.style.cursor = "se-resize"; - } else { - this.canvas.style.cursor = "crosshair"; - } - } - } else { //not over a node - - //search for link connector - var over_link = null; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - over_link = link; - break; - } - if( over_link != this.over_link_center ) - { - this.over_link_center = over_link; - this.dirty_canvas = true; - } - - if (this.canvas) { - this.canvas.style.cursor = ""; - } - } //end - - //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) - if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { - this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); - } - - //node being dragged - if (this.node_dragged && !this.live_mode) { - //console.log("draggin!",this.selected_nodes); - for (var i in this.selected_nodes) { - var n = this.selected_nodes[i]; - n.pos[0] += delta[0] / this.ds.scale; - n.pos[1] += delta[1] / this.ds.scale; - if (!n.is_selected) this.processNodeSelected(n, e); /* - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while dragging. - */ - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - - if (this.resizing_node && !this.live_mode) { - //convert mouse to node space - var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; - var min_size = this.resizing_node.computeSize(); - desired_size[0] = Math.max( min_size[0], desired_size[0] ); - desired_size[1] = Math.max( min_size[1], desired_size[1] ); - this.resizing_node.setSize( desired_size ); - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ - LGraphCanvas.prototype.processMouseUp = function(e) { - - var is_primary = ( e.isPrimary === undefined || e.isPrimary ); - - //early exit for extra pointer - if(!is_primary){ - /*e.stopPropagation(); - e.preventDefault();*/ - //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); - return false; - } - - //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) - return; - - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; - - //restore the mousemove event back to the canvas - if(!this.options.skip_events) - { - //console.log("pointerevents: processMouseUp adjustEventListener"); - LiteGraph.pointerListenerRemove(document,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerAdd(this.canvas,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerRemove(document,"up", this._mouseup_callback,true); - } - - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = now - this.last_mouseclick; - this.last_mouse_dragging = false; - this.last_click_position = null; - - if(this.block_click) - { - //console.log("pointerevents: processMouseUp block_clicks"); - this.block_click = false; //used to avoid sending twice a click in a immediate button - } - - //console.log("pointerevents: processMouseUp which: "+e.which); - - if (e.which == 1) { - - if( this.node_widget ) - { - this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); - } - - //left button - this.node_widget = null; - - if (this.selected_group) { - var diffx = - this.selected_group.pos[0] - - Math.round(this.selected_group.pos[0]); - var diffy = - this.selected_group.pos[1] - - Math.round(this.selected_group.pos[1]); - this.selected_group.move(diffx, diffy, e.ctrlKey); - this.selected_group.pos[0] = Math.round( - this.selected_group.pos[0] - ); - this.selected_group.pos[1] = Math.round( - this.selected_group.pos[1] - ); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - this.selected_group = null; - } - this.selected_group_resizing = false; - - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (this.dragging_rectangle) { - if (this.graph) { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - //compute bounding and flip if left to right - var w = Math.abs(this.dragging_rectangle[2]); - var h = Math.abs(this.dragging_rectangle[3]); - var startx = - this.dragging_rectangle[2] < 0 - ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var starty = - this.dragging_rectangle[3] < 0 - ? this.dragging_rectangle[1] - h - : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = startx; - this.dragging_rectangle[1] = starty; - this.dragging_rectangle[2] = w; - this.dragging_rectangle[3] = h; - - // test dragging rect size, if minimun simulate a click - if (!node || (w > 10 && h > 10 )){ - //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var nodeX = nodes[i]; - nodeX.getBounding(node_bounding); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(nodeX); - } - if (to_select.length) { - this.selectNodes(to_select,e.shiftKey); // add to selection with shift - } - }else{ - // will select of update selection - this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey - } - - } - this.dragging_rectangle = null; - } else if (this.connecting_node) { - //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var connInOrOut = this.connecting_output || this.connecting_input; - var connType = connInOrOut.type; - - //node below mouse - if (node) { - - /* no need to condition on event type.. just another type - if ( - connType == LiteGraph.EVENT && - this.isOverNodeBox(node, e.canvasX, e.canvasY) - ) { - - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - - } else {*/ - - //slot below mouse? connect - - if (this.connecting_output){ - - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect(this.connecting_slot, node, slot); - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByType(this.connecting_slot,node,connType); - } - - }else if (this.connecting_input){ - - var slot = this.isOverNodeOutput( - node, - e.canvasX, - e.canvasY - ); - - if (slot != -1) { - node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); - } - - } - - - //} - - }else{ - - // add menu when releasing link in empty space - if (LiteGraph.release_link_on_empty_shows_menu){ - if (e.shiftKey && this.allow_searchbox){ - if(this.connecting_output){ - this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); - }else if(this.connecting_input){ - this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); - } - }else{ - if(this.connecting_output){ - this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); - }else if(this.connecting_input){ - this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); - } - } - } - } - - this.connecting_output = null; - this.connecting_input = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - } //not dragging connection - else if (this.resizing_node) { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.graph.afterChange(this.resizing_node); - this.resizing_node = null; - } else if (this.node_dragged) { - //node being dragged? - var node = this.node_dragged; - if ( - node && - e.click_time < 300 && - isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) - ) { - node.collapse(); - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - if (this.graph.config.align_to_grid || this.align_to_grid ) { - this.node_dragged.alignToGrid(); - } - if( this.onNodeMoved ) - this.onNodeMoved( this.node_dragged ); - this.graph.afterChange(this.node_dragged); - this.node_dragged = null; - } //no node being dragged - else { - //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (!node && e.click_time < 300) { - this.deselectAllNodes(); - } - - this.dirty_canvas = true; - this.dragging_canvas = false; - - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); - } - if ( - this.node_capturing_input && - this.node_capturing_input.onMouseUp - ) { - this.node_capturing_input.onMouseUp(e, [ - e.canvasX - this.node_capturing_input.pos[0], - e.canvasY - this.node_capturing_input.pos[1] - ]); - } - } - } else if (e.which == 2) { - //middle button - //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } else if (e.which == 3) { - //right button - //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - if (is_primary) - { - this.pointer_is_down = false; - this.pointer_is_double = false; - } - - this.graph.change(); - - //console.log("pointerevents: processMouseUp stopPropagation"); - e.stopPropagation(); - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ - LGraphCanvas.prototype.processMouseWheel = function(e) { - if (!this.graph || !this.allow_dragcanvas) { - return; - } - - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - - this.adjustMouseEvent(e); - - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside) - return; - - var scale = this.ds.scale; - - if (delta > 0) { - scale *= 1.1; - } else if (delta < 0) { - scale *= 1 / 1.1; - } - - //this.setZoom( scale, [ e.clientX, e.clientY ] ); - this.ds.changeScale(scale, [e.clientX, e.clientY]); - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default - }; - - /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ - LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if ( - isInsideRectangle( - canvasx, - canvasy, - node.pos[0] + 2, - node.pos[1] + 2 - title_height, - title_height - 4, - title_height - 4 - ) - ) { - return true; - } - return false; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node input slot - * @method isOverNodeInput - **/ - LGraphCanvas.prototype.isOverNodeInput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node output slot - * @method isOverNodeOuput - **/ - LGraphCanvas.prototype.isOverNodeOutput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * process a key event - * @method processKey - **/ - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - //console.log(e); //debug - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - if (e.keyCode == 32) { - //space - this.dragging_canvas = true; - block_default = true; - } - - if (e.keyCode == 27) { - //esc - if(this.node_panel) this.node_panel.close(); - if(this.options_panel) this.options_panel.close(); - block_default = true; - } - - //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; - } - - if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //copy - if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; - } - } - - if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { - //paste - this.pasteFromClipboard(e.shiftKey); - } - - //delete or backspace - if (e.keyCode == 46 || e.keyCode == 8) { - if ( - e.target.localName != "input" && - e.target.localName != "textarea" - ) { - this.deleteSelectedNodes(); - block_default = true; - } - } - - //collapse - //... - - //TODO - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } - } - } - } else if (e.type == "keyup") { - if (e.keyCode == 32) { - // space - this.dragging_canvas = false; - } - - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } - } - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }; - - LGraphCanvas.prototype.copyToClipboard = function() { - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - if (node.clonable === false) - continue; - node._relative_id = index; - selected_nodes_array.push(node); - index += 1; - } - - for (var i = 0; i < selected_nodes_array.length; ++i) { - var node = selected_nodes_array[i]; - if(node.clonable === false) - continue; - var cloned = node.clone(); - if(!cloned) - { - console.warn("node type not found: " + node.type ); - continue; - } - clipboard_info.nodes.push(cloned.serialize()); - if (node.inputs && node.inputs.length) { - for (var j = 0; j < node.inputs.length; ++j) { - var input = node.inputs[j]; - if (!input || input.link == null) { - continue; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - continue; - } - var target_node = this.graph.getNodeById( - link_info.origin_id - ); - if (!target_node) { - continue; - } - clipboard_info.links.push([ - target_node._relative_id, - link_info.origin_slot, //j, - node._relative_id, - link_info.target_slot, - target_node.id - ]); - } - } - } - localStorage.setItem( - "litegrapheditor_clipboard", - JSON.stringify(clipboard_info) - ); - }; - - LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { - // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior - if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - return; - } - var data = localStorage.getItem("litegrapheditor_clipboard"); - if (!data) { - return; - } - - this.graph.beforeChange(); - - //create nodes - var clipboard_info = JSON.parse(data); - // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos - var posMin = false; - var posMinIndexes = false; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - if (posMin){ - if(posMin[0]>clipboard_info.nodes[i].pos[0]){ - posMin[0] = clipboard_info.nodes[i].pos[0]; - posMinIndexes[0] = i; - } - if(posMin[1]>clipboard_info.nodes[i].pos[1]){ - posMin[1] = clipboard_info.nodes[i].pos[1]; - posMinIndexes[1] = i; - } - } - else{ - posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; - posMinIndexes = [i, i]; - } - } - var nodes = []; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode(node_data.type); - if (node) { - node.configure(node_data); - - //paste in last known mouse position - node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; - node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; - - this.graph.add(node,{doProcessChange:false}); - - nodes.push(node); - } - } - - //create links - for (var i = 0; i < clipboard_info.links.length; ++i) { - var link_info = clipboard_info.links[i]; - var origin_node; - var origin_node_relative_id = link_info[0]; - if (origin_node_relative_id != null) { - origin_node = nodes[origin_node_relative_id]; - } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - var origin_node_id = link_info[4]; - if (origin_node_id) { - origin_node = this.graph.getNodeById(origin_node_id); - } - } - var target_node = nodes[link_info[2]]; - if( origin_node && target_node ) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.warn("Warning, nodes missing on pasting"); - } - - this.selectNodes(nodes); - - this.graph.afterChange(); - }; - - /** - * process a item drop event on top the canvas - * @method processDrop - **/ - LGraphCanvas.prototype.processDrop = function(e) { - e.preventDefault(); - this.adjustMouseEvent(e); - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside){ - return; - // --- BREAK --- - } - - var pos = [e.canvasX, e.canvasY]; - - - var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; - - if (!node) { - var r = null; - if (this.onDropItem) { - r = this.onDropItem(event); - } - if (!r) { - this.checkDropItem(e); - } - return; - } - - if (node.onDropFile || node.onDropData) { - var files = e.dataTransfer.files; - if (files && files.length) { - for (var i = 0; i < files.length; i++) { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension(filename); - //console.log(file); - - if (node.onDropFile) { - node.onDropFile(file); - } - - if (node.onDropData) { - //prepare reader - var reader = new FileReader(); - reader.onload = function(event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; - - //read data - var type = file.type.split("/")[0]; - if (type == "text" || type == "") { - reader.readAsText(file); - } else if (type == "image") { - reader.readAsDataURL(file); - } else { - reader.readAsArrayBuffer(file); - } - } - } - } - } - - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } - - return false; - }; - - //called if the graph doesn't have a default drop item behaviour - LGraphCanvas.prototype.checkDropItem = function(e) { - if (e.dataTransfer.files.length) { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if (nodetype) { - this.graph.beforeChange(); - var node = LiteGraph.createNode(nodetype.type); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); - if (node.onDropFile) { - node.onDropFile(file); - } - this.graph.afterChange(); - } - } - }; - - LGraphCanvas.prototype.processNodeDblClicked = function(n) { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - else - { - this.showShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } - - this.setDirty(true); - }; - - LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } - }; - - /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ - LGraphCanvas.prototype.selectNode = function( - node, - add_to_current_selection - ) { - if (node == null) { - this.deselectAllNodes(); - } else { - this.selectNodes([node], add_to_current_selection); - } - }; - - /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ - LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) - { - if (!add_to_current_selection) { - this.deselectAllNodes(); - } - - nodes = nodes || this.graph._nodes; - if (typeof nodes == "string") nodes = [nodes]; - for (var i in nodes) { - var node = nodes[i]; - if (node.is_selected) { - this.deselectNode(node); - continue; - } - - if (!node.is_selected && node.onSelected) { - node.onSelected(); - } - node.is_selected = true; - this.selected_nodes[node.id] = node; - - if (node.inputs) { - for (var j = 0; j < node.inputs.length; ++j) { - this.highlighted_links[node.inputs[j].link] = true; - } - } - if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; - if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; - } - } - } - } - } - - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - - this.setDirty(true); - }; - - /** - * removes a node from the current selection - * @method deselectNode - **/ - LGraphCanvas.prototype.deselectNode = function(node) { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - - //remove highlighted - if (node.inputs) { - for (var i = 0; i < node.inputs.length; ++i) { - delete this.highlighted_links[node.inputs[i].link]; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; - if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; - } - } - } - } - }; - - /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ - LGraphCanvas.prototype.deselectAllNodes = function() { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; - if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - this.setDirty(true); - }; - - /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ - LGraphCanvas.prototype.deleteSelectedNodes = function() { - - this.graph.beforeChange(); - - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - - if(node.block_delete) - continue; - - //autoconnect when possible (very basic, only takes into account first input-output) - if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) - { - var input_link = node.graph.links[ node.inputs[0].link ]; - var output_link = node.graph.links[ node.outputs[0].links[0] ]; - var input_node = node.getInputNode(0); - var output_node = node.getOutputNodes(0)[0]; - if(input_node && output_node) - input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); - } - this.graph.remove(node); - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(true); - this.graph.afterChange(); - }; - - /** - * centers the camera on a given node - * @method centerOnNode - **/ - LGraphCanvas.prototype.centerOnNode = function(node) { - this.ds.offset[0] = - -node.pos[0] - - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / this.ds.scale; - this.ds.offset[1] = - -node.pos[1] - - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / this.ds.scale; - this.setDirty(true, true); - }; - - /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ - LGraphCanvas.prototype.adjustMouseEvent = function(e) { - var clientX_rel = 0; - var clientY_rel = 0; - - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - clientX_rel = e.clientX - b.left; - clientY_rel = e.clientY - b.top; - } else { - clientX_rel = e.clientX; - clientY_rel = e.clientY; - } - - // e.deltaX = clientX_rel - this.last_mouse_position[0]; - // e.deltaY = clientY_rel- this.last_mouse_position[1]; - - this.last_mouse_position[0] = clientX_rel; - this.last_mouse_position[1] = clientY_rel; - - e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; - e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; - - //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); - }; - - /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ - LGraphCanvas.prototype.setZoom = function(value, zooming_center) { - this.ds.changeScale(value, zooming_center); - /* - if(!zooming_center && this.canvas) - zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.ds.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - */ - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - }; - - /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ - LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { - return this.ds.convertOffsetToCanvas(pos, out); - }; - - /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ - LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { - return this.ds.convertCanvasToOffset(pos, out); - }; - - //converts event coordinates from canvas2D to graph coordinates - LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { - var rect = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([ - e.clientX - rect.left, - e.clientY - rect.top - ]); - }; - - /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ - LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); - }; - - /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ - LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); - }; - - /* Interaction */ - - /* LGraphCanvas render */ - var temp = new Float32Array(4); - - /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ - LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var n = nodes[i]; - - //skip rendering nodes in live mode - if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; - } - - if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) { - continue; - } //out of the visible area - - visible_nodes.push(n); - } - return visible_nodes; - }; - - /** - * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) - * @method draw - **/ - LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { - return; - } - - //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time) * 0.001; - this.last_draw_time = now; - - if (this.graph) { - this.ds.computeVisibleArea(this.viewport); - } - - if ( - this.dirty_bgcanvas || - force_bgcanvas || - this.always_render_background || - (this.graph && - this.graph._last_trigger_time && - now - this.graph._last_trigger_time < 1000) - ) { - this.drawBackCanvas(); - } - - if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); - } - - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - }; - - /** - * draws the front canvas (the one containing all the nodes) - * @method drawFrontCanvas - **/ - LGraphCanvas.prototype.drawFrontCanvas = function() { - this.dirty_canvas = false; - - if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; - } - - var canvas = this.canvas; - if ( ctx.start2D && !this.viewport ) { - ctx.start2D(); - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - - //clip dirty area if there is one, otherwise work in full canvas - var area = this.viewport || this.dirty_area; - if (area) { - ctx.save(); - ctx.beginPath(); - ctx.rect( area[0],area[1],area[2],area[3] ); - ctx.clip(); - } - - //clear - //canvas.width = canvas.width; - if (this.clear_background) { - if(area) - ctx.clearRect( area[0],area[1],area[2],area[3] ); - else - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - //draw bg canvas - if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); - } else { - ctx.drawImage( this.bgcanvas, 0, 0 ); - } - - //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } - - //info widget - if (this.show_info) { - this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); - } - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( - null, - this.visible_nodes - ); - - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); - - //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //on top (debug) - if (this.render_execution_order) { - this.drawExecutionOrder(ctx); - } - - //connections ontop? - if (this.graph.config.links_ontop) { - if (!this.live_mode) { - this.drawConnections(ctx); - } - } - - //current connection (the one being dragged by the mouse) - if (this.connecting_pos != null) { - ctx.lineWidth = this.connections_width; - var link_color = null; - - var connInOrOut = this.connecting_output || this.connecting_input; - - var connType = connInOrOut.type; - var connDir = connInOrOut.dir; - if(connDir == null) - { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; - } - var connShape = connInOrOut.shape; - - switch (connType) { - case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; - default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; - } - - //the connection being dragged by the mouse - this.renderLink( - ctx, - this.connecting_pos, - [this.graph_mouse[0], this.graph_mouse[1]], - null, - false, - null, - link_color, - connDir, - LiteGraph.CENTER - ); - - ctx.beginPath(); - if ( - connType === LiteGraph.EVENT || - connShape === LiteGraph.BOX_SHAPE - ) { - ctx.rect( - this.connecting_pos[0] - 6 + 0.5, - this.connecting_pos[1] - 5 + 0.5, - 14, - 10 - ); - ctx.fill(); - ctx.beginPath(); - ctx.rect( - this.graph_mouse[0] - 6 + 0.5, - this.graph_mouse[1] - 5 + 0.5, - 14, - 10 - ); - } else if (connShape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); - ctx.closePath(); - } - else { - ctx.arc( - this.connecting_pos[0], - this.connecting_pos[1], - 4, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.beginPath(); - ctx.arc( - this.graph_mouse[0], - this.graph_mouse[1], - 4, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - ctx.fillStyle = "#ffcc00"; - if (this._highlight_input) { - ctx.beginPath(); - var shape = this._highlight_input_slot.shape; - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_input[0], - this._highlight_input[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - if (this._highlight_output) { - ctx.beginPath(); - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_output[0], - this._highlight_output[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - } - - //the selection rectangle - if (this.dragging_rectangle) { - ctx.strokeStyle = "#FFF"; - ctx.strokeRect( - this.dragging_rectangle[0], - this.dragging_rectangle[1], - this.dragging_rectangle[2], - this.dragging_rectangle[3] - ); - } - - //on top of link center - if(this.over_link_center && this.render_link_tooltip) - this.drawLinkTooltip( ctx, this.over_link_center ); - else - if(this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx,null); - - //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } - - ctx.restore(); - } - - //draws panel in the corner - if (this._graph_stack && this._graph_stack.length) { - this.drawSubgraphPanel( ctx ); - } - - - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (area){ - ctx.restore(); - } - - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } - }; - - /** - * draws the panel in the corner that shows subgraph properties - * @method drawSubgraphPanel - **/ - LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { - var subgraph = this.graph; - var subnode = subgraph._subgraph_node; - if (!subnode) { - console.warn("subgraph without subnode"); - return; - } - this.drawSubgraphPanelLeft(subgraph, subnode, ctx) - this.drawSubgraphPanelRight(subgraph, subnode, ctx) - } - - LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { - var num = subnode.inputs ? subnode.inputs.length : 0; - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - ctx.fillText("Graph Inputs", 20, 34); - // var pos = this.mouse; - - if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.inputs) - for (var i = 0; i < subnode.inputs.length; ++i) { - var input = subnode.inputs[i]; - if (input.not_subgraph_input) - continue; - - //input button clicked - if (this.drawButton(20, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.input_node_type || "graph/input"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", input.name); - newnode.setProperty("type", input.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(input.name, 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(input.type, 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialog(subnode); - } - } - LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { - var num = subnode.outputs ? subnode.outputs.length : 0; - var canvas_w = this.bgcanvas.width - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - var title_text = "Graph Outputs" - var tw = ctx.measureText(title_text).width - ctx.fillText(title_text, (canvas_w - tw) - 20, 34); - // var pos = this.mouse; - if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.outputs) - for (var i = 0; i < subnode.outputs.length; ++i) { - var output = subnode.outputs[i]; - if (output.not_subgraph_input) - continue; - - //output button clicked - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.output_node_type || "graph/output"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", output.name); - newnode.setProperty("type", output.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialogRight(subnode); - } - } - //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm - LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) - { - var ctx = this.ctx; - bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; - hovercolor = hovercolor || "#555"; - textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; - var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; - if(pos) { - var rect = this.canvas.getBoundingClientRect(); - pos[0] -= rect.left; - pos[1] -= rect.top; - } - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - - ctx.fillStyle = hover ? hovercolor : bgcolor; - if(clicked) - ctx.fillStyle = "#AAA"; - ctx.beginPath(); - ctx.roundRect(x,y,w,h,[4] ); - ctx.fill(); - - if(text != null) - { - if(text.constructor == String) - { - ctx.fillStyle = textcolor; - ctx.textAlign = "center"; - ctx.font = ((h * 0.65)|0) + "px Arial"; - ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); - ctx.textAlign = "left"; - } - } - - var was_clicked = clicked && !this.block_click; - if(clicked) - this.blockClick(); - return was_clicked; - } - - LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) - { - var pos = this.mouse; - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position; - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - var was_clicked = clicked && !this.block_click; - if(clicked && hold_click) - this.blockClick(); - return was_clicked; - } - - /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 10; - y = y || this.canvas.height - 80; - - ctx.save(); - ctx.translate(x, y); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - ctx.textAlign = "left"; - if (this.graph) { - ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); - ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); - ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); - ctx.fillText("V: " + this.graph._version, 5, 13 * 4); - ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); - } else { - ctx.fillText("No graph selected", 5, 13 * 1); - } - ctx.restore(); - }; - - /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ - LGraphCanvas.prototype.drawBackCanvas = function() { - var canvas = this.bgcanvas; - if ( - canvas.width != this.canvas.width || - canvas.height != this.canvas.height - ) { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - } - - if (!this.bgctx) { - this.bgctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.bgctx; - if (ctx.start) { - ctx.start(); - } - - var viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height]; - - //clear - if (this.clear_background) { - ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] ); - } - - //show subgraph stack header - if (this._graph_stack && this._graph_stack.length) { - ctx.save(); - var parent_graph = this._graph_stack[this._graph_stack.length - 1]; - var subgraph_node = this.graph._subgraph_node; - ctx.strokeStyle = subgraph_node.bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); - ctx.lineWidth = 1; - ctx.font = "40px Arial"; - ctx.textAlign = "center"; - ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; - var title = ""; - for (var i = 1; i < this._graph_stack.length; ++i) { - title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; - } - ctx.fillText( - title + subgraph_node.getTitle(), - canvas.width * 0.5, - 40 - ); - ctx.restore(); - } - - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } - - //reset in case of error - if ( !this.viewport ) - { - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - this.visible_links.length = 0; - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //render BG - if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color ) - { - ctx.fillStyle = this.clear_background_color; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - } - - if ( - this.background_image && - this.ds.scale > 0.5 && - !bg_already_painted - ) { - if (this.zoom_modify_alpha) { - ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; - } else { - ctx.globalAlpha = this.editor_alpha; - } - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = - if ( - !this._bg_img || - this._bg_img.name != this.background_image - ) { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; - this._bg_img.onload = function() { - that.draw(true, true); - }; - } - - var pattern = null; - if (this._pattern == null && this._bg_img.width > 0) { - pattern = ctx.createPattern(this._bg_img, "repeat"); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } else { - pattern = this._pattern; - } - if (pattern) { - ctx.fillStyle = pattern; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - ctx.fillStyle = "transparent"; - } - - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled - } - - //groups - if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); - } - - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } - if (this.onBackgroundRender) { - //LEGACY - console.error( - "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; - } - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); - - //bg - if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); - } - - if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } else { - ctx.shadowColor = "rgba(0,0,0,0)"; - } - - //draw connections - if (!this.live_mode) { - this.drawConnections(ctx); - } - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if (ctx.finish) { - ctx.finish(); - } - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas - }; - - var temp_vec2 = new Float32Array(2); - - /** - * draws the given node inside the canvas - * @method drawNode - **/ - LGraphCanvas.prototype.drawNode = function(node, ctx) { - var glow = false; - this.current_node = node; - - var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) { - glow = true; - } - - var low_quality = this.ds.scale < 0.6; //zoomed out - - //only render if it forces it to do it - if (this.live_mode) { - if (!node.flags.collapsed) { - ctx.shadowColor = "transparent"; - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - } - return; - } - - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; - - if (this.render_shadows && !low_quality) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - ctx.shadowOffsetX = 2 * this.ds.scale; - ctx.shadowOffsetY = 2 * this.ds.scale; - ctx.shadowBlur = 3 * this.ds.scale; - } else { - ctx.shadowColor = "transparent"; - } - - //custom draw collapsed method (draw after shadows because they are affected) - if ( - node.flags.collapsed && - node.onDrawCollapsed && - node.onDrawCollapsed(ctx, this) == true - ) { - return; - } - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; - - if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; - if (title != null) { - node._collapsed_width = Math.min( - node.size[0], - ctx.measureText(title).width + - LiteGraph.NODE_TITLE_HEIGHT * 2 - ); //LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; - } - } - - if (node.clip_area) { - //Start clipping - ctx.save(); - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); - } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], [10]); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.clip(); - } - - //draw shape - if (node.has_errors) { - bgcolor = "red"; - } - this.drawNodeShape( - node, - ctx, - size, - color, - bgcolor, - node.is_selected, - node.mouseOver - ); - ctx.shadowColor = "transparent"; - - //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - - //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; - - var out_slot = this.connecting_output; - var in_slot = this.connecting_input; - ctx.lineWidth = 1; - - var max_y = 0; - var slot_pos = new Float32Array(2); //to reuse - - //render inputs and outputs - if (!node.flags.collapsed) { - //input connection slots - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.fillStyle = - slot.link != null - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_off; - - var pos = node.getConnectionPos(true, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.beginPath(); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? - } - - var doStroke = true; - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - ctx.fill(); - - //render name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { - ctx.fillText(text, pos[0], pos[1] - 10); - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5); - } - } - } - } - } - - //output connection slots - - ctx.textAlign = horizontal ? "center" : "right"; - ctx.strokeStyle = "black"; - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - //change opacity of incompatible slots when dragging a connection - if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - var pos = node.getConnectionPos(false, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.fillStyle = - slot.links && slot.links.length - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_off; - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; - } - - var doStroke = true; - - if ( - slot_type === LiteGraph.EVENT || - slot_shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - if(!low_quality && doStroke) - ctx.stroke(); - - //render output name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8); - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5); - } - } - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if (node.widgets) { - var widgets_y = max_y; - if (horizontal || node.widgets_up) { - widgets_y = 2; - } - if( node.widgets_start_y != null ) - widgets_y = node.widgets_start_y; - this.drawNodeWidgets( - node, - widgets_y, - ctx, - this.node_widget && this.node_widget[0] == node - ? this.node_widget[1] - : null - ); - } - } else if (this.render_collapsed_slots) { - //if collapsed - var input_slot = null; - var output_slot = null; - - //get first connected slot to render - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link == null) { - continue; - } - input_slot = slot; - break; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (!slot.links || !slot.links.length) { - continue; - } - output_slot = slot; - } - } - - if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; - } - ctx.fillStyle = "#686"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 8, y); - ctx.lineTo(x + -4, y - 4); - ctx.lineTo(x + -4, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - } - - if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; - } - ctx.fillStyle = "#686"; - ctx.strokeStyle = "black"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 6, y); - ctx.lineTo(x - 6, y - 4); - ctx.lineTo(x - 6, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - //ctx.stroke(); - } - } - - if (node.clip_area) { - ctx.restore(); - } - - ctx.globalAlpha = 1.0; - }; - - //used by this.over_link_center - LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) - { - var pos = link._pos; - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); - ctx.fill(); - - if(link.data == null) - return; - - if(this.onDrawLinkTooltip) - if( this.onDrawLinkTooltip(ctx,link,this) == true ) - return; - - var data = link.data; - var text = null; - - if( data.constructor === Number ) - text = data.toFixed(2); - else if( data.constructor === String ) - text = "\"" + data + "\""; - else if( data.constructor === Boolean ) - text = String(data); - else if (data.toToolTip) - text = data.toToolTip(); - else - text = "[" + data.constructor.name + "]"; - - if(text == null) - return; - text = text.substr(0,30); //avoid weird - - ctx.font = "14px Courier New"; - var info = ctx.measureText(text); - var w = info.width + 20; - var h = 24; - ctx.shadowColor = "black"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - ctx.fillStyle = "#454"; - ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); - ctx.moveTo( pos[0] - 10, pos[1] - 15 ); - ctx.lineTo( pos[0] + 10, pos[1] - 15 ); - ctx.lineTo( pos[0], pos[1] - 5 ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.textAlign = "center"; - ctx.fillStyle = "#CEC"; - ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); - } - - /** - * draws the shape of the given node in the canvas - * @method drawNodeShape - **/ - var tmp_area = new Float32Array(4); - - LGraphCanvas.prototype.drawNodeShape = function( - node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over - ) { - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; - - //render node area depending on shape - var shape = - node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - - var title_mode = node.constructor.title_mode; - - var render_title = true; - if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { - render_title = false; - } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; - } - - var area = tmp_area; - area[0] = 0; //x - area[1] = render_title ? -title_height : 0; //y - area[2] = size[0] + 1; //w - area[3] = render_title ? size[1] + title_height : size[1]; //h - - var old_alpha = ctx.globalAlpha; - - //full node shape - //if(node.flags.collapsed) - { - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - ctx.roundRect( - area[0], - area[1], - area[2], - area[3], - shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - //separator - if(!node.flags.collapsed && render_title) - { - ctx.shadowColor = "transparent"; - ctx.fillStyle = "rgba(0,0,0,0.2)"; - ctx.fillRect(0, -1, area[2], 2); - } - } - ctx.shadowColor = "transparent"; - - if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); - } - - //title bg (remember, it is rendered ABOVE the node) - if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { - //title bar - if (node.onDrawTitleBar) { - node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); - } else if ( - title_mode != LiteGraph.TRANSPARENT_TITLE && - (node.constructor.title_color || this.render_title_colored) - ) { - var title_color = node.constructor.title_color || fgcolor; - - if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - } - - //* gradient test - if (this.use_gradients) { - var grad = LGraphCanvas.gradients[title_color]; - if (!grad) { - grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException - grad.addColorStop(1, "#000"); - } - ctx.fillStyle = grad; - } else { - ctx.fillStyle = title_color; - } - - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); - } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { - ctx.roundRect( - 0, - -title_height, - size[0] + 1, - title_height, - node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] - ); - } - ctx.fill(); - ctx.shadowColor = "transparent"; - } - - var colState = false; - if (LiteGraph.node_box_coloured_by_mode){ - if(LiteGraph.NODE_MODES_COLORS[node.mode]){ - colState = LiteGraph.NODE_MODES_COLORS[node.mode]; - } - } - if (LiteGraph.node_box_coloured_when_on){ - colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); - } - - //title box - var box_size = 10; - if (node.onDrawTitleBox) { - node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CIRCLE_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5 + 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - if(low_quality) - ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); - else - { - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } else { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.fillRect( - (title_height - box_size) * 0.5 - 1, - (title_height + box_size) * -0.5 - 1, - box_size + 2, - box_size + 2 - ); - } - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.fillRect( - (title_height - box_size) * 0.5, - (title_height + box_size) * -0.5, - box_size, - box_size - ); - } - ctx.globalAlpha = old_alpha; - - //title text - if (node.onDrawTitleText) { - node.onDrawTitleText( - ctx, - title_height, - size, - this.ds.scale, - this.title_text_font, - selected - ); - } - if (!low_quality) { - ctx.font = this.title_text_font; - var title = String(node.getTitle()); - if (title) { - if (selected) { - ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; - } else { - ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; - } - if (node.flags.collapsed) { - ctx.textAlign = "left"; - var measure = ctx.measureText(title); - ctx.fillText( - title.substr(0,20), //avoid urls too long - title_height,// + measure.width * 0.5, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - ctx.textAlign = "left"; - } else { - ctx.textAlign = "left"; - ctx.fillText( - title, - title_height, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - } - } - } - - //subgraph box - if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = node.size[0] - w; - var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); - ctx.fillStyle = over ? "#888" : "#555"; - if( shape == LiteGraph.BOX_SHAPE || low_quality) - ctx.fillRect(x+2, -w+2, w-4, w-4); - else - { - ctx.beginPath(); - ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); - ctx.fill(); - } - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - } - - //custom title render - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } - } - - //render selection marker - if (selected) { - if (node.onBounding) { - node.onBounding(area); - } - - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; - } - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3] - ); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) - ) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2] - ); - } else if (shape == LiteGraph.CARD_SHAPE) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2,2,this.round_radius * 2,2] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5 + 6, - 0, - Math.PI * 2 - ); - } - ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - // these counter helps in conditioning drawing based on if the node has been executed or an action occurred - if (node.execute_triggered>0) node.execute_triggered--; - if (node.action_triggered>0) node.action_triggered--; - }; - - var margin_area = new Float32Array(4); - var link_bounding = new Float32Array(4); - var tempA = new Float32Array(2); - var tempB = new Float32Array(2); - - /** - * draws every connection visible in the canvas - * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time - * @method drawConnections - **/ - LGraphCanvas.prototype.drawConnections = function(ctx) { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - margin_area[0] = visible_area[0] - 20; - margin_area[1] = visible_area[1] - 20; - margin_area[2] = visible_area[2] + 40; - margin_area[3] = visible_area[3] + 40; - - //draw connections - ctx.lineWidth = this.connections_width; - - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; - //for every node - var nodes = this.graph._nodes; - for (var n = 0, l = nodes.length; n < l; ++n) { - var node = nodes[n]; - //for every input (we render just inputs because it is easier as every slot can only have one input) - if (!node.inputs || !node.inputs.length) { - continue; - } - - for (var i = 0; i < node.inputs.length; ++i) { - var input = node.inputs[i]; - if (!input || input.link == null) { - continue; - } - var link_id = input.link; - var link = this.graph.links[link_id]; - if (!link) { - continue; - } - - //find link info - var start_node = this.graph.getNodeById(link.origin_id); - if (start_node == null) { - continue; - } - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; - if (start_node_slot == -1) { - start_node_slotpos = [ - start_node.pos[0] + 10, - start_node.pos[1] + 10 - ]; - } else { - start_node_slotpos = start_node.getConnectionPos( - false, - start_node_slot, - tempA - ); - } - var end_node_slotpos = node.getConnectionPos(true, i, tempB); - - //compute link bounding - link_bounding[0] = start_node_slotpos[0]; - link_bounding[1] = start_node_slotpos[1]; - link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; - if (link_bounding[2] < 0) { - link_bounding[0] += link_bounding[2]; - link_bounding[2] = Math.abs(link_bounding[2]); - } - if (link_bounding[3] < 0) { - link_bounding[1] += link_bounding[3]; - link_bounding[3] = Math.abs(link_bounding[3]); - } - - //skip links outside of the visible area of the canvas - if (!overlapBounding(link_bounding, margin_area)) { - continue; - } - - var start_slot = start_node.outputs[start_node_slot]; - var end_slot = node.inputs[i]; - if (!start_slot || !end_slot) { - continue; - } - var start_dir = - start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); - var end_dir = - end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); - - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - false, - 0, - null, - start_dir, - end_dir - ); - - //event triggered rendered on top - if (link && link._last_time && now - link._last_time < 1000) { - var f = 2.0 - (now - link._last_time) * 0.002; - var tmp = ctx.globalAlpha; - ctx.globalAlpha = tmp * f; - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - true, - f, - "white", - start_dir, - end_dir - ); - ctx.globalAlpha = tmp; - } - } - } - ctx.globalAlpha = 1; - }; - - /** - * draws a link between two points - * @method renderLink - * @param {vec2} a start pos - * @param {vec2} b end pos - * @param {Object} link the link object with all the link info - * @param {boolean} skip_border ignore the shadow of the link - * @param {boolean} flow show flow animation (for events) - * @param {string} color the color for the link - * @param {number} start_dir the direction enum - * @param {number} end_dir the direction enum - * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) - **/ - LGraphCanvas.prototype.renderLink = function( - ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines - ) { - if (link) { - this.visible_links.push(link); - } - - //choose color - if (!color && link) { - color = link.color || LGraphCanvas.link_type_colors[link.type]; - } - if (!color) { - color = this.default_link_color; - } - if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; - } - - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - - if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; - } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; - if (num_sublines > 1) { - ctx.lineWidth = 0.5; - } - - //begin line shape - ctx.beginPath(); - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; - - if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; - } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); - } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = -1; - break; - case LiteGraph.RIGHT: - start_offset_x = 1; - break; - case LiteGraph.UP: - start_offset_y = -1; - break; - case LiteGraph.DOWN: - start_offset_y = 1; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = -1; - break; - case LiteGraph.RIGHT: - end_offset_x = 1; - break; - case LiteGraph.UP: - end_offset_y = -1; - break; - case LiteGraph.DOWN: - end_offset_y = 1; - break; - } - var l = 15; - ctx.lineTo( - a[0] + start_offset_x * l, - a[1] + start_offset_y * l + offsety - ); - ctx.lineTo( - b[0] + end_offset_x * l, - b[1] + end_offset_y * l + offsety - ); - ctx.lineTo(b[0], b[1] + offsety); - } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - ctx.moveTo(a[0], a[1]); - var start_x = a[0]; - var start_y = a[1]; - var end_x = b[0]; - var end_y = b[1]; - if (start_dir == LiteGraph.RIGHT) { - start_x += 10; - } else { - start_y += 10; - } - if (end_dir == LiteGraph.LEFT) { - end_x -= 10; - } else { - end_y -= 10; - } - ctx.lineTo(start_x, start_y); - ctx.lineTo((start_x + end_x) * 0.5, start_y); - ctx.lineTo((start_x + end_x) * 0.5, end_y); - ctx.lineTo(end_x, end_y); - ctx.lineTo(b[0], b[1]); - } else { - return; - } //unknown - } - - //rendering the outline of the connection can be a little bit slow - if ( - this.render_connections_border && - this.ds.scale > 0.6 && - !skip_border - ) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //end line shape - - var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); - if (link && link._pos) { - link._pos[0] = pos[0]; - link._pos[1] = pos[1]; - } - - //render arrow in the middle - if ( - this.ds.scale >= 0.6 && - this.highquality_render && - end_dir != LiteGraph.CENTER - ) { - //render arrow - if (this.render_connection_arrows) { - //compute two points in the connection - var posA = this.computeConnectionPoint( - a, - b, - 0.25, - start_dir, - end_dir - ); - var posB = this.computeConnectionPoint( - a, - b, - 0.26, - start_dir, - end_dir - ); - var posC = this.computeConnectionPoint( - a, - b, - 0.75, - start_dir, - end_dir - ); - var posD = this.computeConnectionPoint( - a, - b, - 0.76, - start_dir, - end_dir - ); - - //compute the angle between them so the arrow points in the right direction - var angleA = 0; - var angleB = 0; - if (this.render_curved_connections) { - angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); - angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); - } else { - angleB = angleA = b[1] > a[1] ? 0 : Math.PI; - } - - //render arrow - ctx.save(); - ctx.translate(posA[0], posA[1]); - ctx.rotate(angleA); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.translate(posC[0], posC[1]); - ctx.rotate(angleB); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - } - - //circle - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); - ctx.fill(); - } - - //render flowing points - if (flow) { - ctx.fillStyle = color; - for (var i = 0; i < 5; ++i) { - var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; - var pos = this.computeConnectionPoint( - a, - b, - f, - start_dir, - end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); - } - } - }; - - //returns the link center point based on curvature - LGraphCanvas.prototype.computeConnectionPoint = function( - a, - b, - t, - start_dir, - end_dir - ) { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - var p0 = a; - var p1 = [a[0], a[1]]; - var p2 = [b[0], b[1]]; - var p3 = b; - - switch (start_dir) { - case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; - case LiteGraph.UP: - p1[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; - case LiteGraph.UP: - p2[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p2[1] += dist * 0.25; - break; - } - - var c1 = (1 - t) * (1 - t) * (1 - t); - var c2 = 3 * ((1 - t) * (1 - t)) * t; - var c3 = 3 * (1 - t) * (t * t); - var c4 = t * t * t; - - var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; - var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; - return [x, y]; - }; - - LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; - - ctx.textAlign = "center"; - ctx.strokeStyle = "white"; - ctx.globalAlpha = 0.75; - - var visible_nodes = this.visible_nodes; - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - ctx.fillStyle = "black"; - ctx.fillRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - if (node.order == 0) { - ctx.strokeRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - } - ctx.fillStyle = "#FFF"; - ctx.fillText( - node.order, - node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, - node.pos[1] - 6 - ); - } - ctx.globalAlpha = 1; - }; - - /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ - LGraphCanvas.prototype.drawNodeWidgets = function( - node, - posY, - ctx, - active_widget - ) { - if (!node.widgets || !node.widgets.length) { - return 0; - } - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.ds.scale > 0.5; - ctx.save(); - ctx.globalAlpha = this.editor_alpha; - var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; - var background_color = LiteGraph.WIDGET_BGCOLOR; - var text_color = LiteGraph.WIDGET_TEXT_COLOR; - var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - var margin = 15; - - for (var i = 0; i < widgets.length; ++i) { - var w = widgets[i]; - var y = posY; - if (w.y) { - y = w.y; - } - w.last_y = y; - ctx.strokeStyle = outline_color; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; - //ctx.lineWidth = 2; - if(w.disabled) - ctx.globalAlpha *= 0.5; - var widget_width = w.width || width; - - switch (w.type) { - case "button": - if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(margin, y, widget_width - margin * 2, H); - if(show_text && !w.disabled) - ctx.strokeRect( margin, y, widget_width - margin * 2, H ); - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); - } - break; - case "toggle": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if(show_text && !w.disabled) - ctx.stroke(); - ctx.fillStyle = w.value ? "#89A" : "#333"; - ctx.beginPath(); - ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); - ctx.fill(); - if (show_text) { - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = w.value ? text_color : secondary_text_color; - ctx.textAlign = "right"; - ctx.fillText( - w.value - ? w.options.on || "true" - : w.options.off || "false", - widget_width - 40, - y + H * 0.7 - ); - } - break; - case "slider": - ctx.fillStyle = background_color; - ctx.fillRect(margin, y, widget_width - margin * 2, H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - if(nvalue < 0.0) nvalue = 0.0; - if(nvalue > 1.0) nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); - ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); - if(show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H); - if (w.marker) { - var marker_nvalue = (w.marker - w.options.min) / range; - if(marker_nvalue < 0.0) marker_nvalue = 0.0; - if(marker_nvalue > 1.0) marker_nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; - ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H ); - } - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText( - w.label || w.name + " " + Number(w.value).toFixed( - w.options.precision != null - ? w.options.precision - : 3 - ), - widget_width * 0.5, - y + H * 0.7 - ); - } - break; - case "number": - case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if(show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] ); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.fillStyle = text_color; - if(!w.disabled) - { - ctx.beginPath(); - ctx.moveTo(margin + 16, y + 5); - ctx.lineTo(margin + 6, y + H * 0.5); - ctx.lineTo(margin + 16, y + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(widget_width - margin - 16, y + 5); - ctx.lineTo(widget_width - margin - 6, y + H * 0.5); - ctx.lineTo(widget_width - margin - 16, y + H - 5); - ctx.fill(); - } - ctx.fillStyle = secondary_text_color; - ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - if (w.type == "number") { - ctx.fillText( - Number(w.value).toFixed( - w.options.precision !== undefined - ? w.options.precision - : 3 - ), - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } else { - var v = w.value; - if( w.options.values ) - { - var values = w.options.values; - if( values.constructor === Function ) - values = values(); - if(values && values.constructor !== Array) - v = values[ w.value ]; - } - ctx.fillText( - v, - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } - } - break; - case "string": - case "text": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect( margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.save(); - ctx.beginPath(); - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.clip(); - - //ctx.stroke(); - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max - ctx.restore(); - } - break; - default: - if (w.draw) { - w.draw(ctx, node, widget_width, y, H); - } - break; - } - posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; - ctx.globalAlpha = this.editor_alpha; - - } - ctx.restore(); - ctx.textAlign = "left"; - }; - - /** - * process an event on widgets - * @method processNodeWidgets - **/ - LGraphCanvas.prototype.processNodeWidgets = function( - node, - pos, - event, - active_widget - ) { - if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { - return null; - } - - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var deltaX = event.deltaX || event.deltax || 0; - var that = this; - var ref_window = this.getCanvasWindow(); - - for (var i = 0; i < node.widgets.length; ++i) { - var w = node.widgets[i]; - if(!w || w.disabled) - continue; - var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; - var widget_width = w.width || width; - //outside - if ( w != active_widget && - (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) - continue; - - var old_value = w.value; - - //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { - //inside widget - switch (w.type) { - case "button": - if (event.type === LiteGraph.pointerevents_method+"down") { - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - w.clicked = true; - this.dirty_canvas = true; - } - break; - case "slider": - var old_value = w.value; - var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); - if(w.options.read_only) break; - w.value = w.options.min + (w.options.max - w.options.min) * nvalue; - if (old_value != w.value) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - this.dirty_canvas = true; - break; - case "number": - case "combo": - var old_value = w.value; - if (event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") { - if(deltaX) - w.value += deltaX * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (event.type == LiteGraph.pointerevents_method+"down") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - var values_list = null; - - if( w.type != "number") - values_list = values.constructor === Array ? values : Object.keys(values); - - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (delta) { //clicked in arrow, used for combos - var index = -1; - this.last_mouseclick = 0; //avoids dobl click event - if(values.constructor === Object) - index = values_list.indexOf( String( w.value ) ) + delta; - else - index = values_list.indexOf( w.value ) + delta; - if (index >= values_list.length) { - index = values_list.length - 1; - } - if (index < 0) { - index = 0; - } - if( values.constructor === Array ) - w.value = values[index]; - else - w.value = index; - } else { //combo clicked - var text_values = values != values_list ? Object.values(values) : values; - var menu = new LiteGraph.ContextMenu(text_values, { - scale: Math.max(1, this.ds.scale), - event: event, - className: "dark", - callback: inner_clicked.bind(w) - }, - ref_window); - function inner_clicked(v, option, event) { - if(values != values_list) - v = text_values.indexOf(v); - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; - } - } - } //end mousedown - else if(event.type == LiteGraph.pointerevents_method+"up" && w.type == "number") - { - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (event.click_time < 200 && delta == 0) { - this.prompt("Value",w.value,function(v) { - // check if v is a valid equation or a number - if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { - try {//solve the equation if possible - v = eval(v); - } catch (e) { } - } - this.value = Number(v); - inner_value_change(this, this.value); - }.bind(w), - event); - } - } - - if( old_value != w.value ) - setTimeout( - function() { - inner_value_change(this, this.value); - }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == LiteGraph.pointerevents_method+"down") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == LiteGraph.pointerevents_method+"down") { - this.prompt("Value",w.value,function(v) { - inner_value_change(this, v); - }.bind(w), - event,w.options ? w.options.multiline : false ); - } - break; - default: - if (w.mouse) { - this.dirty_canvas = w.mouse(event, [x, y], node); - } - break; - } //end switch - - //value changed - if( old_value != w.value ) - { - if(node.onWidgetChanged) - node.onWidgetChanged( w.name,w.value,old_value,w ); - node.graph._version++; - } - - return w; - }//end for - - function inner_value_change(widget, value) { - if(widget.type == "number"){ - value = Number(value); - } - widget.value = value; - if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { - node.setProperty( widget.options.property, value ); - } - if (widget.callback) { - widget.callback(widget.value, that, node, pos, event); - } - } - - return null; - }; - - /** - * draws every group area in the background - * @method drawGroups - **/ - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); - ctx.fill(); - - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.font = font_size + "px Arial"; - ctx.textAlign = "left"; - ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); - } - - ctx.restore(); - }; - - LGraphCanvas.prototype.adjustNodesSize = function() { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); - } - this.setDirty(true, true); - }; - - /** - * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode - * @method resize - **/ - LGraphCanvas.prototype.resize = function(width, height) { - if (!width && !height) { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if (this.canvas.width == width && this.canvas.height == height) { - return; - } - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true, true); - }; - - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - * @method switchLiveMode - **/ - LGraphCanvas.prototype.switchLiveMode = function(transition) { - if (!transition) { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; - } - - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; - if (this.live_mode) { - this.live_mode = false; - this.editor_alpha = 0.1; - } - - var t = setInterval(function() { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; - - if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } - } - if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; - } - }, 1); - }; - - LGraphCanvas.prototype.onNodeSelectionChange = function(node) { - return; //disabled - }; - - /* this is an implementation for touch not in production and not ready - */ - /*LGraphCanvas.prototype.touchHandler = function(event) { - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch (event.type) { - case "touchstart": - type = "mousedown"; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; - default: - return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - // this is eventually a Dom object, get the LGraphCanvas back - if(typeof this.getCanvasWindow == "undefined"){ - var window = this.lgraphcanvas.getCanvasWindow(); - }else{ - var window = this.getCanvasWindow(); - } - - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent( - type, - true, - true, - window, - 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, - false, - false, - false, - 0, //left - null - ); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); - };*/ - - /* CONTEXT MENU ********************/ - - LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvasOffset(mouse_event); - canvas.graph.add(group); - }; - - /** - * Determines the furthest nodes in each direction - * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.getBoundaryNodes = function(nodes) { - let top = null; - let right = null; - let bottom = null; - let left = null; - for (const nID in nodes) { - const node = nodes[nID]; - const [x, y] = node.pos; - const [width, height] = node.size; - - if (top === null || y < top.pos[1]) { - top = node; - } - if (right === null || x + width > right.pos[0] + right.size[0]) { - right = node; - } - if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { - bottom = node; - } - if (left === null || x < left.pos[0]) { - left = node; - } - } - - return { - "top": top, - "right": right, - "bottom": bottom, - "left": left - }; - } - /** - * Determines the furthest nodes in each direction for the currently selected nodes - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.prototype.boundaryNodesForSelection = function() { - return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); - } - - /** - * - * @param {LGraphNode[]} nodes a list of nodes - * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes - * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) - */ - LGraphCanvas.alignNodes = function (nodes, direction, align_to) { - if (!nodes) { - return; - } - - const canvas = LGraphCanvas.active_canvas; - let boundaryNodes = [] - if (align_to === undefined) { - boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) - } else { - boundaryNodes = { - "top": align_to, - "right": align_to, - "bottom": align_to, - "left": align_to - } - } - - for (const [_, node] of Object.entries(canvas.selected_nodes)) { - switch (direction) { - case "right": - node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; - break; - case "left": - node.pos[0] = boundaryNodes["left"].pos[0]; - break; - case "top": - node.pos[1] = boundaryNodes["top"].pos[1]; - break; - case "bottom": - node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; - break; - } - } - - canvas.dirty_canvas = true; - canvas.dirty_bgcanvas = true; - }; - - LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); - } - } - - LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); - } - } - - LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { - - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - var graph = canvas.graph; - if (!graph) - return; - - function inner_onMenuAdded(base_category ,prev_menu){ - - var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); - var entries = []; - - categories.map(function(category){ - - if (!category) - return; - - var base_category_regex = new RegExp('^(' + base_category + ')'); - var category_name = category.replace(base_category_regex,"").split('/')[0]; - var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; - - var name = category_name; - if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace - name = name.split("::")[1]; - - var index = entries.findIndex(function(entry){return entry.value === category_path}); - if (index === -1) { - entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){ - inner_onMenuAdded(value.value, contextMenu) - }}); - } - - }); - - var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); - nodes.map(function(node){ - - if (node.skip_list) - return; - - var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){ - - var first_event = contextMenu.getFirstEvent(); - canvas.graph.beforeChange(); - var node = LiteGraph.createNode(value.value); - if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); - } - if(callback) - callback(node); - canvas.graph.afterChange(); - - } - } - - entries.push(entry); - - }); - - new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); - - } - - inner_onMenuAdded('',prev_menu); - return false; - - }; - - LGraphCanvas.onMenuCollapseAll = function() {}; - - LGraphCanvas.onMenuNodeEdit = function() {}; - - LGraphCanvas.showMenuNodeOptionalInputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - entries.push(null); - continue; - } - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - - if (entry[2].label) { - label = entry[2].label; - } - - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; - } - entries.push(data); - } - } - - if (node.onMenuNodeInputs) { - var retEntries = node.onMenuNodeInputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - console.log("no input entries"); - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (v.value) { - node.graph.beforeChange(); - node.addInput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeInputAdd) { // callback to the node when adding a slot - node.onNodeInputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.showMenuNodeOptionalOutputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - //separator? - entries.push(null); - continue; - } - - if ( - node.flags && - node.flags.skip_repeated_outputs && - node.findOutputSlot(entry[0]) != -1 - ) { - continue; - } //skip the ones already on - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - if (entry[2].label) { - label = entry[2].label; - } - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; - } - entries.push(data); - } - } - - if (this.onMenuNodeOutputs) { - entries = this.onMenuNodeOutputs(entries); - } - if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted - if (node.findOutputSlot("onExecuted") == -1){ - entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} - } - } - // add callback for modifing the menu elements onMenuNodeOutputs - if (node.onMenuNodeOutputs) { - var retEntries = node.onMenuNodeOutputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (!v.value) { - return; - } - - var value = v.value[1]; - - if ( - value && - (value.constructor === Object || value.constructor === Array) - ) { - //submenu why? - var entries = []; - for (var i in value) { - entries.push({ content: i, value: value[i] }); - } - new LiteGraph.ContextMenu(entries, { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }); - return false; - } else { - node.graph.beforeChange(); - node.addOutput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeOutputAdd) { // a callback to the node when adding a slot - node.onNodeOutputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.onShowMenuNodeProperties = function( - value, - options, - e, - prev_menu, - node - ) { - if (!node || !node.properties) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - if( typeof value == "object" ) - value = JSON.stringify(value); - var info = node.getPropertyInfo(i); - if(info.type == "enum" || info.type == "combo") - value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); - - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({ - content: - "" + - (info.label ? info.label : i) + - "" + - "" + - value + - "", - value: i - }); - } - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node: node - }, - ref_window - ); - - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue(node, v.value, { - position: [rect.left, rect.top] - }); - } - - return false; - }; - - LGraphCanvas.decodeHTML = function(str) { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; - }; - - LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { - if (!node) { - return; - } - - var fApplyMultiNode = function(node){ - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.prototype.showLinkMenu = function(link, e) { - var that = this; - // console.log(link); - var node_left = that.graph.getNodeById( link.origin_id ); - var node_right = that.graph.getNodeById( link.target_id ); - var fromType = false; - if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; - var destType = false; - if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; - - var options = ["Add Node",null,"Delete",null]; - - - var menu = new LiteGraph.ContextMenu(options, { - event: e, - title: link.data != null ? link.data.constructor.name : null, - callback: inner_clicked - }); - - function inner_clicked(v,options,e) { - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - // console.debug("node autoconnect"); - if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ - return; - } - // leave the connection type checking inside connectByType - if (node_left.connectByType( link.origin_slot, node, fromType )){ - node.connectByType( link.target_slot, node_right, destType ); - node.pos[0] -= node.size[0] * 0.5; - } - }); - break; - - case "Delete": - that.graph.removeLink(link.id); - break; - default: - /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left - ,slotFrom: link.origin_slot - ,nodeTo: node - ,slotTo: link.target_slot - ,e: e - ,nodeType: "AUTO" - }); - if(nodeCreated) console.log("new node in beetween "+v+" created");*/ - } - } - - return false; - }; - - LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,position: [] // pass the event coords - ,nodeType: null // choose a nodetype to add, AUTO to set at first good - ,posAdd:[0,0] // adjust x,y - ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom!==null; - var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; - - if (!isFrom && !isTo){ - console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); - return false; - } - if (!opts.nodeType){ - console.warn("No type to createDefaultNodeForSlot"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "undefined": - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - if (slotX===false || iSlotConn===false){ - console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); - } - - // check for defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if (slotX.link !== null) { - // is connected - }else{ - // is not not connected - } - nodeNewType = false; - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ - nodeNewType = slotTypesDefault[fromSlotType][typeX]; - // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); - break; // -------- - } - } - }else{ - if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; - } - if (nodeNewType) { - var nodeNewOpts = false; - if (typeof nodeNewType == "object" && nodeNewType.node){ - nodeNewOpts = nodeNewType; - nodeNewType = nodeNewType.node; - } - - //that.graph.beforeChange(); - - var newNode = LiteGraph.createNode(nodeNewType); - if(newNode){ - // if is object pass options - if (nodeNewOpts){ - if (nodeNewOpts.properties) { - for (var i in nodeNewOpts.properties) { - newNode.addProperty( i, nodeNewOpts.properties[i] ); - } - } - if (nodeNewOpts.inputs) { - newNode.inputs = []; - for (var i in nodeNewOpts.inputs) { - newNode.addOutput( - nodeNewOpts.inputs[i][0], - nodeNewOpts.inputs[i][1] - ); - } - } - if (nodeNewOpts.outputs) { - newNode.outputs = []; - for (var i in nodeNewOpts.outputs) { - newNode.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ); - } - } - if (nodeNewOpts.title) { - newNode.title = nodeNewOpts.title; - } - if (nodeNewOpts.json) { - newNode.configure(nodeNewOpts.json); - } - - } - - // add the node - that.graph.add(newNode); - newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) - ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ - - //that.graph.afterChange(); - - // connect the two! - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); - } - - // if connecting in between - if (isFrom && isTo){ - // TODO - } - - return true; - - }else{ - console.log("failed creating "+nodeNewType); - } - } - } - return false; - } - - LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,e: null - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom; - var isTo = !isFrom && opts.nodeTo && opts.slotTo; - - if (!isFrom && !isTo){ - console.warn("No data passed to showConnectionMenu"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - var options = ["Add Node",null]; - - if (that.allow_searchbox){ - options.push("Search"); - options.push(null); - } - - // get defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - options.push(slotTypesDefault[fromSlotType][typeX]); - } - }else{ - options.push(slotTypesDefault[fromSlotType]); - } - } - - // build menu - var menu = new LiteGraph.ContextMenu(options, { - event: opts.e, - title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), - callback: inner_clicked - }); - - // callback - function inner_clicked(v,options,e) { - //console.log("Process showConnectionMenu selection"); - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); - } - }); - break; - case "Search": - if(isFrom){ - that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); - }else{ - that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); - } - break; - default: - // check for defaults nodes for this slottype - var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] - ,nodeType: v - })); - if (nodeCreated){ - // new node created - //console.log("node "+v+" created") - }else{ - // failed or v is not in defaults - } - break; - } - } - - return false; - }; - - // TODO refactor :: this is used fot title but not for properties! - LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; - - // TODO refactor :: use createDialog ? - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog"; - dialog.innerHTML = - ""; - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - var title = dialog.querySelector(".name"); - title.innerText = property; - var input = dialog.querySelector(".value"); - if (input) { - input.value = value; - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); - - if(input) input.focus(); - - var dialogCloseTimer = null; - dialog.addEventListener("mouseleave", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - - function inner() { - if(input) setValue(input.value); - } - - function setValue(value) { - if (item.type == "Number") { - value = Number(value); - } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - node.setDirtyCanvas(true, true); - } - }; - - // refactor: there are different dialogs, some uses createDialog some dont - LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { - var that = this; - var input_html = ""; - title = title || ""; - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog rounded"; - if(multiline) - dialog.innerHTML = " "; - else - dialog.innerHTML = " "; - dialog.close = function() { - that.prompt_box = null; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - canvas.parentNode.appendChild(dialog); - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - var dialogCloseTimer = null; - var prevent_timeout = false; - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout) - return; - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - if (that.prompt_box) { - that.prompt_box.close(); - } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; - - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - - var input = value_element; - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13 && e.target.localName != "textarea") { - if (callback) { - callback(this.value); - } - dialog.close(); - } else { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - - var button = dialog.querySelector("button"); - button.addEventListener("click", function(e) { - if (callback) { - callback(input.value); - } - that.setDirty(true); - dialog.close(); - }); - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - setTimeout(function() { - input.focus(); - }, 10); - - return dialog; - }; - - LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event, options) { - // proposed defaults - var def_options = { slot_from: null - ,node_from: null - ,node_to: null - ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out - ,type_filter_in: false // these are default: pass to set initially set values - ,type_filter_out: false - ,show_general_if_none_on_typefilter: true - ,show_general_after_typefiltered: true - ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave - ,show_all_if_empty: true - ,show_all_on_open: LiteGraph.search_show_all_on_open - }; - options = Object.assign(def_options, options || {}); - - //console.log(options); - - var that = this; - var input_html = ""; - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var root_document = canvas.ownerDocument || document; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = "Search "; - if (options.do_type_filter){ - dialog.innerHTML += ""; - dialog.innerHTML += ""; - } - dialog.innerHTML += "
"; - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - // dialog element has been appended - - if (options.do_type_filter){ - var selIn = dialog.querySelector(".slot_in_type_filter"); - var selOut = dialog.querySelector(".slot_out_type_filter"); - } - - dialog.close = function() { - that.search_box = null; - this.blur(); - canvas.focus(); - root_document.body.style.overflow = ""; - - setTimeout(function() { - that.canvas.focus(); - }, 20); //important, if canvas loses focus keys wont be captured - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - // hide on mouse leave - if(options.hide_on_mouse_leave){ - var prevent_timeout = false; - var timeout_close = null; - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout){ - return; - } - timeout_close = setTimeout(function() { - dialog.close(); - }, 500); - }); - // if filtering, check focus changed to comboboxes and prevent closing - if (options.do_type_filter){ - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - selOut.addEventListener("click", function(e) { - prevent_timeout++; - }); - selOut.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selOut.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - } - } - - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - if(that.search_box) - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode == 38) { - //UP - changeSelection(false); - } else if (e.keyCode == 40) { - //DOWN - changeSelection(true); - } else if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - refreshHelper(); - if (selected) { - select(selected.innerHTML); - } else if (first) { - select(first); - } else { - dialog.close(); - } - } else { - if (timeout) { - clearInterval(timeout); - } - timeout = setTimeout(refreshHelper, 250); - return; - } - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - return true; - }); - } - - // if should filter on type, load and fill selected and choose elements if passed - if (options.do_type_filter){ - if (selIn){ - var aSlots = LiteGraph.slot_types_in; - var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; - - if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) - options.type_filter_in = "_event_"; - /* this will filter on * .. but better do it manually in case - else if(options.type_filter_in === "" || options.type_filter_in === 0) - options.type_filter_in = "*";*/ - - for (var iK=0; iK (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; - - /* - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - canvas.parentNode.appendChild(dialog); - */ - - input.focus(); - if (options.show_all_on_open) refreshHelper(); - - function select(name) { - if (name) { - if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); - } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } - - graphcanvas.graph.beforeChange(); - var node = LiteGraph.createNode(name); - if (node) { - node.pos = graphcanvas.convertEventToCanvasOffset( - event - ); - graphcanvas.graph.add(node, false); - } - - if (extra && extra.data) { - if (extra.data.properties) { - for (var i in extra.data.properties) { - node.addProperty( i, extra.data.properties[i] ); - } - } - if (extra.data.inputs) { - node.inputs = []; - for (var i in extra.data.inputs) { - node.addOutput( - extra.data.inputs[i][0], - extra.data.inputs[i][1] - ); - } - } - if (extra.data.outputs) { - node.outputs = []; - for (var i in extra.data.outputs) { - node.addOutput( - extra.data.outputs[i][0], - extra.data.outputs[i][1] - ); - } - } - if (extra.data.title) { - node.title = extra.data.title; - } - if (extra.data.json) { - node.configure(extra.data.json); - } - - } - - // join node after inserting - if (options.node_from){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_from.findOutputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_from.findOutputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_from.outputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); - } - }else{ - // console.warn("cant find slot " + options.slot_from); - } - } - if (options.node_to){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_to.findInputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_to.findInputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_to.inputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - // try connection - options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); - } - }else{ - // console.warn("cant find slot_nodeTO " + options.slot_from); - } - } - - graphcanvas.graph.afterChange(); - } - } - - dialog.close(); - } - - function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } - if (!selected) { - selected = forward - ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; - } else { - selected = forward - ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - if (!selected) { - return; - } - selected.classList.add("selected"); - selected.scrollIntoView({block: "end", behavior: "smooth"}); - } - - function refreshHelper() { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if (!str && !options.show_all_if_empty) { - return; - } - - if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); - if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); - } - } - } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; - - // filter by type preprocess - if(options.do_type_filter && that.search_box){ - var sIn = that.search_box.querySelector(".slot_in_type_filter"); - var sOut = that.search_box.querySelector(".slot_out_type_filter"); - }else{ - var sIn = false; - var sOut = false; - } - - //extras - for (var i in LiteGraph.searchbox_extras) { - var extra = LiteGraph.searchbox_extras[i]; - if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { - continue; - } - var ctor = LiteGraph.registered_node_types[ extra.type ]; - if( ctor && ctor.filter != filter ) - continue; - if( ! inner_test_filter(extra.type) ) - continue; - addResult( extra.desc, "searchbox_extra" ); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - var filtered = null; - if (Array.prototype.filter) { //filter supported - var keys = Object.keys( LiteGraph.registered_node_types ); //types - var filtered = keys.filter( inner_test_filter ); - } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i) ) - filtered.push(i); - } - } - - for (var i = 0; i < filtered.length; i++) { - addResult(filtered[i]); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - // add general type if filtering - if (options.show_general_after_typefiltered - && (sIn.value || sOut.value) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "generic_type"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - // check il filtering gave no results - if ((sIn.value || sOut.value) && - ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {skipFilter: true}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "not_in_filter"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - function inner_test_filter( type, optsIn ) - { - var optsIn = optsIn || {}; - var optsDef = { skipFilter: false - ,inTypeOverride: false - ,outTypeOverride: false - }; - var opts = Object.assign(optsDef,optsIn); - var ctor = LiteGraph.registered_node_types[ type ]; - if(filter && ctor.filter != filter ) - return false; - if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) - return false; - - // filter by slot IN, OUT types - if(options.do_type_filter && !opts.skipFilter){ - var sType = type; - - var sV = sIn.value; - if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sIn && sV){ - //console.log("will check filter against "+sV); - if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_in_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - - var sV = sOut.value; - if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sOut && sV){ - //console.log("search will check filter against "+sV); - if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_out_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - } - return true; - } - } - - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } - help.innerText = type; - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; - if (className) { - help.className += " " + className; - } - help.addEventListener("click", function(e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); - } - } - - return dialog; - }; - - LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { - if (!node || node.properties[property] === undefined) { - return; - } - - options = options || {}; - var that = this; - - var info = node.getPropertyInfo(property); - var type = info.type; - - var input_html = ""; - - if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; - } else if ( (type == "enum" || type == "combo") && info.values) { - input_html = ""; - } else if (type == "boolean" || type == "toggle") { - input_html = - ""; - } else { - console.warn("unknown type: " + type); - return; - } - - var dialog = this.createDialog( - "" + - (info.label ? info.label : property) + - "" + - input_html + - "", - options - ); - - var input = false; - if ((type == "enum" || type == "combo") && info.values) { - input = dialog.querySelector("select"); - input.addEventListener("change", function(e) { - dialog.modified(); - setValue(e.target.value); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } else if (type == "boolean" || type == "toggle") { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("click", function(e) { - dialog.modified(); - setValue(!!input.checked); - }); - } - } else { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - - var v = node.properties[property] !== undefined ? node.properties[property] : ""; - if (type !== 'string') { - v = JSON.stringify(v); - } - - input.value = v; - input.addEventListener("keydown", function(e) { - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - // ENTER - inner(); // save - } else if (e.keyCode != 13) { - dialog.modified(); - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - } - if (input) input.focus(); - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - - if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) - value = info.values[value]; - - if (typeof node.properties[property] == "number") { - value = Number(value); - } - if (type == "array" || type == "object") { - value = JSON.parse(value); - } - node.properties[property] = value; - if (node.graph) { - node.graph._version++; - } - if (node.onPropertyChanged) { - node.onPropertyChanged(property, value); - } - if(options.onclose) - options.onclose(); - dialog.close(); - node.setDirtyCanvas(true, true); - } - - return dialog; - }; - - // TODO refactor, theer are different dialog, some uses createDialog, some dont - LGraphCanvas.prototype.createDialog = function(html, options) { - var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; - options = Object.assign(def_options, options || {}); - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - dialog.is_modified = false; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (options.position) { - offsetx += options.position[0]; - offsety += options.position[1]; - } else if (options.event) { - offsetx += options.event.clientX; - offsety += options.event.clientY; - } //centered - else { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild(dialog); - - // acheck for input and use default behaviour: save on enter, close on esc - if (options.checkForInput){ - var aI = []; - var focused = false; - if (aI = dialog.querySelectorAll("input")){ - aI.forEach(function(iX) { - iX.addEventListener("keydown",function(e){ - dialog.modified(); - if (e.keyCode == 27) { - dialog.close(); - } else if (e.keyCode != 13) { - return; - } - // set value ? - e.preventDefault(); - e.stopPropagation(); - }); - if (!focused) iX.focus(); - }); - } - } - - dialog.modified = function(){ - dialog.is_modified = true; - } - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var dialogCloseTimer = null; - var prevent_timeout = false; - dialog.addEventListener("mouseleave", function(e) { - if (prevent_timeout) - return; - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - return dialog; - }; - - LGraphCanvas.prototype.createPanel = function(title, options) { - options = options || {}; - - var ref_window = options.window || window; - var root = document.createElement("div"); - root.className = "litegraph dialog"; - root.innerHTML = "
"; - root.header = root.querySelector(".dialog-header"); - - if(options.width) - root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); - if(options.height) - root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); - if(options.closable) - { - var close = document.createElement("span"); - close.innerHTML = "✕"; - close.classList.add("close"); - close.addEventListener("click",function(){ - root.close(); - }); - root.header.appendChild(close); - } - root.title_element = root.querySelector(".dialog-title"); - root.title_element.innerText = title; - root.content = root.querySelector(".dialog-content"); - root.alt_content = root.querySelector(".dialog-alt-content"); - root.footer = root.querySelector(".dialog-footer"); - - root.close = function() - { - if (root.onClose && typeof root.onClose == "function"){ - root.onClose(); - } - if(root.parentNode) - root.parentNode.removeChild(root); - /* XXX CHECK THIS */ - if(this.parentNode){ - this.parentNode.removeChild(this); - } - /* XXX this was not working, was fixed with an IF, check this */ - } - - // function to swap panel content - root.toggleAltContent = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - var vAlt = force ? "none" : "block"; - }else{ - var vTo = root.alt_content.style.display != "block" ? "block" : "none"; - var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; - } - root.alt_content.style.display = vTo; - root.content.style.display = vAlt; - } - - root.toggleFooterVisibility = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - }else{ - var vTo = root.footer.style.display != "block" ? "block" : "none"; - } - root.footer.style.display = vTo; - } - - root.clear = function() - { - this.content.innerHTML = ""; - } - - root.addHTML = function(code, classname, on_footer) - { - var elem = document.createElement("div"); - if(classname) - elem.className = classname; - elem.innerHTML = code; - if(on_footer) - root.footer.appendChild(elem); - else - root.content.appendChild(elem); - return elem; - } - - root.addButton = function( name, callback, options ) - { - var elem = document.createElement("button"); - elem.innerText = name; - elem.options = options; - elem.classList.add("btn"); - elem.addEventListener("click",callback); - root.footer.appendChild(elem); - return elem; - } - - root.addSeparator = function() - { - var elem = document.createElement("div"); - elem.className = "separator"; - root.content.appendChild(elem); - } - - root.addWidget = function( type, name, value, options, callback ) - { - options = options || {}; - var str_value = String(value); - type = type.toLowerCase(); - if(type == "number") - str_value = value.toFixed(3); - - var elem = document.createElement("div"); - elem.className = "property"; - elem.innerHTML = ""; - elem.querySelector(".property_name").innerText = options.label || name; - var value_element = elem.querySelector(".property_value"); - value_element.innerText = str_value; - elem.dataset["property"] = name; - elem.dataset["type"] = options.type || type; - elem.options = options; - elem.value = value; - - if( type == "code" ) - elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); - else if (type == "boolean") - { - elem.classList.add("boolean"); - if(value) - elem.classList.add("bool-on"); - elem.addEventListener("click", function(){ - //var v = node.properties[this.dataset["property"]]; - //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; - var propname = this.dataset["property"]; - this.value = !this.value; - this.classList.toggle("bool-on"); - this.querySelector(".property_value").innerText = this.value ? "true" : "false"; - innerChange(propname, this.value ); - }); - } - else if (type == "string" || type == "number") - { - value_element.setAttribute("contenteditable",true); - value_element.addEventListener("keydown", function(e){ - if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline - { - e.preventDefault(); - this.blur(); - } - }); - value_element.addEventListener("blur", function(){ - var v = this.innerText; - var propname = this.parentNode.dataset["property"]; - var proptype = this.parentNode.dataset["type"]; - if( proptype == "number") - v = Number(v); - innerChange(propname, v); - }); - } - else if (type == "enum" || type == "combo") { - var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); - value_element.innerText = str_value; - - value_element.addEventListener("click", function(event){ - var values = options.values || []; - var propname = this.parentNode.dataset["property"]; - var elem_that = this; - var menu = new LiteGraph.ContextMenu(values,{ - event: event, - className: "dark", - callback: inner_clicked - }, - ref_window); - function inner_clicked(v, option, event) { - //node.setProperty(propname,v); - //graphcanvas.dirty_canvas = true; - elem_that.innerText = v; - innerChange(propname,v); - return false; - } - }); - } - - root.content.appendChild(elem); - - function innerChange(name, value) - { - //console.log("change",name,value); - //that.dirty_canvas = true; - if(options.callback) - options.callback(name,value,options); - if(callback) - callback(name,value,options); - } - - return elem; - } - - if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); - - return root; - }; - - LGraphCanvas.getPropertyPrintableValue = function(value, values) - { - if(!values) - return String(value); - - if(values.constructor === Array) - { - return String(value); - } - - if(values.constructor === Object) - { - var desc_value = ""; - for(var k in values) - { - if(values[k] != value) - continue; - desc_value = k; - break; - } - return String(value) + " ("+desc_value+")"; - } - } - - LGraphCanvas.prototype.closePanels = function(){ - var panel = document.querySelector("#node-panel"); - if(panel) - panel.close(); - var panel = document.querySelector("#option-panel"); - if(panel) - panel.close(); - } - - LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ - if(this.constructor && this.constructor.name == "HTMLDivElement"){ - // assume coming from the menu event click - if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ - console.warn("Canvas not found"); // need a ref to canvas obj - /*console.debug(event); - console.debug(event.target);*/ - return; - } - var graphcanvas = obEv.event.target.lgraphcanvas; - }else{ - // assume called internally - var graphcanvas = this; - } - graphcanvas.closePanels(); - var ref_window = graphcanvas.getCanvasWindow(); - panel = graphcanvas.createPanel("Options",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = false; - graphcanvas.options_panel = null; - } - }); - graphcanvas.options_panel = panel; - panel.id = "option-panel"; - panel.classList.add("settings"); - - function inner_refresh(){ - - panel.content.innerHTML = ""; //clear - - var fUpdate = function(name, value, options){ - switch(name){ - /*case "Render mode": - // Case "".. - if (options.values && options.key){ - var kV = Object.values(options.values).indexOf(value); - if (kV>=0 && options.values[kV]){ - console.debug("update graph options: "+options.key+": "+kV); - graphcanvas[options.key] = kV; - //console.debug(graphcanvas); - break; - } - } - console.warn("unexpected options"); - console.debug(options); - break;*/ - default: - //console.debug("want to update graph options: "+name+": "+value); - if (options && options.key){ - name = options.key; - } - if (options.values){ - value = Object.values(options.values).indexOf(value); - } - //console.debug("update graph option: "+name+": "+value); - graphcanvas[name] = value; - break; - } - }; - - // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement - - var aProps = LiteGraph.availableCanvasOptions; - aProps.sort(); - for(var pI in aProps){ - var pX = aProps[pI]; - panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); - } - - var aLinks = [ graphcanvas.links_render_mode ]; - panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); - - panel.addSeparator(); - - panel.footer.innerHTML = ""; // clear - - } - inner_refresh(); - - graphcanvas.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showShowNodePanel = function( node ) - { - this.SELECTED_NODE = node; - this.closePanels(); - var ref_window = this.getCanvasWindow(); - var that = this; - var graphcanvas = this; - var panel = this.createPanel(node.title || "",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.NODEPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.NODEPANEL_IS_OPEN = false; - graphcanvas.node_panel = null; - } - }); - graphcanvas.node_panel = panel; - panel.id = "node-panel"; - panel.node = node; - panel.classList.add("settings"); - - function inner_refresh() - { - panel.content.innerHTML = ""; //clear - panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); - - panel.addHTML("

Properties

"); - - var fUpdate = function(name,value){ - graphcanvas.graph.beforeChange(node); - switch(name){ - case "Title": - node.title = value; - break; - case "Mode": - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); - if (kV>=0 && LiteGraph.NODE_MODES[kV]){ - node.changeMode(kV); - }else{ - console.warn("unexpected mode: "+value); - } - break; - case "Color": - if (LGraphCanvas.node_colors[value]){ - node.color = LGraphCanvas.node_colors[value].color; - node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; - }else{ - console.warn("unexpected color: "+value); - } - break; - default: - node.setProperty(name,value); - break; - } - graphcanvas.graph.afterChange(); - graphcanvas.dirty_canvas = true; - }; - - panel.addWidget( "string", "Title", node.title, {}, fUpdate); - - panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); - - var nodeCol = ""; - if (node.color !== undefined){ - nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); - } - - panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); - - for(var pName in node.properties) - { - var value = node.properties[pName]; - var info = node.getPropertyInfo(pName); - var type = info.type || "string"; - - //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) - continue; - - panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); - } - - panel.addSeparator(); - - if(node.onShowCustomPanelInfo) - node.onShowCustomPanelInfo(panel); - - panel.footer.innerHTML = ""; // clear - panel.addButton("Delete",function(){ - if(node.block_delete) - return; - node.graph.remove(node); - panel.close(); - }).classList.add("delete"); - } - - panel.inner_showCodePad = function( propname ) - { - panel.classList.remove("settings"); - panel.classList.add("centered"); - - - /*if(window.CodeFlask) //disabled for now - { - panel.content.innerHTML = "
"; - var flask = new CodeFlask( "div.code", { language: 'js' }); - flask.updateCode(node.properties[propname]); - flask.onUpdate( function(code) { - node.setProperty(propname, code); - }); - } - else - {*/ - panel.alt_content.innerHTML = ""; - var textarea = panel.alt_content.querySelector("textarea"); - var fDoneWith = function(){ - panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); - panel.toggleFooterVisibility(true); - textarea.parentNode.removeChild(textarea); - panel.classList.add("settings"); - panel.classList.remove("centered"); - inner_refresh(); - } - textarea.value = node.properties[propname]; - textarea.addEventListener("keydown", function(e){ - if(e.code == "Enter" && e.ctrlKey ) - { - node.setProperty(propname, textarea.value); - fDoneWith(); - } - }); - panel.toggleAltContent(true); - panel.toggleFooterVisibility(false); - textarea.style.height = "calc(100% - 40px)"; - /*}*/ - var assign = panel.addButton( "Assign", function(){ - node.setProperty(propname, textarea.value); - fDoneWith(); - }); - panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); - var button = panel.addButton( "Close", fDoneWith); - button.style.float = "right"; - panel.alt_content.appendChild(button); // panel.content.appendChild(button); - } - - inner_refresh(); - - this.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) - { - console.log("showing subgraph properties dialog"); - - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if(old_panel) - old_panel.close(); - - var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() - { - panel.clear(); - - //show currents - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - if(input.not_subgraph_input) - continue; - var html = " "; - var elem = panel.addHTML(html,"subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click",function(e){ - node.removeInput( Number( this.parentNode.dataset["slot"] ) ); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html,"subgraph_property extra", true); - elem.querySelector("button").addEventListener("click", function(e){ - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if(!name || node.findInputSlot(name) != -1) - return; - node.addInput(name,type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - }); - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { - - // console.log("showing subgraph properties dialog"); - var that = this; - // old_panel if old_panel is exist close it - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if (old_panel) - old_panel.close(); - // new panel - var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() { - panel.clear(); - //show currents - if (node.outputs) - for (var i = 0; i < node.outputs.length; ++i) { - var input = node.outputs[i]; - if (input.not_subgraph_output) - continue; - var html = " "; - var elem = panel.addHTML(html, "subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click", function (e) { - node.removeOutput(Number(this.parentNode.dataset["slot"])); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html, "subgraph_property extra", true); - elem.querySelector(".name").addEventListener("keydown", function (e) { - if (e.keyCode == 13) { - addOutput.apply(this) - } - }) - elem.querySelector("button").addEventListener("click", function (e) { - addOutput.apply(this) - }); - function addOutput() { - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if (!name || node.findOutputSlot(name) != -1) - return; - node.addOutput(name, type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - } - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.checkPanels = function() - { - if(!this.canvas) - return; - var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); - for(var i = 0; i < panels.length; ++i) - { - var panel = panels[i]; - if( !panel.node ) - continue; - if( !panel.node.graph || panel.graph != this.graph ) - panel.close(); - } - } - - LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.graph.beforeChange(/*?*/); - - var fApplyMultiNode = function(node){ - node.collapse(); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); - }; - - LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { - node.pin(); - }; - - LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { - new LiteGraph.ContextMenu( - LiteGraph.NODE_MODES, - { event: e, callback: inner_clicked, parentMenu: menu, node: node } - ); - - function inner_clicked(v) { - if (!node) { - return; - } - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); - var fApplyMultiNode = function(node){ - if (kV>=0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); - else{ - console.warn("unexpected mode: "+v); - node.changeMode(LiteGraph.ALWAYS); - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - } - - return false; - }; - - LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { - if (!node) { - throw "no node for color"; - } - - var values = []; - values.push({ - value: null, - content: - "No color" - }); - - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { - value: i, - content: - "" + - i + - "" - }; - values.push(value); - } - new LiteGraph.ContextMenu(values, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - - var fApplyColor = function(node){ - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyColor(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyColor(graphcanvas.selected_nodes[i]); - } - } - node.setDirtyCanvas(true, true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - node.graph.beforeChange(/*?*/); //node - - var fApplyMultiNode = function(node){ - node.shape = v; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); //node - node.setDirtyCanvas(true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - var graph = node.graph; - graph.beforeChange(); - - - var fApplyMultiNode = function(node){ - if (node.removable === false) { - return; - } - graph.remove(node); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - graph.afterChange(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { - var graph = node.graph; - var graphcanvas = LGraphCanvas.active_canvas; - if(!graphcanvas) //?? - return; - - var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); - if( !nodes_list.length ) - nodes_list = [ node ]; - - var subgraph_node = LiteGraph.createNode("graph/subgraph"); - subgraph_node.pos = node.pos.concat(); - graph.add(subgraph_node); - - subgraph_node.buildFromNodes( nodes_list ); - - graphcanvas.deselectAllNodes(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - - node.graph.beforeChange(); - - var newSelected = {}; - - var fApplyMultiNode = function(node){ - if (node.clonable === false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - node.graph.add(newnode); - newSelected[newnode.id] = newnode; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - if(Object.keys(newSelected).length){ - graphcanvas.selectNodes(newSelected); - } - - node.graph.afterChange(); - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.node_colors = { - red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, - brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, - green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, - blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, - cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, - purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, - yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, - black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - - LGraphCanvas.prototype.getCanvasMenuOptions = function() { - var options = null; - var that = this; - if (this.getMenuOptions) { - options = this.getMenuOptions(); - } else { - options = [ - { - content: "Add Node", - has_submenu: true, - callback: LGraphCanvas.onMenuAdd - }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - //{ content: "Arrange", callback: that.graph.arrange }, - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - /*if (LiteGraph.showCanvasOptions){ - options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); - }*/ - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align", - has_submenu: true, - callback: LGraphCanvas.onGroupAlign, - }) - } - - if (this._graph_stack && this._graph_stack.length > 0) { - options.push(null, { - content: "Close subgraph", - callback: this.closeSubgraph.bind(this) - }); - } - } - - if (this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this, options); - if (extra) { - options = options.concat(extra); - } - } - - return options; - }; - - //called by processContextMenu to extract the menu list - LGraphCanvas.prototype.getNodeMenuOptions = function(node) { - var options = null; - - if (node.getMenuOptions) { - options = node.getMenuOptions(this); - } else { - options = [ - { - content: "Inputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalInputs - }, - { - content: "Outputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalOutputs - }, - null, - { - content: "Properties", - has_submenu: true, - callback: LGraphCanvas.onShowMenuNodeProperties - }, - null, - { - content: "Title", - callback: LGraphCanvas.onShowPropertyEditor - }, - { - content: "Mode", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeMode - }]; - if(node.resizable !== false){ - options.push({ - content: "Resize", callback: LGraphCanvas.onMenuResizeNode - }); - } - options.push( - { - content: "Collapse", - callback: LGraphCanvas.onMenuNodeCollapse - }, - { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, - { - content: "Colors", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Shapes", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeShapes - }, - null - ); - } - - if (node.onGetInputs) { - var inputs = node.onGetInputs(); - if (inputs && inputs.length) { - options[0].disabled = false; - } - } - - if (node.onGetOutputs) { - var outputs = node.onGetOutputs(); - if (outputs && outputs.length) { - options[1].disabled = false; - } - } - - if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this, options); - if (extra) { - extra.push(null); - options = extra.concat(options); - } - } - - if (node.clonable !== false) { - options.push({ - content: "Clone", - callback: LGraphCanvas.onMenuNodeClone - }); - } - - if(0) //TODO - options.push({ - content: "To Subgraph", - callback: LGraphCanvas.onMenuNodeToSubgraph - }); - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align Selected To", - has_submenu: true, - callback: LGraphCanvas.onNodeAlign, - }) - } - - options.push(null, { - content: "Remove", - disabled: !(node.removable !== false && !node.block_delete ), - callback: LGraphCanvas.onMenuNodeRemove - }); - - if (node.graph && node.graph.onGetNodeMenuOptions) { - node.graph.onGetNodeMenuOptions(options, node); - } - - return options; - }; - - LGraphCanvas.prototype.getGroupMenuOptions = function(node) { - var o = [ - { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, - { - content: "Color", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Font size", - property: "font_size", - type: "Number", - callback: LGraphCanvas.onShowPropertyEditor - }, - null, - { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; - }; - - LGraphCanvas.prototype.processContextMenu = function(node, event) { - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { - event: event, - callback: inner_option_clicked, - extra: node - }; - - if(node) - options.title = node.type; - - //check if mouse is in input - var slot = null; - if (node) { - slot = node.getSlotInPosition(event.canvasX, event.canvasY); - LGraphCanvas.active_node = node; - } - - if (slot) { - //on slot - menu_info = []; - if (node.getSlotMenuOptions) { - menu_info = node.getSlotMenuOptions(slot); - } else { - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); - } - var _slot = slot.input || slot.output; - if (_slot.removable){ - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - } - if (!_slot.nameLocked){ - menu_info.push({ content: "Rename Slot", slot: slot }); - } - - } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { - options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { - options.title = "Event"; - } - } else { - if (node) { - //on node - menu_info = this.getNodeMenuOptions(node); - } else { - menu_info = this.getCanvasMenuOptions(); - var group = this.graph.getGroupOnPos( - event.canvasX, - event.canvasY - ); - if (group) { - //on group - menu_info.push(null, { - content: "Edit Group", - has_submenu: true, - submenu: { - title: "Group", - extra: group, - options: this.getGroupMenuOptions(group) - } - }); - } - } - } - - //show menu - if (!menu_info) { - return; - } - - var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); - - function inner_option_clicked(v, options, e) { - if (!v) { - return; - } - - if (v.content == "Remove Slot") { - var info = v.slot; - node.graph.beforeChange(); - if (info.input) { - node.removeInput(info.slot); - } else if (info.output) { - node.removeOutput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Disconnect Links") { - var info = v.slot; - node.graph.beforeChange(); - if (info.output) { - node.disconnectOutput(info.slot); - } else if (info.input) { - node.disconnectInput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Rename Slot") { - var info = v.slot; - var slot_info = info.input - ? node.getInputInfo(info.slot) - : node.getOutputInfo(info.slot); - var dialog = that.createDialog( - "Name", - options - ); - var input = dialog.querySelector("input"); - if (input && slot_info) { - input.value = slot_info.label || ""; - } - var inner = function(){ - node.graph.beforeChange(); - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); - } - dialog.close(); - node.graph.afterChange(); - } - dialog.querySelector("button").addEventListener("click", inner); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - input.focus(); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } - }; - - //API ************************************************* - function compareObjects(a, b) { - for (var i in a) { - if (a[i] != b[i]) { - return false; - } - } - return true; - } - LiteGraph.compareObjects = compareObjects; - - function distance(a, b) { - return Math.sqrt( - (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); - } - LiteGraph.distance = distance; - - function colorToString(c) { - return ( - "rgba(" + - Math.round(c[0] * 255).toFixed() + - "," + - Math.round(c[1] * 255).toFixed() + - "," + - Math.round(c[2] * 255).toFixed() + - "," + - (c.length == 4 ? c[3].toFixed(2) : "1.0") + - ")" - ); - } - LiteGraph.colorToString = colorToString; - - function isInsideRectangle(x, y, left, top, width, height) { - if (left < x && left + width > x && top < y && top + height > y) { - return true; - } - return false; - } - LiteGraph.isInsideRectangle = isInsideRectangle; - - //[minx,miny,maxx,maxy] - function growBounding(bounding, x, y) { - if (x < bounding[0]) { - bounding[0] = x; - } else if (x > bounding[2]) { - bounding[2] = x; - } - - if (y < bounding[1]) { - bounding[1] = y; - } else if (y > bounding[3]) { - bounding[3] = y; - } - } - LiteGraph.growBounding = growBounding; - - //point inside bounding box - function isInsideBounding(p, bb) { - if ( - p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1] - ) { - return false; - } - return true; - } - LiteGraph.isInsideBounding = isInsideBounding; - - //bounding overlap, format: [ startx, starty, width, height ] - function overlapBounding(a, b) { - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( - a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1] - ) { - return false; - } - return true; - } - LiteGraph.overlapBounding = overlapBounding; - - //Convert a hex value to its decimal value - the inputted hex must be in the - // format of a hex triplet - the kind we use for HTML colours. The function - // will return an array with three values. - function hex2num(hex) { - if (hex.charAt(0) == "#") { - hex = hex.slice(1); - } //Remove the '#' char - if there is one. - hex = hex.toUpperCase(); - var hex_alphabets = "0123456789ABCDEF"; - var value = new Array(3); - var k = 0; - var int1, int2; - for (var i = 0; i < 6; i += 2) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); - value[k] = int1 * 16 + int2; - k++; - } - return value; - } - - LiteGraph.hex2num = hex2num; - - //Give a array with three values as the argument and the function will return - // the corresponding hex triplet. - function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; - for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return hex; - } - - LiteGraph.num2hex = num2hex; - - /* LiteGraph GUI elements used for canvas editing *************************************/ - - /** - * ContextMenu from LiteGUI - * - * @class ContextMenu - * @constructor - * @param {Array} values (allows object { title: "Nice text", callback: function ... }) - * @param {Object} options [optional] Some options:\ - * - title: title to show on top of the menu - * - callback: function to call when an option is clicked, it receives the item information - * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback - * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position - */ - function ContextMenu(values, options) { - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if (options.parentMenu) { - if (options.parentMenu.constructor !== this.constructor) { - console.error( - "parentMenu must be of class ContextMenu, ignoring it" - ); - options.parentMenu = null; - } else { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - var eventClass = null; - if(options.event) //use strings because comparing classes between windows doesnt work - eventClass = options.event.constructor.name; - if ( eventClass !== "MouseEvent" && - eventClass !== "CustomEvent" && - eventClass !== "PointerEvent" - ) { - console.error( - "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" - ); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - if (options.className) { - root.className += " " + options.className; - } - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout(function() { - root.style.pointerEvents = "auto"; - }, 100); //delay so the mouse up event is not caught by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - LiteGraph.pointerListenerAdd(root,"up", - function(e) { - //console.log("pointerevents: ContextMenu up root prevent"); - e.preventDefault(); - return true; - }, - true - ); - root.addEventListener( - "contextmenu", - function(e) { - if (e.button != 2) { - //right button - return false; - } - e.preventDefault(); - return false; - }, - true - ); - - LiteGraph.pointerListenerAdd(root,"down", - function(e) { - //console.log("pointerevents: ContextMenu down"); - if (e.button == 2) { - that.close(); - e.preventDefault(); - return true; - } - }, - true - ); - - function on_mouse_wheel(e) { - var pos = parseInt(root.style.top); - root.style.top = - (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; - e.preventDefault(); - return true; - } - - if (!options.scroll_speed) { - options.scroll_speed = 0.1; - } - - root.addEventListener("wheel", on_mouse_wheel, true); - root.addEventListener("mousewheel", on_mouse_wheel, true); - - this.root = root; - - //title - if (options.title) { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for (var i=0; i < values.length; i++) { - var name = values.constructor == Array ? values[i] : i; - if (name != null && name.constructor !== String) { - name = name.content === undefined ? String(name) : name.content; - } - var value = values[i]; - this.addItem(name, value, options); - num++; - } - - //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that - /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { - console.log("pointerevents: ContextMenu leave"); - if (that.lock) { - return; - } - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - root.closing_timer = setTimeout(that.close.bind(that, e), 500); - //that.close(e); - });*/ - - LiteGraph.pointerListenerAdd(root,"enter", function(e) { - //console.log("pointerevents: ContextMenu enter"); - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - }); - - //insert before checking position - var root_document = document; - if (options.event) { - root_document = options.event.target.ownerDocument; - } - - if (!root_document) { - root_document = document; - } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(root); - else - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if (options.event) { - left = options.event.clientX - 10; - top = options.event.clientY - 10; - if (options.title) { - top -= 20; - } - - if (options.parentMenu) { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - if(body_rect.height == 0) - console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); - - if (body_rect.width && left > body_rect.width - root_rect.width - 10) { - left = body_rect.width - root_rect.width - 10; - } - if (body_rect.height && top > body_rect.height - root_rect.height - 10) { - top = body_rect.height - root_rect.height - 10; - } - } - - root.style.left = left + "px"; - root.style.top = top + "px"; - - if (options.scale) { - root.style.transform = "scale(" + options.scale + ")"; - } - } - - ContextMenu.prototype.addItem = function(name, value, options) { - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if (value === null) { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } else { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if (value) { - if (value.disabled) { - disabled = true; - element.classList.add("disabled"); - } - if (value.submenu || value.has_submenu) { - element.classList.add("has_submenu"); - } - } - - if (typeof value == "function") { - element.dataset["value"] = name; - element.onclick_callback = value; - } else { - element.dataset["value"] = value; - } - - if (value.className) { - element.className += " " + value.className; - } - } - - this.root.appendChild(element); - if (!disabled) { - element.addEventListener("click", inner_onclick); - } - if (!disabled && options.autoopen) { - LiteGraph.pointerListenerAdd(element,"enter",inner_over); - } - - function inner_over(e) { - var value = this.value; - if (!value || !value.has_submenu) { - return; - } - //if it is a submenu, autoopen like the item was clicked - inner_onclick.call(this, e); - } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if (that.current_submenu) { - that.current_submenu.close(e); - } - - //global callback - if (options.callback) { - var r = options.callback.call( - this, - value, - options, - e, - that, - options.node - ); - if (r === true) { - close_parent = false; - } - } - - //special cases - if (value) { - if ( - value.callback && - !options.ignore_item_callbacks && - value.disabled !== true - ) { - //item callback - var r = value.callback.call( - this, - value, - options, - e, - that, - options.extra - ); - if (r === true) { - close_parent = false; - } - } - if (value.submenu) { - if (!value.submenu.options) { - throw "ContextMenu submenu needs options"; - } - var submenu = new that.constructor(value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: - value.submenu.ignore_item_callbacks, - title: value.submenu.title, - extra: value.submenu.extra, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if (close_parent && !that.lock) { - that.close(); - } - } - - return element; - }; - - ContextMenu.prototype.close = function(e, ignore_parent_menu) { - if (this.root.parentNode) { - this.root.parentNode.removeChild(this.root); - } - if (this.parentMenu && !ignore_parent_menu) { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if (e === undefined) { - this.parentMenu.close(); - } else if ( - e && - !ContextMenu.isCursorOverElement(e, this.parentMenu.root) - ) { - ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); - } - } - if (this.current_submenu) { - this.current_submenu.close(e, true); - } - - if (this.root.closing_timer) { - clearTimeout(this.root.closing_timer); - } - - // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu - // on key press, allow filtering/selecting the context menu elements - }; - - //this code is used to trigger events easily (used in the context menu mouseleave - ContextMenu.trigger = function(element, event_name, params, origin) { - var evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail - evt.srcElement = origin; - if (element.dispatchEvent) { - element.dispatchEvent(evt); - } else if (element.__events) { - element.__events.dispatchEvent(evt); - } - //else nothing seems binded here so nothing to do - return evt; - }; - - //returns the top most menu - ContextMenu.prototype.getTopMenu = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getTopMenu(); - } - return this; - }; - - ContextMenu.prototype.getFirstEvent = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getFirstEvent(); - } - return this.options.event; - }; - - ContextMenu.isCursorOverElement = function(event, element) { - var left = event.clientX; - var top = event.clientY; - var rect = element.getBoundingClientRect(); - if (!rect) { - return false; - } - if ( - top > rect.top && - top < rect.top + rect.height && - left > rect.left && - left < rect.left + rect.width - ) { - return true; - } - return false; - }; - - LiteGraph.ContextMenu = ContextMenu; - - LiteGraph.closeAllContextMenus = function(ref_window) { - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if (!elements.length) { - return; - } - - var result = []; - for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); - } - - for (var i=0; i < result.length; i++) { - if (result[i].close) { - result[i].close(); - } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); - } - } - }; - - LiteGraph.extendClass = function(target, origin) { - for (var i in origin) { - //copy class properties - if (target.hasOwnProperty(i)) { - continue; - } - target[i] = origin[i]; - } - - if (origin.prototype) { - //copy prototype properties - for (var i in origin.prototype) { - //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue; - } - - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue; - } - - //copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ); - } else { - target.prototype[i] = origin.prototype[i]; - } - - //and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ); - } - } - } - }; - - //used by some widgets to render a curve editor - function CurveEditor( points ) - { - this.points = points; - this.selected = -1; - this.nearest = -1; - this.size = null; //stores last size used - this.must_update = true; - this.margin = 5; - } - - CurveEditor.sampleCurve = function(f,points) - { - if(!points) - return; - for(var i = 0; i < points.length - 1; ++i) - { - var p = points[i]; - var pn = points[i+1]; - if(pn[0] < f) - continue; - var r = (pn[0] - p[0]); - if( Math.abs(r) < 0.00001 ) - return p[1]; - var local_f = (f - p[0]) / r; - return p[1] * (1.0 - local_f) + pn[1] * local_f; - } - return 0; - } - - CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) - { - var points = this.points; - if(!points) - return; - this.size = size; - var w = size[0] - this.margin * 2; - var h = size[1] - this.margin * 2; - - line_color = line_color || "#666"; - - ctx.save(); - ctx.translate(this.margin,this.margin); - - if(background_color) - { - ctx.fillStyle = "#111"; - ctx.fillRect(0,0,w,h); - ctx.fillStyle = "#222"; - ctx.fillRect(w*0.5,0,1,h); - ctx.strokeStyle = "#333"; - ctx.strokeRect(0,0,w,h); - } - ctx.strokeStyle = line_color; - if(inactive) - ctx.globalAlpha = 0.5; - ctx.beginPath(); - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); - } - ctx.stroke(); - ctx.globalAlpha = 1; - if(!inactive) - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); - ctx.beginPath(); - ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); - ctx.fill(); - } - ctx.restore(); - } - - //localpos is mouse in curve editor space - CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - if( localpos[1] < 0 ) - return; - - //this.captureInput(true); - var w = this.size[0] - this.margin * 2; - var h = this.size[1] - this.margin * 2; - var x = localpos[0] - this.margin; - var y = localpos[1] - this.margin; - var pos = [x,y]; - var max_dist = 30 / graphcanvas.ds.scale; - //search closer one - this.selected = this.getCloserPoint(pos, max_dist); - //create one - if(this.selected == -1) - { - var point = [x / w, 1 - y / h]; - points.push(point); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - if(this.selected != -1) - return true; - } - - CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - var s = this.selected; - if(s < 0) - return; - var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); - var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); - var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; - var max_dist = 30 / graphcanvas.ds.scale; - this._nearest = this.getCloserPoint(curvepos, max_dist); - var point = points[s]; - if(point) - { - var is_edge_point = s == 0 || s == points.length - 1; - if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) - { - points.splice(s,1); - this.selected = -1; - return; - } - if( !is_edge_point ) //not edges - point[0] = clamp(x, 0, 1); - else - point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - clamp(y, 0, 1); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - } - - CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) - { - this.selected = -1; - return false; - } - - CurveEditor.prototype.getCloserPoint = function(pos, max_dist) - { - var points = this.points; - if(!points) - return -1; - max_dist = max_dist || 30; - var w = (this.size[0] - this.margin * 2); - var h = (this.size[1] - this.margin * 2); - var num = points.length; - var p2 = [0,0]; - var min_dist = 1000000; - var closest = -1; - var last_valid = -1; - for(var i = 0; i < num; ++i) - { - var p = points[i]; - p2[0] = p[0] * w; - p2[1] = (1.0 - p[1]) * h; - if(p2[0] < pos[0]) - last_valid = i; - var dist = vec2.distance(pos,p2); - if(dist > min_dist || dist > max_dist) - continue; - closest = i; - min_dist = dist; - } - return closest; - } - - LiteGraph.CurveEditor = CurveEditor; - - //used to create nodes from wrapping functions - LiteGraph.getParameterNames = function(func) { - return (func + "") - .replace(/[/][/].*$/gm, "") // strip single-line comments - .replace(/\s+/g, "") // strip white space - .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ - .split("){", 1)[0] - .replace(/^[^(]*[(]/, "") // extract the parameters - .replace(/=[^,]+/g, "") // strip any ES6 defaults - .split(",") - .filter(Boolean); // split & filter [""] - }; - - /* helper for interaction: pointer, touch, mouse Listeners - used by LGraphCanvas DragAndScale ContextMenu*/ - LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ - //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - - var sMethod = LiteGraph.pointerevents_method; - var sEvent = sEvIn; - - // UNDER CONSTRUCTION - // convert pointerevents to touch event when not available - if (sMethod=="pointer" && !window.PointerEvent){ - console.warn("sMethod=='pointer' && !window.PointerEvent"); - console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); - switch(sEvent){ - case "down":{ - sMethod = "touch"; - sEvent = "start"; - break; - } - case "move":{ - sMethod = "touch"; - //sEvent = "move"; - break; - } - case "up":{ - sMethod = "touch"; - sEvent = "end"; - break; - } - case "cancel":{ - sMethod = "touch"; - //sEvent = "cancel"; - break; - } - case "enter":{ - console.log("debug: Should I send a move event?"); // ??? - break; - } - // case "over": case "out": not used at now - default:{ - console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); - } - } - } - - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (sMethod!="mouse"){ - return oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.addEventListener(sEvent, fCall, capture); - } - } - LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ - //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ - oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (LiteGraph.pointerevents_method=="pointer"){ - return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.removeEventListener(sEvent, fCall, capture); - } - } - - function clamp(v, a, b) { - return a > v ? a : b < v ? b : v; - }; - global.clamp = clamp; - - if (typeof window != "undefined" && !window["requestAnimationFrame"]) { - window.requestAnimationFrame = - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - } -})(this); - -if (typeof exports != "undefined") { - exports.LiteGraph = this.LiteGraph; - exports.LGraph = this.LGraph; - exports.LLink = this.LLink; - exports.LGraphNode = this.LGraphNode; - exports.LGraphGroup = this.LGraphGroup; - exports.DragAndScale = this.DragAndScale; - exports.LGraphCanvas = this.LGraphCanvas; - exports.ContextMenu = this.ContextMenu; -} - - -//basic nodes + (function(global) { - var LiteGraph = global.LiteGraph; + // ************************************************************* + // LiteGraph CLASS ******* + // ************************************************************* + + /** + * The Global Scope. It contains all the registered node classes. + * + * @class LiteGraph + * @constructor + */ + + var LiteGraph = (global.LiteGraph = { + VERSION: 0.4, + + CANVAS_GRID_SIZE: 10, + + NODE_TITLE_HEIGHT: 30, + NODE_TITLE_TEXT_Y: 20, + NODE_SLOT_HEIGHT: 20, + NODE_WIDGET_HEIGHT: 20, + NODE_WIDTH: 140, + NODE_MIN_WIDTH: 50, + NODE_COLLAPSED_RADIUS: 10, + NODE_COLLAPSED_WIDTH: 80, + NODE_TITLE_COLOR: "#999", + NODE_SELECTED_TITLE_COLOR: "#FFF", + NODE_TEXT_SIZE: 14, + NODE_TEXT_COLOR: "#AAA", + NODE_SUBTEXT_SIZE: 12, + NODE_DEFAULT_COLOR: "#333", + NODE_DEFAULT_BGCOLOR: "#353535", + NODE_DEFAULT_BOXCOLOR: "#666", + NODE_DEFAULT_SHAPE: "box", + NODE_BOX_OUTLINE_COLOR: "#FFF", + DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)", + DEFAULT_GROUP_FONT: 24, + + WIDGET_BGCOLOR: "#222", + WIDGET_OUTLINE_COLOR: "#666", + WIDGET_TEXT_COLOR: "#DDD", + WIDGET_SECONDARY_TEXT_COLOR: "#999", + + LINK_COLOR: "#9A9", + EVENT_LINK_COLOR: "#A86", + CONNECTING_LINK_COLOR: "#AFA", + + MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops + DEFAULT_POSITION: [100, 100], //default node position + VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" + + //shapes are used for nodes but also for slots + BOX_SHAPE: 1, + ROUND_SHAPE: 2, + CIRCLE_SHAPE: 3, + CARD_SHAPE: 4, + ARROW_SHAPE: 5, + GRID_SHAPE: 6, // intended for slot arrays + + //enums + INPUT: 1, + OUTPUT: 2, + + EVENT: -1, //for outputs + ACTION: -1, //for inputs + + NODE_MODES: ["Always", "On Event", "Never", "On Trigger"], // helper, will add "On Request" and more in the future + NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode + ALWAYS: 0, + ON_EVENT: 1, + NEVER: 2, + ON_TRIGGER: 3, + + UP: 1, + DOWN: 2, + LEFT: 3, + RIGHT: 4, + CENTER: 5, + + LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper + STRAIGHT_LINK: 0, + LINEAR_LINK: 1, + SPLINE_LINK: 2, + + NORMAL_TITLE: 0, + NO_TITLE: 1, + TRANSPARENT_TITLE: 2, + AUTOHIDE_TITLE: 3, + VERTICAL_LAYOUT: "vertical", // arrange nodes vertically + + proxy: null, //used to redirect calls + node_images_path: "", + + debug: false, + catch_exceptions: true, + throw_errors: true, + allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits + use_deferred_actions: true, //executes actions during the graph execution flow + registered_node_types: {}, //nodetypes by string + node_types_by_file_extension: {}, //used for dropping files in the canvas + Nodes: {}, //node types by classname + Globals: {}, //used to store vars between graphs + + searchbox_extras: {}, //used to add extra features to the search box + auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus + + node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback + node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback + + dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + dialog_close_on_mouse_leave_delay: 500, + + shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys + click_do_break_link_to: false, // [false!]prefer false, way too easy to break links + + search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] + search_show_all_on_open: true, // [true!] opens the results list when opening the search widget + + auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] + + // set these values if not using auto_load_slot_types + registered_slot_in_types: {}, // slot types for nodeclass + registered_slot_out_types: {}, // slot types for nodeclass + slot_types_in: [], // slot types IN + slot_types_out: [], // slot types OUT + slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + slot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + + alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node - //Constant - function Time() { - this.addOutput("in ms", "number"); - this.addOutput("in sec", "number"); - } + do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this + + allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one - Time.title = "Time"; - Time.desc = "Time"; + middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel) + + release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults + + pointerevents_method: "mouse", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) + // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - Time.prototype.onExecute = function() { - this.setOutputData(0, this.graph.globaltime * 1000); - this.setOutputData(1, this.graph.globaltime); - }; + ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes - LiteGraph.registerNodeType("basic/time", Time); + // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. + // use this if you must have node IDs that are unique across all graphs and subgraphs. + use_uuids: false, - //Subgraph: a node that contains a graph - function Subgraph() { - var that = this; - this.size = [140, 80]; - this.properties = { enabled: true }; - this.enabled = true; + /** + * Register a node class so it can be listed when the user wants to create a new one + * @method registerNodeType + * @param {String} type name of the node and path + * @param {Class} base_class class containing the structure of a node + */ - //create inner graph - this.subgraph = new LiteGraph.LGraph(); - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = true; + registerNodeType: function(type, base_class) { + if (!base_class.prototype) { + throw "Cannot register a simple object, it must be a class with a prototype"; + } + base_class.type = type; - this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); + if (LiteGraph.debug) { + console.log("Node registered: " + type); + } - //nodes input node added inside - this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); - this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); - this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); - this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); + const classname = base_class.name; - this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); - this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); - this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); - this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); - } + const pos = type.lastIndexOf("/"); + base_class.category = type.substring(0, pos); - Subgraph.title = "Subgraph"; - Subgraph.desc = "Graph inside a node"; - Subgraph.title_color = "#334"; + if (!base_class.title) { + base_class.title = classname; + } - Subgraph.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; + //extend class + for (var i in LGraphNode.prototype) { + if (!base_class.prototype[i]) { + base_class.prototype[i] = LGraphNode.prototype[i]; + } + } - /* - Subgraph.prototype.onDrawTitle = function(ctx) { - if (this.flags.collapsed) { - return; - } + const prev = this.registered_node_types[type]; + if(prev) { + console.log("replacing node type: " + type); + } + if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) { + Object.defineProperty(base_class.prototype, "shape", { + set: function(v) { + switch (v) { + case "default": + delete this._shape; + break; + case "box": + this._shape = LiteGraph.BOX_SHAPE; + break; + case "round": + this._shape = LiteGraph.ROUND_SHAPE; + break; + case "circle": + this._shape = LiteGraph.CIRCLE_SHAPE; + break; + case "card": + this._shape = LiteGraph.CARD_SHAPE; + break; + default: + this._shape = v; + } + }, + get: function() { + return this._shape; + }, + enumerable: true, + configurable: true + }); + + + //used to know which nodes to create when dragging files to the canvas + if (base_class.supported_extensions) { + for (let i in base_class.supported_extensions) { + const ext = base_class.supported_extensions[i]; + if(ext && ext.constructor === String) { + this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; + } + } + } + } - ctx.fillStyle = "#555"; - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = this.size[0] - w; - ctx.fillRect(x, -w, w, w); - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - }; - */ + this.registered_node_types[type] = base_class; + if (base_class.constructor.name) { + this.Nodes[classname] = base_class; + } + if (LiteGraph.onNodeTypeRegistered) { + LiteGraph.onNodeTypeRegistered(type, base_class); + } + if (prev && LiteGraph.onNodeTypeReplaced) { + LiteGraph.onNodeTypeReplaced(type, base_class, prev); + } - Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - }; + //warnings + if (base_class.prototype.onPropertyChange) { + console.warn( + "LiteGraph node class " + + type + + " has onPropertyChange method, it must be called onPropertyChanged with d at the end" + ); + } + + // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types + if (this.auto_load_slot_types) { + new base_class(base_class.title || "tmpnode"); + } + }, - /* - Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { - if ( - !this.flags.collapsed && - pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT && - pos[1] < 0 - ) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - } - }; - */ + /** + * removes a node type from the system + * @method unregisterNodeType + * @param {String|Object} type name of the node or the node constructor itself + */ + unregisterNodeType: function(type) { + const base_class = + type.constructor === String + ? this.registered_node_types[type] + : type; + if (!base_class) { + throw "node type not found: " + type; + } + delete this.registered_node_types[base_class.type]; + if (base_class.constructor.name) { + delete this.Nodes[base_class.constructor.name]; + } + }, - Subgraph.prototype.onAction = function(action, param) { - this.subgraph.onAction(action, param); - }; + /** + * Save a slot type and his node + * @method registerSlotType + * @param {String|Object} type name of the node or the node constructor itself + * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. + */ + registerNodeAndSlotType: function(type, slot_type, out){ + out = out || false; + const base_class = + type.constructor === String && + this.registered_node_types[type] !== "anonymous" + ? this.registered_node_types[type] + : type; + + const class_type = base_class.constructor.type; + + let allTypes = []; + if (typeof slot_type === "string") { + allTypes = slot_type.split(","); + } else if (slot_type == this.EVENT || slot_type == this.ACTION) { + allTypes = ["_event_"]; + } else { + allTypes = ["*"]; + } - Subgraph.prototype.onExecute = function() { - this.enabled = this.getInputOrProperty("enabled"); - if (!this.enabled) { - return; - } + for (let i = 0; i < allTypes.length; ++i) { + let slotType = allTypes[i]; + if (slotType === "") { + slotType = "*"; + } + const registerTo = out + ? "registered_slot_out_types" + : "registered_slot_in_types"; + if (this[registerTo][slotType] === undefined) { + this[registerTo][slotType] = { nodes: [] }; + } + if (!this[registerTo][slotType].nodes.includes(class_type)) { + this[registerTo][slotType].nodes.push(class_type); + } - //send inputs to subgraph global inputs - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var value = this.getInputData(i); - this.subgraph.setInputData(input.name, value); + // check if is a new type + if (!out) { + if (!this.slot_types_in.includes(slotType.toLowerCase())) { + this.slot_types_in.push(slotType.toLowerCase()); + this.slot_types_in.sort(); + } + } else { + if (!this.slot_types_out.includes(slotType.toLowerCase())) { + this.slot_types_out.push(slotType.toLowerCase()); + this.slot_types_out.sort(); + } + } } - } + }, + + /** + * Create a new nodetype by passing an object with some properties + * like onCreate, inputs:Array, outputs:Array, properties, onExecute + * @method buildNodeClassFromObject + * @param {String} name node name with namespace (p.e.: 'math/sum') + * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute + */ + buildNodeClassFromObject: function( + name, + object + ) { + var ctor_code = ""; + if(object.inputs) + for(var i=0; i < object.inputs.length; ++i) + { + var _name = object.inputs[i][0]; + var _type = object.inputs[i][1]; + if(_type && _type.constructor === String) + _type = '"'+_type+'"'; + ctor_code += "this.addInput('"+_name+"',"+_type+");\n"; + } + if(object.outputs) + for(var i=0; i < object.outputs.length; ++i) + { + var _name = object.outputs[i][0]; + var _type = object.outputs[i][1]; + if(_type && _type.constructor === String) + _type = '"'+_type+'"'; + ctor_code += "this.addOutput('"+_name+"',"+_type+");\n"; + } + if(object.properties) + for(var i in object.properties) + { + var prop = object.properties[i]; + if(prop && prop.constructor === String) + prop = '"'+prop+'"'; + ctor_code += "this.addProperty('"+i+"',"+prop+");\n"; + } + ctor_code += "if(this.onCreate)this.onCreate()"; + var classobj = Function(ctor_code); + for(var i in object) + if(i!="inputs" && i!="outputs" && i!="properties") + classobj.prototype[i] = object[i]; + classobj.title = object.title || name.split("/").pop(); + classobj.desc = object.desc || "Generated from object"; + this.registerNodeType(name, classobj); + return classobj; + }, + + /** + * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. + * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. + * @method wrapFunctionAsNode + * @param {String} name node name with namespace (p.e.: 'math/sum') + * @param {Function} func + * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type + * @param {String} return_type [optional] string with the return type, otherwise it will be generic + * @param {Object} properties [optional] properties to be configurable + */ + wrapFunctionAsNode: function( + name, + func, + param_types, + return_type, + properties + ) { + var params = Array(func.length); + var code = ""; + if(param_types !== null) //null means no inputs + { + var names = LiteGraph.getParameterNames(func); + for (var i = 0; i < names.length; ++i) { + var type = 0; + if(param_types) + { + //type = param_types[i] != null ? "'" + param_types[i] + "'" : "0"; + if( param_types[i] != null && param_types[i].constructor === String ) + type = "'" + param_types[i] + "'" ; + else if( param_types[i] != null ) + type = param_types[i]; + } + code += + "this.addInput('" + + names[i] + + "'," + + type + + ");\n"; + } + } + if(return_type !== null) //null means no output + code += + "this.addOutput('out'," + + (return_type != null ? (return_type.constructor === String ? "'" + return_type + "'" : return_type) : 0) + + ");\n"; + if (properties) { + code += + "this.properties = " + JSON.stringify(properties) + ";\n"; + } + var classobj = Function(code); + classobj.title = name.split("/").pop(); + classobj.desc = "Generated from " + func.name; + classobj.prototype.onExecute = function onExecute() { + for (var i = 0; i < params.length; ++i) { + params[i] = this.getInputData(i); + } + var r = func.apply(this, params); + this.setOutputData(0, r); + }; + this.registerNodeType(name, classobj); + return classobj; + }, - //execute - this.subgraph.runStep(); + /** + * Removes all previously registered node's types + */ + clearRegisteredTypes: function() { + this.registered_node_types = {}; + this.node_types_by_file_extension = {}; + this.Nodes = {}; + this.searchbox_extras = {}; + }, - //send subgraph global outputs to outputs - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - var value = this.subgraph.getOutputData(output.name); - this.setOutputData(i, value); + /** + * Adds this method to all nodetypes, existing and to be created + * (You can add it to LGraphNode.prototype but then existing node types wont have it) + * @method addNodeMethod + * @param {Function} func + */ + addNodeMethod: function(name, func) { + LGraphNode.prototype[name] = func; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if (type.prototype[name]) { + type.prototype["_" + name] = type.prototype[name]; + } //keep old in case of replacing + type.prototype[name] = func; } - } - }; + }, - Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) { - if (this.enabled) { - this.subgraph.sendEventToAllNodes(eventname, param, mode); - } - }; + /** + * Create a node of a given type with a name. The node is not attached to any graph yet. + * @method createNode + * @param {String} type full name of the node class. p.e. "math/sin" + * @param {String} name a name to distinguish from other nodes + * @param {Object} options to set options + */ + + createNode: function(type, title, options) { + var base_class = this.registered_node_types[type]; + if (!base_class) { + if (LiteGraph.debug) { + console.log( + 'GraphNode type "' + type + '" not registered.' + ); + } + return null; + } - Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) { - if (this.flags.collapsed) - return; - var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - // button - var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT); - let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT) - ctx.fillStyle = over ? "#555" : "#222"; - ctx.beginPath(); - if (this._shape == LiteGraph.BOX_SHAPE) { - if (overleft) { - ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + var prototype = base_class.prototype || base_class; + + title = title || base_class.title || type; + + var node = null; + + if (LiteGraph.catch_exceptions) { + try { + node = new base_class(title); + } catch (err) { + console.error(err); + return null; + } } else { - ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + node = new base_class(title); } - } - else { - if (overleft) { - ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); - } else { - ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + + node.type = type; + + if (!node.title && title) { + node.title = title; + } + if (!node.properties) { + node.properties = {}; + } + if (!node.properties_info) { + node.properties_info = []; + } + if (!node.flags) { + node.flags = {}; + } + if (!node.size) { + node.size = node.computeSize(); + //call onresize? + } + if (!node.pos) { + node.pos = LiteGraph.DEFAULT_POSITION.concat(); + } + if (!node.mode) { + node.mode = LiteGraph.ALWAYS; } - } - if (over) { - ctx.fill(); - } else { - ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT); - } - // button - ctx.textAlign = "center"; - ctx.font = "24px Arial"; - ctx.fillStyle = over ? "#DDD" : "#999"; - ctx.fillText("+", this.size[0] * 0.25, y + 24); - ctx.fillText("+", this.size[0] * 0.75, y + 24); - } - // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) - // { - // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - // if(localpos[1] > y) - // { - // graphcanvas.showSubgraphPropertiesDialog(this); - // } - // } - Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) { - var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - console.log(0) - if (localpos[1] > y) { - if (localpos[0] < this.size[0] / 2) { - console.log(1) - graphcanvas.showSubgraphPropertiesDialog(this); - } else { - console.log(2) - graphcanvas.showSubgraphPropertiesDialogRight(this); + //extra options + if (options) { + for (var i in options) { + node[i] = options[i]; + } } - } - } - Subgraph.prototype.computeSize = function() - { - var num_inputs = this.inputs ? this.inputs.length : 0; - var num_outputs = this.outputs ? this.outputs.length : 0; - return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; - } - //**** INPUTS *********************************** - Subgraph.prototype.onSubgraphTrigger = function(event, param) { - var slot = this.findOutputSlot(event); - if (slot != -1) { - this.triggerSlot(slot); - } - }; + // callback + if ( node.onNodeCreated ) { + node.onNodeCreated(); + } + + return node; + }, - Subgraph.prototype.onSubgraphNewInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - //add input to the node - this.addInput(name, type); + /** + * Returns a registered node type with a given name + * @method getNodeType + * @param {String} type full name of the node class. p.e. "math/sin" + * @return {Class} the node class + */ + getNodeType: function(type) { + return this.registered_node_types[type]; + }, + + /** + * Returns a list of node types matching one category + * @method getNodeType + * @param {String} category category name + * @return {Array} array with all the node classes + */ + + getNodeTypesInCategory: function(category, filter) { + var r = []; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if (type.filter != filter) { + continue; + } + + if (category == "") { + if (type.category == null) { + r.push(type); + } + } else if (type.category == category) { + r.push(type); + } + } + + if (this.auto_sort_node_types) { + r.sort(function(a,b){return a.title.localeCompare(b.title)}); + } + + return r; + }, + + /** + * Returns a list with all the node type categories + * @method getNodeTypesCategories + * @param {String} filter only nodes with ctor.filter equal can be shown + * @return {Array} array with all the names of the categories + */ + getNodeTypesCategories: function( filter ) { + var categories = { "": 1 }; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if ( type.category && !type.skip_list ) + { + if(type.filter != filter) + continue; + categories[type.category] = 1; + } + } + var result = []; + for (var i in categories) { + result.push(i); + } + return this.auto_sort_node_types ? result.sort() : result; + }, + + //debug purposes: reloads all the js scripts that matches a wildcard + reloadNodes: function(folder_wildcard) { + var tmp = document.getElementsByTagName("script"); + //weird, this array changes by its own, so we use a copy + var script_files = []; + for (var i=0; i < tmp.length; i++) { + script_files.push(tmp[i]); + } + + var docHeadObj = document.getElementsByTagName("head")[0]; + folder_wildcard = document.location.href + folder_wildcard; + + for (var i=0; i < script_files.length; i++) { + var src = script_files[i].src; + if ( + !src || + src.substr(0, folder_wildcard.length) != folder_wildcard + ) { + continue; + } + + try { + if (LiteGraph.debug) { + console.log("Reloading: " + src); + } + var dynamicScript = document.createElement("script"); + dynamicScript.type = "text/javascript"; + dynamicScript.src = src; + docHeadObj.appendChild(dynamicScript); + docHeadObj.removeChild(script_files[i]); + } catch (err) { + if (LiteGraph.throw_errors) { + throw err; + } + if (LiteGraph.debug) { + console.log("Error while reloading " + src); + } + } + } + + if (LiteGraph.debug) { + console.log("Nodes reloaded"); + } + }, + + //separated just to improve if it doesn't work + cloneObject: function(obj, target) { + if (obj == null) { + return null; + } + var r = JSON.parse(JSON.stringify(obj)); + if (!target) { + return r; + } + + for (var i in r) { + target[i] = r[i]; + } + return target; + }, + + /* + * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 + */ + uuidv4: function() { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)); + }, + + /** + * Returns if the types of two slots are compatible (taking into account wildcards, etc) + * @method isValidConnection + * @param {String} type_a + * @param {String} type_b + * @return {Boolean} true if they can be connected + */ + isValidConnection: function(type_a, type_b) { + if (type_a=="" || type_a==="*") type_a = 0; + if (type_b=="" || type_b==="*") type_b = 0; + if ( + !type_a //generic output + || !type_b // generic input + || type_a == type_b //same type (is valid for triggers) + || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) + ) { + return true; + } + + // Enforce string type to handle toLowerCase call (-1 number not ok) + type_a = String(type_a); + type_b = String(type_b); + type_a = type_a.toLowerCase(); + type_b = type_b.toLowerCase(); + + // For nodes supporting multiple connection types + if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { + return type_a == type_b; + } + + // Check all permutations to see if one is valid + var supported_types_a = type_a.split(","); + var supported_types_b = type_b.split(","); + for (var i = 0; i < supported_types_a.length; ++i) { + for (var j = 0; j < supported_types_b.length; ++j) { + if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ + //if (supported_types_a[i] == supported_types_b[j]) { + return true; + } + } + } + + return false; + }, + + /** + * Register a string in the search box so when the user types it it will recommend this node + * @method registerSearchboxExtra + * @param {String} node_type the node recommended + * @param {String} description text to show next to it + * @param {Object} data it could contain info of how the node should be configured + * @return {Boolean} true if they can be connected + */ + registerSearchboxExtra: function(node_type, description, data) { + this.searchbox_extras[description.toLowerCase()] = { + type: node_type, + desc: description, + data: data + }; + }, + + /** + * Wrapper to load files (from url using fetch or from file using FileReader) + * @method fetchFile + * @param {String|File|Blob} url the url of the file (or the file itself) + * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" + * @param {Function} on_complete callback(data) + * @param {Function} on_error in case of an error + * @return {FileReader|Promise} returns the object used to + */ + fetchFile: function( url, type, on_complete, on_error ) { + var that = this; + if(!url) + return null; + + type = type || "text"; + if( url.constructor === String ) + { + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + return fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); //it will be catch below + if(type == "arraybuffer") + return response.arrayBuffer(); + else if(type == "text" || type == "string") + return response.text(); + else if(type == "json") + return response.json(); + else if(type == "blob") + return response.blob(); + }) + .then(function(data) { + if(on_complete) + on_complete(data); + }) + .catch(function(error) { + console.error("error fetching file:",url); + if(on_error) + on_error(error); + }); + } + else if( url.constructor === File || url.constructor === Blob) + { + var reader = new FileReader(); + reader.onload = function(e) + { + var v = e.target.result; + if( type == "json" ) + v = JSON.parse(v); + if(on_complete) + on_complete(v); + } + if(type == "arraybuffer") + return reader.readAsArrayBuffer(url); + else if(type == "text" || type == "json") + return reader.readAsText(url); + else if(type == "blob") + return reader.readAsBinaryString(url); + } + return null; + } + }); + + //timer that works everywhere + if (typeof performance != "undefined") { + LiteGraph.getTime = performance.now.bind(performance); + } else if (typeof Date != "undefined" && Date.now) { + LiteGraph.getTime = Date.now.bind(Date); + } else if (typeof process != "undefined") { + LiteGraph.getTime = function() { + var t = process.hrtime(); + return t[0] * 0.001 + t[1] * 1e-6; + }; + } else { + LiteGraph.getTime = function getTime() { + return new Date().getTime(); + }; + } + + //********************************************************************************* + // LGraph CLASS + //********************************************************************************* + + /** + * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. + * supported callbacks: + + onNodeAdded: when a new node is added to the graph + + onNodeRemoved: when a node inside this graph is removed + + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) + * + * @class LGraph + * @constructor + * @param {Object} o data from previous serialization [optional] + */ + + function LGraph(o) { + if (LiteGraph.debug) { + console.log("Graph created"); } - }; + this.list_of_graphcanvas = null; + this.clear(); - Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) { - var slot = this.findInputSlot(oldname); - if (slot == -1) { - return; + if (o) { + this.configure(o); } - var info = this.getInputInfo(slot); - info.name = name; + } + + global.LGraph = LiteGraph.LGraph = LGraph; + + //default supported types + LGraph.supported_types = ["number", "string", "boolean"]; + + //used to know which types of connections support this graph (some graphs do not allow certain types) + LGraph.prototype.getSupportedTypes = function() { + return this.supported_types || LGraph.supported_types; }; - Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; + LGraph.STATUS_STOPPED = 1; + LGraph.STATUS_RUNNING = 2; + + /** + * Removes all nodes from this graph + * @method clear + */ + + LGraph.prototype.clear = function() { + this.stop(); + this.status = LGraph.STATUS_STOPPED; + + this.last_node_id = 0; + this.last_link_id = 0; + + this._version = -1; //used to detect changes + + //safe clear + if (this._nodes) { + for (var i = 0; i < this._nodes.length; ++i) { + var node = this._nodes[i]; + if (node.onRemoved) { + node.onRemoved(); + } + } } - var info = this.getInputInfo(slot); - info.type = type; + + //nodes + this._nodes = []; + this._nodes_by_id = {}; + this._nodes_in_order = []; //nodes sorted in execution order + this._nodes_executable = null; //nodes that contain onExecute sorted in execution order + + //other scene stuff + this._groups = []; + + //links + this.links = {}; //container with all the links + + //iterations + this.iteration = 0; + + //custom data + this.config = {}; + this.vars = {}; + this.extra = {}; //to store custom data + + //timing + this.globaltime = 0; + this.runningtime = 0; + this.fixedtime = 0; + this.fixedtime_lapse = 0.01; + this.elapsed_time = 0.01; + this.last_update_time = 0; + this.starttime = 0; + + this.catch_errors = true; + + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + + //subgraph_data + this.inputs = {}; + this.outputs = {}; + + //notify canvas to redraw + this.change(); + + this.sendActionToCanvas("clear"); }; - Subgraph.prototype.onSubgraphRemovedInput = function(name) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; + /** + * Attach Canvas to this graph + * @method attachCanvas + * @param {GraphCanvas} graph_canvas + */ + + LGraph.prototype.attachCanvas = function(graphcanvas) { + if (graphcanvas.constructor != LGraphCanvas) { + throw "attachCanvas expects a LGraphCanvas instance"; + } + if (graphcanvas.graph && graphcanvas.graph != this) { + graphcanvas.graph.detachCanvas(graphcanvas); } - this.removeInput(slot); - }; - //**** OUTPUTS *********************************** - Subgraph.prototype.onSubgraphNewOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { - this.addOutput(name, type); + graphcanvas.graph = this; + + if (!this.list_of_graphcanvas) { + this.list_of_graphcanvas = []; } + this.list_of_graphcanvas.push(graphcanvas); }; - Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) { - var slot = this.findOutputSlot(oldname); - if (slot == -1) { + /** + * Detach Canvas from this graph + * @method detachCanvas + * @param {GraphCanvas} graph_canvas + */ + LGraph.prototype.detachCanvas = function(graphcanvas) { + if (!this.list_of_graphcanvas) { return; } - var info = this.getOutputInfo(slot); - info.name = name; - }; - Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { + var pos = this.list_of_graphcanvas.indexOf(graphcanvas); + if (pos == -1) { return; } - var info = this.getOutputInfo(slot); - info.type = type; + graphcanvas.graph = null; + this.list_of_graphcanvas.splice(pos, 1); }; - Subgraph.prototype.onSubgraphRemovedOutput = function(name) { - var slot = this.findOutputSlot(name); - if (slot == -1) { + /** + * Starts running this graph every interval milliseconds. + * @method start + * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate + */ + + LGraph.prototype.start = function(interval) { + if (this.status == LGraph.STATUS_RUNNING) { return; } - this.removeOutput(slot); - }; - // ***************************************************** + this.status = LGraph.STATUS_RUNNING; - Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) { + if (this.onPlayEvent) { + this.onPlayEvent(); + } + + this.sendEventToAllNodes("onStart"); + + //launch + this.starttime = LiteGraph.getTime(); + this.last_update_time = this.starttime; + interval = interval || 0; var that = this; - return [ - { - content: "Open", - callback: function() { - graphcanvas.openSubgraph(that.subgraph); + + //execute once per frame + if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { + function on_frame() { + if (that.execution_timer_id != -1) { + return; } + window.requestAnimationFrame(on_frame); + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); } - ]; - }; + this.execution_timer_id = -1; + on_frame(); + } else { //execute every 'interval' ms + this.execution_timer_id = setInterval(function() { + //execute + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); + }, interval); + } + }; + + /** + * Stops the execution loop of the graph + * @method stop execution + */ + + LGraph.prototype.stop = function() { + if (this.status == LGraph.STATUS_STOPPED) { + return; + } - Subgraph.prototype.onResize = function(size) { - size[1] += 20; - }; + this.status = LGraph.STATUS_STOPPED; - Subgraph.prototype.serialize = function() { - var data = LiteGraph.LGraphNode.prototype.serialize.call(this); - data.subgraph = this.subgraph.serialize(); - return data; + if (this.onStopEvent) { + this.onStopEvent(); + } + + if (this.execution_timer_id != null) { + if (this.execution_timer_id != -1) { + clearInterval(this.execution_timer_id); + } + this.execution_timer_id = null; + } + + this.sendEventToAllNodes("onStop"); }; - //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() - Subgraph.prototype.reassignSubgraphUUIDs = function(graph) { - const idMap = { nodeIDs: {}, linkIDs: {} } + /** + * Run N steps (cycles) of the graph + * @method runStep + * @param {number} num number of steps to run, default is 1 + * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors + * @param {number} limit max number of nodes to execute (used to execute from start to a node) + */ - for (const node of graph.nodes) { - const oldID = node.id - const newID = LiteGraph.uuidv4() - node.id = newID + LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { + num = num || 1; - if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) { - throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`) - } + var start = LiteGraph.getTime(); + this.globaltime = 0.001 * (start - this.starttime); - idMap.nodeIDs[oldID] = newID - idMap.nodeIDs[newID] = oldID + //not optimal: executes possible pending actions in node, problem is it is not optimized + //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute + + //from now on it will iterate only on executable nodes which is faster + var nodes = this._nodes_executable + ? this._nodes_executable + : this._nodes; + if (!nodes) { + return; } - for (const link of graph.links) { - const oldID = link[0] - const newID = LiteGraph.uuidv4(); - link[0] = newID + limit = limit || nodes.length; - if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) { - throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`) + if (do_not_catch_errors) { + //iterations + for (var i = 0; i < num; i++) { + for (var j = 0; j < limit; ++j) { + var node = nodes[j]; + if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) + node.executePendingActions(); + if (node.mode == LiteGraph.ALWAYS && node.onExecute) { + //wrap node.onExecute(); + node.doExecute(); + } + } + + this.fixedtime += this.fixedtime_lapse; + if (this.onExecuteStep) { + this.onExecuteStep(); + } } - idMap.linkIDs[oldID] = newID - idMap.linkIDs[newID] = oldID + if (this.onAfterExecute) { + this.onAfterExecute(); + } + } else { //catch errors + try { + //iterations + for (var i = 0; i < num; i++) { + for (var j = 0; j < limit; ++j) { + var node = nodes[j]; + if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) + node.executePendingActions(); + if (node.mode == LiteGraph.ALWAYS && node.onExecute) { + node.onExecute(); + } + } - const nodeFrom = link[1] - const nodeTo = link[3] + this.fixedtime += this.fixedtime_lapse; + if (this.onExecuteStep) { + this.onExecuteStep(); + } + } - if (!idMap.nodeIDs[nodeFrom]) { - throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`) + if (this.onAfterExecute) { + this.onAfterExecute(); + } + this.errors_in_execution = false; + } catch (err) { + this.errors_in_execution = true; + if (LiteGraph.throw_errors) { + throw err; + } + if (LiteGraph.debug) { + console.log("Error during execution: " + err); + } + this.stop(); } + } - link[1] = idMap.nodeIDs[nodeFrom] + var now = LiteGraph.getTime(); + var elapsed = now - start; + if (elapsed == 0) { + elapsed = 1; + } + this.execution_time = 0.001 * elapsed; + this.globaltime += 0.001 * elapsed; + this.iteration += 1; + this.elapsed_time = (now - this.last_update_time) * 0.001; + this.last_update_time = now; + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + }; + + /** + * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than + * nodes with only inputs. + * @method updateExecutionOrder + */ + LGraph.prototype.updateExecutionOrder = function() { + this._nodes_in_order = this.computeExecutionOrder(false); + this._nodes_executable = []; + for (var i = 0; i < this._nodes_in_order.length; ++i) { + if (this._nodes_in_order[i].onExecute) { + this._nodes_executable.push(this._nodes_in_order[i]); + } + } + }; - if (!idMap.nodeIDs[nodeTo]) { - throw new Error(`Old node UUID not found in mapping! ${nodeTo}`) + //This is more internal, it computes the executable nodes in order and returns it + LGraph.prototype.computeExecutionOrder = function( + only_onExecute, + set_level + ) { + var L = []; + var S = []; + var M = {}; + var visited_links = {}; //to avoid repeating links + var remaining_links = {}; //to a + + //search for the nodes without inputs (starting nodes) + for (var i = 0, l = this._nodes.length; i < l; ++i) { + var node = this._nodes[i]; + if (only_onExecute && !node.onExecute) { + continue; } - link[3] = idMap.nodeIDs[nodeTo] - } + M[node.id] = node; //add to pending nodes - // Reconnect links - for (const node of graph.nodes) { + var num = 0; //num of input connections if (node.inputs) { - for (const input of node.inputs) { - if (input.link) { - input.link = idMap.linkIDs[input.link] + for (var j = 0, l2 = node.inputs.length; j < l2; j++) { + if (node.inputs[j] && node.inputs[j].link != null) { + num += 1; } } } - if (node.outputs) { - for (const output of node.outputs) { - if (output.links) { - output.links = output.links.map(l => idMap.linkIDs[l]); - } + + if (num == 0) { + //is a starting node + S.push(node); + if (set_level) { + node._level = 1; + } + } //num of input links + else { + if (set_level) { + node._level = 0; } + remaining_links[node.id] = num; } } - // Recurse! - for (const node of graph.nodes) { - if (node.type === "graph/subgraph") { - const merge = reassignGraphUUIDs(node.subgraph); - idMap.nodeIDs.assign(merge.nodeIDs) - idMap.linkIDs.assign(merge.linkIDs) + while (true) { + if (S.length == 0) { + break; } - } - }; - Subgraph.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - var data = this.serialize(); - - if (LiteGraph.use_uuids) { - // LGraph.serialize() seems to reuse objects in the original graph. But we - // need to change node IDs here, so clone it first. - const subgraph = LiteGraph.cloneObject(data.subgraph) + //get an starting node + var node = S.shift(); + L.push(node); //add to ordered list + delete M[node.id]; //remove from the pending nodes - this.reassignSubgraphUUIDs(subgraph); + if (!node.outputs) { + continue; + } - data.subgraph = subgraph; - } + //for every output + for (var i = 0; i < node.outputs.length; i++) { + var output = node.outputs[i]; + //not connected + if ( + output == null || + output.links == null || + output.links.length == 0 + ) { + continue; + } - delete data["id"]; - delete data["inputs"]; - delete data["outputs"]; - node.configure(data); - return node; - }; + //for every connection + for (var j = 0; j < output.links.length; j++) { + var link_id = output.links[j]; + var link = this.links[link_id]; + if (!link) { + continue; + } - Subgraph.prototype.buildFromNodes = function(nodes) - { - //clear all? - //TODO + //already visited link (ignore it) + if (visited_links[link.id]) { + continue; + } - //nodes that connect data between parent graph and subgraph - var subgraph_inputs = []; - var subgraph_outputs = []; + var target_node = this.getNodeById(link.target_id); + if (target_node == null) { + visited_links[link.id] = true; + continue; + } - //mark inner nodes - var ids = {}; - var min_x = 0; - var max_x = 0; - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - ids[ node.id ] = node; - min_x = Math.min( node.pos[0], min_x ); - max_x = Math.max( node.pos[0], min_x ); - } - - var last_input_y = 0; - var last_output_y = 0; + if ( + set_level && + (!target_node._level || + target_node._level <= node._level) + ) { + target_node._level = node._level + 1; + } - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - //check inputs - if( node.inputs ) - for(var j = 0; j < node.inputs.length; ++j) - { - var input = node.inputs[j]; - if( !input || !input.link ) - continue; - var link = node.graph.links[ input.link ]; - if(!link) - continue; - if( ids[ link.origin_id ] ) - continue; - //this.addInput(input.name,link.type); - this.subgraph.addInput(input.name,link.type); - /* - var input_node = LiteGraph.createNode("graph/input"); - this.subgraph.add( input_node ); - input_node.pos = [min_x - 200, last_input_y ]; - last_input_y += 100; - */ - } + visited_links[link.id] = true; //mark as visited + remaining_links[target_node.id] -= 1; //reduce the number of links remaining + if (remaining_links[target_node.id] == 0) { + S.push(target_node); + } //if no more links, then add to starters array + } + } + } - //check outputs - if( node.outputs ) - for(var j = 0; j < node.outputs.length; ++j) - { - var output = node.outputs[j]; - if( !output || !output.links || !output.links.length ) - continue; - var is_external = false; - for(var k = 0; k < output.links.length; ++k) - { - var link = node.graph.links[ output.links[k] ]; - if(!link) - continue; - if( ids[ link.target_id ] ) - continue; - is_external = true; - break; - } - if(!is_external) - continue; - //this.addOutput(output.name,output.type); - /* - var output_node = LiteGraph.createNode("graph/output"); - this.subgraph.add( output_node ); - output_node.pos = [max_x + 50, last_output_y ]; - last_output_y += 100; - */ - } - } + //the remaining ones (loops) + for (var i in M) { + L.push(M[i]); + } - //detect inputs and outputs - //split every connection in two data_connection nodes - //keep track of internal connections - //connect external connections + if (L.length != this._nodes.length && LiteGraph.debug) { + console.warn("something went wrong, nodes missing"); + } - //clone nodes inside subgraph and try to reconnect them + var l = L.length; - //connect edge subgraph nodes to extarnal connections nodes - } + //save order number in the node + for (var i = 0; i < l; ++i) { + L[i].order = i; + } - LiteGraph.Subgraph = Subgraph; - LiteGraph.registerNodeType("graph/subgraph", Subgraph); + //sort now by priority + L = L.sort(function(A, B) { + var Ap = A.constructor.priority || A.priority || 0; + var Bp = B.constructor.priority || B.priority || 0; + if (Ap == Bp) { + //if same priority, sort by order + return A.order - B.order; + } + return Ap - Bp; //sort by priority + }); - //Input for a subgraph - function GraphInput() { - this.addOutput("", "number"); + //save order number in the node, again... + for (var i = 0; i < l; ++i) { + L[i].order = i; + } - this.name_in_graph = ""; - this.properties = { - name: "", - type: "number", - value: 0 - }; + return L; + }; - var that = this; + /** + * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. + * It doesn't include the node itself + * @method getAncestors + * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution + */ + LGraph.prototype.getAncestors = function(node) { + var ancestors = []; + var pending = [node]; + var visited = {}; - this.name_widget = this.addWidget( - "text", - "Name", - this.properties.name, - function(v) { - if (!v) { - return; - } - that.setProperty("name",v); + while (pending.length) { + var current = pending.shift(); + if (!current.inputs) { + continue; } - ); - this.type_widget = this.addWidget( - "text", - "Type", - this.properties.type, - function(v) { - that.setProperty("type",v); + if (!visited[current.id] && current != node) { + visited[current.id] = true; + ancestors.push(current); } - ); - this.value_widget = this.addWidget( - "number", - "Value", - this.properties.value, - function(v) { - that.setProperty("value",v); + for (var i = 0; i < current.inputs.length; ++i) { + var input = current.getInputNode(i); + if (input && ancestors.indexOf(input) == -1) { + pending.push(input); + } } - ); - - this.widgets_up = true; - this.size = [180, 90]; - } + } - GraphInput.title = "Input"; - GraphInput.desc = "Input of the graph"; + ancestors.sort(function(a, b) { + return a.order - b.order; + }); + return ancestors; + }; + + /** + * Positions every node in a more readable manner + * @method arrange + */ + LGraph.prototype.arrange = function (margin, layout) { + margin = margin || 100; + + const nodes = this.computeExecutionOrder(false, true); + const columns = []; + for (let i = 0; i < nodes.length; ++i) { + const node = nodes[i]; + const col = node._level || 1; + if (!columns[col]) { + columns[col] = []; + } + columns[col].push(node); + } - GraphInput.prototype.onConfigure = function() + let x = margin; - { - this.updateType(); - } + for (let i = 0; i < columns.length; ++i) { + const column = columns[i]; + if (!column) { + continue; + } + let max_size = 100; + let y = margin + LiteGraph.NODE_TITLE_HEIGHT; + for (let j = 0; j < column.length; ++j) { + const node = column[j]; + node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; + node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; + const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; + if (node.size[max_size_index] > max_size) { + max_size = node.size[max_size_index]; + } + const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; + y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; + } + x += max_size + margin; + } - //ensures the type in the node output and the type in the associated graph input are the same - GraphInput.prototype.updateType = function() - { - var type = this.properties.type; - this.type_widget.value = type; + this.setDirtyCanvas(true, true); + }; - //update output - if(this.outputs[0].type != type) - { - if (!LiteGraph.isValidConnection(this.outputs[0].type,type)) - this.disconnectOutput(0); - this.outputs[0].type = type; - } + /** + * Returns the amount of time the graph has been running in milliseconds + * @method getTime + * @return {number} number of milliseconds the graph has been running + */ + LGraph.prototype.getTime = function() { + return this.globaltime; + }; - //update widget - if(type == "number") - { - this.value_widget.type = "number"; - this.value_widget.value = 0; - } - else if(type == "boolean") - { - this.value_widget.type = "toggle"; - this.value_widget.value = true; - } - else if(type == "string") - { - this.value_widget.type = "text"; - this.value_widget.value = ""; - } - else - { - this.value_widget.type = null; - this.value_widget.value = null; - } - this.properties.value = this.value_widget.value; + /** + * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant + * @method getFixedTime + * @return {number} number of milliseconds the graph has been running + */ - //update graph - if (this.graph && this.name_in_graph) { - this.graph.changeInputType(this.name_in_graph, type); - } - } + LGraph.prototype.getFixedTime = function() { + return this.fixedtime; + }; - //this is executed AFTER the property has changed - GraphInput.prototype.onPropertyChanged = function(name,v) - { - if( name == "name" ) - { - if (v == "" || v == this.name_in_graph || v == "enabled") { - return false; - } - if(this.graph) - { - if (this.name_in_graph) { - //already added - this.graph.renameInput( this.name_in_graph, v ); - } else { - this.graph.addInput( v, this.properties.type ); - } - } //what if not?! - this.name_widget.value = v; - this.name_in_graph = v; - } - else if( name == "type" ) - { - this.updateType(); - } - else if( name == "value" ) - { - } - } + /** + * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct + * if the nodes are using graphical actions + * @method getElapsedTime + * @return {number} number of milliseconds it took the last cycle + */ - GraphInput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; - } - return this.title; + LGraph.prototype.getElapsedTime = function() { + return this.elapsed_time; }; - GraphInput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.EVENT) { - this.triggerSlot(0, param); + /** + * Sends an event to all the nodes, useful to trigger stuff + * @method sendEventToAllNodes + * @param {String} eventname the name of the event (function to be called) + * @param {Array} params parameters in array format + */ + LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { + mode = mode || LiteGraph.ALWAYS; + + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if (!nodes) { + return; } - }; - GraphInput.prototype.onExecute = function() { - var name = this.properties.name; - //read from global input - var data = this.graph.inputs[name]; - if (!data) { - this.setOutputData(0, this.properties.value ); - return; - } + for (var j = 0, l = nodes.length; j < l; ++j) { + var node = nodes[j]; - this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value ); + if ( + node.constructor === LiteGraph.Subgraph && + eventname != "onExecute" + ) { + if (node.mode == mode) { + node.sendEventToAllNodes(eventname, params, mode); + } + continue; + } + + if (!node[eventname] || node.mode != mode) { + continue; + } + if (params === undefined) { + node[eventname](); + } else if (params && params.constructor === Array) { + node[eventname].apply(node, params); + } else { + node[eventname](params); + } + } }; - GraphInput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeInput(this.name_in_graph); + LGraph.prototype.sendActionToCanvas = function(action, params) { + if (!this.list_of_graphcanvas) { + return; + } + + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var c = this.list_of_graphcanvas[i]; + if (c[action]) { + c[action].apply(c, params); + } } }; - LiteGraph.GraphInput = GraphInput; - LiteGraph.registerNodeType("graph/input", GraphInput); + /** + * Adds a new node instance to this graph + * @method add + * @param {LGraphNode} node the instance of the node + */ - //Output for a subgraph - function GraphOutput() { - this.addInput("", ""); + LGraph.prototype.add = function(node, skip_compute_order) { + if (!node) { + return; + } - this.name_in_graph = ""; - this.properties = { name: "", type: "" }; - var that = this; + //groups + if (node.constructor === LGraphGroup) { + this._groups.push(node); + this.setDirtyCanvas(true); + this.change(); + node.graph = this; + this._version++; + return; + } - // Object.defineProperty(this.properties, "name", { - // get: function() { - // return that.name_in_graph; - // }, - // set: function(v) { - // if (v == "" || v == that.name_in_graph) { - // return; - // } - // if (that.name_in_graph) { - // //already added - // that.graph.renameOutput(that.name_in_graph, v); - // } else { - // that.graph.addOutput(v, that.properties.type); - // } - // that.name_widget.value = v; - // that.name_in_graph = v; - // }, - // enumerable: true - // }); + //nodes + if (node.id != -1 && this._nodes_by_id[node.id] != null) { + console.warn( + "LiteGraph: there is already a node with this ID, changing it" + ); + if (LiteGraph.use_uuids) { + node.id = LiteGraph.uuidv4(); + } + else { + node.id = ++this.last_node_id; + } + } - // Object.defineProperty(this.properties, "type", { - // get: function() { - // return that.inputs[0].type; - // }, - // set: function(v) { - // if (v == "action" || v == "event") { - // v = LiteGraph.ACTION; - // } - // if (!LiteGraph.isValidConnection(that.inputs[0].type,v)) - // that.disconnectInput(0); - // that.inputs[0].type = v; - // if (that.name_in_graph) { - // //already added - // that.graph.changeOutputType( - // that.name_in_graph, - // that.inputs[0].type - // ); - // } - // that.type_widget.value = v || ""; - // }, - // enumerable: true - // }); + if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { + throw "LiteGraph: max number of nodes in a graph reached"; + } - this.name_widget = this.addWidget("text","Name",this.properties.name,"name"); - this.type_widget = this.addWidget("text","Type",this.properties.type,"type"); - this.widgets_up = true; - this.size = [180, 60]; - } + //give him an id + if (LiteGraph.use_uuids) { + if (node.id == null || node.id == -1) + node.id = LiteGraph.uuidv4(); + } + else { + if (node.id == null || node.id == -1) { + node.id = ++this.last_node_id; + } else if (this.last_node_id < node.id) { + this.last_node_id = node.id; + } + } - GraphOutput.title = "Output"; - GraphOutput.desc = "Output of the graph"; + node.graph = this; + this._version++; - GraphOutput.prototype.onPropertyChanged = function (name, v) { - if (name == "name") { - if (v == "" || v == this.name_in_graph || v == "enabled") { - return false; + this._nodes.push(node); + this._nodes_by_id[node.id] = node; + + if (node.onAdded) { + node.onAdded(this); + } + + if (this.config.align_to_grid) { + node.alignToGrid(); + } + + if (!skip_compute_order) { + this.updateExecutionOrder(); + } + + if (this.onNodeAdded) { + this.onNodeAdded(node); + } + + this.setDirtyCanvas(true); + this.change(); + + return node; //to chain actions + }; + + /** + * Removes a node from the graph + * @method remove + * @param {LGraphNode} node the instance of the node + */ + + LGraph.prototype.remove = function(node) { + if (node.constructor === LiteGraph.LGraphGroup) { + var index = this._groups.indexOf(node); + if (index != -1) { + this._groups.splice(index, 1); } - if (this.graph) { - if (this.name_in_graph) { - //already added - this.graph.renameOutput(this.name_in_graph, v); - } else { - this.graph.addOutput(v, this.properties.type); + node.graph = null; + this._version++; + this.setDirtyCanvas(true, true); + this.change(); + return; + } + + if (this._nodes_by_id[node.id] == null) { + return; + } //not found + + if (node.ignore_remove) { + return; + } //cannot be removed + + this.beforeChange(); //sure? - almost sure is wrong + + //disconnect inputs + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + if (slot.link != null) { + node.disconnectInput(i); } - } //what if not?! - this.name_widget.value = v; - this.name_in_graph = v; + } } - else if (name == "type") { - this.updateType(); + + //disconnect outputs + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + if (slot.links != null && slot.links.length) { + node.disconnectOutput(i); + } + } } - else if (name == "value") { + + //node.id = -1; //why? + + //callback + if (node.onRemoved) { + node.onRemoved(); } - } - - GraphOutput.prototype.updateType = function () { - var type = this.properties.type; - if (this.type_widget) - this.type_widget.value = type; - //update output - if (this.inputs[0].type != type) { + node.graph = null; + this._version++; - if ( type == "action" || type == "event") - type = LiteGraph.EVENT; - if (!LiteGraph.isValidConnection(this.inputs[0].type, type)) - this.disconnectInput(0); - this.inputs[0].type = type; + //remove from canvas render + if (this.list_of_graphcanvas) { + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var canvas = this.list_of_graphcanvas[i]; + if (canvas.selected_nodes[node.id]) { + delete canvas.selected_nodes[node.id]; + } + if (canvas.node_dragged == node) { + canvas.node_dragged = null; + } + } } - //update graph - if (this.graph && this.name_in_graph) { - this.graph.changeOutputType(this.name_in_graph, type); + //remove from containers + var pos = this._nodes.indexOf(node); + if (pos != -1) { + this._nodes.splice(pos, 1); } - } + delete this._nodes_by_id[node.id]; + if (this.onNodeRemoved) { + this.onNodeRemoved(node); + } + //close panels + this.sendActionToCanvas("checkPanels"); - GraphOutput.prototype.onExecute = function() { - this._value = this.getInputData(0); - this.graph.setOutputData(this.properties.name, this._value); + this.setDirtyCanvas(true, true); + this.afterChange(); //sure? - almost sure is wrong + this.change(); + + this.updateExecutionOrder(); }; - GraphOutput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.ACTION) { - this.graph.trigger( this.properties.name, param ); + /** + * Returns a node by its id. + * @method getNodeById + * @param {Number} id + */ + + LGraph.prototype.getNodeById = function(id) { + if (id == null) { + return null; + } + return this._nodes_by_id[id]; + }; + + /** + * Returns a list of nodes that matches a class + * @method findNodesByClass + * @param {Class} classObject the class itself (not an string) + * @return {Array} a list with all the nodes of this type + */ + LGraph.prototype.findNodesByClass = function(classObject, result) { + result = result || []; + result.length = 0; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].constructor === classObject) { + result.push(this._nodes[i]); + } + } + return result; + }; + + /** + * Returns a list of nodes that matches a type + * @method findNodesByType + * @param {String} type the name of the node type + * @return {Array} a list with all the nodes of this type + */ + LGraph.prototype.findNodesByType = function(type, result) { + var type = type.toLowerCase(); + result = result || []; + result.length = 0; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].type.toLowerCase() == type) { + result.push(this._nodes[i]); + } } + return result; }; - GraphOutput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeOutput(this.name_in_graph); + /** + * Returns the first node that matches a name in its title + * @method findNodeByTitle + * @param {String} name the name of the node to search + * @return {Node} the node or null + */ + LGraph.prototype.findNodeByTitle = function(title) { + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].title == title) { + return this._nodes[i]; + } } + return null; }; - GraphOutput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; + /** + * Returns a list of nodes that matches a name + * @method findNodesByTitle + * @param {String} name the name of the node to search + * @return {Array} a list with all the nodes with this name + */ + LGraph.prototype.findNodesByTitle = function(title) { + var result = []; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].title == title) { + result.push(this._nodes[i]); + } } - return this.title; + return result; + }; + + /** + * Returns the top-most node in this position of the canvas + * @method getNodeOnPos + * @param {number} x the x coordinate in canvas space + * @param {number} y the y coordinate in canvas space + * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph + * @return {LGraphNode} the node at this position or null + */ + LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { + nodes_list = nodes_list || this._nodes; + var nRet = null; + for (var i = nodes_list.length - 1; i >= 0; i--) { + var n = nodes_list[i]; + if (n.isPointInside(x, y, margin)) { + // check for lesser interest nodes (TODO check for overlapping, use the top) + /*if (typeof n == "LGraphGroup"){ + nRet = n; + }else{*/ + return n; + /*}*/ + } + } + return nRet; + }; + + /** + * Returns the top-most group in that position + * @method getGroupOnPos + * @param {number} x the x coordinate in canvas space + * @param {number} y the y coordinate in canvas space + * @return {LGraphGroup} the group or null + */ + LGraph.prototype.getGroupOnPos = function(x, y) { + for (var i = this._groups.length - 1; i >= 0; i--) { + var g = this._groups[i]; + if (g.isPointInside(x, y, 2, true)) { + return g; + } + } + return null; }; - LiteGraph.GraphOutput = GraphOutput; - LiteGraph.registerNodeType("graph/output", GraphOutput); - - //Constant - function ConstantNumber() { - this.addOutput("value", "number"); - this.addProperty("value", 1.0); - this.widget = this.addWidget("number","value",1,"value"); - this.widgets_up = true; - this.size = [180, 30]; - } + /** + * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution + * this replaces the ones using the old version with the new version + * @method checkNodeTypes + */ + LGraph.prototype.checkNodeTypes = function() { + var changes = false; + for (var i = 0; i < this._nodes.length; i++) { + var node = this._nodes[i]; + var ctor = LiteGraph.registered_node_types[node.type]; + if (node.constructor == ctor) { + continue; + } + console.log("node being replaced by newer version: " + node.type); + var newnode = LiteGraph.createNode(node.type); + changes = true; + this._nodes[i] = newnode; + newnode.configure(node.serialize()); + newnode.graph = this; + this._nodes_by_id[newnode.id] = newnode; + if (node.inputs) { + newnode.inputs = node.inputs.concat(); + } + if (node.outputs) { + newnode.outputs = node.outputs.concat(); + } + } + this.updateExecutionOrder(); + }; - ConstantNumber.title = "Const Number"; - ConstantNumber.desc = "Constant number"; + // ********** GLOBALS ***************** - ConstantNumber.prototype.onExecute = function() { - this.setOutputData(0, parseFloat(this.properties["value"])); + LGraph.prototype.onAction = function(action, param, options) { + this._input_nodes = this.findNodesByClass( + LiteGraph.GraphInput, + this._input_nodes + ); + for (var i = 0; i < this._input_nodes.length; ++i) { + var node = this._input_nodes[i]; + if (node.properties.name != action) { + continue; + } + //wrap node.onAction(action, param); + node.actionDo(action, param, options); + break; + } }; - ConstantNumber.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.value; + LGraph.prototype.trigger = function(action, param) { + if (this.onTrigger) { + this.onTrigger(action, param); } - return this.title; }; - ConstantNumber.prototype.setValue = function(v) - { - this.setProperty("value",v); - } + /** + * Tell this graph it has a global graph input of this type + * @method addGlobalInput + * @param {String} name + * @param {String} type + * @param {*} value [optional] + */ + LGraph.prototype.addInput = function(name, type, value) { + var input = this.inputs[name]; + if (input) { + //already exist + return; + } - ConstantNumber.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = this.properties["value"].toFixed(3); - }; + this.beforeChange(); + this.inputs[name] = { name: name, type: type, value: value }; + this._version++; + this.afterChange(); - LiteGraph.registerNodeType("basic/const", ConstantNumber); + if (this.onInputAdded) { + this.onInputAdded(name, type); + } - function ConstantBoolean() { - this.addOutput("bool", "boolean"); - this.addProperty("value", true); - this.widget = this.addWidget("toggle","value",true,"value"); - this.serialize_widgets = true; - this.widgets_up = true; - this.size = [140, 30]; - } + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + }; - ConstantBoolean.title = "Const Boolean"; - ConstantBoolean.desc = "Constant boolean"; - ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle; + /** + * Assign a data to the global graph input + * @method setGlobalInputData + * @param {String} name + * @param {*} data + */ + LGraph.prototype.setInputData = function(name, data) { + var input = this.inputs[name]; + if (!input) { + return; + } + input.value = data; + }; - ConstantBoolean.prototype.onExecute = function() { - this.setOutputData(0, this.properties["value"]); + /** + * Returns the current value of a global graph input + * @method getInputData + * @param {String} name + * @return {*} the data + */ + LGraph.prototype.getInputData = function(name) { + var input = this.inputs[name]; + if (!input) { + return null; + } + return input.value; }; - ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Changes the name of a global graph input + * @method renameInput + * @param {String} old_name + * @param {String} new_name + */ + LGraph.prototype.renameInput = function(old_name, name) { + if (name == old_name) { + return; + } - ConstantBoolean.prototype.onGetInputs = function() { - return [["toggle", LiteGraph.ACTION]]; - }; - - ConstantBoolean.prototype.onAction = function(action) - { - this.setValue( !this.properties.value ); - } - - LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + if (!this.inputs[old_name]) { + return false; + } - function ConstantString() { - this.addOutput("string", "string"); - this.addProperty("value", ""); - this.widget = this.addWidget("text","value","","value"); //link to property value - this.widgets_up = true; - this.size = [180, 30]; - } + if (this.inputs[name]) { + console.error("there is already one input with that name"); + return false; + } - ConstantString.title = "Const String"; - ConstantString.desc = "Constant string"; + this.inputs[name] = this.inputs[old_name]; + delete this.inputs[old_name]; + this._version++; - ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; + if (this.onInputRenamed) { + this.onInputRenamed(old_name, name); + } - ConstantString.prototype.onExecute = function() { - this.setOutputData(0, this.properties["value"]); + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } }; - ConstantString.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Changes the type of a global graph input + * @method changeInputType + * @param {String} name + * @param {String} type + */ + LGraph.prototype.changeInputType = function(name, type) { + if (!this.inputs[name]) { + return false; + } - ConstantString.prototype.onDropFile = function(file) - { - var that = this; - var reader = new FileReader(); - reader.onload = function(e) - { - that.setProperty("value",e.target.result); - } - reader.readAsText(file); - } + if ( + this.inputs[name].type && + String(this.inputs[name].type).toLowerCase() == + String(type).toLowerCase() + ) { + return; + } - LiteGraph.registerNodeType("basic/string", ConstantString); + this.inputs[name].type = type; + this._version++; + if (this.onInputTypeChanged) { + this.onInputTypeChanged(name, type); + } + }; - function ConstantObject() { - this.addOutput("obj", "object"); - this.size = [120, 30]; - this._object = {}; - } + /** + * Removes a global graph input + * @method removeInput + * @param {String} name + * @param {String} type + */ + LGraph.prototype.removeInput = function(name) { + if (!this.inputs[name]) { + return false; + } - ConstantObject.title = "Const Object"; - ConstantObject.desc = "Constant Object"; + delete this.inputs[name]; + this._version++; - ConstantObject.prototype.onExecute = function() { - this.setOutputData(0, this._object); + if (this.onInputRemoved) { + this.onInputRemoved(name); + } + + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + return true; }; - LiteGraph.registerNodeType( "basic/object", ConstantObject ); + /** + * Creates a global graph output + * @method addOutput + * @param {String} name + * @param {String} type + * @param {*} value + */ + LGraph.prototype.addOutput = function(name, type, value) { + this.outputs[name] = { name: name, type: type, value: value }; + this._version++; - function ConstantFile() { - this.addInput("url", "string"); - this.addOutput("file", "string"); - this.addProperty("url", ""); - this.addProperty("type", "text"); - this.widget = this.addWidget("text","url","","url"); - this._data = null; - } + if (this.onOutputAdded) { + this.onOutputAdded(name, type); + } - ConstantFile.title = "Const File"; - ConstantFile.desc = "Fetches a file from an url"; - ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] }; + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + }; - ConstantFile.prototype.onPropertyChanged = function(name, value) { - if (name == "url") - { - if( value == null || value == "") - this._data = null; - else - { - this.fetchFile(value); - } - } - } + /** + * Assign a data to the global output + * @method setOutputData + * @param {String} name + * @param {String} value + */ + LGraph.prototype.setOutputData = function(name, value) { + var output = this.outputs[name]; + if (!output) { + return; + } + output.value = value; + }; - ConstantFile.prototype.onExecute = function() { - var url = this.getInputData(0) || this.properties.url; - if(url && (url != this._url || this._type != this.properties.type)) - this.fetchFile(url); - this.setOutputData(0, this._data ); + /** + * Returns the current value of a global graph output + * @method getOutputData + * @param {String} name + * @return {*} the data + */ + LGraph.prototype.getOutputData = function(name) { + var output = this.outputs[name]; + if (!output) { + return null; + } + return output.value; }; - ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Renames a global graph output + * @method renameOutput + * @param {String} old_name + * @param {String} new_name + */ + LGraph.prototype.renameOutput = function(old_name, name) { + if (!this.outputs[old_name]) { + return false; + } - ConstantFile.prototype.fetchFile = function(url) { - var that = this; - if(!url || url.constructor !== String) - { - that._data = null; - that.boxcolor = null; - return; - } + if (this.outputs[name]) { + console.error("there is already one output with that name"); + return false; + } - this._url = url; - this._type = this.properties.type; - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + this.outputs[name] = this.outputs[old_name]; + delete this.outputs[old_name]; + this._version++; + + if (this.onOutputRenamed) { + this.onOutputRenamed(old_name, name); } - fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); - if(that.properties.type == "arraybuffer") - return response.arrayBuffer(); - else if(that.properties.type == "text") - return response.text(); - else if(that.properties.type == "json") - return response.json(); - else if(that.properties.type == "blob") - return response.blob(); - }) - .then(function(data) { - that._data = data; - that.boxcolor = "#AEA"; - }) - .catch(function(error) { - that._data = null; - that.boxcolor = "red"; - console.error("error fetching file:",url); - }); + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } }; - ConstantFile.prototype.onDropFile = function(file) - { - var that = this; - this._url = file.name; - this._type = this.properties.type; - this.properties.url = file.name; - var reader = new FileReader(); - reader.onload = function(e) - { - that.boxcolor = "#AEA"; - var v = e.target.result; - if( that.properties.type == "json" ) - v = JSON.parse(v); - that._data = v; - } - if(that.properties.type == "arraybuffer") - reader.readAsArrayBuffer(file); - else if(that.properties.type == "text" || that.properties.type == "json") - reader.readAsText(file); - else if(that.properties.type == "blob") - return reader.readAsBinaryString(file); - } + /** + * Changes the type of a global graph output + * @method changeOutputType + * @param {String} name + * @param {String} type + */ + LGraph.prototype.changeOutputType = function(name, type) { + if (!this.outputs[name]) { + return false; + } - LiteGraph.registerNodeType("basic/file", ConstantFile); + if ( + this.outputs[name].type && + String(this.outputs[name].type).toLowerCase() == + String(type).toLowerCase() + ) { + return; + } + this.outputs[name].type = type; + this._version++; + if (this.onOutputTypeChanged) { + this.onOutputTypeChanged(name, type); + } + }; -//to store json objects -function JSONParse() { - this.addInput("parse", LiteGraph.ACTION); - this.addInput("json", "string"); - this.addOutput("done", LiteGraph.EVENT); - this.addOutput("object", "object"); - this.widget = this.addWidget("button","parse","",this.parse.bind(this)); - this._str = null; - this._obj = null; -} + /** + * Removes a global graph output + * @method removeOutput + * @param {String} name + */ + LGraph.prototype.removeOutput = function(name) { + if (!this.outputs[name]) { + return false; + } + delete this.outputs[name]; + this._version++; -JSONParse.title = "JSON Parse"; -JSONParse.desc = "Parses JSON String into object"; + if (this.onOutputRemoved) { + this.onOutputRemoved(name); + } -JSONParse.prototype.parse = function() -{ - if(!this._str) - return; + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + return true; + }; - try { - this._str = this.getInputData(1); - this._obj = JSON.parse(this._str); - this.boxcolor = "#AEA"; - this.triggerSlot(0); - } catch (err) { - this.boxcolor = "red"; - } -} + LGraph.prototype.triggerInput = function(name, value) { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].onTrigger(value); + } + }; -JSONParse.prototype.onExecute = function() { - this._str = this.getInputData(1); - this.setOutputData(1, this._obj); -}; + LGraph.prototype.setCallback = function(name, func) { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].setTrigger(func); + } + }; -JSONParse.prototype.onAction = function(name) { - if(name == "parse") - this.parse(); -} + //used for undo, called before any change is made to the graph + LGraph.prototype.beforeChange = function(info) { + if (this.onBeforeChange) { + this.onBeforeChange(this,info); + } + this.sendActionToCanvas("onBeforeChange", this); + }; -LiteGraph.registerNodeType("basic/jsonparse", JSONParse); + //used to resend actions, called after any change is made to the graph + LGraph.prototype.afterChange = function(info) { + if (this.onAfterChange) { + this.onAfterChange(this,info); + } + this.sendActionToCanvas("onAfterChange", this); + }; - //to store json objects - function ConstantData() { - this.addOutput("data", "object"); - this.addProperty("value", ""); - this.widget = this.addWidget("text","json","","value"); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } + LGraph.prototype.connectionChange = function(node, link_info) { + this.updateExecutionOrder(); + if (this.onConnectionChange) { + this.onConnectionChange(node); + } + this._version++; + this.sendActionToCanvas("onConnectionChange"); + }; - ConstantData.title = "Const Data"; - ConstantData.desc = "Constant Data"; + /** + * returns if the graph is in live mode + * @method isLive + */ - ConstantData.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - if (value == null || value == "") { - return; + LGraph.prototype.isLive = function() { + if (!this.list_of_graphcanvas) { + return false; } - try { - this._value = JSON.parse(value); - this.boxcolor = "#AEA"; - } catch (err) { - this.boxcolor = "red"; + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var c = this.list_of_graphcanvas[i]; + if (c.live_mode) { + return true; + } } + return false; }; - ConstantData.prototype.onExecute = function() { - this.setOutputData(0, this._value); + /** + * clears the triggered slot animation in all links (stop visual animation) + * @method clearTriggeredSlots + */ + LGraph.prototype.clearTriggeredSlots = function() { + for (var i in this.links) { + var link_info = this.links[i]; + if (!link_info) { + continue; + } + if (link_info._last_time) { + link_info._last_time = 0; + } + } }; - ConstantData.prototype.setValue = ConstantNumber.prototype.setValue; + /* Called when something visually changed (not the graph!) */ + LGraph.prototype.change = function() { + if (LiteGraph.debug) { + console.log("Graph changed"); + } + this.sendActionToCanvas("setDirty", [true, true]); + if (this.on_change) { + this.on_change(this); + } + }; - LiteGraph.registerNodeType("basic/data", ConstantData); + LGraph.prototype.setDirtyCanvas = function(fg, bg) { + this.sendActionToCanvas("setDirty", [fg, bg]); + }; - //to store json objects - function ConstantArray() { - this._value = []; - this.addInput("json", ""); - this.addOutput("arrayOut", "array"); - this.addOutput("length", "number"); - this.addProperty("value", "[]"); - this.widget = this.addWidget("text","array",this.properties.value,"value"); - this.widgets_up = true; - this.size = [140, 50]; - } - - ConstantArray.title = "Const Array"; - ConstantArray.desc = "Constant Array"; - - ConstantArray.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - if (value == null || value == "") { + /** + * Destroys a link + * @method removeLink + * @param {Number} link_id + */ + LGraph.prototype.removeLink = function(link_id) { + var link = this.links[link_id]; + if (!link) { return; } + var node = this.getNodeById(link.target_id); + if (node) { + node.disconnectInput(link.target_slot); + } + }; + + //save and recover app state *************************************** + /** + * Creates a Object containing all the info about this graph, it can be serialized + * @method serialize + * @return {Object} value of the node + */ + LGraph.prototype.serialize = function() { + var nodes_info = []; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + nodes_info.push(this._nodes[i].serialize()); + } + + //pack link info into a non-verbose format + var links = []; + for (var i in this.links) { + //links is an OBJECT + var link = this.links[i]; + if (!link.serialize) { + //weird bug I havent solved yet + console.warn( + "weird LLink bug, link info is not a LLink but a regular object" + ); + var link2 = new LLink(); + for (var j in link) { + link2[j] = link[j]; + } + this.links[i] = link2; + link = link2; + } - try { - if(value[0] != "[") - this._value = JSON.parse("[" + value + "]"); - else - this._value = JSON.parse(value); - this.boxcolor = "#AEA"; - } catch (err) { - this.boxcolor = "red"; + links.push(link.serialize()); } - }; - ConstantArray.prototype.onExecute = function() { - var v = this.getInputData(0); - if(v && v.length) //clone - { - if(!this._value) - this._value = new Array(); - this._value.length = v.length; - for(var i = 0; i < v.length; ++i) - this._value[i] = v[i]; - } - this.setOutputData(0, this._value); - this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); - }; + var groups_info = []; + for (var i = 0; i < this._groups.length; ++i) { + groups_info.push(this._groups[i].serialize()); + } - ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue; + var data = { + last_node_id: this.last_node_id, + last_link_id: this.last_link_id, + nodes: nodes_info, + links: links, + groups: groups_info, + config: this.config, + extra: this.extra, + version: LiteGraph.VERSION + }; - LiteGraph.registerNodeType("basic/array", ConstantArray); + if(this.onSerialize) + this.onSerialize(data); - function SetArray() - { - this.addInput("arr", "array"); - this.addInput("value", ""); - this.addOutput("arr", "array"); - this.properties = { index: 0 }; - this.widget = this.addWidget("number","i",this.properties.index,"index",{precision: 0, step: 10, min: 0}); - } + return data; + }; - SetArray.title = "Set Array"; - SetArray.desc = "Sets index of array"; + /** + * Configure a graph from a JSON string + * @method configure + * @param {String} str configure a graph from a JSON string + * @param {Boolean} returns if there was any error parsing + */ + LGraph.prototype.configure = function(data, keep_old) { + if (!data) { + return; + } - SetArray.prototype.onExecute = function() { - var arr = this.getInputData(0); - if(!arr) - return; - var v = this.getInputData(1); - if(v === undefined ) - return; - if(this.properties.index) - arr[ Math.floor(this.properties.index) ] = v; - this.setOutputData(0,arr); - }; + if (!keep_old) { + this.clear(); + } - LiteGraph.registerNodeType("basic/set_array", SetArray ); + var nodes = data.nodes; - function ArrayElement() { - this.addInput("array", "array,table,string"); - this.addInput("index", "number"); - this.addOutput("value", ""); - this.addProperty("index",0); - } + //decode links info (they are very verbose) + if (data.links && data.links.constructor === Array) { + var links = []; + for (var i = 0; i < data.links.length; ++i) { + var link_data = data.links[i]; + if(!link_data) //weird bug + { + console.warn("serialized graph link data contains errors, skipping."); + continue; + } + var link = new LLink(); + link.configure(link_data); + links[link.id] = link; + } + data.links = links; + } - ArrayElement.title = "Array[i]"; - ArrayElement.desc = "Returns an element from an array"; + //copy all stored fields + for (var i in data) { + if(i == "nodes" || i == "groups" ) //links must be accepted + continue; + this[i] = data[i]; + } - ArrayElement.prototype.onExecute = function() { - var array = this.getInputData(0); - var index = this.getInputData(1); - if(index == null) - index = this.properties.index; - if(array == null || index == null ) - return; - this.setOutputData(0, array[Math.floor(Number(index))] ); - }; + var error = false; - LiteGraph.registerNodeType("basic/array[]", ArrayElement); + //create nodes + this._nodes = []; + if (nodes) { + for (var i = 0, l = nodes.length; i < l; ++i) { + var n_info = nodes[i]; //stored info + var node = LiteGraph.createNode(n_info.type, n_info.title); + if (!node) { + if (LiteGraph.debug) { + console.log( + "Node not found or has errors: " + n_info.type + ); + } - function TableElement() { - this.addInput("table", "table"); - this.addInput("row", "number"); - this.addInput("col", "number"); - this.addOutput("value", ""); - this.addProperty("row",0); - this.addProperty("column",0); - } + //in case of error we create a replacement node to avoid losing info + node = new LGraphNode(); + node.last_serialization = n_info; + node.has_errors = true; + error = true; + //continue; + } - TableElement.title = "Table[row][col]"; - TableElement.desc = "Returns an element from a table"; + node.id = n_info.id; //id it or it will create a new id + this.add(node, true); //add before configure, otherwise configure cannot create links + } - TableElement.prototype.onExecute = function() { - var table = this.getInputData(0); - var row = this.getInputData(1); - var col = this.getInputData(2); - if(row == null) - row = this.properties.row; - if(col == null) - col = this.properties.column; - if(table == null || row == null || col == null) - return; - var row = table[Math.floor(Number(row))]; - if(row) - this.setOutputData(0, row[Math.floor(Number(col))] ); - else - this.setOutputData(0, null ); - }; + //configure nodes afterwards so they can reach each other + for (var i = 0, l = nodes.length; i < l; ++i) { + var n_info = nodes[i]; + var node = this.getNodeById(n_info.id); + if (node) { + node.configure(n_info); + } + } + } - LiteGraph.registerNodeType("basic/table[][]", TableElement); + //groups + this._groups.length = 0; + if (data.groups) { + for (var i = 0; i < data.groups.length; ++i) { + var group = new LiteGraph.LGraphGroup(); + group.configure(data.groups[i]); + this.add(group); + } + } - function ObjectProperty() { - this.addInput("obj", "object"); - this.addOutput("property", 0); - this.addProperty("value", 0); - this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } + this.updateExecutionOrder(); - ObjectProperty.title = "Object property"; - ObjectProperty.desc = "Outputs the property of an object"; + this.extra = data.extra || {}; - ObjectProperty.prototype.setValue = function(v) { - this.properties.value = v; - this.widget.value = v; - }; + if(this.onConfigure) + this.onConfigure(data); - ObjectProperty.prototype.getTitle = function() { - if (this.flags.collapsed) { - return "in." + this.properties.value; - } - return this.title; + this._version++; + this.setDirtyCanvas(true, true); + return error; }; - ObjectProperty.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; + LGraph.prototype.load = function(url, callback) { + var that = this; + + //from file + if(url.constructor === File || url.constructor === Blob) + { + var reader = new FileReader(); + reader.addEventListener('load', function(event) { + var data = JSON.parse(event.target.result); + that.configure(data); + if(callback) + callback(); + }); + + reader.readAsText(url); + return; + } + + //is a string, then an URL + var req = new XMLHttpRequest(); + req.open("GET", url, true); + req.send(null); + req.onload = function(oEvent) { + if (req.status !== 200) { + console.error("Error loading graph:", req.status, req.response); + return; + } + var data = JSON.parse( req.response ); + that.configure(data); + if(callback) + callback(); + }; + req.onerror = function(err) { + console.error("Error loading graph:", err); + }; }; - ObjectProperty.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, data[this.properties.value]); - } + LGraph.prototype.onNodeTrace = function(node, msg, color) { + //TODO }; - LiteGraph.registerNodeType("basic/object_property", ObjectProperty); + //this is the class in charge of storing link information + function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { + this.id = id; + this.type = type; + this.origin_id = origin_id; + this.origin_slot = origin_slot; + this.target_id = target_id; + this.target_slot = target_slot; - function ObjectKeys() { - this.addInput("obj", ""); - this.addOutput("keys", "array"); - this.size = [140, 30]; + this._data = null; + this._pos = new Float32Array(2); //center } - ObjectKeys.title = "Object keys"; - ObjectKeys.desc = "Outputs an array with the keys of an object"; - - ObjectKeys.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, Object.keys(data) ); + LLink.prototype.configure = function(o) { + if (o.constructor === Array) { + this.id = o[0]; + this.origin_id = o[1]; + this.origin_slot = o[2]; + this.target_id = o[3]; + this.target_slot = o[4]; + this.type = o[5]; + } else { + this.id = o.id; + this.type = o.type; + this.origin_id = o.origin_id; + this.origin_slot = o.origin_slot; + this.target_id = o.target_id; + this.target_slot = o.target_slot; } }; - LiteGraph.registerNodeType("basic/object_keys", ObjectKeys); + LLink.prototype.serialize = function() { + return [ + this.id, + this.origin_id, + this.origin_slot, + this.target_id, + this.target_slot, + this.type + ]; + }; + LiteGraph.LLink = LLink; - function SetObject() - { - this.addInput("obj", ""); - this.addInput("value", ""); - this.addOutput("obj", ""); - this.properties = { property: "" }; - this.name_widget = this.addWidget("text","prop.",this.properties.property,"property"); - } + // ************************************************************* + // Node CLASS ******* + // ************************************************************* - SetObject.title = "Set Object"; - SetObject.desc = "Adds propertiesrty to object"; + /* + title: string + pos: [x,y] + size: [x,y] - SetObject.prototype.onExecute = function() { - var obj = this.getInputData(0); - if(!obj) - return; - var v = this.getInputData(1); - if(v === undefined ) - return; - if(this.properties.property) - obj[ this.properties.property ] = v; - this.setOutputData(0,obj); - }; + input|output: every connection + + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - LiteGraph.registerNodeType("basic/set_object", SetObject ); + general properties: + + clip_area: if you render outside the node, it will be clipped + + unsafe_execution: not allowed for safe execution + + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + + resizable: if set to false it wont be resizable with the mouse + + horizontal: slots are distributed horizontally (applies to both inputs and outputs) + + inputs_horizontal: input slots are distributed horizontally (overrides horizontal for inputs) + + outputs_horizontal: output slots are distributed horizontally (overrides horizontal for outputs) + + widgets_start_y: widgets start at y distance from the top of the node + + flags object: + + collapsed: if it is collapsed + + supported callbacks: + + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) + + onRemoved: when removed from graph + + onStart: when the graph starts playing + + onStop: when the graph stops playing + + onDrawForeground: render the inside widgets inside the node + + onDrawBackground: render the background area inside the node (only in edit mode) + + onMouseDown + + onMouseMove + + onMouseUp + + onMouseEnter + + onMouseLeave + + onExecute: execute the node + + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) + + onGetInputs: returns an array of possible inputs + + onGetOutputs: returns an array of possible outputs + + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) + + onDblClick: double clicked in the node + + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) + + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) + + onConfigure: called after the node has been configured + + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) + + onSelected + + onDeselected + + onDropItem : DOM item dropped over the node + + onDropFile : file dropped over the node + + onConnectInput : if returns false the incoming connection will be canceled + + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) + + onAction: action slot triggered + + getExtraMenuOptions: to add option to context menu +*/ + /** + * Base Class for all the node type classes + * @class LGraphNode + * @param {String} name a name for the node + */ - function MergeObjects() { - this.addInput("A", "object"); - this.addInput("B", "object"); - this.addOutput("out", "object"); - this._result = {}; - var that = this; - this.addWidget("button","clear","",function(){ - that._result = {}; - }); - this.size = this.computeSize(); + function LGraphNode(title) { + this._ctor(title); } - MergeObjects.title = "Merge Objects"; - MergeObjects.desc = "Creates an object copying properties from others"; + global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - MergeObjects.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this._result; - if(A) - for(var i in A) - C[i] = A[i]; - if(B) - for(var i in B) - C[i] = B[i]; - this.setOutputData(0,C); - }; + LGraphNode.prototype._ctor = function(title) { + this.title = title || "Unnamed"; + this.size = [LiteGraph.NODE_WIDTH, 60]; + this.graph = null; - LiteGraph.registerNodeType("basic/merge_objects", MergeObjects ); + this._pos = new Float32Array(10, 10); - //Store as variable - function Variable() { - this.size = [60, 30]; - this.addInput("in"); - this.addOutput("out"); - this.properties = { varname: "myname", container: Variable.LITEGRAPH }; - this.value = null; - } + Object.defineProperty(this, "pos", { + set: function(v) { + if (!v || v.length < 2) { + return; + } + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() { + return this._pos; + }, + enumerable: true + }); - Variable.title = "Variable"; - Variable.desc = "store/read variable value"; - - Variable.LITEGRAPH = 0; //between all graphs - Variable.GRAPH = 1; //only inside this graph - Variable.GLOBALSCOPE = 2; //attached to Window - - Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} }; + if (LiteGraph.use_uuids) { + this.id = LiteGraph.uuidv4(); + } + else { + this.id = -1; //not know till not added + } + this.type = null; - Variable.prototype.onExecute = function() { - var container = this.getContainer(); + //inputs available: array of inputs + this.inputs = []; + this.outputs = []; + this.connections = []; - if(this.isInputConnected(0)) - { - this.value = this.getInputData(0); - container[ this.properties.varname ] = this.value; - this.setOutputData(0, this.value ); - return; - } + //local data + this.properties = {}; //for the values + this.properties_info = []; //for the info - this.setOutputData( 0, container[ this.properties.varname ] ); + this.flags = {}; }; - Variable.prototype.getContainer = function() - { - switch(this.properties.container) - { - case Variable.GRAPH: - if(this.graph) - return this.graph.vars; - return {}; - break; - case Variable.GLOBALSCOPE: - return global; - break; - case Variable.LITEGRAPH: - default: - return LiteGraph.Globals; - break; - } - } + /** + * configure a node from an object containing the serialized info + * @method configure + */ + LGraphNode.prototype.configure = function(info) { + if (this.graph) { + this.graph._version++; + } + for (var j in info) { + if (j == "properties") { + //i don't want to clone properties, I want to reuse the old container + for (var k in info.properties) { + this.properties[k] = info.properties[k]; + if (this.onPropertyChanged) { + this.onPropertyChanged( k, info.properties[k] ); + } + } + continue; + } - Variable.prototype.getTitle = function() { - return this.properties.varname; - }; + if (info[j] == null) { + continue; + } else if (typeof info[j] == "object") { + //object + if (this[j] && this[j].configure) { + this[j].configure(info[j]); + } else { + this[j] = LiteGraph.cloneObject(info[j], this[j]); + } + } //value + else { + this[j] = info[j]; + } + } - LiteGraph.registerNodeType("basic/variable", Variable); + if (!info.title) { + this.title = this.constructor.title; + } - function length(v) { - if(v && v.length != null) - return Number(v.length); - return 0; - } + if (this.inputs) { + for (var i = 0; i < this.inputs.length; ++i) { + var input = this.inputs[i]; + var link_info = this.graph ? this.graph.links[input.link] : null; + if (this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - LiteGraph.wrapFunctionAsNode( - "basic/length", - length, - [""], - "number" - ); + if( this.onInputAdded ) + this.onInputAdded(input); - function length(v) { - if(v && v.length != null) - return Number(v.length); - return 0; - } + } + } - LiteGraph.wrapFunctionAsNode( - "basic/not", - function(a){ return !a; }, - [""], - "boolean" - ); + if (this.outputs) { + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + if (!output.links) { + continue; + } + for (var j = 0; j < output.links.length; ++j) { + var link_info = this.graph ? this.graph.links[output.links[j]] : null; + if (this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated + } - function DownloadData() { - this.size = [60, 30]; - this.addInput("data", 0 ); - this.addInput("download", LiteGraph.ACTION ); - this.properties = { filename: "data.json" }; - this.value = null; - var that = this; - this.addWidget("button","Download","", function(v){ - if(!that.value) - return; - that.downloadAsFile(); - }); - } + if( this.onOutputAdded ) + this.onOutputAdded(output); + } + } - DownloadData.title = "Download"; - DownloadData.desc = "Download some data"; + if( this.widgets ) + { + for (var i = 0; i < this.widgets.length; ++i) + { + var w = this.widgets[i]; + if(!w) + continue; + if(w.options && w.options.property && (this.properties[ w.options.property ] != undefined)) + w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); + } + if (info.widgets_values) { + for (var i = 0; i < info.widgets_values.length; ++i) { + if (this.widgets[i]) { + this.widgets[i].value = info.widgets_values[i]; + } + } + } + } - DownloadData.prototype.downloadAsFile = function() - { - if(this.value == null) - return; + if (this.onConfigure) { + this.onConfigure(info); + } + }; - var str = null; - if(this.value.constructor === String) - str = this.value; - else - str = JSON.stringify(this.value); + /** + * serialize the content + * @method serialize + */ - var file = new Blob([str]); - var url = URL.createObjectURL( file ); - var element = document.createElement("a"); - element.setAttribute('href', url); - element.setAttribute('download', this.properties.filename ); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url - } + LGraphNode.prototype.serialize = function() { + //create serialization object + var o = { + id: this.id, + type: this.type, + pos: this.pos, + size: this.size, + flags: LiteGraph.cloneObject(this.flags), + order: this.order, + mode: this.mode + }; - DownloadData.prototype.onAction = function(action, param) { - var that = this; - setTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup - } + //special case for when there were errors + if (this.constructor === LGraphNode && this.last_serialization) { + return this.last_serialization; + } - DownloadData.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); + if (this.inputs) { + o.inputs = this.inputs; } - }; - DownloadData.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.filename; + if (this.outputs) { + //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) + for (var i = 0; i < this.outputs.length; i++) { + delete this.outputs[i]._data; + } + o.outputs = this.outputs; } - return this.title; - }; - LiteGraph.registerNodeType("basic/download", DownloadData); + if (this.title && this.title != this.constructor.title) { + o.title = this.title; + } + if (this.properties) { + o.properties = LiteGraph.cloneObject(this.properties); + } + if (this.widgets && this.serialize_widgets) { + o.widgets_values = []; + for (var i = 0; i < this.widgets.length; ++i) { + if(this.widgets[i]) + o.widgets_values[i] = this.widgets[i].value; + else + o.widgets_values[i] = null; + } + } - //Watch a value in the editor - function Watch() { - this.size = [60, 30]; - this.addInput("value", 0, { label: "" }); - this.value = 0; - } + if (!o.type) { + o.type = this.constructor.type; + } - Watch.title = "Watch"; - Watch.desc = "Show value of input"; + if (this.color) { + o.color = this.color; + } + if (this.bgcolor) { + o.bgcolor = this.bgcolor; + } + if (this.boxcolor) { + o.boxcolor = this.boxcolor; + } + if (this.shape) { + o.shape = this.shape; + } - Watch.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); + if (this.onSerialize) { + if (this.onSerialize(o)) { + console.warn( + "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" + ); + } } + + return o; }; - Watch.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.inputs[0].label; + /* Creates a clone of this node */ + LGraphNode.prototype.clone = function() { + var node = LiteGraph.createNode(this.type); + if (!node) { + return null; } - return this.title; - }; - Watch.toString = function(o) { - if (o == null) { - return "null"; - } else if (o.constructor === Number) { - return o.toFixed(3); - } else if (o.constructor === Array) { - var str = "["; - for (var i = 0; i < o.length; ++i) { - str += Watch.toString(o[i]) + (i + 1 != o.length ? "," : ""); + //we clone it because serialize returns shared containers + var data = LiteGraph.cloneObject(this.serialize()); + + //remove links + if (data.inputs) { + for (var i = 0; i < data.inputs.length; ++i) { + data.inputs[i].link = null; } - str += "]"; - return str; - } else { - return String(o); } - }; - Watch.prototype.onDrawBackground = function(ctx) { - //show the current value - this.inputs[0].label = Watch.toString(this.value); - }; + if (data.outputs) { + for (var i = 0; i < data.outputs.length; ++i) { + if (data.outputs[i].links) { + data.outputs[i].links.length = 0; + } + } + } - LiteGraph.registerNodeType("basic/watch", Watch); + delete data["id"]; - //in case one type doesnt match other type but you want to connect them anyway - function Cast() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [40, 30]; - } + if (LiteGraph.use_uuids) { + data["id"] = LiteGraph.uuidv4() + } - Cast.title = "Cast"; - Cast.desc = "Allows to connect different types"; + //remove links + node.configure(data); - Cast.prototype.onExecute = function() { - this.setOutputData(0, this.getInputData(0)); + return node; }; - LiteGraph.registerNodeType("basic/cast", Cast); + /** + * serialize and stringify + * @method toString + */ - //Show value inside the debug console - function Console() { - this.mode = LiteGraph.ON_EVENT; - this.size = [80, 30]; - this.addProperty("msg", ""); - this.addInput("log", LiteGraph.EVENT); - this.addInput("msg", 0); - } + LGraphNode.prototype.toString = function() { + return JSON.stringify(this.serialize()); + }; + //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - Console.title = "Console"; - Console.desc = "Show value inside the console"; + /** + * get the title string + * @method getTitle + */ - Console.prototype.onAction = function(action, param) { - // param is the action - var msg = this.getInputData(1); //getInputDataByName("msg"); - //if (msg == null || typeof msg == "undefined") return; - if (!msg) msg = this.properties.msg; - if (!msg) msg = "Event: "+param; // msg is undefined if the slot is lost? - if (action == "log") { - console.log(msg); - } else if (action == "warn") { - console.warn(msg); - } else if (action == "error") { - console.error(msg); - } + LGraphNode.prototype.getTitle = function() { + return this.title || this.constructor.title; }; - Console.prototype.onExecute = function() { - var msg = this.getInputData(1); //getInputDataByName("msg"); - if (!msg) msg = this.properties.msg; - if (msg != null && typeof msg != "undefined") { - this.properties.msg = msg; - console.log(msg); + /** + * sets the value of a property + * @method setProperty + * @param {String} name + * @param {*} value + */ + LGraphNode.prototype.setProperty = function(name, value) { + if (!this.properties) { + this.properties = {}; } + if( value === this.properties[name] ) + return; + var prev_value = this.properties[name]; + this.properties[name] = value; + if (this.onPropertyChanged) { + if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change + this.properties[name] = prev_value; + } + if(this.widgets) //widgets could be linked to properties + for(var i = 0; i < this.widgets.length; ++i) + { + var w = this.widgets[i]; + if(!w) + continue; + if(w.options.property == name) + { + w.value = value; + break; + } + } }; - Console.prototype.onGetInputs = function() { - return [ - ["log", LiteGraph.ACTION], - ["warn", LiteGraph.ACTION], - ["error", LiteGraph.ACTION] - ]; - }; + // Execution ************************* + /** + * sets the output data + * @method setOutputData + * @param {number} slot + * @param {*} data + */ + LGraphNode.prototype.setOutputData = function(slot, data) { + if (!this.outputs) { + return; + } - LiteGraph.registerNodeType("basic/console", Console); + //this maybe slow and a niche case + //if(slot && slot.constructor === String) + // slot = this.findOutputSlot(slot); - //Show value inside the debug console - function Alert() { - this.mode = LiteGraph.ON_EVENT; - this.addProperty("msg", ""); - this.addInput("", LiteGraph.EVENT); - var that = this; - this.widget = this.addWidget("text", "Text", "", "msg"); - this.widgets_up = true; - this.size = [200, 30]; - } + if (slot == -1 || slot >= this.outputs.length) { + return; + } - Alert.title = "Alert"; - Alert.desc = "Show an alert window"; - Alert.color = "#510"; + var output_info = this.outputs[slot]; + if (!output_info) { + return; + } - Alert.prototype.onConfigure = function(o) { - this.widget.value = o.properties.msg; + //store data in the output itself in case we want to debug + output_info._data = data; + + //if there are connections, pass the data to the connections + if (this.outputs[slot].links) { + for (var i = 0; i < this.outputs[slot].links.length; i++) { + var link_id = this.outputs[slot].links[i]; + var link = this.graph.links[link_id]; + if(link) + link.data = data; + } + } }; - Alert.prototype.onAction = function(action, param) { - var msg = this.properties.msg; - setTimeout(function() { - alert(msg); - }, 10); + /** + * sets the output data type, useful when you want to be able to overwrite the data type + * @method setOutputDataType + * @param {number} slot + * @param {String} datatype + */ + LGraphNode.prototype.setOutputDataType = function(slot, type) { + if (!this.outputs) { + return; + } + if (slot == -1 || slot >= this.outputs.length) { + return; + } + var output_info = this.outputs[slot]; + if (!output_info) { + return; + } + //store data in the output itself in case we want to debug + output_info.type = type; + + //if there are connections, pass the data to the connections + if (this.outputs[slot].links) { + for (var i = 0; i < this.outputs[slot].links.length; i++) { + var link_id = this.outputs[slot].links[i]; + this.graph.links[link_id].type = type; + } + } }; - LiteGraph.registerNodeType("basic/alert", Alert); + /** + * Retrieves the input data (data traveling through the connection) from one slot + * @method getInputData + * @param {number} slot + * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link + * @return {*} data or if it is not connected returns undefined + */ + LGraphNode.prototype.getInputData = function(slot, force_update) { + if (!this.inputs) { + return; + } //undefined; - //Execites simple code - function NodeScript() { - this.size = [60, 30]; - this.addProperty("onExecute", "return A;"); - this.addInput("A", 0); - this.addInput("B", 0); - this.addOutput("out", 0); + if (slot >= this.inputs.length || this.inputs[slot].link == null) { + return; + } - this._func = null; - this.data = {}; - } + var link_id = this.inputs[slot].link; + var link = this.graph.links[link_id]; + if (!link) { + //bug: weird case but it happens sometimes + return null; + } - NodeScript.prototype.onConfigure = function(o) { - if (o.properties.onExecute && LiteGraph.allow_scripts) - this.compileCode(o.properties.onExecute); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); - }; + if (!force_update) { + return link.data; + } - NodeScript.title = "Script"; - NodeScript.desc = "executes a code (max 256 characters)"; + //special case: used to extract data from the incoming connection before the graph has been executed + var node = this.graph.getNodeById(link.origin_id); + if (!node) { + return link.data; + } - NodeScript.widgets_info = { - onExecute: { type: "code" } + if (node.updateOutputData) { + node.updateOutputData(link.origin_slot); + } else if (node.onExecute) { + node.onExecute(); + } + + return link.data; }; - NodeScript.prototype.onPropertyChanged = function(name, value) { - if (name == "onExecute" && LiteGraph.allow_scripts) - this.compileCode(value); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + /** + * Retrieves the input data type (in case this supports multiple input types) + * @method getInputDataType + * @param {number} slot + * @return {String} datatype in string format + */ + LGraphNode.prototype.getInputDataType = function(slot) { + if (!this.inputs) { + return null; + } //undefined; + + if (slot >= this.inputs.length || this.inputs[slot].link == null) { + return null; + } + var link_id = this.inputs[slot].link; + var link = this.graph.links[link_id]; + if (!link) { + //bug: weird case but it happens sometimes + return null; + } + var node = this.graph.getNodeById(link.origin_id); + if (!node) { + return link.type; + } + var output_info = node.outputs[link.origin_slot]; + if (output_info) { + return output_info.type; + } + return null; }; - NodeScript.prototype.compileCode = function(code) { - this._func = null; - if (code.length > 256) { - console.warn("Script too long, max 256 chars"); - } else { - var code_low = code.toLowerCase(); - var forbidden_words = [ - "script", - "body", - "document", - "eval", - "nodescript", - "function" - ]; //bad security solution - for (var i = 0; i < forbidden_words.length; ++i) { - if (code_low.indexOf(forbidden_words[i]) != -1) { - console.warn("invalid script"); - return; - } - } - try { - this._func = new Function("A", "B", "C", "DATA", "node", code); - } catch (err) { - console.error("Error parsing script"); - console.error(err); - } + /** + * Retrieves the input data from one slot using its name instead of slot number + * @method getInputDataByName + * @param {String} slot_name + * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link + * @return {*} data or if it is not connected returns null + */ + LGraphNode.prototype.getInputDataByName = function( + slot_name, + force_update + ) { + var slot = this.findInputSlot(slot_name); + if (slot == -1) { + return null; } + return this.getInputData(slot, force_update); }; - NodeScript.prototype.onExecute = function() { - if (!this._func) { - return; + /** + * tells you if there is a connection in one input slot + * @method isInputConnected + * @param {number} slot + * @return {boolean} + */ + LGraphNode.prototype.isInputConnected = function(slot) { + if (!this.inputs) { + return false; } + return slot < this.inputs.length && this.inputs[slot].link != null; + }; - try { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this.getInputData(2); - this.setOutputData(0, this._func(A, B, C, this.data, this)); - } catch (err) { - console.error("Error in script"); - console.error(err); + /** + * tells you info about an input connection (which node, type, etc) + * @method getInputInfo + * @param {number} slot + * @return {Object} object or null { link: id, name: string, type: string or 0 } + */ + LGraphNode.prototype.getInputInfo = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + return this.inputs[slot]; } + return null; }; - NodeScript.prototype.onGetOutputs = function() { - return [["C", ""]]; + /** + * Returns the link info in the connection of an input slot + * @method getInputLink + * @param {number} slot + * @return {LLink} object or null + */ + LGraphNode.prototype.getInputLink = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + var slot_info = this.inputs[slot]; + return this.graph.links[ slot_info.link ]; + } + return null; }; - LiteGraph.registerNodeType("basic/script", NodeScript); - - - function GenericCompare() { - this.addInput("A", 0); - this.addInput("B", 0); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "==", "enum", { values: GenericCompare.values }); - this.addWidget("combo","Op.",this.properties.OP,{ property: "OP", values: GenericCompare.values } ); + /** + * returns the node connected in the input slot + * @method getInputNode + * @param {number} slot + * @return {LGraphNode} node or null + */ + LGraphNode.prototype.getInputNode = function(slot) { + if (!this.inputs) { + return null; + } + if (slot >= this.inputs.length) { + return null; + } + var input = this.inputs[slot]; + if (!input || input.link === null) { + return null; + } + var link_info = this.graph.links[input.link]; + if (!link_info) { + return null; + } + return this.graph.getNodeById(link_info.origin_id); + }; - this.size = [80, 60]; - } + /** + * returns the value of an input with this name, otherwise checks if there is a property with that name + * @method getInputOrProperty + * @param {string} name + * @return {*} value + */ + LGraphNode.prototype.getInputOrProperty = function(name) { + if (!this.inputs || !this.inputs.length) { + return this.properties ? this.properties[name] : null; + } - GenericCompare.values = ["==", "!="]; //[">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; - GenericCompare["@OP"] = { - type: "enum", - title: "operation", - values: GenericCompare.values + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input_info = this.inputs[i]; + if (name == input_info.name && input_info.link != null) { + var link = this.graph.links[input_info.link]; + if (link) { + return link.data; + } + } + } + return this.properties[name]; }; - GenericCompare.title = "Compare *"; - GenericCompare.desc = "evaluates condition between A and B"; + /** + * tells you the last output data that went in that slot + * @method getOutputData + * @param {number} slot + * @return {Object} object or null + */ + LGraphNode.prototype.getOutputData = function(slot) { + if (!this.outputs) { + return null; + } + if (slot >= this.outputs.length) { + return null; + } - GenericCompare.prototype.getTitle = function() { - return "*A " + this.properties.OP + " *B"; + var info = this.outputs[slot]; + return info._data; }; - GenericCompare.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A === undefined) { - A = this.properties.A; - } else { - this.properties.A = A; + /** + * tells you info about an output connection (which node, type, etc) + * @method getOutputInfo + * @param {number} slot + * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } + */ + LGraphNode.prototype.getOutputInfo = function(slot) { + if (!this.outputs) { + return null; + } + if (slot < this.outputs.length) { + return this.outputs[slot]; } + return null; + }; - var B = this.getInputData(1); - if (B === undefined) { - B = this.properties.B; - } else { - this.properties.B = B; + /** + * tells you if there is a connection in one output slot + * @method isOutputConnected + * @param {number} slot + * @return {boolean} + */ + LGraphNode.prototype.isOutputConnected = function(slot) { + if (!this.outputs) { + return false; } + return ( + slot < this.outputs.length && + this.outputs[slot].links && + this.outputs[slot].links.length + ); + }; - var result = false; - if (typeof A == typeof B){ - switch (this.properties.OP) { - case "==": - case "!=": - // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual() - result = true; - switch(typeof A){ - case "object": - var aProps = Object.getOwnPropertyNames(A); - var bProps = Object.getOwnPropertyNames(B); - if (aProps.length != bProps.length){ - result = false; - break; - } - for (var i = 0; i < aProps.length; i++) { - var propName = aProps[i]; - if (A[propName] !== B[propName]) { - result = false; - break; - } - } - break; - default: - result = A == B; - } - if (this.properties.OP == "!=") result = !result; - break; - /*case ">": - result = A > B; - break; - case "<": - result = A < B; - break; - case "<=": - result = A <= B; - break; - case ">=": - result = A >= B; - break; - case "||": - result = A || B; - break; - case "&&": - result = A && B; - break;*/ + /** + * tells you if there is any connection in the output slots + * @method isAnyOutputConnected + * @return {boolean} + */ + LGraphNode.prototype.isAnyOutputConnected = function() { + if (!this.outputs) { + return false; + } + for (var i = 0; i < this.outputs.length; ++i) { + if (this.outputs[i].links && this.outputs[i].links.length) { + return true; } } - this.setOutputData(0, result); - this.setOutputData(1, !result); + return false; }; - LiteGraph.registerNodeType("basic/CompareValues", GenericCompare); - -})(this); - -//event related nodes -(function(global) { - var LiteGraph = global.LiteGraph; + /** + * retrieves all the nodes connected to this output slot + * @method getOutputNodes + * @param {number} slot + * @return {array} + */ + LGraphNode.prototype.getOutputNodes = function(slot) { + if (!this.outputs || this.outputs.length == 0) { + return null; + } - //Show value inside the debug console - function LogEvent() { - this.size = [60, 30]; - this.addInput("event", LiteGraph.ACTION); - } + if (slot >= this.outputs.length) { + return null; + } - LogEvent.title = "Log Event"; - LogEvent.desc = "Log event in console"; + var output = this.outputs[slot]; + if (!output.links || output.links.length == 0) { + return null; + } - LogEvent.prototype.onAction = function(action, param, options) { - console.log(action, param); + var r = []; + for (var i = 0; i < output.links.length; i++) { + var link_id = output.links[i]; + var link = this.graph.links[link_id]; + if (link) { + var target_node = this.graph.getNodeById(link.target_id); + if (target_node) { + r.push(target_node); + } + } + } + return r; }; - LiteGraph.registerNodeType("events/log", LogEvent); - - //convert to Event if the value is true - function TriggerEvent() { - this.size = [60, 30]; - this.addInput("if", ""); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("change", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.properties = { only_on_change: true }; - this.prev = 0; + LGraphNode.prototype.addOnTriggerInput = function(){ + var trigS = this.findInputSlot("onTrigger"); + if (trigS == -1){ //!trigS || + var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); + return this.findInputSlot("onTrigger"); + } + return trigS; } - - TriggerEvent.title = "TriggerEvent"; - TriggerEvent.desc = "Triggers event if input evaluates to true"; - - TriggerEvent.prototype.onExecute = function( param, options) { - var v = this.getInputData(0); - var changed = (v != this.prev); - if(this.prev === 0) - changed = false; - var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); - if(v && must_resend ) - this.triggerSlot(0, param, null, options); - if(!v && must_resend) - this.triggerSlot(2, param, null, options); - if(changed) - this.triggerSlot(1, param, null, options); - this.prev = v; + + LGraphNode.prototype.addOnExecutedOutput = function(){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS == -1){ //!trigS || + var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); + return this.findOutputSlot("onExecuted"); + } + return trigS; + } + + LGraphNode.prototype.onAfterExecuteNode = function(param, options){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS != -1){ + + //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); + //console.debug(param); + //console.debug(options); + this.triggerSlot(trigS, param, null, options); + + } + } + + LGraphNode.prototype.changeMode = function(modeTo){ + switch(modeTo){ + case LiteGraph.ON_EVENT: + // this.addOnExecutedOutput(); + break; + + case LiteGraph.ON_TRIGGER: + this.addOnTriggerInput(); + this.addOnExecutedOutput(); + break; + + case LiteGraph.NEVER: + break; + + case LiteGraph.ALWAYS: + break; + + case LiteGraph.ON_REQUEST: + break; + + default: + return false; + break; + } + this.mode = modeTo; + return true; }; - LiteGraph.registerNodeType("events/trigger", TriggerEvent); - - //Sequence of events - function Sequence() { - var that = this; - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addWidget("button","+",null,function(){ - that.addInput("", LiteGraph.ACTION); - that.addOutput("", LiteGraph.EVENT); - }); - this.size = [90, 70]; - this.flags = { horizontal: true, render_box: false }; + /** + * Triggers the execution of actions that were deferred when the action was triggered + * @method executePendingActions + */ + LGraphNode.prototype.executePendingActions = function() { + if(!this._waiting_actions || !this._waiting_actions.length) + return; + for(var i = 0; i < this._waiting_actions.length;++i) + { + var p = this._waiting_actions[i]; + this.onAction(p[0],p[1],p[2],p[3],p[4]); + } + this._waiting_actions.length = 0; } - Sequence.title = "Sequence"; - Sequence.desc = "Triggers a sequence of events when an event arrives"; - - Sequence.prototype.getTitle = function() { - return ""; + + /** + * Triggers the node code execution, place a boolean/counter to mark the node as being executed + * @method doExecute + * @param {*} param + * @param {*} options + */ + LGraphNode.prototype.doExecute = function(param, options) { + options = options || {}; + if (this.onExecute){ + + // enable this to give the event an ID + if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); + + this.graph.nodes_executing[this.id] = true; //.push(this.id); + + this.onExecute(param, options); + + this.graph.nodes_executing[this.id] = false; //.pop(); + + // save execution/action ref + this.exec_version = this.graph.iteration; + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; + } + } + else { + } + this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event + if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback }; - - Sequence.prototype.onAction = function(action, param, options) { - if (this.outputs) { - options = options || {}; - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - //needs more info about this... - if( options.action_call ) // CREATE A NEW ID FOR THE ACTION - options.action_call = options.action_call + "_seq_" + i; - else - options.action_call = this.id + "_" + (action ? action : "action")+"_seq_"+i+"_"+Math.floor(Math.random()*9999); - this.triggerSlot(i, param, null, options); + + /** + * Triggers an action, wrapped by logics to control execution flow + * @method actionDo + * @param {String} action name + * @param {*} param + */ + LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) { + options = options || {}; + if (this.onAction){ + + // enable this to give the event an ID + if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); + + this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); + + this.onAction(action, param, options, action_slot); + + this.graph.nodes_actioning[this.id] = false; //.pop(); + + // save execution/action ref + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; } } + this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event + if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); }; + + /** + * Triggers an event in this node, this will trigger any output with the same name + * @method trigger + * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all + * @param {*} param + */ + LGraphNode.prototype.trigger = function(action, param, options) { + if (!this.outputs || !this.outputs.length) { + return; + } - LiteGraph.registerNodeType("events/sequence", Sequence); - + if (this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); - //Sequence of events - function WaitAll() { - var that = this; - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addOutput("", LiteGraph.EVENT); - this.addWidget("button","+",null,function(){ - that.addInput("", LiteGraph.ACTION); - that.size[0] = 90; - }); - this.size = [90, 70]; - this.ready = []; -} + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) + continue; + this.triggerSlot(i, param, null, options); + } + }; -WaitAll.title = "WaitAll"; -WaitAll.desc = "Wait until all input events arrive then triggers output"; + /** + * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes + * @method triggerSlot + * @param {Number} slot the index of the output slot + * @param {*} param + * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot + */ + LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { + options = options || {}; + if (!this.outputs) { + return; + } -WaitAll.prototype.getTitle = function() { - return ""; -}; + if(slot == null) + { + console.error("slot must be a number"); + return; + } -WaitAll.prototype.onDrawBackground = function(ctx) -{ - if (this.flags.collapsed) { - return; - } - for(var i = 0; i < this.inputs.length; ++i) - { - var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10; - ctx.fillStyle = this.ready[i] ? "#AFB" : "#000"; - ctx.fillRect(20, y, 10, 10); - } -} + if(slot.constructor !== Number) + console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); -WaitAll.prototype.onAction = function(action, param, options, slot_index) { - if(slot_index == null) - return; + var output = this.outputs[slot]; + if (!output) { + return; + } - //check all - this.ready.length = this.outputs.length; - this.ready[slot_index] = true; - for(var i = 0; i < this.ready.length;++i) - if(!this.ready[i]) + var links = output.links; + if (!links || !links.length) { return; - //pass - this.reset(); - this.triggerSlot(0); -}; + } -WaitAll.prototype.reset = function() -{ - this.ready.length = 0; -} + if (this.graph) { + this.graph._last_trigger_time = LiteGraph.getTime(); + } -LiteGraph.registerNodeType("events/waitAll", WaitAll); + //for every link attached here + for (var k = 0; k < links.length; ++k) { + var id = links[k]; + if (link_id != null && link_id != id) { + //to skip links + continue; + } + var link_info = this.graph.links[links[k]]; + if (!link_info) { + //not connected + continue; + } + link_info._last_time = LiteGraph.getTime(); + var node = this.graph.getNodeById(link_info.target_id); + if (!node) { + //node not found? + continue; + } + //used to mark events in graph + var target_connection = node.inputs[link_info.target_slot]; - //Sequencer for events - function Stepper() { - var that = this; - this.properties = { index: 0 }; - this.addInput("index", "number"); - this.addInput("step", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("index", "number"); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT,{removable:true}); - this.addWidget("button","+",null,function(){ - that.addOutput("", LiteGraph.EVENT, {removable:true}); - }); - this.size = [120, 120]; - this.flags = { render_box: false }; - } + if (node.mode === LiteGraph.ON_TRIGGER) + { + // generate unique trigger ID if not present + if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); + if (node.onExecute) { + // -- wrapping node.onExecute(param); -- + node.doExecute(param, options); + } + } + else if (node.onAction) { + // generate unique action ID if not present + if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); + //pass the action name + var target_connection = node.inputs[link_info.target_slot]; + + //instead of executing them now, it will be executed in the next graph loop, to ensure data flow + if(LiteGraph.use_deferred_actions && node.onExecute) + { + if(!node._waiting_actions) + node._waiting_actions = []; + node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]); + } + else + { + // wrap node.onAction(target_connection.name, param); + node.actionDo( target_connection.name, param, options, link_info.target_slot ); + } + } + } + }; - Stepper.title = "Stepper"; - Stepper.desc = "Trigger events sequentially when an tick arrives"; + /** + * clears the trigger slot animation + * @method clearTriggeredSlot + * @param {Number} slot the index of the output slot + * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot + */ + LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { + if (!this.outputs) { + return; + } - Stepper.prototype.onDrawBackground = function(ctx) - { - if (this.flags.collapsed) { + var output = this.outputs[slot]; + if (!output) { return; } - var index = this.properties.index || 0; - ctx.fillStyle = "#AFB"; - var w = this.size[0]; - var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4; - ctx.beginPath(); - ctx.moveTo(w - 30, y); - ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); - } - Stepper.prototype.onExecute = function() - { - var index = this.getInputData(0); - if(index != null) - { - index = Math.floor(index); - index = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 ); - if( index != this.properties.index ) - { - this.properties.index = index; - this.triggerSlot( index+1 ); - } - } + var links = output.links; + if (!links || !links.length) { + return; + } - this.setOutputData(0, this.properties.index ); + //for every link attached here + for (var k = 0; k < links.length; ++k) { + var id = links[k]; + if (link_id != null && link_id != id) { + //to skip links + continue; + } + var link_info = this.graph.links[links[k]]; + if (!link_info) { + //not connected + continue; + } + link_info._last_time = 0; + } + }; + + /** + * changes node size and triggers callback + * @method setSize + * @param {vec2} size + */ + LGraphNode.prototype.setSize = function(size) + { + this.size = size; + if(this.onResize) + this.onResize(this.size); } - Stepper.prototype.onAction = function(action, param) { - if(action == "reset") - this.properties.index = 0; - else if(action == "step") - { - this.triggerSlot(this.properties.index+1, param); - var n = this.outputs ? this.outputs.length - 1 : 0; - this.properties.index = (this.properties.index + 1) % n; + /** + * add a new property to this node + * @method addProperty + * @param {string} name + * @param {*} default_value + * @param {string} type string defining the output type ("vec3","number",...) + * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) + */ + LGraphNode.prototype.addProperty = function( + name, + default_value, + type, + extra_info + ) { + var o = { name: name, type: type, default_value: default_value }; + if (extra_info) { + for (var i in extra_info) { + o[i] = extra_info[i]; + } + } + if (!this.properties_info) { + this.properties_info = []; } + this.properties_info.push(o); + if (!this.properties) { + this.properties = {}; + } + this.properties[name] = default_value; + return o; }; - LiteGraph.registerNodeType("events/stepper", Stepper); + //connections - //Filter events - function FilterEvent() { - this.size = [60, 30]; - this.addInput("event", LiteGraph.ACTION); - this.addOutput("event", LiteGraph.EVENT); - this.properties = { - equal_to: "", - has_property: "", - property_equal_to: "" - }; - } - - FilterEvent.title = "Filter Event"; - FilterEvent.desc = "Blocks events that do not match the filter"; - - FilterEvent.prototype.onAction = function(action, param, options) { - if (param == null) { - return; - } - - if (this.properties.equal_to && this.properties.equal_to != param) { - return; + /** + * add a new output slot to use in this node + * @method addOutput + * @param {string} name + * @param {string} type string defining the output type ("vec3","number",...) + * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) + */ + LGraphNode.prototype.addOutput = function(name, type, extra_info) { + var output = { name: name, type: type, links: null }; + if (extra_info) { + for (var i in extra_info) { + output[i] = extra_info[i]; + } } - if (this.properties.has_property) { - var prop = param[this.properties.has_property]; - if (prop == null) { - return; + if (!this.outputs) { + this.outputs = []; + } + this.outputs.push(output); + if (this.onOutputAdded) { + this.onOutputAdded(output); + } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); + + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); + return output; + }; + + /** + * add a new output slot to use in this node + * @method addOutputs + * @param {Array} array of triplets like [[name,type,extra_info],[...]] + */ + LGraphNode.prototype.addOutputs = function(array) { + for (var i = 0; i < array.length; ++i) { + var info = array[i]; + var o = { name: info[0], type: info[1], link: null }; + if (array[2]) { + for (var j in info[2]) { + o[j] = info[2][j]; + } } - if ( - this.properties.property_equal_to && - this.properties.property_equal_to != prop - ) { - return; + if (!this.outputs) { + this.outputs = []; + } + this.outputs.push(o); + if (this.onOutputAdded) { + this.onOutputAdded(o); } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); + } - this.triggerSlot(0, param, null, options); + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); }; - LiteGraph.registerNodeType("events/filter", FilterEvent); - - - function EventBranch() { - this.addInput("in", LiteGraph.ACTION); - this.addInput("cond", "boolean"); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.size = [120, 60]; - this._value = false; - } - - EventBranch.title = "Branch"; - EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + /** + * remove an existing output slot + * @method removeOutput + * @param {number} slot + */ + LGraphNode.prototype.removeOutput = function(slot) { + this.disconnectOutput(slot); + this.outputs.splice(slot, 1); + for (var i = slot; i < this.outputs.length; ++i) { + if (!this.outputs[i] || !this.outputs[i].links) { + continue; + } + var links = this.outputs[i].links; + for (var j = 0; j < links.length; ++j) { + var link = this.graph.links[links[j]]; + if (!link) { + continue; + } + link.origin_slot -= 1; + } + } - EventBranch.prototype.onExecute = function() { - this._value = this.getInputData(1); - } + this.setSize( this.computeSize() ); + if (this.onOutputRemoved) { + this.onOutputRemoved(slot); + } + this.setDirtyCanvas(true, true); + }; - EventBranch.prototype.onAction = function(action, param, options) { - this._value = this.getInputData(1); - this.triggerSlot(this._value ? 0 : 1, param, null, options); - } + /** + * add a new input slot to use in this node + * @method addInput + * @param {string} name + * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 + * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) + */ + LGraphNode.prototype.addInput = function(name, type, extra_info) { + type = type || 0; + var input = { name: name, type: type, link: null }; + if (extra_info) { + for (var i in extra_info) { + input[i] = extra_info[i]; + } + } - LiteGraph.registerNodeType("events/branch", EventBranch); + if (!this.inputs) { + this.inputs = []; + } - //Show value inside the debug console - function EventCounter() { - this.addInput("inc", LiteGraph.ACTION); - this.addInput("dec", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("change", LiteGraph.EVENT); - this.addOutput("num", "number"); - this.addProperty("doCountExecution", false, "boolean", {name: "Count Executions"}); - this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"); - this.num = 0; - } + this.inputs.push(input); + this.setSize( this.computeSize() ); - EventCounter.title = "Counter"; - EventCounter.desc = "Counts events"; + if (this.onInputAdded) { + this.onInputAdded(input); + } + + LiteGraph.registerNodeAndSlotType(this,type); + + this.setDirtyCanvas(true, true); + return input; + }; + + /** + * add several new input slots in this node + * @method addInputs + * @param {Array} array of triplets like [[name,type,extra_info],[...]] + */ + LGraphNode.prototype.addInputs = function(array) { + for (var i = 0; i < array.length; ++i) { + var info = array[i]; + var o = { name: info[0], type: info[1], link: null }; + if (array[2]) { + for (var j in info[2]) { + o[j] = info[2][j]; + } + } - EventCounter.prototype.getTitle = function() { - if (this.flags.collapsed) { - return String(this.num); + if (!this.inputs) { + this.inputs = []; + } + this.inputs.push(o); + if (this.onInputAdded) { + this.onInputAdded(o); + } + + LiteGraph.registerNodeAndSlotType(this,info[1]); } - return this.title; - }; - EventCounter.prototype.onAction = function(action, param, options) { - var v = this.num; - if (action == "inc") { - this.num += 1; - } else if (action == "dec") { - this.num -= 1; - } else if (action == "reset") { - this.num = 0; - } - if (this.num != v) { - this.trigger("change", this.num); - } + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); }; - EventCounter.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - ctx.fillStyle = "#AAA"; - ctx.font = "20px Arial"; - ctx.textAlign = "center"; - ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); + /** + * remove an existing input slot + * @method removeInput + * @param {number} slot + */ + LGraphNode.prototype.removeInput = function(slot) { + this.disconnectInput(slot); + var slot_info = this.inputs.splice(slot, 1); + for (var i = slot; i < this.inputs.length; ++i) { + if (!this.inputs[i]) { + continue; + } + var link = this.graph.links[this.inputs[i].link]; + if (!link) { + continue; + } + link.target_slot -= 1; + } + this.setSize( this.computeSize() ); + if (this.onInputRemoved) { + this.onInputRemoved(slot, slot_info[0] ); + } + this.setDirtyCanvas(true, true); + }; + + /** + * add an special connection to this node (used for special kinds of graphs) + * @method addConnection + * @param {string} name + * @param {string} type string defining the input type ("vec3","number",...) + * @param {[x,y]} pos position of the connection inside the node + * @param {string} direction if is input or output + */ + LGraphNode.prototype.addConnection = function(name, type, pos, direction) { + var o = { + name: name, + type: type, + pos: pos, + direction: direction, + links: null + }; + this.connections.push(o); + return o; }; - EventCounter.prototype.onExecute = function() { - if(this.properties.doCountExecution){ - this.num += 1; + /** + * computes the minimum size of a node according to its inputs and output slots + * @method computeSize + * @param {vec2} minHeight + * @return {vec2} the total size + */ + LGraphNode.prototype.computeSize = function(out) { + if (this.constructor.size) { + return this.constructor.size.concat(); } - this.setOutputData(1, this.num); - }; - LiteGraph.registerNodeType("events/counter", EventCounter); + var rows = Math.max( + this.inputs ? this.inputs.length : 1, + this.outputs ? this.outputs.length : 1 + ); + var size = out || new Float32Array([0, 0]); + rows = Math.max(rows, 1); + var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - //Show value inside the debug console - function DelayEvent() { - this.size = [60, 30]; - this.addProperty("time_in_ms", 1000); - this.addInput("event", LiteGraph.ACTION); - this.addOutput("on_time", LiteGraph.EVENT); + var title_width = compute_text_size(this.title); + var input_width = 0; + var output_width = 0; - this._pending = []; - } + if (this.inputs) { + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input = this.inputs[i]; + var text = input.label || input.name || ""; + var text_width = compute_text_size(text); + if (input_width < text_width) { + input_width = text_width; + } + } + } - DelayEvent.title = "Delay"; - DelayEvent.desc = "Delays one event"; + if (this.outputs) { + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + var text = output.label || output.name || ""; + var text_width = compute_text_size(text); + if (output_width < text_width) { + output_width = text_width; + } + } + } - DelayEvent.prototype.onAction = function(action, param, options) { - var time = this.properties.time_in_ms; - if (time <= 0) { - this.trigger(null, param, options); - } else { - this._pending.push([time, param]); + size[0] = Math.max(input_width + output_width + 10, title_width); + size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); + if (this.widgets && this.widgets.length) { + size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); } - }; - DelayEvent.prototype.onExecute = function(param, options) { - var dt = this.graph.elapsed_time * 1000; //in ms + size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - if (this.isInputConnected(1)) { - this.properties.time_in_ms = this.getInputData(1); + var widgets_height = 0; + if (this.widgets && this.widgets.length) { + for (var i = 0, l = this.widgets.length; i < l; ++i) { + if (this.widgets[i].computeSize) + widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; + else + widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; + } + widgets_height += 8; } - for (var i = 0; i < this._pending.length; ++i) { - var actionPass = this._pending[i]; - actionPass[0] -= dt; - if (actionPass[0] > 0) { - continue; - } + //compute height using widgets height + if( this.widgets_up ) + size[1] = Math.max( size[1], widgets_height ); + else if( this.widgets_start_y != null ) + size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); + else + size[1] += widgets_height; - //remove - this._pending.splice(i, 1); - --i; + function compute_text_size(text) { + if (!text) { + return 0; + } + return font_size * text.length * 0.6; + } - //trigger - this.trigger(null, actionPass[1], options); + if ( + this.constructor.min_height && + size[1] < this.constructor.min_height + ) { + size[1] = this.constructor.min_height; } - }; - DelayEvent.prototype.onGetInputs = function() { - return [["event", LiteGraph.ACTION], ["time_in_ms", "number"]]; + size[1] += 6; //margin + + return size; }; - LiteGraph.registerNodeType("events/delay", DelayEvent); + /** + * returns all the info available about a property of this node. + * + * @method getPropertyInfo + * @param {String} property name of the property + * @return {Object} the object with all the available info + */ + LGraphNode.prototype.getPropertyInfo = function( property ) + { + var info = null; + + //there are several ways to define info about a property + //legacy mode + if (this.properties_info) { + for (var i = 0; i < this.properties_info.length; ++i) { + if (this.properties_info[i].name == property) { + info = this.properties_info[i]; + break; + } + } + } + //litescene mode using the constructor + if(this.constructor["@" + property]) + info = this.constructor["@" + property]; - //Show value inside the debug console - function TimerEvent() { - this.addProperty("interval", 1000); - this.addProperty("event", "tick"); - this.addOutput("on_tick", LiteGraph.EVENT); - this.time = 0; - this.last_interval = 1000; - this.triggered = false; - } + if(this.constructor.widgets_info && this.constructor.widgets_info[property]) + info = this.constructor.widgets_info[property]; - TimerEvent.title = "Timer"; - TimerEvent.desc = "Sends an event every N milliseconds"; + //litescene mode using the constructor + if (!info && this.onGetPropertyInfo) { + info = this.onGetPropertyInfo(property); + } - TimerEvent.prototype.onStart = function() { - this.time = 0; - }; + if (!info) + info = {}; + if(!info.type) + info.type = typeof this.properties[property]; + if(info.widget == "combo") + info.type = "enum"; - TimerEvent.prototype.getTitle = function() { - return "Timer: " + this.last_interval.toString() + "ms"; - }; + return info; + } - TimerEvent.on_color = "#AAA"; - TimerEvent.off_color = "#222"; + /** + * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties + * + * @method addWidget + * @param {String} type the widget type (could be "number","string","combo" + * @param {String} name the text to show on the widget + * @param {String} value the default value + * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) + * @param {Object} options the object that contains special properties of this widget + * @return {Object} the created widget object + */ + LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) + { + if (!this.widgets) { + this.widgets = []; + } - TimerEvent.prototype.onDrawBackground = function() { - this.boxcolor = this.triggered - ? TimerEvent.on_color - : TimerEvent.off_color; - this.triggered = false; - }; + if(!options && callback && callback.constructor === Object) + { + options = callback; + callback = null; + } - TimerEvent.prototype.onExecute = function() { - var dt = this.graph.elapsed_time * 1000; //in ms + if(options && options.constructor === String) //options can be the property name + options = { property: options }; - var trigger = this.time == 0; + if(callback && callback.constructor === String) //callback can be the property name + { + if(!options) + options = {}; + options.property = callback; + callback = null; + } - this.time += dt; - this.last_interval = Math.max( - 1, - this.getInputOrProperty("interval") | 0 - ); + if(callback && callback.constructor !== Function) + { + console.warn("addWidget: callback must be a function"); + callback = null; + } - if ( - !trigger && - (this.time < this.last_interval || isNaN(this.last_interval)) + var w = { + type: type.toLowerCase(), + name: name, + value: value, + callback: callback, + options: options || {} + }; + + if (w.options.y !== undefined) { + w.y = w.options.y; + } + + if (!callback && !w.options.callback && !w.options.property) { + console.warn("LiteGraph addWidget(...) without a callback or property assigned"); + } + if (type == "combo" && !w.options.values) { + throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; + } + this.widgets.push(w); + this.setSize( this.computeSize() ); + return w; + }; + + LGraphNode.prototype.addCustomWidget = function(custom_widget) { + if (!this.widgets) { + this.widgets = []; + } + this.widgets.push(custom_widget); + return custom_widget; + }; + + /** + * returns the bounding of the object, used for rendering purposes + * @method getBounding + * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage + * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation + * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] + */ + LGraphNode.prototype.getBounding = function(out, compute_outer) { + out = out || new Float32Array(4); + const nodePos = this.pos; + const isCollapsed = this.flags.collapsed; + const nodeSize = this.size; + + let left_offset = 0; + // 1 offset due to how nodes are rendered + let right_offset = 1 ; + let top_offset = 0; + let bottom_offset = 0; + + if (compute_outer) { + // 4 offset for collapsed node connection points + left_offset = 4; + // 6 offset for right shadow and collapsed node connection points + right_offset = 6 + left_offset; + // 4 offset for collapsed nodes top connection points + top_offset = 4; + // 5 offset for bottom shadow and collapsed node connection points + bottom_offset = 5 + top_offset; + } + + out[0] = nodePos[0] - left_offset; + out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset; + out[2] = isCollapsed ? + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset : + nodeSize[0] + right_offset; + out[3] = isCollapsed ? + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset : + nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset; + + if (this.onBounding) { + this.onBounding(out); + } + return out; + }; + + /** + * checks if a point is inside the shape of a node + * @method isPointInside + * @param {number} x + * @param {number} y + * @return {boolean} + */ + LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { + margin = margin || 0; + + var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; + if (skip_title) { + margin_top = 0; + } + if (this.flags && this.flags.collapsed) { + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if ( + isInsideRectangle( + x, + y, + this.pos[0] - margin, + this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + + 2 * margin, + LiteGraph.NODE_TITLE_HEIGHT + 2 * margin + ) + ) { + return true; + } + } else if ( + this.pos[0] - 4 - margin < x && + this.pos[0] + this.size[0] + 4 + margin > x && + this.pos[1] - margin_top - margin < y && + this.pos[1] + this.size[1] + margin > y ) { - if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { - this.setOutputData(1, false); + return true; + } + return false; + }; + + /** + * checks if a point is inside a node slot, and returns info about which slot + * @method getSlotInPosition + * @param {number} x + * @param {number} y + * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } + */ + LGraphNode.prototype.getSlotInPosition = function(x, y) { + //search for inputs + var link_pos = new Float32Array(2); + if (this.inputs) { + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input = this.inputs[i]; + this.getConnectionPos(true, i, link_pos); + if ( + isInsideRectangle( + x, + y, + link_pos[0] - 10, + link_pos[1] - 5, + 20, + 10 + ) + ) { + return { input: input, slot: i, link_pos: link_pos }; + } } - return; } - this.triggered = true; - this.time = this.time % this.last_interval; - this.trigger("on_tick", this.properties.event); - if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { - this.setOutputData(1, true); + if (this.outputs) { + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + this.getConnectionPos(false, i, link_pos); + if ( + isInsideRectangle( + x, + y, + link_pos[0] - 10, + link_pos[1] - 5, + 20, + 10 + ) + ) { + return { output: output, slot: i, link_pos: link_pos }; + } + } } - }; - TimerEvent.prototype.onGetInputs = function() { - return [["interval", "number"]]; + return null; }; - TimerEvent.prototype.onGetOutputs = function() { - return [["tick", "boolean"]]; + /** + * returns the input slot with a given name (used for dynamic slots), -1 if not found + * @method findInputSlot + * @param {string} name the name of the slot + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findInputSlot = function(name, returnObj) { + if (!this.inputs) { + return -1; + } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + if (name == this.inputs[i].name) { + return !returnObj ? i : this.inputs[i]; + } + } + return -1; }; - LiteGraph.registerNodeType("events/timer", TimerEvent); + /** + * returns the output slot with a given name (used for dynamic slots), -1 if not found + * @method findOutputSlot + * @param {string} name the name of the slot + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findOutputSlot = function(name, returnObj) { + returnObj = returnObj || false; + if (!this.outputs) { + return -1; + } + for (var i = 0, l = this.outputs.length; i < l; ++i) { + if (name == this.outputs[i].name) { + return !returnObj ? i : this.outputs[i]; + } + } + return -1; + }; + + // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options + + /** + * returns the first free input slot + * @method findInputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findInputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = {returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.inputs) { + return -1; + } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + if (this.inputs[i].link && this.inputs[i].link != null) { + continue; + } + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.inputs[i]; + } + return -1; + }; + /** + * returns the first output slot free + * @method findOutputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findOutputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.outputs) { + return -1; + } + for (var i = 0, l = this.outputs.length; i < l; ++i) { + if (this.outputs[i].links && this.outputs[i].links != null) { + continue; + } + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.outputs[i]; + } + return -1; + }; + + /** + * findSlotByType for INPUTS + */ + LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); + }; + /** + * findSlotByType for OUTPUTS + */ + LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); + }; + + /** + * returns the output (or input) slot with a given type, -1 if not found + * @method findSlotByType + * @param {boolean} input uise inputs instead of outputs + * @param {string} type the type of the slot + * @param {boolean} returnObj if the obj itself wanted + * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { + input = input || false; + returnObj = returnObj || false; + preferFreeSlot = preferFreeSlot || false; + doNotUseOccupied = doNotUseOccupied || false; + var aSlots = input ? this.inputs : this.outputs; + if (!aSlots) { + return -1; + } + // !! empty string type is considered 0, * !! + if (type == "" || type == "*") type = 0; + for (var i = 0, l = aSlots.length; i < l; ++i) { + var tFound = false; + var aSource = (type+"").toLowerCase().split(","); + var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; + aDest = (aDest+"").toLowerCase().split(","); + for(var sI=0;sI= 0 && target_slot !== null){ + //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) + return this.connect(slot, target_node, target_slot); + }else{ + //console.log("type "+target_slotType+" not found or not free?") + if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onTrigger IN SLOT + //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); + return this.connect(slot, target_node, -1); + } + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var target_slot = target_node.findInputSlotByType(0, false, true, true); + //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); + if (target_slot >= 0){ + return this.connect(slot, target_node, target_slot); + } + } + // connect to the first free input slot if not found a specific type and this output is general + if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ + var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); + //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); + if (target_slot >= 0){ + return this.connect(slot, target_node, target_slot); + } + } + + console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); + //TODO filter + + return null; + } + } + + /** + * connect this node input to the output of another node BY TYPE + * @method connectByType + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {string} target_type the output slot type of the target node + * @return {Object} the link_info is created, otherwise null + */ + LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { createEventInCase: true + ,firstFreeIfInputGeneralInCase: true + ,generalTypeInCase: true + }; + var opts = Object.assign(optsDef,optsIn); + if (source_node && source_node.constructor === Number) { + source_node = this.graph.getNodeById(source_node); + } + var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); + if (source_slot >= 0 && source_slot !== null){ + //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) + return source_node.connect(source_slot, this, slot); + }else{ + + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var source_slot = source_node.findOutputSlotByType(0, false, true, true); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onExecuted OUT SLOT + if (LiteGraph.do_add_triggers_slots){ + var source_slot = source_node.addOnExecutedOutput(); + return source_node.connect(source_slot, this, slot); + } + } + // connect to the first free output slot if not found a specific type and this input is general + if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ + var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); + //TODO filter + + //console.log("type OUT! "+source_slotType+" not found or not free?") + return null; + } } + + /** + * connect this node output to the input of another node + * @method connect + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) + * @return {Object} the link_info is created, otherwise null + */ + LGraphNode.prototype.connect = function(slot, target_node, target_slot) { + target_slot = target_slot || 0; + + if (!this.graph) { + //could be connected before adding it to a graph + console.log( + "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." + ); //due to link ids being associated with graphs + return null; + } - SemaphoreEvent.title = "Semaphore Event"; - SemaphoreEvent.desc = "Until both events are not triggered, it doesnt continue."; + //seek for the output slot + if (slot.constructor === String) { + slot = this.findOutputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return null; + } + } else if (!this.outputs || slot >= this.outputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return null; + } - SemaphoreEvent.prototype.onExecute = function() - { - this.setOutputData(1,this._ready); - this.boxcolor = this._ready ? "#9F9" : "#FA5"; - } + if (target_node && target_node.constructor === Number) { + target_node = this.graph.getNodeById(target_node); + } + if (!target_node) { + throw "target node is null"; + } - SemaphoreEvent.prototype.onAction = function(action, param) { - if( action == "go" ) - this.triggerSlot( this._ready ? 0 : 1 ); - else if( action == "green" ) - this._ready = true; - else if( action == "red" ) - this._ready = false; - }; + //avoid loopback + if (target_node == this) { + return null; + } - LiteGraph.registerNodeType("events/semaphore", SemaphoreEvent); + //you can specify the slot by name + if (target_slot.constructor === String) { + target_slot = target_node.findInputSlot(target_slot); + if (target_slot == -1) { + if (LiteGraph.debug) { + console.log( + "Connect: Error, no slot of name " + target_slot + ); + } + return null; + } + } else if (target_slot === LiteGraph.EVENT) { + + if (LiteGraph.do_add_triggers_slots){ + //search for first slot with event? :: NO this is done outside + //console.log("Connect: Creating triggerEvent"); + // force mode + target_node.changeMode(LiteGraph.ON_TRIGGER); + target_slot = target_node.findInputSlot("onTrigger"); + }else{ + return null; // -- break -- + } + } else if ( + !target_node.inputs || + target_slot >= target_node.inputs.length + ) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return null; + } - function OnceEvent() { - this.addInput("in", LiteGraph.ACTION ); - this.addInput("reset", LiteGraph.ACTION ); - this.addOutput("out", LiteGraph.EVENT ); - this._once = false; - this.properties = {}; - var that = this; - this.addWidget("button","reset","",function(){ - that._once = false; - }); - } + var changed = false; - OnceEvent.title = "Once"; - OnceEvent.desc = "Only passes an event once, then gets locked"; + var input = target_node.inputs[target_slot]; + var link_info = null; + var output = this.outputs[slot]; + + if (!this.outputs[slot]){ + /*console.debug("Invalid slot passed: "+slot); + console.debug(this.outputs);*/ + return null; + } - OnceEvent.prototype.onAction = function(action, param) { - if( action == "in" && !this._once ) + // allow target node to change slot + if (target_node.onBeforeConnectInput) { + // This way node can choose another slot (or make a new one?) + target_slot = target_node.onBeforeConnectInput(target_slot); //callback + } + + //check target_slot and check connection types + if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) { - this._once = true; - this.triggerSlot( 0, param ); + this.setDirtyCanvas(false, true); + if(changed) + this.graph.connectionChange(this, link_info); + return null; + }else{ + //console.debug("valid connection",output.type, input.type); } - else if( action == "reset" ) - this._once = false; - }; - LiteGraph.registerNodeType("events/once", OnceEvent); + //allows nodes to block connection, callback + if (target_node.onConnectInput) { + if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { + return null; + } + } + if (this.onConnectOutput) { // callback + if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { + return null; + } + } - function DataStore() { - this.addInput("data", 0); - this.addInput("assign", LiteGraph.ACTION); - this.addOutput("data", 0); - this._last_value = null; - this.properties = { data: null, serialize: true }; - var that = this; - this.addWidget("button","store","",function(){ - that.properties.data = that._last_value; - }); - } + //if there is something already plugged there, disconnect + if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { + this.graph.beforeChange(); + target_node.disconnectInput(target_slot, {doProcessChange: false}); + changed = true; + } + if (output.links !== null && output.links.length){ + switch(output.type){ + case LiteGraph.EVENT: + if (!LiteGraph.allow_multi_output_for_events){ + this.graph.beforeChange(); + this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); + changed = true; + } + break; + default: + break; + } + } - DataStore.title = "Data Store"; - DataStore.desc = "Stores data and only changes when event is received"; + var nextId + if (LiteGraph.use_uuids) + nextId = LiteGraph.uuidv4(); + else + nextId = ++this.graph.last_link_id; + + //create link class + link_info = new LLink( + nextId, + input.type || output.type, + this.id, + slot, + target_node.id, + target_slot + ); + + //add to graph links list + this.graph.links[link_info.id] = link_info; + + //connect in output + if (output.links == null) { + output.links = []; + } + output.links.push(link_info.id); + //connect in input + target_node.inputs[target_slot].link = link_info.id; + if (this.graph) { + this.graph._version++; + } + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + true, + link_info, + output + ); + } //link_info has been created now, so its updated + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + target_slot, + true, + link_info, + input + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + target_slot, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot, + target_node, + target_slot + ); + } - DataStore.prototype.onExecute = function() - { - this._last_value = this.getInputData(0); - this.setOutputData(0, this.properties.data ); - } + this.setDirtyCanvas(false, true); + this.graph.afterChange(); + this.graph.connectionChange(this, link_info); + + return link_info; + }; + + /** + * disconnect one output to an specific node + * @method disconnectOutput + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] + * @return {boolean} if it was disconnected successfully + */ + LGraphNode.prototype.disconnectOutput = function(slot, target_node) { + if (slot.constructor === String) { + slot = this.findOutputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return false; + } + } else if (!this.outputs || slot >= this.outputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return false; + } - DataStore.prototype.onAction = function(action, param, options) { - this.properties.data = this._last_value; - }; + //get output slot + var output = this.outputs[slot]; + if (!output || !output.links || output.links.length == 0) { + return false; + } - DataStore.prototype.onSerialize = function(o) - { - if(o.data == null) - return; - if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) - o.data = null; - } + //one of the output links in this slot + if (target_node) { + if (target_node.constructor === Number) { + target_node = this.graph.getNodeById(target_node); + } + if (!target_node) { + throw "Target Node not found"; + } - LiteGraph.registerNodeType("basic/data_store", DataStore); + for (var i = 0, l = output.links.length; i < l; i++) { + var link_id = output.links[i]; + var link_info = this.graph.links[link_id]; + + //is the link we are searching for... + if (link_info.target_id == target_node.id) { + output.links.splice(i, 1); //remove here + var input = target_node.inputs[link_info.target_slot]; + input.link = null; //remove there + delete this.graph.links[link_id]; //remove the link from the links pool + if (this.graph) { + this.graph._version++; + } + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + link_info.target_slot, + false, + link_info, + input + ); + } //link_info hasn't been modified so its ok + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + break; + } + } + } //all the links in this output slot + else { + for (var i = 0, l = output.links.length; i < l; i++) { + var link_id = output.links[i]; + var link_info = this.graph.links[link_id]; + if (!link_info) { + //bug: it happens sometimes + continue; + } + var target_node = this.graph.getNodeById(link_info.target_id); + var input = null; + if (this.graph) { + this.graph._version++; + } + if (target_node) { + input = target_node.inputs[link_info.target_slot]; + input.link = null; //remove other side link + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + link_info.target_slot, + false, + link_info, + input + ); + } //link_info hasn't been modified so its ok + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + } + delete this.graph.links[link_id]; //remove the link from the links pool + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + } + output.links = null; + } + this.setDirtyCanvas(false, true); + this.graph.connectionChange(this); + return true; + }; -})(this); - -//widgets -(function(global) { - var LiteGraph = global.LiteGraph; - - /* Button ****************/ - - function WidgetButton() { - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", "boolean"); - this.addProperty("text", "click me"); - this.addProperty("font_size", 30); - this.addProperty("message", ""); - this.size = [164, 84]; - this.clicked = false; - } - - WidgetButton.title = "Button"; - WidgetButton.desc = "Triggers an event"; - - WidgetButton.font = "Arial"; - WidgetButton.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - var margin = 10; - ctx.fillStyle = "black"; - ctx.fillRect( - margin + 1, - margin + 1, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - ctx.fillStyle = "#AAF"; - ctx.fillRect( - margin - 1, - margin - 1, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - ctx.fillStyle = this.clicked - ? "white" - : this.mouseOver - ? "#668" - : "#334"; - ctx.fillRect( - margin, - margin, - this.size[0] - margin * 2, - this.size[1] - margin * 2 - ); - - if (this.properties.text || this.properties.text === 0) { - var font_size = this.properties.font_size || 30; - ctx.textAlign = "center"; - ctx.fillStyle = this.clicked ? "black" : "white"; - ctx.font = font_size + "px " + WidgetButton.font; - ctx.fillText( - this.properties.text, - this.size[0] * 0.5, - this.size[1] * 0.5 + font_size * 0.3 - ); - ctx.textAlign = "left"; - } - }; - - WidgetButton.prototype.onMouseDown = function(e, local_pos) { - if ( - local_pos[0] > 1 && - local_pos[1] > 1 && - local_pos[0] < this.size[0] - 2 && - local_pos[1] < this.size[1] - 2 - ) { - this.clicked = true; - this.setOutputData(1, this.clicked); - this.triggerSlot(0, this.properties.message); - return true; - } - }; - - WidgetButton.prototype.onExecute = function() { - this.setOutputData(1, this.clicked); - }; - - WidgetButton.prototype.onMouseUp = function(e) { - this.clicked = false; - }; - - LiteGraph.registerNodeType("widget/button", WidgetButton); - - function WidgetToggle() { - this.addInput("", "boolean"); - this.addInput("e", LiteGraph.ACTION); - this.addOutput("v", "boolean"); - this.addOutput("e", LiteGraph.EVENT); - this.properties = { font: "", value: false }; - this.size = [160, 44]; - } - - WidgetToggle.title = "Toggle"; - WidgetToggle.desc = "Toggles between true or false"; - - WidgetToggle.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - var size = this.size[1] * 0.5; - var margin = 0.25; - var h = this.size[1] * 0.8; - ctx.font = this.properties.font || (size * 0.8).toFixed(0) + "px Arial"; - var w = ctx.measureText(this.title).width; - var x = (this.size[0] - (w + size)) * 0.5; - - ctx.fillStyle = "#AAA"; - ctx.fillRect(x, h - size, size, size); - - ctx.fillStyle = this.properties.value ? "#AEF" : "#000"; - ctx.fillRect( - x + size * margin, - h - size + size * margin, - size * (1 - margin * 2), - size * (1 - margin * 2) - ); - - ctx.textAlign = "left"; - ctx.fillStyle = "#AAA"; - ctx.fillText(this.title, size * 1.2 + x, h * 0.85); - ctx.textAlign = "left"; - }; - - WidgetToggle.prototype.onAction = function(action) { - this.properties.value = !this.properties.value; - this.trigger("e", this.properties.value); - }; - - WidgetToggle.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != null) { - this.properties.value = v; - } - this.setOutputData(0, this.properties.value); - }; - - WidgetToggle.prototype.onMouseDown = function(e, local_pos) { - if ( - local_pos[0] > 1 && - local_pos[1] > 1 && - local_pos[0] < this.size[0] - 2 && - local_pos[1] < this.size[1] - 2 - ) { - this.properties.value = !this.properties.value; - this.graph._version++; - this.trigger("e", this.properties.value); - return true; - } - }; - - LiteGraph.registerNodeType("widget/toggle", WidgetToggle); - - /* Number ****************/ - - function WidgetNumber() { - this.addOutput("", "number"); - this.size = [80, 60]; - this.properties = { min: -1000, max: 1000, value: 1, step: 1 }; - this.old_y = -1; - this._remainder = 0; - this._precision = 0; - this.mouse_captured = false; - } - - WidgetNumber.title = "Number"; - WidgetNumber.desc = "Widget to select number value"; - - WidgetNumber.pixels_threshold = 10; - WidgetNumber.markers_color = "#666"; - - WidgetNumber.prototype.onDrawForeground = function(ctx) { - var x = this.size[0] * 0.5; - var h = this.size[1]; - if (h > 30) { - ctx.fillStyle = WidgetNumber.markers_color; - ctx.beginPath(); - ctx.moveTo(x, h * 0.1); - ctx.lineTo(x + h * 0.1, h * 0.2); - ctx.lineTo(x + h * -0.1, h * 0.2); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(x, h * 0.9); - ctx.lineTo(x + h * 0.1, h * 0.8); - ctx.lineTo(x + h * -0.1, h * 0.8); - ctx.fill(); - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - } else { - ctx.font = (h * 0.8).toFixed(1) + "px Arial"; - } - - ctx.textAlign = "center"; - ctx.font = (h * 0.7).toFixed(1) + "px Arial"; - ctx.fillStyle = "#EEE"; - ctx.fillText( - this.properties.value.toFixed(this._precision), - x, - h * 0.75 - ); - }; - - WidgetNumber.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - - WidgetNumber.prototype.onPropertyChanged = function(name, value) { - var t = (this.properties.step + "").split("."); - this._precision = t.length > 1 ? t[1].length : 0; - }; - - WidgetNumber.prototype.onMouseDown = function(e, pos) { - if (pos[1] < 0) { - return; - } - - this.old_y = e.canvasY; - this.captureInput(true); - this.mouse_captured = true; - - return true; - }; - - WidgetNumber.prototype.onMouseMove = function(e) { - if (!this.mouse_captured) { - return; - } - - var delta = this.old_y - e.canvasY; - if (e.shiftKey) { - delta *= 10; - } - if (e.metaKey || e.altKey) { - delta *= 0.1; - } - this.old_y = e.canvasY; - - var steps = this._remainder + delta / WidgetNumber.pixels_threshold; - this._remainder = steps % 1; - steps = steps | 0; - - var v = clamp( - this.properties.value + steps * this.properties.step, - this.properties.min, - this.properties.max - ); - this.properties.value = v; - this.graph._version++; - this.setDirtyCanvas(true); - }; - - WidgetNumber.prototype.onMouseUp = function(e, pos) { - if (e.click_time < 200) { - var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1; - this.properties.value = clamp( - this.properties.value + steps * this.properties.step, - this.properties.min, - this.properties.max - ); - this.graph._version++; - this.setDirtyCanvas(true); - } - - if (this.mouse_captured) { - this.mouse_captured = false; - this.captureInput(false); - } - }; - - LiteGraph.registerNodeType("widget/number", WidgetNumber); - - - /* Combo ****************/ - - function WidgetCombo() { - this.addOutput("", "string"); - this.addOutput("change", LiteGraph.EVENT); - this.size = [80, 60]; - this.properties = { value: "A", values:"A;B;C" }; - this.old_y = -1; - this.mouse_captured = false; - this._values = this.properties.values.split(";"); - var that = this; - this.widgets_up = true; - this.widget = this.addWidget("combo","", this.properties.value, function(v){ - that.properties.value = v; - that.triggerSlot(1, v); - }, { property: "value", values: this._values } ); - } - - WidgetCombo.title = "Combo"; - WidgetCombo.desc = "Widget to select from a list"; - - WidgetCombo.prototype.onExecute = function() { - this.setOutputData( 0, this.properties.value ); - }; - - WidgetCombo.prototype.onPropertyChanged = function(name, value) { - if(name == "values") - { - this._values = value.split(";"); - this.widget.options.values = this._values; - } - else if(name == "value") - { - this.widget.value = value; - } - }; - - LiteGraph.registerNodeType("widget/combo", WidgetCombo); - - - /* Knob ****************/ - - function WidgetKnob() { - this.addOutput("", "number"); - this.size = [64, 84]; - this.properties = { - min: 0, - max: 1, - value: 0.5, - color: "#7AF", - precision: 2 - }; - this.value = -1; - } - - WidgetKnob.title = "Knob"; - WidgetKnob.desc = "Circular controller"; - WidgetKnob.size = [80, 100]; - - WidgetKnob.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - if (this.value == -1) { - this.value = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - } - - var center_x = this.size[0] * 0.5; - var center_y = this.size[1] * 0.5; - var radius = Math.min(this.size[0], this.size[1]) * 0.5 - 5; - var w = Math.floor(radius * 0.05); - - ctx.globalAlpha = 1; - ctx.save(); - ctx.translate(center_x, center_y); - ctx.rotate(Math.PI * 0.75); - - //bg - ctx.fillStyle = "rgba(0,0,0,0.5)"; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.arc(0, 0, radius, 0, Math.PI * 1.5); - ctx.fill(); - - //value - ctx.strokeStyle = "black"; - ctx.fillStyle = this.properties.color; - ctx.lineWidth = 2; - ctx.beginPath(); - ctx.moveTo(0, 0); - ctx.arc( - 0, - 0, - radius - 4, - 0, - Math.PI * 1.5 * Math.max(0.01, this.value) - ); - ctx.closePath(); - ctx.fill(); - //ctx.stroke(); - ctx.lineWidth = 1; - ctx.globalAlpha = 1; - ctx.restore(); - - //inner - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc(center_x, center_y, radius * 0.75, 0, Math.PI * 2, true); - ctx.fill(); - - //miniball - ctx.fillStyle = this.mouseOver ? "white" : this.properties.color; - ctx.beginPath(); - var angle = this.value * Math.PI * 1.5 + Math.PI * 0.75; - ctx.arc( - center_x + Math.cos(angle) * radius * 0.65, - center_y + Math.sin(angle) * radius * 0.65, - radius * 0.05, - 0, - Math.PI * 2, - true - ); - ctx.fill(); - - //text - ctx.fillStyle = this.mouseOver ? "white" : "#AAA"; - ctx.font = Math.floor(radius * 0.5) + "px Arial"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.value.toFixed(this.properties.precision), - center_x, - center_y + radius * 0.15 - ); - }; - - WidgetKnob.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - this.boxcolor = LiteGraph.colorToString([ - this.value, - this.value, - this.value - ]); - }; - - WidgetKnob.prototype.onMouseDown = function(e) { - this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; - this.radius = this.size[0] * 0.5; - if ( - e.canvasY - this.pos[1] < 20 || - LiteGraph.distance( - [e.canvasX, e.canvasY], - [this.pos[0] + this.center[0], this.pos[1] + this.center[1]] - ) > this.radius - ) { - return false; - } - this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - this.captureInput(true); - return true; - }; - - WidgetKnob.prototype.onMouseMove = function(e) { - if (!this.oldmouse) { - return; - } - - var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - - var v = this.value; - v -= (m[1] - this.oldmouse[1]) * 0.01; - if (v > 1.0) { - v = 1.0; - } else if (v < 0.0) { - v = 0.0; - } - this.value = v; - this.properties.value = - this.properties.min + - (this.properties.max - this.properties.min) * this.value; - this.oldmouse = m; - this.setDirtyCanvas(true); - }; - - WidgetKnob.prototype.onMouseUp = function(e) { - if (this.oldmouse) { - this.oldmouse = null; - this.captureInput(false); - } - }; - - WidgetKnob.prototype.onPropertyChanged = function(name, value) { - if (name == "min" || name == "max" || name == "value") { - this.properties[name] = parseFloat(value); - return true; //block - } - }; - - LiteGraph.registerNodeType("widget/knob", WidgetKnob); - - //Show value inside the debug console - function WidgetSliderGUI() { - this.addOutput("", "number"); - this.properties = { - value: 0.5, - min: 0, - max: 1, - text: "V" - }; - var that = this; - this.size = [140, 40]; - this.slider = this.addWidget( - "slider", - "V", - this.properties.value, - function(v) { - that.properties.value = v; - }, - this.properties - ); - this.widgets_up = true; - } - - WidgetSliderGUI.title = "Inner Slider"; - - WidgetSliderGUI.prototype.onPropertyChanged = function(name, value) { - if (name == "value") { - this.slider.value = value; - } - }; - - WidgetSliderGUI.prototype.onExecute = function() { - this.setOutputData(0, this.properties.value); - }; - - LiteGraph.registerNodeType("widget/internal_slider", WidgetSliderGUI); - - //Widget H SLIDER - function WidgetHSlider() { - this.size = [160, 26]; - this.addOutput("", "number"); - this.properties = { color: "#7AF", min: 0, max: 1, value: 0.5 }; - this.value = -1; - } - - WidgetHSlider.title = "H.Slider"; - WidgetHSlider.desc = "Linear slider controller"; - - WidgetHSlider.prototype.onDrawForeground = function(ctx) { - if (this.value == -1) { - this.value = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - } - - //border - ctx.globalAlpha = 1; - ctx.lineWidth = 1; - ctx.fillStyle = "#000"; - ctx.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4); - - ctx.fillStyle = this.properties.color; - ctx.beginPath(); - ctx.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8); - ctx.fill(); - }; - - WidgetHSlider.prototype.onExecute = function() { - this.properties.value = - this.properties.min + - (this.properties.max - this.properties.min) * this.value; - this.setOutputData(0, this.properties.value); - this.boxcolor = LiteGraph.colorToString([ - this.value, - this.value, - this.value - ]); - }; - - WidgetHSlider.prototype.onMouseDown = function(e) { - if (e.canvasY - this.pos[1] < 0) { - return false; - } - - this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - this.captureInput(true); - return true; - }; - - WidgetHSlider.prototype.onMouseMove = function(e) { - if (!this.oldmouse) { - return; - } - - var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; - - var v = this.value; - var delta = m[0] - this.oldmouse[0]; - v += delta / this.size[0]; - if (v > 1.0) { - v = 1.0; - } else if (v < 0.0) { - v = 0.0; - } - - this.value = v; - - this.oldmouse = m; - this.setDirtyCanvas(true); - }; - - WidgetHSlider.prototype.onMouseUp = function(e) { - this.oldmouse = null; - this.captureInput(false); - }; - - WidgetHSlider.prototype.onMouseLeave = function(e) { - //this.oldmouse = null; - }; - - LiteGraph.registerNodeType("widget/hslider", WidgetHSlider); - - function WidgetProgress() { - this.size = [160, 26]; - this.addInput("", "number"); - this.properties = { min: 0, max: 1, value: 0, color: "#AAF" }; - } - - WidgetProgress.title = "Progress"; - WidgetProgress.desc = "Shows data in linear progress"; - - WidgetProgress.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != undefined) { - this.properties["value"] = v; - } - }; - - WidgetProgress.prototype.onDrawForeground = function(ctx) { - //border - ctx.lineWidth = 1; - ctx.fillStyle = this.properties.color; - var v = - (this.properties.value - this.properties.min) / - (this.properties.max - this.properties.min); - v = Math.min(1, v); - v = Math.max(0, v); - ctx.fillRect(2, 2, (this.size[0] - 4) * v, this.size[1] - 4); - }; - - LiteGraph.registerNodeType("widget/progress", WidgetProgress); - - function WidgetText() { - this.addInputs("", 0); - this.properties = { - value: "...", - font: "Arial", - fontsize: 18, - color: "#AAA", - align: "left", - glowSize: 0, - decimals: 1 - }; - } - - WidgetText.title = "Text"; - WidgetText.desc = "Shows the input value"; - WidgetText.widgets = [ - { name: "resize", text: "Resize box", type: "button" }, - { name: "led_text", text: "LED", type: "minibutton" }, - { name: "normal_text", text: "Normal", type: "minibutton" } - ]; - - WidgetText.prototype.onDrawForeground = function(ctx) { - //ctx.fillStyle="#000"; - //ctx.fillRect(0,0,100,60); - ctx.fillStyle = this.properties["color"]; - var v = this.properties["value"]; - - if (this.properties["glowSize"]) { - ctx.shadowColor = this.properties.color; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["glowSize"]; - } else { - ctx.shadowColor = "transparent"; - } - - var fontsize = this.properties["fontsize"]; - - ctx.textAlign = this.properties["align"]; - ctx.font = fontsize.toString() + "px " + this.properties["font"]; - this.str = - typeof v == "number" ? v.toFixed(this.properties["decimals"]) : v; - - if (typeof this.str == "string") { - var lines = this.str.replace(/[\r\n]/g, "\\n").split("\\n"); - for (var i=0; i < lines.length; i++) { - ctx.fillText( - lines[i], - this.properties["align"] == "left" ? 15 : this.size[0] - 15, - fontsize * -0.15 + fontsize * (parseInt(i) + 1) - ); - } - } - - ctx.shadowColor = "transparent"; - this.last_ctx = ctx; - ctx.textAlign = "left"; - }; - - WidgetText.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v != null) { - this.properties["value"] = v; - } - //this.setDirtyCanvas(true); - }; - - WidgetText.prototype.resize = function() { - if (!this.last_ctx) { - return; - } - - var lines = this.str.split("\\n"); - this.last_ctx.font = - this.properties["fontsize"] + "px " + this.properties["font"]; - var max = 0; - for (var i=0; i < lines.length; i++) { - var w = this.last_ctx.measureText(lines[i]).width; - if (max < w) { - max = w; - } - } - this.size[0] = max + 20; - this.size[1] = 4 + lines.length * this.properties["fontsize"]; - - this.setDirtyCanvas(true); - }; - - WidgetText.prototype.onPropertyChanged = function(name, value) { - this.properties[name] = value; - this.str = typeof value == "number" ? value.toFixed(3) : value; - //this.resize(); - return true; - }; - - LiteGraph.registerNodeType("widget/text", WidgetText); - - function WidgetPanel() { - this.size = [200, 100]; - this.properties = { - borderColor: "#ffffff", - bgcolorTop: "#f0f0f0", - bgcolorBottom: "#e0e0e0", - shadowSize: 2, - borderRadius: 3 - }; - } - - WidgetPanel.title = "Panel"; - WidgetPanel.desc = "Non interactive panel"; - WidgetPanel.widgets = [{ name: "update", text: "Update", type: "button" }]; - - WidgetPanel.prototype.createGradient = function(ctx) { - if ( - this.properties["bgcolorTop"] == "" || - this.properties["bgcolorBottom"] == "" - ) { - this.lineargradient = 0; - return; - } - - this.lineargradient = ctx.createLinearGradient(0, 0, 0, this.size[1]); - this.lineargradient.addColorStop(0, this.properties["bgcolorTop"]); - this.lineargradient.addColorStop(1, this.properties["bgcolorBottom"]); - }; - - WidgetPanel.prototype.onDrawForeground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - if (this.lineargradient == null) { - this.createGradient(ctx); - } - - if (!this.lineargradient) { - return; - } - - ctx.lineWidth = 1; - ctx.strokeStyle = this.properties["borderColor"]; - //ctx.fillStyle = "#ebebeb"; - ctx.fillStyle = this.lineargradient; - - if (this.properties["shadowSize"]) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = this.properties["shadowSize"]; - } else { - ctx.shadowColor = "transparent"; - } - - ctx.roundRect( - 0, - 0, - this.size[0] - 1, - this.size[1] - 1, - this.properties["shadowSize"] - ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.stroke(); - }; - - LiteGraph.registerNodeType("widget/panel", WidgetPanel); -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; + /** + * disconnect one input + * @method disconnectInput + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @return {boolean} if it was disconnected successfully + */ + LGraphNode.prototype.disconnectInput = function(slot) { + //seek for the output slot + if (slot.constructor === String) { + slot = this.findInputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return false; + } + } else if (!this.inputs || slot >= this.inputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return false; + } - function GamepadInput() { - this.addOutput("left_x_axis", "number"); - this.addOutput("left_y_axis", "number"); - this.addOutput("button_pressed", LiteGraph.EVENT); - this.properties = { gamepad_index: 0, threshold: 0.1 }; + var input = this.inputs[slot]; + if (!input) { + return false; + } - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); - } + var link_id = this.inputs[slot].link; + if(link_id != null) + { + this.inputs[slot].link = null; + + //remove other side + var link_info = this.graph.links[link_id]; + if (link_info) { + var target_node = this.graph.getNodeById(link_info.origin_id); + if (!target_node) { + return false; + } - GamepadInput.title = "Gamepad"; - GamepadInput.desc = "gets the input of the gamepad"; + var output = target_node.outputs[link_info.origin_slot]; + if (!output || !output.links || output.links.length == 0) { + return false; + } - GamepadInput.CENTER = 0; - GamepadInput.LEFT = 1; - GamepadInput.RIGHT = 2; - GamepadInput.UP = 4; - GamepadInput.DOWN = 8; + //search in the inputs list for this link + for (var i = 0, l = output.links.length; i < l; i++) { + if (output.links[i] == link_id) { + output.links.splice(i, 1); + break; + } + } - GamepadInput.zero = new Float32Array(2); - GamepadInput.buttons = [ - "a", - "b", - "x", - "y", - "lb", - "rb", - "lt", - "rt", - "back", - "start", - "ls", - "rs", - "home" - ]; + delete this.graph.links[link_id]; //remove from the pool + if (this.graph) { + this.graph._version++; + } + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.INPUT, + slot, + false, + link_info, + input + ); + } + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.OUTPUT, + i, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + target_node, + i + ); + this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); + } + } + } //link != null - GamepadInput.prototype.onExecute = function() { - //get gamepad - var gamepad = this.getGamepad(); - var threshold = this.properties.threshold || 0.0; + this.setDirtyCanvas(false, true); + if(this.graph) + this.graph.connectionChange(this); + return true; + }; - if (gamepad) { - this._left_axis[0] = - Math.abs(gamepad.xbox.axes["lx"]) > threshold - ? gamepad.xbox.axes["lx"] - : 0; - this._left_axis[1] = - Math.abs(gamepad.xbox.axes["ly"]) > threshold - ? gamepad.xbox.axes["ly"] - : 0; - this._right_axis[0] = - Math.abs(gamepad.xbox.axes["rx"]) > threshold - ? gamepad.xbox.axes["rx"] - : 0; - this._right_axis[1] = - Math.abs(gamepad.xbox.axes["ry"]) > threshold - ? gamepad.xbox.axes["ry"] - : 0; - this._triggers[0] = - Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold - ? gamepad.xbox.axes["ltrigger"] - : 0; - this._triggers[1] = - Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold - ? gamepad.xbox.axes["rtrigger"] - : 0; + /** + * returns the center of a connection point in canvas coords + * @method getConnectionPos + * @param {boolean} is_input true if if a input slot, false if it is an output + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {vec2} out [optional] a place to store the output, to free garbage + * @return {[x,y]} the position + **/ + LGraphNode.prototype.getConnectionPos = function( + is_input, + slot_number, + out + ) { + out = out || new Float32Array(2); + var num_slots = 0; + if (is_input && this.inputs) { + num_slots = this.inputs.length; + } + if (!is_input && this.outputs) { + num_slots = this.outputs.length; } - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - var v = null; + var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - if (gamepad) { - switch (output.name) { - case "left_axis": - v = this._left_axis; - break; - case "right_axis": - v = this._right_axis; - break; - case "left_x_axis": - v = this._left_axis[0]; - break; - case "left_y_axis": - v = this._left_axis[1]; - break; - case "right_x_axis": - v = this._right_axis[0]; - break; - case "right_y_axis": - v = this._right_axis[1]; - break; - case "trigger_left": - v = this._triggers[0]; - break; - case "trigger_right": - v = this._triggers[1]; - break; - case "a_button": - v = gamepad.xbox.buttons["a"] ? 1 : 0; - break; - case "b_button": - v = gamepad.xbox.buttons["b"] ? 1 : 0; - break; - case "x_button": - v = gamepad.xbox.buttons["x"] ? 1 : 0; - break; - case "y_button": - v = gamepad.xbox.buttons["y"] ? 1 : 0; - break; - case "lb_button": - v = gamepad.xbox.buttons["lb"] ? 1 : 0; - break; - case "rb_button": - v = gamepad.xbox.buttons["rb"] ? 1 : 0; - break; - case "ls_button": - v = gamepad.xbox.buttons["ls"] ? 1 : 0; - break; - case "rs_button": - v = gamepad.xbox.buttons["rs"] ? 1 : 0; - break; - case "hat_left": - v = gamepad.xbox.hatmap & GamepadInput.LEFT; - break; - case "hat_right": - v = gamepad.xbox.hatmap & GamepadInput.RIGHT; - break; - case "hat_up": - v = gamepad.xbox.hatmap & GamepadInput.UP; - break; - case "hat_down": - v = gamepad.xbox.hatmap & GamepadInput.DOWN; - break; - case "hat": - v = gamepad.xbox.hatmap; - break; - case "start_button": - v = gamepad.xbox.buttons["start"] ? 1 : 0; - break; - case "back_button": - v = gamepad.xbox.buttons["back"] ? 1 : 0; - break; - case "button_pressed": - for ( - var j = 0; - j < this._current_buttons.length; - ++j - ) { - if ( - this._current_buttons[j] && - !this._previous_buttons[j] - ) { - this.triggerSlot( - i, - GamepadInput.buttons[j] - ); - } - } - break; - default: - break; - } + // Check for separate input/output orientation, fallback to general horizontal property + var is_horizontal = false; + if (is_input) { + is_horizontal = this.inputs_horizontal !== undefined && this.inputs_horizontal !== null ? this.inputs_horizontal : this.horizontal; + } else { + is_horizontal = this.outputs_horizontal !== undefined && this.outputs_horizontal !== null ? this.outputs_horizontal : this.horizontal; + } + + if (this.flags.collapsed) { + var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; + if (is_horizontal) { + out[0] = this.pos[0] + w * 0.5; + if (is_input) { + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; } else { - //if no gamepad is connected, output 0 - switch (output.name) { - case "button_pressed": - break; - case "left_axis": - case "right_axis": - v = GamepadInput.zero; - break; - default: - v = 0; - } + out[1] = this.pos[1]; } - this.setOutputData(i, v); + } else { + if (is_input) { + out[0] = this.pos[0]; + } else { + out[0] = this.pos[0] + w; + } + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; + } + return out; + } + + //weird feature that never got finished + if (is_input && slot_number == -1) { + out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; + out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; + return out; + } + + //hard-coded pos + if ( + is_input && + num_slots > slot_number && + this.inputs[slot_number].pos + ) { + out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; + out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; + return out; + } else if ( + !is_input && + num_slots > slot_number && + this.outputs[slot_number].pos + ) { + out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; + out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; + return out; + } + + //horizontal distributed slots + if (is_horizontal) { + out[0] = + this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); + if (is_input) { + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; + } else { + out[1] = this.pos[1] + this.size[1]; } + return out; + } + + //default vertical slots + if (is_input) { + out[0] = this.pos[0] + offset; + } else { + out[0] = this.pos[0] + this.size[0] + 1 - offset; } + out[1] = + this.pos[1] + + (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + + (this.constructor.slot_start_y || 0); + return out; }; - GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; - GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; + /* Force align to grid */ + LGraphNode.prototype.alignToGrid = function() { + this.pos[0] = + LiteGraph.CANVAS_GRID_SIZE * + Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); + this.pos[1] = + LiteGraph.CANVAS_GRID_SIZE * + Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); + }; - GamepadInput.prototype.getGamepad = function() { - var getGamepads = - navigator.getGamepads || - navigator.webkitGetGamepads || - navigator.mozGetGamepads; - if (!getGamepads) { - return null; + /* Console output */ + LGraphNode.prototype.trace = function(msg) { + if (!this.console) { + this.console = []; } - var gamepads = getGamepads.call(navigator); - var gamepad = null; - this._previous_buttons.set(this._current_buttons); + this.console.push(msg); + if (this.console.length > LGraphNode.MAX_CONSOLE) { + this.console.shift(); + } - //pick the first connected - for (var i = this.properties.gamepad_index; i < 4; i++) { - if (!gamepads[i]) { + if(this.graph.onNodeTrace) + this.graph.onNodeTrace(this, msg); + }; + + /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ + LGraphNode.prototype.setDirtyCanvas = function( + dirty_foreground, + dirty_background + ) { + if (!this.graph) { + return; + } + this.graph.sendActionToCanvas("setDirty", [ + dirty_foreground, + dirty_background + ]); + }; + + LGraphNode.prototype.loadImage = function(url) { + var img = new Image(); + img.src = LiteGraph.node_images_path + url; + img.ready = false; + + var that = this; + img.onload = function() { + this.ready = true; + that.setDirtyCanvas(true); + }; + return img; + }; + + //safe LGraphNode action execution (not sure if safe) + /* +LGraphNode.prototype.executeAction = function(action) +{ + if(action == "") return false; + + if( action.indexOf(";") != -1 || action.indexOf("}") != -1) + { + this.trace("Error: Action contains unsafe characters"); + return false; + } + + var tokens = action.split("("); + var func_name = tokens[0]; + if( typeof(this[func_name]) != "function") + { + this.trace("Error: Action not found on node: " + func_name); + return false; + } + + var code = action; + + try + { + var _foo = eval; + eval = null; + (new Function("with(this) { " + code + "}")).call(this); + eval = _foo; + } + catch (err) + { + this.trace("Error executing action {" + action + "} :" + err); + return false; + } + + return true; +} +*/ + + /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ + LGraphNode.prototype.captureInput = function(v) { + if (!this.graph || !this.graph.list_of_graphcanvas) { + return; + } + + var list = this.graph.list_of_graphcanvas; + + for (var i = 0; i < list.length; ++i) { + var c = list[i]; + //releasing somebody elses capture?! + if (!v && c.node_capturing_input != this) { continue; } - gamepad = gamepads[i]; - //xbox controller mapping - var xbox = this.xbox_mapping; - if (!xbox) { - xbox = this.xbox_mapping = { - axes: [], - buttons: {}, - hat: "", - hatmap: GamepadInput.CENTER - }; + //change + c.node_capturing_input = v ? this : null; + } + }; + + /** + * Collapse the node to make it smaller on the canvas + * @method collapse + **/ + LGraphNode.prototype.collapse = function(force) { + this.graph._version++; + if (this.constructor.collapsable === false && !force) { + return; + } + if (!this.flags.collapsed) { + this.flags.collapsed = true; + } else { + this.flags.collapsed = false; + } + this.setDirtyCanvas(true, true); + }; + + /** + * Forces the node to do not move or realign on Z + * @method pin + **/ + + LGraphNode.prototype.pin = function(v) { + this.graph._version++; + if (v === undefined) { + this.flags.pinned = !this.flags.pinned; + } else { + this.flags.pinned = v; + } + }; + + LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { + return [ + (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], + (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] + ]; + }; + + function LGraphGroup(title) { + this._ctor(title); + } + + global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; + + LGraphGroup.prototype._ctor = function(title) { + this.title = title || "Group"; + this.font_size = 24; + this.color = LGraphCanvas.node_colors.pale_blue + ? LGraphCanvas.node_colors.pale_blue.groupcolor + : "#AAA"; + this._bounding = new Float32Array([10, 10, 140, 80]); + this._pos = this._bounding.subarray(0, 2); + this._size = this._bounding.subarray(2, 4); + this._nodes = []; + this.graph = null; + + Object.defineProperty(this, "pos", { + set: function(v) { + if (!v || v.length < 2) { + return; + } + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() { + return this._pos; + }, + enumerable: true + }); + + Object.defineProperty(this, "size", { + set: function(v) { + if (!v || v.length < 2) { + return; + } + this._size[0] = Math.max(140, v[0]); + this._size[1] = Math.max(80, v[1]); + }, + get: function() { + return this._size; + }, + enumerable: true + }); + }; + + LGraphGroup.prototype.configure = function(o) { + this.title = o.title; + this._bounding.set(o.bounding); + this.color = o.color; + this.font_size = o.font_size; + }; + + LGraphGroup.prototype.serialize = function() { + var b = this._bounding; + return { + title: this.title, + bounding: [ + Math.round(b[0]), + Math.round(b[1]), + Math.round(b[2]), + Math.round(b[3]) + ], + color: this.color, + font_size: this.font_size + }; + }; + + LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { + this._pos[0] += deltax; + this._pos[1] += deltay; + if (ignore_nodes) { + return; + } + for (var i = 0; i < this._nodes.length; ++i) { + var node = this._nodes[i]; + node.pos[0] += deltax; + node.pos[1] += deltay; + } + }; + + LGraphGroup.prototype.recomputeInsideNodes = function() { + this._nodes.length = 0; + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; + node.getBounding(node_bounding); + if (!overlapBounding(this._bounding, node_bounding)) { + continue; + } //out of the visible area + this._nodes.push(node); + } + }; + + LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; + LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; + + //**************************************** + + //Scale and Offset + function DragAndScale(element, skip_events) { + this.offset = new Float32Array([0, 0]); + this.scale = 1; + this.max_scale = 10; + this.min_scale = 0.1; + this.onredraw = null; + this.enabled = true; + this.last_mouse = [0, 0]; + this.element = null; + this.visible_area = new Float32Array(4); + + if (element) { + this.element = element; + if (!skip_events) { + this.bindEvents(element); } + } + } - xbox.axes["lx"] = gamepad.axes[0]; - xbox.axes["ly"] = gamepad.axes[1]; - xbox.axes["rx"] = gamepad.axes[2]; - xbox.axes["ry"] = gamepad.axes[3]; - xbox.axes["ltrigger"] = gamepad.buttons[6].value; - xbox.axes["rtrigger"] = gamepad.buttons[7].value; - xbox.hat = ""; - xbox.hatmap = GamepadInput.CENTER; + LiteGraph.DragAndScale = DragAndScale; + + DragAndScale.prototype.bindEvents = function(element) { + this.last_mouse = new Float32Array(2); + + this._binded_mouse_callback = this.onMouse.bind(this); + + LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); + + element.addEventListener( + "mousewheel", + this._binded_mouse_callback, + false + ); + element.addEventListener("wheel", this._binded_mouse_callback, false); + }; + + DragAndScale.prototype.computeVisibleArea = function( viewport ) { + if (!this.element) { + this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; + return; + } + var width = this.element.width; + var height = this.element.height; + var startx = -this.offset[0]; + var starty = -this.offset[1]; + if( viewport ) + { + startx += viewport[0] / this.scale; + starty += viewport[1] / this.scale; + width = viewport[2]; + height = viewport[3]; + } + var endx = startx + width / this.scale; + var endy = starty + height / this.scale; + this.visible_area[0] = startx; + this.visible_area[1] = starty; + this.visible_area[2] = endx - startx; + this.visible_area[3] = endy - starty; + }; + + DragAndScale.prototype.onMouse = function(e) { + if (!this.enabled) { + return; + } + + var canvas = this.element; + var rect = canvas.getBoundingClientRect(); + var x = e.clientX - rect.left; + var y = e.clientY - rect.top; + e.canvasx = x; + e.canvasy = y; + e.dragging = this.dragging; + + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + + //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); + + var ignore = false; + if (this.onmouse) { + ignore = this.onmouse(e); + } + + if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { + this.dragging = true; + LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); + } else if (e.type == LiteGraph.pointerevents_method+"move") { + if (!ignore) { + var deltax = x - this.last_mouse[0]; + var deltay = y - this.last_mouse[1]; + if (this.dragging) { + this.mouseDrag(deltax, deltay); + } + } + } else if (e.type == LiteGraph.pointerevents_method+"up") { + this.dragging = false; + LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); + } else if ( is_inside && + (e.type == "mousewheel" || + e.type == "wheel" || + e.type == "DOMMouseScroll") + ) { + e.eventType = "mousewheel"; + if (e.type == "wheel") { + e.wheel = -e.deltaY; + } else { + e.wheel = + e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; + } + + //from stack overflow + e.delta = e.wheelDelta + ? e.wheelDelta / 40 + : e.deltaY + ? -e.deltaY / 3 + : 0; + this.changeDeltaScale(1.0 + e.delta * 0.05); + } + + this.last_mouse[0] = x; + this.last_mouse[1] = y; + + if(is_inside) + { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + DragAndScale.prototype.toCanvasContext = function(ctx) { + ctx.scale(this.scale, this.scale); + ctx.translate(this.offset[0], this.offset[1]); + }; + + DragAndScale.prototype.convertOffsetToCanvas = function(pos) { + //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; + return [ + (pos[0] + this.offset[0]) * this.scale, + (pos[1] + this.offset[1]) * this.scale + ]; + }; + + DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { + out = out || [0, 0]; + out[0] = pos[0] / this.scale - this.offset[0]; + out[1] = pos[1] / this.scale - this.offset[1]; + return out; + }; + + DragAndScale.prototype.mouseDrag = function(x, y) { + this.offset[0] += x / this.scale; + this.offset[1] += y / this.scale; + + if (this.onredraw) { + this.onredraw(this); + } + }; + + DragAndScale.prototype.changeScale = function(value, zooming_center) { + if (value < this.min_scale) { + value = this.min_scale; + } else if (value > this.max_scale) { + value = this.max_scale; + } + + if (value == this.scale) { + return; + } + + if (!this.element) { + return; + } + + var rect = this.element.getBoundingClientRect(); + if (!rect) { + return; + } + + zooming_center = zooming_center || [ + rect.width * 0.5, + rect.height * 0.5 + ]; + var center = this.convertCanvasToOffset(zooming_center); + this.scale = value; + if (Math.abs(this.scale - 1) < 0.01) { + this.scale = 1; + } + + var new_center = this.convertCanvasToOffset(zooming_center); + var delta_offset = [ + new_center[0] - center[0], + new_center[1] - center[1] + ]; + + this.offset[0] += delta_offset[0]; + this.offset[1] += delta_offset[1]; + + if (this.onredraw) { + this.onredraw(this); + } + }; + + DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { + this.changeScale(this.scale * value, zooming_center); + }; + + DragAndScale.prototype.reset = function() { + this.scale = 1; + this.offset[0] = 0; + this.offset[1] = 0; + }; + + //********************************************************************************* + // LGraphCanvas: LGraph renderer CLASS + //********************************************************************************* + + /** + * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. + * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked + * + * @class LGraphCanvas + * @constructor + * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) + * @param {LGraph} graph [optional] + * @param {Object} options [optional] { skip_rendering, autoresize, viewport } + */ + function LGraphCanvas(canvas, graph, options) { + this.options = options = options || {}; + + //if(graph === undefined) + // throw ("No graph assigned"); + this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; + + if (canvas && canvas.constructor === String) { + canvas = document.querySelector(canvas); + } + + this.ds = new DragAndScale(); + this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much + + this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; + this.inner_text_font = + "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; + this.node_title_color = LiteGraph.NODE_TITLE_COLOR; + this.default_link_color = LiteGraph.LINK_COLOR; + this.default_connection_color = { + input_off: "#778", + input_on: "#7F7", //"#BBD" + output_off: "#778", + output_on: "#7F7" //"#BBD" + }; + this.default_connection_color_byType = { + /*number: "#7F7", + string: "#77F", + boolean: "#F77",*/ + } + this.default_connection_color_byTypeOff = { + /*number: "#474", + string: "#447", + boolean: "#744",*/ + }; + + this.highquality_render = true; + this.use_gradients = false; //set to true to render titlebar with gradients + this.editor_alpha = 1; //used for transition + this.pause_rendering = false; + this.clear_background = true; + this.clear_background_color = "#222"; + + this.read_only = false; //if set to true users cannot modify the graph + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc + this.multi_select = false; //allow selecting multi nodes without pressing extra keys + this.allow_searchbox = true; + this.allow_reconnect_links = true; //allows to change a connection with having to redo it again + this.align_to_grid = false; //snap to grid + + this.drag_mode = false; + this.dragging_rectangle = null; + + this.filter = null; //allows to filter to only accept some type of nodes in a graph + + this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything + this.always_render_background = false; + this.render_shadows = true; + this.render_canvas_border = true; + this.render_connections_shadows = false; //too much cpu + this.render_connections_border = true; + this.render_curved_connections = false; + this.render_connection_arrows = false; + this.render_collapsed_slots = true; + this.render_execution_order = false; + this.render_title_colored = true; + this.render_link_tooltip = true; + + this.links_render_mode = LiteGraph.SPLINE_LINK; + + this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD + + //to personalize the search box + this.onSearchBox = null; + this.onSearchBoxSelection = null; + + //callbacks + this.onMouse = null; + this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform + this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform + this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) + this.onDrawLinkTooltip = null; //called when rendering a tooltip + this.onNodeMoved = null; //called after moving a node + this.onSelectionChange = null; //called if the selection changes + this.onConnectingChange = null; //called before any link changes + this.onBeforeChange = null; //called before modifying the graph + this.onAfterChange = null; //called after modifying the graph + + this.connections_width = 3; + this.round_radius = 8; + + this.current_node = null; + this.node_widget = null; //used for widgets + this.over_link_center = null; + this.last_mouse_position = [0, 0]; + this.visible_area = this.ds.visible_area; + this.visible_links = []; + + this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas + + //link canvas and graph + if (graph) { + graph.attachCanvas(this); + } + + this.setCanvas(canvas,options.skip_events); + this.clear(); + + if (!options.skip_render) { + this.startRendering(); + } + + this.autoresize = options.autoresize; + } + + global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; + + LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; + + LGraphCanvas.link_type_colors = { + "-1": LiteGraph.EVENT_LINK_COLOR, + number: "#AAA", + node: "#DCA" + }; + LGraphCanvas.gradients = {}; //cache of gradients + + /** + * clears all the data inside + * + * @method clear + */ + LGraphCanvas.prototype.clear = function() { + this.frame = 0; + this.last_draw_time = 0; + this.render_time = 0; + this.fps = 0; + + //this.scale = 1; + //this.offset = [0,0]; + + this.dragging_rectangle = null; + + this.selected_nodes = {}; + this.selected_group = null; + + this.visible_nodes = []; + this.node_dragged = null; + this.node_over = null; + this.node_capturing_input = null; + this.connecting_node = null; + this.highlighted_links = {}; + + this.dragging_canvas = false; + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.dirty_area = null; + + this.node_in_panel = null; + this.node_widget = null; + + this.last_mouse = [0, 0]; + this.last_mouseclick = 0; + this.pointer_is_down = false; + this.pointer_is_double = false; + this.visible_area.set([0, 0, 0, 0]); + + if (this.onClear) { + this.onClear(); + } + }; + + /** + * assigns a graph, you can reassign graphs to the same canvas + * + * @method setGraph + * @param {LGraph} graph + */ + LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { + if (this.graph == graph) { + return; + } + + if (!skip_clear) { + this.clear(); + } + + if (!graph && this.graph) { + this.graph.detachCanvas(this); + return; + } + + graph.attachCanvas(this); + + //remove the graph stack in case a subgraph was open + if (this._graph_stack) + this._graph_stack = null; + + this.setDirty(true, true); + }; + + /** + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @method getTopGraph + * @return {LGraph} graph + */ + LGraphCanvas.prototype.getTopGraph = function() + { + if(this._graph_stack.length) + return this._graph_stack[0]; + return this.graph; + } + + /** + * opens a graph contained inside a node in the current graph + * + * @method openSubgraph + * @param {LGraph} graph + */ + LGraphCanvas.prototype.openSubgraph = function(graph) { + if (!graph) { + throw "graph cannot be null"; + } + + if (this.graph == graph) { + throw "graph cannot be the same"; + } + + this.clear(); + + if (this.graph) { + if (!this._graph_stack) { + this._graph_stack = []; + } + this._graph_stack.push(this.graph); + } + + graph.attachCanvas(this); + this.checkPanels(); + this.setDirty(true, true); + }; + + /** + * closes a subgraph contained inside a node + * + * @method closeSubgraph + * @param {LGraph} assigns a graph + */ + LGraphCanvas.prototype.closeSubgraph = function() { + if (!this._graph_stack || this._graph_stack.length == 0) { + return; + } + var subgraph_node = this.graph._subgraph_node; + var graph = this._graph_stack.pop(); + this.selected_nodes = {}; + this.highlighted_links = {}; + graph.attachCanvas(this); + this.setDirty(true, true); + if (subgraph_node) { + this.centerOnNode(subgraph_node); + this.selectNodes([subgraph_node]); + } + // when close sub graph back to offset [0, 0] scale 1 + this.ds.offset = [0, 0] + this.ds.scale = 1 + }; + + /** + * returns the visually active graph (in case there are more in the stack) + * @method getCurrentGraph + * @return {LGraph} the active graph + */ + LGraphCanvas.prototype.getCurrentGraph = function() { + return this.graph; + }; + + /** + * assigns a canvas + * + * @method setCanvas + * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) + */ + LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { + var that = this; + + if (canvas) { + if (canvas.constructor === String) { + canvas = document.getElementById(canvas); + if (!canvas) { + throw "Error creating LiteGraph canvas: Canvas not found"; + } + } + } + + if (canvas === this.canvas) { + return; + } + + if (!canvas && this.canvas) { + //maybe detach events from old_canvas + if (!skip_events) { + this.unbindEvents(); + } + } + + this.canvas = canvas; + this.ds.element = canvas; + + if (!canvas) { + return; + } + + //this.canvas.tabindex = "1000"; + canvas.className += " lgraphcanvas"; + canvas.data = this; + canvas.tabindex = "1"; //to allow key events + + //bg canvas: used for non changing stuff + this.bgcanvas = null; + if (!this.bgcanvas) { + this.bgcanvas = document.createElement("canvas"); + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + } + + if (canvas.getContext == null) { + if (canvas.localName != "canvas") { + throw "Element supplied for LGraphCanvas must be a element, you passed a " + + canvas.localName; + } + throw "This browser doesn't support Canvas"; + } + + var ctx = (this.ctx = canvas.getContext("2d")); + if (ctx == null) { + if (!canvas.webgl_enabled) { + console.warn( + "This canvas seems to be WebGL, enabling WebGL renderer" + ); + } + this.enableWebGL(); + } + + //input: (move and up could be unbinded) + // why here? this._mousemove_callback = this.processMouseMove.bind(this); + // why here? this._mouseup_callback = this.processMouseUp.bind(this); + + if (!skip_events) { + this.bindEvents(); + } + }; + + //used in some events to capture them + LGraphCanvas.prototype._doNothing = function doNothing(e) { + //console.log("pointerevents: _doNothing "+e.type); + e.preventDefault(); + return false; + }; + LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { + e.preventDefault(); + return true; + }; + + /** + * binds mouse, keyboard, touch and drag events to the canvas + * @method bindEvents + **/ + LGraphCanvas.prototype.bindEvents = function() { + if (this._events_binded) { + console.warn("LGraphCanvas: events already binded"); + return; + } + + //console.log("pointerevents: bindEvents"); + + var canvas = this.canvas; + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; //hack used when moving canvas between windows + + this._mousedown_callback = this.processMouseDown.bind(this); + this._mousewheel_callback = this.processMouseWheel.bind(this); + // why mousemove and mouseup were not binded here? + this._mousemove_callback = this.processMouseMove.bind(this); + this._mouseup_callback = this.processMouseUp.bind(this); + + //touch events -- TODO IMPLEMENT + //this._touch_callback = this.touchHandler.bind(this); + + LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded + canvas.addEventListener("mousewheel", this._mousewheel_callback, false); + + LiteGraph.pointerListenerAdd(canvas,"up", this._mouseup_callback, true); // CHECK: ??? binded or not + LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); + + canvas.addEventListener("contextmenu", this._doNothing); + canvas.addEventListener( + "DOMMouseScroll", + this._mousewheel_callback, + false + ); + + //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents + /*if( 'touchstart' in document.documentElement ) + { + canvas.addEventListener("touchstart", this._touch_callback, true); + canvas.addEventListener("touchmove", this._touch_callback, true); + canvas.addEventListener("touchend", this._touch_callback, true); + canvas.addEventListener("touchcancel", this._touch_callback, true); + }*/ + + //Keyboard ****************** + this._key_callback = this.processKey.bind(this); + canvas.setAttribute("tabindex",1); //otherwise key events are ignored + canvas.addEventListener("keydown", this._key_callback, true); + document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup + + //Dropping Stuff over nodes ************************************ + this._ondrop_callback = this.processDrop.bind(this); + + canvas.addEventListener("dragover", this._doNothing, false); + canvas.addEventListener("dragend", this._doNothing, false); + canvas.addEventListener("drop", this._ondrop_callback, false); + canvas.addEventListener("dragenter", this._doReturnTrue, false); + + this._events_binded = true; + }; + + /** + * unbinds mouse events from the canvas + * @method unbindEvents + **/ + LGraphCanvas.prototype.unbindEvents = function() { + if (!this._events_binded) { + console.warn("LGraphCanvas: no events binded"); + return; + } + + //console.log("pointerevents: unbindEvents"); + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + + LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); + LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_callback); + LiteGraph.pointerListenerRemove(this.canvas,"down", this._mousedown_callback); + this.canvas.removeEventListener( + "mousewheel", + this._mousewheel_callback + ); + this.canvas.removeEventListener( + "DOMMouseScroll", + this._mousewheel_callback + ); + this.canvas.removeEventListener("keydown", this._key_callback); + document.removeEventListener("keyup", this._key_callback); + this.canvas.removeEventListener("contextmenu", this._doNothing); + this.canvas.removeEventListener("drop", this._ondrop_callback); + this.canvas.removeEventListener("dragenter", this._doReturnTrue); + + //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents + /*this.canvas.removeEventListener("touchstart", this._touch_callback ); + this.canvas.removeEventListener("touchmove", this._touch_callback ); + this.canvas.removeEventListener("touchend", this._touch_callback ); + this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ + + this._mousedown_callback = null; + this._mousewheel_callback = null; + this._key_callback = null; + this._ondrop_callback = null; + + this._events_binded = false; + }; + + LGraphCanvas.getFileExtension = function(url) { + var question = url.indexOf("?"); + if (question != -1) { + url = url.substr(0, question); + } + var point = url.lastIndexOf("."); + if (point == -1) { + return ""; + } + return url.substr(point + 1).toLowerCase(); + }; + + /** + * this function allows to render the canvas using WebGL instead of Canvas2D + * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL + * @method enableWebGL + **/ + LGraphCanvas.prototype.enableWebGL = function() { + if (typeof GL === "undefined") { + throw "litegl.js must be included to use a WebGL canvas"; + } + if (typeof enableWebGLCanvas === "undefined") { + throw "webglCanvas.js must be included to use this feature"; + } + + this.gl = this.ctx = enableWebGLCanvas(this.canvas); + this.ctx.webgl = true; + this.bgcanvas = this.canvas; + this.bgctx = this.gl; + this.canvas.webgl_enabled = true; + + /* + GL.create({ canvas: this.bgcanvas }); + this.bgctx = enableWebGLCanvas( this.bgcanvas ); + window.gl = this.gl; + */ + }; + + /** + * marks as dirty the canvas, this way it will be rendered again + * + * @class LGraphCanvas + * @method setDirty + * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) + * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) + */ + LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { + if (fgcanvas) { + this.dirty_canvas = true; + } + if (bgcanvas) { + this.dirty_bgcanvas = true; + } + }; + + /** + * Used to attach the canvas in a popup + * + * @method getCanvasWindow + * @return {window} returns the window where the canvas is attached (the DOM root node) + */ + LGraphCanvas.prototype.getCanvasWindow = function() { + if (!this.canvas) { + return window; + } + var doc = this.canvas.ownerDocument; + return doc.defaultView || doc.parentWindow; + }; + + /** + * starts rendering the content of the canvas when needed + * + * @method startRendering + */ + LGraphCanvas.prototype.startRendering = function() { + if (this.is_rendering) { + return; + } //already rendering + + this.is_rendering = true; + renderFrame.call(this); + + function renderFrame() { + if (!this.pause_rendering) { + this.draw(); + } + + var window = this.getCanvasWindow(); + if (this.is_rendering) { + window.requestAnimationFrame(renderFrame.bind(this)); + } + } + }; + + /** + * stops rendering the content of the canvas (to save resources) + * + * @method stopRendering + */ + LGraphCanvas.prototype.stopRendering = function() { + this.is_rendering = false; + /* + if(this.rendering_timer_id) + { + clearInterval(this.rendering_timer_id); + this.rendering_timer_id = null; + } + */ + }; + + /* LiteGraphCanvas input */ + + //used to block future mouse events (because of im gui) + LGraphCanvas.prototype.blockClick = function() + { + this.block_click = true; + this.last_mouseclick = 0; + } + + LGraphCanvas.prototype.processMouseDown = function(e) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { + return; + } + + this.adjustMouseEvent(e); + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + LGraphCanvas.active_canvas = this; + var that = this; + + var x = e.clientX; + var y = e.clientY; + //console.log(y,this.viewport); + //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); + + this.ds.viewport = this.viewport; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + + //move mouse move event to the window in case it drags outside of the canvas + if(!this.options.skip_events) + { + LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousemove_callback); + LiteGraph.pointerListenerAdd(ref_window.document,"move", this._mousemove_callback,true); //catch for the entire window + LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); + } + + if(!is_inside){ + return; + } + + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); + var skip_dragging = false; + var skip_action = false; + var now = LiteGraph.getTime(); + var is_primary = (e.isPrimary === undefined || !e.isPrimary); + var is_double_click = (now - this.last_mouseclick < 300) && is_primary; + this.mouse[0] = e.clientX; + this.mouse[1] = e.clientY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + this.last_click_position = [this.mouse[0],this.mouse[1]]; + + if (this.pointer_is_down && is_primary ){ + this.pointer_is_double = true; + //console.log("pointerevents: pointer_is_double start"); + }else{ + this.pointer_is_double = false; + } + this.pointer_is_down = true; + + + this.canvas.focus(); + + LiteGraph.closeAllContextMenus(ref_window); + + if (this.onMouse) + { + if (this.onMouse(e) == true) + return; + } + + //left button mouse / single finger + if (e.which == 1 && !this.pointer_is_double) + { + if (e.ctrlKey) + { + this.dragging_rectangle = new Float32Array(4); + this.dragging_rectangle[0] = e.canvasX; + this.dragging_rectangle[1] = e.canvasY; + this.dragging_rectangle[2] = 1; + this.dragging_rectangle[3] = 1; + skip_action = true; + } + + // clone node ALT dragging + if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) + { + if (cloned = node.clone()){ + cloned.pos[0] += 5; + cloned.pos[1] += 5; + this.graph.add(cloned,false,{doCalcSize: false}); + node = cloned; + skip_action = true; + if (!block_drag_node) { + if (this.allow_dragnodes) { + this.graph.beforeChange(); + this.node_dragged = node; + } + if (!this.selected_nodes[node.id]) { + this.processNodeSelected(node, e); + } + } + } + } + + var clicking_canvas_bg = false; + + //when clicked on top of a node + //and it is not interactive + if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { + if (!this.live_mode && !node.flags.pinned) { + this.bringToFront(node); + } //if it wasn't selected? + + //not dragging mouse to connect two slots + if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { + //Search for corner for resize + if ( !skip_action && + node.resizable !== false && + isInsideRectangle( e.canvasX, + e.canvasY, + node.pos[0] + node.size[0] - 5, + node.pos[1] + node.size[1] - 5, + 10, + 10 + ) + ) { + this.graph.beforeChange(); + this.resizing_node = node; + this.canvas.style.cursor = "se-resize"; + skip_action = true; + } else { + //search for outputs + if (node.outputs) { + for ( var i = 0, l = node.outputs.length; i < l; ++i ) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + link_pos[0] - 15, + link_pos[1] - 10, + 30, + 20 + ) + ) { + this.connecting_node = node; + this.connecting_output = output; + this.connecting_output.slot_index = i; + this.connecting_pos = node.getConnectionPos( false, i ); + this.connecting_slot = i; + + if (LiteGraph.shift_click_do_break_link_from){ + if (e.shiftKey) { + node.disconnectOutput(i); + } + } + + if (is_double_click) { + if (node.onOutputDblClick) { + node.onOutputDblClick(i, e); + } + } else { + if (node.onOutputClick) { + node.onOutputClick(i, e); + } + } + + skip_action = true; + break; + } + } + } + + //search for inputs + if (node.inputs) { + for ( var i = 0, l = node.inputs.length; i < l; ++i ) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + link_pos[0] - 15, + link_pos[1] - 10, + 30, + 20 + ) + ) { + if (is_double_click) { + if (node.onInputDblClick) { + node.onInputDblClick(i, e); + } + } else { + if (node.onInputClick) { + node.onInputClick(i, e); + } + } + + if (input.link !== null) { + var link_info = this.graph.links[ + input.link + ]; //before disconnecting + if (LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + this.dirty_bgcanvas = true; + skip_action = true; + }else{ + // do same action as has not node ? + } + + if ( + this.allow_reconnect_links || + //this.move_destination_link_without_shift || + e.shiftKey + ) { + if (!LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + } + this.connecting_node = this.graph._nodes_by_id[ + link_info.origin_id + ]; + this.connecting_slot = + link_info.origin_slot; + this.connecting_output = this.connecting_node.outputs[ + this.connecting_slot + ]; + this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); + + this.dirty_bgcanvas = true; + skip_action = true; + } + + + }else{ + // has not node + } + + if (!skip_action){ + // connect from in to out, from to to from + this.connecting_node = node; + this.connecting_input = input; + this.connecting_input.slot_index = i; + this.connecting_pos = node.getConnectionPos( true, i ); + this.connecting_slot = i; + + this.dirty_bgcanvas = true; + skip_action = true; + } + } + } + } + } //not resizing + } + + //it wasn't clicked on the links boxes + if (!skip_action) { + var block_drag_node = false; + var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; + + //widgets + var widget = this.processNodeWidgets( node, this.graph_mouse, e ); + if (widget) { + block_drag_node = true; + this.node_widget = [node, widget]; + } + + //double clicking + if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { + //double click node + if (node.onDblClick) { + node.onDblClick( e, pos, this ); + } + this.processNodeDblClicked(node); + block_drag_node = true; + } + + //if do not capture mouse + if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { + block_drag_node = true; + } else { + //open subgraph button + if(node.subgraph && !node.skip_subgraph_button) + { + if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { + var that = this; + setTimeout(function() { + that.openSubgraph(node.subgraph); + }, 10); + } + } + + if (this.live_mode) { + clicking_canvas_bg = true; + block_drag_node = true; + } + } + + if (!block_drag_node) { + if (this.allow_dragnodes) { + this.graph.beforeChange(); + this.node_dragged = node; + } + this.processNodeSelected(node, e); + } else { // double-click + /** + * Don't call the function if the block is already selected. + * Otherwise, it could cause the block to be unselected while its panel is open. + */ + if (!node.is_selected) this.processNodeSelected(node, e); + } + + this.dirty_canvas = true; + } + } //clicked outside of nodes + else { + if (!skip_action){ + //search for link connector + if(!this.read_only) { + for (var i = 0; i < this.visible_links.length; ++i) { + var link = this.visible_links[i]; + var center = link._pos; + if ( + !center || + e.canvasX < center[0] - 4 || + e.canvasX > center[0] + 4 || + e.canvasY < center[1] - 4 || + e.canvasY > center[1] + 4 + ) { + continue; + } + //link clicked + this.showLinkMenu(link, e); + this.over_link_center = null; //clear tooltip + break; + } + } + + this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); + this.selected_group_resizing = false; + if (this.selected_group && !this.read_only ) { + if (e.ctrlKey) { + this.dragging_rectangle = null; + } + + var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); + if (dist * this.ds.scale < 10) { + this.selected_group_resizing = true; + } else { + this.selected_group.recomputeInsideNodes(); + } + } + + if (is_double_click && !this.read_only && this.allow_searchbox) { + this.showSearchBox(e); + e.preventDefault(); + e.stopPropagation(); + } + + clicking_canvas_bg = true; + } + } + + if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { + //console.log("pointerevents: dragging_canvas start"); + this.dragging_canvas = true; + } + + } else if (e.which == 2) { + //middle button + + if (LiteGraph.middle_click_slot_add_default_node){ + if (node && this.allow_interaction && !skip_action && !this.read_only){ + //not dragging mouse to connect two slots + if ( + !this.connecting_node && + !node.flags.collapsed && + !this.live_mode + ) { + var mClikSlot = false; + var mClikSlot_index = false; + var mClikSlot_isOut = false; + //search for outputs + if (node.outputs) { + for ( var i = 0, l = node.outputs.length; i < l; ++i ) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = output; + mClikSlot_index = i; + mClikSlot_isOut = true; + break; + } + } + } + + //search for inputs + if (node.inputs) { + for ( var i = 0, l = node.inputs.length; i < l; ++i ) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = input; + mClikSlot_index = i; + mClikSlot_isOut = false; + break; + } + } + } + //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); + if (mClikSlot && mClikSlot_index!==false){ + + var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); + var node_bounding = node.getBounding(); + // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes + var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 + ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" + ]; + var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node + ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index + ,nodeTo: !mClikSlot_isOut?node:null + ,slotTo: !mClikSlot_isOut?mClikSlot_index:null + ,position: posRef //,e: e + ,nodeType: "AUTO" //nodeNewType + ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] + ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ + }); + + } + } + } + } else if (!skip_action && this.allow_dragcanvas) { + //console.log("pointerevents: dragging_canvas start from middle button"); + this.dragging_canvas = true; + } + + + } else if (e.which == 3 || this.pointer_is_double) { + + //right button + if (this.allow_interaction && !skip_action && !this.read_only){ + + // is it hover a node ? + if (node){ + if(Object.keys(this.selected_nodes).length + && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) + ){ + // is multiselected or using shift to include the now node + if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present + }else{ + // update selection + this.selectNodes([node]); + } + } + + // show menu on this node + this.processContextMenu(node, e); + } + + } + + //TODO + //if(this.node_selected != prev_selected) + // this.onNodeSelectionChange(this.node_selected); + + this.last_mouse[0] = e.clientX; + this.last_mouse[1] = e.clientY; + this.last_mouseclick = LiteGraph.getTime(); + this.last_mouse_dragging = true; + + /* + if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + //this is to ensure to defocus(blur) if a text input element is on focus + if ( + !ref_window.document.activeElement || + (ref_window.document.activeElement.nodeName.toLowerCase() != + "input" && + ref_window.document.activeElement.nodeName.toLowerCase() != + "textarea") + ) { + e.preventDefault(); + } + e.stopPropagation(); + + if (this.onMouseDown) { + this.onMouseDown(e); + } + + return false; + }; + + /** + * Called when a mouse move event has to be processed + * @method processMouseMove + **/ + LGraphCanvas.prototype.processMouseMove = function(e) { + if (this.autoresize) { + this.resize(); + } + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { + return; + } + + LGraphCanvas.active_canvas = this; + this.adjustMouseEvent(e); + var mouse = [e.clientX, e.clientY]; + this.mouse[0] = mouse[0]; + this.mouse[1] = mouse[1]; + var delta = [ + mouse[0] - this.last_mouse[0], + mouse[1] - this.last_mouse[1] + ]; + this.last_mouse = mouse; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + + //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); + + if(this.block_click) + { + //console.log("pointerevents: processMouseMove block_click"); + e.preventDefault(); + return false; + } + + e.dragging = this.last_mouse_dragging; + + if (this.node_widget) { + this.processNodeWidgets( + this.node_widget[0], + this.graph_mouse, + e, + this.node_widget[1] + ); + this.dirty_canvas = true; + } + + //get node over + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); + + if (this.dragging_rectangle) + { + this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; + this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; + this.dirty_canvas = true; + } + else if (this.selected_group && !this.read_only) + { + //moving/resizing a group + if (this.selected_group_resizing) { + this.selected_group.size = [ + e.canvasX - this.selected_group.pos[0], + e.canvasY - this.selected_group.pos[1] + ]; + } else { + var deltax = delta[0] / this.ds.scale; + var deltay = delta[1] / this.ds.scale; + this.selected_group.move(deltax, deltay, e.ctrlKey); + if (this.selected_group._nodes.length) { + this.dirty_canvas = true; + } + } + this.dirty_bgcanvas = true; + } else if (this.dragging_canvas) { + ////console.log("pointerevents: processMouseMove is dragging_canvas"); + this.ds.offset[0] += delta[0] / this.ds.scale; + this.ds.offset[1] += delta[1] / this.ds.scale; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { + if (this.connecting_node) { + this.dirty_canvas = true; + } + + //remove mouseover flag + for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { + if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { + //mouse leave + this.graph._nodes[i].mouseOver = false; + if (this.node_over && this.node_over.onMouseLeave) { + this.node_over.onMouseLeave(e); + } + this.node_over = null; + this.dirty_canvas = true; + } + } + + //mouse over a node + if (node) { + + if(node.redraw_on_mouse) + this.dirty_canvas = true; + + //this.canvas.style.cursor = "move"; + if (!node.mouseOver) { + //mouse enter + node.mouseOver = true; + this.node_over = node; + this.dirty_canvas = true; + + if (node.onMouseEnter) { + node.onMouseEnter(e); + } + } + + //in case the node wants to do something + if (node.onMouseMove) { + node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); + } + + //if dragging a link + if (this.connecting_node) { + + if (this.connecting_output){ + + var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput + + //on top of input + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do + } else { + //check if I have a slot below de mouse + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.inputs[slot]) { + var slot_type = node.inputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { + this._highlight_input = pos; + this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS + } + } else { + this._highlight_input = null; + this._highlight_input_slot = null; // XXX CHECK THIS + } + } + + }else if(this.connecting_input){ + + var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput + + //on top of output + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do + } else { + //check if I have a slot below de mouse + var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.outputs[slot]) { + var slot_type = node.outputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { + this._highlight_output = pos; + } + } else { + this._highlight_output = null; + } + } + } + } + + //Search for corner + if (this.canvas) { + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + node.pos[0] + node.size[0] - 5, + node.pos[1] + node.size[1] - 5, + 5, + 5 + ) + ) { + this.canvas.style.cursor = "se-resize"; + } else { + this.canvas.style.cursor = "crosshair"; + } + } + } else { //not over a node + + //search for link connector + var over_link = null; + for (var i = 0; i < this.visible_links.length; ++i) { + var link = this.visible_links[i]; + var center = link._pos; + if ( + !center || + e.canvasX < center[0] - 4 || + e.canvasX > center[0] + 4 || + e.canvasY < center[1] - 4 || + e.canvasY > center[1] + 4 + ) { + continue; + } + over_link = link; + break; + } + if( over_link != this.over_link_center ) + { + this.over_link_center = over_link; + this.dirty_canvas = true; + } + + if (this.canvas) { + this.canvas.style.cursor = ""; + } + } //end + + //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) + if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { + this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); + } + + //node being dragged + if (this.node_dragged && !this.live_mode) { + //console.log("draggin!",this.selected_nodes); + for (var i in this.selected_nodes) { + var n = this.selected_nodes[i]; + n.pos[0] += delta[0] / this.ds.scale; + n.pos[1] += delta[1] / this.ds.scale; + if (!n.is_selected) this.processNodeSelected(n, e); /* + * Don't call the function if the block is already selected. + * Otherwise, it could cause the block to be unselected while dragging. + */ + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + + if (this.resizing_node && !this.live_mode) { + //convert mouse to node space + var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; + var min_size = this.resizing_node.computeSize(); + desired_size[0] = Math.max( min_size[0], desired_size[0] ); + desired_size[1] = Math.max( min_size[1], desired_size[1] ); + this.resizing_node.setSize( desired_size ); + + this.canvas.style.cursor = "se-resize"; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + } + + e.preventDefault(); + return false; + }; + + /** + * Called when a mouse up event has to be processed + * @method processMouseUp + **/ + LGraphCanvas.prototype.processMouseUp = function(e) { + + var is_primary = ( e.isPrimary === undefined || e.isPrimary ); + + //early exit for extra pointer + if(!is_primary){ + /*e.stopPropagation(); + e.preventDefault();*/ + //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); + return false; + } + + //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) + return; + + var window = this.getCanvasWindow(); + var document = window.document; + LGraphCanvas.active_canvas = this; + + //restore the mousemove event back to the canvas + if(!this.options.skip_events) + { + //console.log("pointerevents: processMouseUp adjustEventListener"); + LiteGraph.pointerListenerRemove(document,"move", this._mousemove_callback,true); + LiteGraph.pointerListenerAdd(this.canvas,"move", this._mousemove_callback,true); + LiteGraph.pointerListenerRemove(document,"up", this._mouseup_callback,true); + } + + this.adjustMouseEvent(e); + var now = LiteGraph.getTime(); + e.click_time = now - this.last_mouseclick; + this.last_mouse_dragging = false; + this.last_click_position = null; + + if(this.block_click) + { + //console.log("pointerevents: processMouseUp block_clicks"); + this.block_click = false; //used to avoid sending twice a click in a immediate button + } + + //console.log("pointerevents: processMouseUp which: "+e.which); + + if (e.which == 1) { + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); + } + + //left button + this.node_widget = null; + + if (this.selected_group) { + var diffx = + this.selected_group.pos[0] - + Math.round(this.selected_group.pos[0]); + var diffy = + this.selected_group.pos[1] - + Math.round(this.selected_group.pos[1]); + this.selected_group.move(diffx, diffy, e.ctrlKey); + this.selected_group.pos[0] = Math.round( + this.selected_group.pos[0] + ); + this.selected_group.pos[1] = Math.round( + this.selected_group.pos[1] + ); + if (this.selected_group._nodes.length) { + this.dirty_canvas = true; + } + this.selected_group = null; + } + this.selected_group_resizing = false; + + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + + if (this.dragging_rectangle) { + if (this.graph) { + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + + //compute bounding and flip if left to right + var w = Math.abs(this.dragging_rectangle[2]); + var h = Math.abs(this.dragging_rectangle[3]); + var startx = + this.dragging_rectangle[2] < 0 + ? this.dragging_rectangle[0] - w + : this.dragging_rectangle[0]; + var starty = + this.dragging_rectangle[3] < 0 + ? this.dragging_rectangle[1] - h + : this.dragging_rectangle[1]; + this.dragging_rectangle[0] = startx; + this.dragging_rectangle[1] = starty; + this.dragging_rectangle[2] = w; + this.dragging_rectangle[3] = h; + + // test dragging rect size, if minimun simulate a click + if (!node || (w > 10 && h > 10 )){ + //test against all nodes (not visible because the rectangle maybe start outside + var to_select = []; + for (var i = 0; i < nodes.length; ++i) { + var nodeX = nodes[i]; + nodeX.getBounding(node_bounding); + if ( + !overlapBounding( + this.dragging_rectangle, + node_bounding + ) + ) { + continue; + } //out of the visible area + to_select.push(nodeX); + } + if (to_select.length) { + this.selectNodes(to_select,e.shiftKey); // add to selection with shift + } + }else{ + // will select of update selection + this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey + } + + } + this.dragging_rectangle = null; + } else if (this.connecting_node) { + //dragging a connection + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + + var connInOrOut = this.connecting_output || this.connecting_input; + var connType = connInOrOut.type; + + //node below mouse + if (node) { + + /* no need to condition on event type.. just another type + if ( + connType == LiteGraph.EVENT && + this.isOverNodeBox(node, e.canvasX, e.canvasY) + ) { + + this.connecting_node.connect( + this.connecting_slot, + node, + LiteGraph.EVENT + ); + + } else {*/ + + //slot below mouse? connect + + if (this.connecting_output){ + + var slot = this.isOverNodeInput( + node, + e.canvasX, + e.canvasY + ); + if (slot != -1) { + this.connecting_node.connect(this.connecting_slot, node, slot); + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByType(this.connecting_slot,node,connType); + } + + }else if (this.connecting_input){ + + var slot = this.isOverNodeOutput( + node, + e.canvasX, + e.canvasY + ); + + if (slot != -1) { + node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); + } + + } + + + //} + + }else{ + + // add menu when releasing link in empty space + if (LiteGraph.release_link_on_empty_shows_menu){ + if (e.shiftKey && this.allow_searchbox){ + if(this.connecting_output){ + this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); + }else if(this.connecting_input){ + this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); + } + }else{ + if(this.connecting_output){ + this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); + }else if(this.connecting_input){ + this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); + } + } + } + } + + this.connecting_output = null; + this.connecting_input = null; + this.connecting_pos = null; + this.connecting_node = null; + this.connecting_slot = -1; + } //not dragging connection + else if (this.resizing_node) { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.graph.afterChange(this.resizing_node); + this.resizing_node = null; + } else if (this.node_dragged) { + //node being dragged? + var node = this.node_dragged; + if ( + node && + e.click_time < 300 && + isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) + ) { + node.collapse(); + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + if (this.graph.config.align_to_grid || this.align_to_grid ) { + this.node_dragged.alignToGrid(); + } + if( this.onNodeMoved ) + this.onNodeMoved( this.node_dragged ); + this.graph.afterChange(this.node_dragged); + this.node_dragged = null; + } //no node being dragged + else { + //get node over + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + + if (!node && e.click_time < 300) { + this.deselectAllNodes(); + } + + this.dirty_canvas = true; + this.dragging_canvas = false; + + if (this.node_over && this.node_over.onMouseUp) { + this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); + } + if ( + this.node_capturing_input && + this.node_capturing_input.onMouseUp + ) { + this.node_capturing_input.onMouseUp(e, [ + e.canvasX - this.node_capturing_input.pos[0], + e.canvasY - this.node_capturing_input.pos[1] + ]); + } + } + } else if (e.which == 2) { + //middle button + //trace("middle"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } else if (e.which == 3) { + //right button + //trace("right"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + if (is_primary) + { + this.pointer_is_down = false; + this.pointer_is_double = false; + } + + this.graph.change(); + + //console.log("pointerevents: processMouseUp stopPropagation"); + e.stopPropagation(); + e.preventDefault(); + return false; + }; + + /** + * Called when a mouse wheel event has to be processed + * @method processMouseWheel + **/ + LGraphCanvas.prototype.processMouseWheel = function(e) { + if (!this.graph || !this.allow_dragcanvas) { + return; + } + + var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; + + this.adjustMouseEvent(e); + + var x = e.clientX; + var y = e.clientY; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + if(!is_inside) + return; + + var scale = this.ds.scale; + + if (delta > 0) { + scale *= 1.1; + } else if (delta < 0) { + scale *= 1 / 1.1; + } + + //this.setZoom( scale, [ e.clientX, e.clientY ] ); + this.ds.changeScale(scale, [e.clientX, e.clientY]); + + this.graph.change(); + + e.preventDefault(); + return false; // prevent default + }; + + /** + * returns true if a position (in graph space) is on top of a node little corner box + * @method isOverNodeBox + **/ + LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + if ( + isInsideRectangle( + canvasx, + canvasy, + node.pos[0] + 2, + node.pos[1] + 2 - title_height, + title_height - 4, + title_height - 4 + ) + ) { + return true; + } + return false; + }; + + /** + * returns the INDEX if a position (in graph space) is on top of a node input slot + * @method isOverNodeInput + **/ + LGraphCanvas.prototype.isOverNodeInput = function( + node, + canvasx, + canvasy, + slot_pos + ) { + if (node.inputs) { + for (var i = 0, l = node.inputs.length; i < l; ++i) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + var is_inside = false; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : node.horizontal; + if (inputs_horizontal) { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 5, + link_pos[1] - 10, + 10, + 20 + ); + } else { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 10, + link_pos[1] - 5, + 40, + 10 + ); + } + if (is_inside) { + if (slot_pos) { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + } + return -1; + }; + + /** + * returns the INDEX if a position (in graph space) is on top of a node output slot + * @method isOverNodeOuput + **/ + LGraphCanvas.prototype.isOverNodeOutput = function( + node, + canvasx, + canvasy, + slot_pos + ) { + if (node.outputs) { + for (var i = 0, l = node.outputs.length; i < l; ++i) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + var is_inside = false; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : node.horizontal; + if (outputs_horizontal) { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 5, + link_pos[1] - 10, + 10, + 20 + ); + } else { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 10, + link_pos[1] - 5, + 40, + 10 + ); + } + if (is_inside) { + if (slot_pos) { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + } + return -1; + }; + + /** + * process a key event + * @method processKey + **/ + LGraphCanvas.prototype.processKey = function(e) { + if (!this.graph) { + return; + } + + var block_default = false; + //console.log(e); //debug + + if (e.target.localName == "input") { + return; + } + + if (e.type == "keydown") { + if (e.keyCode == 32) { + //space + this.dragging_canvas = true; + block_default = true; + } + + if (e.keyCode == 27) { + //esc + if(this.node_panel) this.node_panel.close(); + if(this.options_panel) this.options_panel.close(); + block_default = true; + } + + //select all Control A + if (e.keyCode == 65 && e.ctrlKey) { + this.selectNodes(); + block_default = true; + } + + if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { + //copy + if (this.selected_nodes) { + this.copyToClipboard(); + block_default = true; + } + } + + if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { + //paste + this.pasteFromClipboard(e.shiftKey); + } + + //delete or backspace + if (e.keyCode == 46 || e.keyCode == 8) { + if ( + e.target.localName != "input" && + e.target.localName != "textarea" + ) { + this.deleteSelectedNodes(); + block_default = true; + } + } + + //collapse + //... + + //TODO + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].onKeyDown) { + this.selected_nodes[i].onKeyDown(e); + } + } + } + } else if (e.type == "keyup") { + if (e.keyCode == 32) { + // space + this.dragging_canvas = false; + } + + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].onKeyUp) { + this.selected_nodes[i].onKeyUp(e); + } + } + } + } + + this.graph.change(); + + if (block_default) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + }; + + LGraphCanvas.prototype.copyToClipboard = function() { + var clipboard_info = { + nodes: [], + links: [] + }; + var index = 0; + var selected_nodes_array = []; + for (var i in this.selected_nodes) { + var node = this.selected_nodes[i]; + if (node.clonable === false) + continue; + node._relative_id = index; + selected_nodes_array.push(node); + index += 1; + } + + for (var i = 0; i < selected_nodes_array.length; ++i) { + var node = selected_nodes_array[i]; + if(node.clonable === false) + continue; + var cloned = node.clone(); + if(!cloned) + { + console.warn("node type not found: " + node.type ); + continue; + } + clipboard_info.nodes.push(cloned.serialize()); + if (node.inputs && node.inputs.length) { + for (var j = 0; j < node.inputs.length; ++j) { + var input = node.inputs[j]; + if (!input || input.link == null) { + continue; + } + var link_info = this.graph.links[input.link]; + if (!link_info) { + continue; + } + var target_node = this.graph.getNodeById( + link_info.origin_id + ); + if (!target_node) { + continue; + } + clipboard_info.links.push([ + target_node._relative_id, + link_info.origin_slot, //j, + node._relative_id, + link_info.target_slot, + target_node.id + ]); + } + } + } + localStorage.setItem( + "litegrapheditor_clipboard", + JSON.stringify(clipboard_info) + ); + }; + + LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { + // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior + if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { + return; + } + var data = localStorage.getItem("litegrapheditor_clipboard"); + if (!data) { + return; + } + + this.graph.beforeChange(); + + //create nodes + var clipboard_info = JSON.parse(data); + // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos + var posMin = false; + var posMinIndexes = false; + for (var i = 0; i < clipboard_info.nodes.length; ++i) { + if (posMin){ + if(posMin[0]>clipboard_info.nodes[i].pos[0]){ + posMin[0] = clipboard_info.nodes[i].pos[0]; + posMinIndexes[0] = i; + } + if(posMin[1]>clipboard_info.nodes[i].pos[1]){ + posMin[1] = clipboard_info.nodes[i].pos[1]; + posMinIndexes[1] = i; + } + } + else{ + posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; + posMinIndexes = [i, i]; + } + } + var nodes = []; + for (var i = 0; i < clipboard_info.nodes.length; ++i) { + var node_data = clipboard_info.nodes[i]; + var node = LiteGraph.createNode(node_data.type); + if (node) { + node.configure(node_data); + + //paste in last known mouse position + node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; + node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; + + this.graph.add(node,{doProcessChange:false}); + + nodes.push(node); + } + } + + //create links + for (var i = 0; i < clipboard_info.links.length; ++i) { + var link_info = clipboard_info.links[i]; + var origin_node; + var origin_node_relative_id = link_info[0]; + if (origin_node_relative_id != null) { + origin_node = nodes[origin_node_relative_id]; + } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { + var origin_node_id = link_info[4]; + if (origin_node_id) { + origin_node = this.graph.getNodeById(origin_node_id); + } + } + var target_node = nodes[link_info[2]]; + if( origin_node && target_node ) + origin_node.connect(link_info[1], target_node, link_info[3]); + else + console.warn("Warning, nodes missing on pasting"); + } + + this.selectNodes(nodes); + + this.graph.afterChange(); + }; + + /** + * process a item drop event on top the canvas + * @method processDrop + **/ + LGraphCanvas.prototype.processDrop = function(e) { + e.preventDefault(); + this.adjustMouseEvent(e); + var x = e.clientX; + var y = e.clientY; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + if(!is_inside){ + return; + // --- BREAK --- + } + + var pos = [e.canvasX, e.canvasY]; + + + var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; + + if (!node) { + var r = null; + if (this.onDropItem) { + r = this.onDropItem(event); + } + if (!r) { + this.checkDropItem(e); + } + return; + } + + if (node.onDropFile || node.onDropData) { + var files = e.dataTransfer.files; + if (files && files.length) { + for (var i = 0; i < files.length; i++) { + var file = e.dataTransfer.files[0]; + var filename = file.name; + var ext = LGraphCanvas.getFileExtension(filename); + //console.log(file); + + if (node.onDropFile) { + node.onDropFile(file); + } + + if (node.onDropData) { + //prepare reader + var reader = new FileReader(); + reader.onload = function(event) { + //console.log(event.target); + var data = event.target.result; + node.onDropData(data, filename, file); + }; + + //read data + var type = file.type.split("/")[0]; + if (type == "text" || type == "") { + reader.readAsText(file); + } else if (type == "image") { + reader.readAsDataURL(file); + } else { + reader.readAsArrayBuffer(file); + } + } + } + } + } + + if (node.onDropItem) { + if (node.onDropItem(event)) { + return true; + } + } + + if (this.onDropItem) { + return this.onDropItem(event); + } + + return false; + }; + + //called if the graph doesn't have a default drop item behaviour + LGraphCanvas.prototype.checkDropItem = function(e) { + if (e.dataTransfer.files.length) { + var file = e.dataTransfer.files[0]; + var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); + var nodetype = LiteGraph.node_types_by_file_extension[ext]; + if (nodetype) { + this.graph.beforeChange(); + var node = LiteGraph.createNode(nodetype.type); + node.pos = [e.canvasX, e.canvasY]; + this.graph.add(node); + if (node.onDropFile) { + node.onDropFile(file); + } + this.graph.afterChange(); + } + } + }; + + LGraphCanvas.prototype.processNodeDblClicked = function(n) { + if (this.onShowNodePanel) { + this.onShowNodePanel(n); + } + else + { + this.showShowNodePanel(n); + } + + if (this.onNodeDblClicked) { + this.onNodeDblClicked(n); + } + + this.setDirty(true); + }; + + LGraphCanvas.prototype.processNodeSelected = function(node, e) { + this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); + if (this.onNodeSelected) { + this.onNodeSelected(node); + } + }; + + /** + * selects a given node (or adds it to the current selection) + * @method selectNode + **/ + LGraphCanvas.prototype.selectNode = function( + node, + add_to_current_selection + ) { + if (node == null) { + this.deselectAllNodes(); + } else { + this.selectNodes([node], add_to_current_selection); + } + }; + + /** + * selects several nodes (or adds them to the current selection) + * @method selectNodes + **/ + LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) + { + if (!add_to_current_selection) { + this.deselectAllNodes(); + } + + nodes = nodes || this.graph._nodes; + if (typeof nodes == "string") nodes = [nodes]; + for (var i in nodes) { + var node = nodes[i]; + if (node.is_selected) { + this.deselectNode(node); + continue; + } + + if (!node.is_selected && node.onSelected) { + node.onSelected(); + } + node.is_selected = true; + this.selected_nodes[node.id] = node; + + if (node.inputs) { + for (var j = 0; j < node.inputs.length; ++j) { + this.highlighted_links[node.inputs[j].link] = true; + } + } + if (node.outputs) { + for (var j = 0; j < node.outputs.length; ++j) { + var out = node.outputs[j]; + if (out.links) { + for (var k = 0; k < out.links.length; ++k) { + this.highlighted_links[out.links[k]] = true; + } + } + } + } + } + + if( this.onSelectionChange ) + this.onSelectionChange( this.selected_nodes ); + + this.setDirty(true); + }; + + /** + * removes a node from the current selection + * @method deselectNode + **/ + LGraphCanvas.prototype.deselectNode = function(node) { + if (!node.is_selected) { + return; + } + if (node.onDeselected) { + node.onDeselected(); + } + node.is_selected = false; + + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + + //remove highlighted + if (node.inputs) { + for (var i = 0; i < node.inputs.length; ++i) { + delete this.highlighted_links[node.inputs[i].link]; + } + } + if (node.outputs) { + for (var i = 0; i < node.outputs.length; ++i) { + var out = node.outputs[i]; + if (out.links) { + for (var j = 0; j < out.links.length; ++j) { + delete this.highlighted_links[out.links[j]]; + } + } + } + } + }; + + /** + * removes all nodes from the current selection + * @method deselectAllNodes + **/ + LGraphCanvas.prototype.deselectAllNodes = function() { + if (!this.graph) { + return; + } + var nodes = this.graph._nodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var node = nodes[i]; + if (!node.is_selected) { + continue; + } + if (node.onDeselected) { + node.onDeselected(); + } + node.is_selected = false; + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + } + this.selected_nodes = {}; + this.current_node = null; + this.highlighted_links = {}; + if( this.onSelectionChange ) + this.onSelectionChange( this.selected_nodes ); + this.setDirty(true); + }; + + /** + * deletes all nodes in the current selection from the graph + * @method deleteSelectedNodes + **/ + LGraphCanvas.prototype.deleteSelectedNodes = function() { + + this.graph.beforeChange(); + + for (var i in this.selected_nodes) { + var node = this.selected_nodes[i]; + + if(node.block_delete) + continue; + + //autoconnect when possible (very basic, only takes into account first input-output) + if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) + { + var input_link = node.graph.links[ node.inputs[0].link ]; + var output_link = node.graph.links[ node.outputs[0].links[0] ]; + var input_node = node.getInputNode(0); + var output_node = node.getOutputNodes(0)[0]; + if(input_node && output_node) + input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); + } + this.graph.remove(node); + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + } + this.selected_nodes = {}; + this.current_node = null; + this.highlighted_links = {}; + this.setDirty(true); + this.graph.afterChange(); + }; + + /** + * centers the camera on a given node + * @method centerOnNode + **/ + LGraphCanvas.prototype.centerOnNode = function(node) { + this.ds.offset[0] = + -node.pos[0] - + node.size[0] * 0.5 + + (this.canvas.width * 0.5) / this.ds.scale; + this.ds.offset[1] = + -node.pos[1] - + node.size[1] * 0.5 + + (this.canvas.height * 0.5) / this.ds.scale; + this.setDirty(true, true); + }; + + /** + * adds some useful properties to a mouse event, like the position in graph coordinates + * @method adjustMouseEvent + **/ + LGraphCanvas.prototype.adjustMouseEvent = function(e) { + var clientX_rel = 0; + var clientY_rel = 0; + + if (this.canvas) { + var b = this.canvas.getBoundingClientRect(); + clientX_rel = e.clientX - b.left; + clientY_rel = e.clientY - b.top; + } else { + clientX_rel = e.clientX; + clientY_rel = e.clientY; + } + + // e.deltaX = clientX_rel - this.last_mouse_position[0]; + // e.deltaY = clientY_rel- this.last_mouse_position[1]; + + this.last_mouse_position[0] = clientX_rel; + this.last_mouse_position[1] = clientY_rel; + + e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; + e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; + + //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); + }; + + /** + * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom + * @method setZoom + **/ + LGraphCanvas.prototype.setZoom = function(value, zooming_center) { + this.ds.changeScale(value, zooming_center); + /* + if(!zooming_center && this.canvas) + zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; + + var center = this.convertOffsetToCanvas( zooming_center ); + + this.ds.scale = value; + + if(this.scale > this.max_zoom) + this.scale = this.max_zoom; + else if(this.scale < this.min_zoom) + this.scale = this.min_zoom; + + var new_center = this.convertOffsetToCanvas( zooming_center ); + var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; + + this.offset[0] += delta_offset[0]; + this.offset[1] += delta_offset[1]; + */ + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + }; + + /** + * converts a coordinate from graph coordinates to canvas2D coordinates + * @method convertOffsetToCanvas + **/ + LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { + return this.ds.convertOffsetToCanvas(pos, out); + }; + + /** + * converts a coordinate from Canvas2D coordinates to graph space + * @method convertCanvasToOffset + **/ + LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { + return this.ds.convertCanvasToOffset(pos, out); + }; + + //converts event coordinates from canvas2D to graph coordinates + LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { + var rect = this.canvas.getBoundingClientRect(); + return this.convertCanvasToOffset([ + e.clientX - rect.left, + e.clientY - rect.top + ]); + }; + + /** + * brings a node to front (above all other nodes) + * @method bringToFront + **/ + LGraphCanvas.prototype.bringToFront = function(node) { + var i = this.graph._nodes.indexOf(node); + if (i == -1) { + return; + } + + this.graph._nodes.splice(i, 1); + this.graph._nodes.push(node); + }; + + /** + * sends a node to the back (below all other nodes) + * @method sendToBack + **/ + LGraphCanvas.prototype.sendToBack = function(node) { + var i = this.graph._nodes.indexOf(node); + if (i == -1) { + return; + } + + this.graph._nodes.splice(i, 1); + this.graph._nodes.unshift(node); + }; + + /* Interaction */ + + /* LGraphCanvas render */ + var temp = new Float32Array(4); + + /** + * checks which nodes are visible (inside the camera area) + * @method computeVisibleNodes + **/ + LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { + var visible_nodes = out || []; + visible_nodes.length = 0; + nodes = nodes || this.graph._nodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var n = nodes[i]; + + //skip rendering nodes in live mode + if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { + continue; + } + + if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) { + continue; + } //out of the visible area + + visible_nodes.push(n); + } + return visible_nodes; + }; + + /** + * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) + * @method draw + **/ + LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { + return; + } + + //fps counting + var now = LiteGraph.getTime(); + this.render_time = (now - this.last_draw_time) * 0.001; + this.last_draw_time = now; + + if (this.graph) { + this.ds.computeVisibleArea(this.viewport); + } + + if ( + this.dirty_bgcanvas || + force_bgcanvas || + this.always_render_background || + (this.graph && + this.graph._last_trigger_time && + now - this.graph._last_trigger_time < 1000) + ) { + this.drawBackCanvas(); + } + + if (this.dirty_canvas || force_canvas) { + this.drawFrontCanvas(); + } + + this.fps = this.render_time ? 1.0 / this.render_time : 0; + this.frame += 1; + }; + + /** + * draws the front canvas (the one containing all the nodes) + * @method drawFrontCanvas + **/ + LGraphCanvas.prototype.drawFrontCanvas = function() { + this.dirty_canvas = false; + + if (!this.ctx) { + this.ctx = this.bgcanvas.getContext("2d"); + } + var ctx = this.ctx; + if (!ctx) { + //maybe is using webgl... + return; + } + + var canvas = this.canvas; + if ( ctx.start2D && !this.viewport ) { + ctx.start2D(); + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + + //clip dirty area if there is one, otherwise work in full canvas + var area = this.viewport || this.dirty_area; + if (area) { + ctx.save(); + ctx.beginPath(); + ctx.rect( area[0],area[1],area[2],area[3] ); + ctx.clip(); + } + + //clear + //canvas.width = canvas.width; + if (this.clear_background) { + if(area) + ctx.clearRect( area[0],area[1],area[2],area[3] ); + else + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + + //draw bg canvas + if (this.bgcanvas == this.canvas) { + this.drawBackCanvas(); + } else { + ctx.drawImage( this.bgcanvas, 0, 0 ); + } + + //rendering + if (this.onRender) { + this.onRender(canvas, ctx); + } + + //info widget + if (this.show_info) { + this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); + } + + if (this.graph) { + //apply transformations + ctx.save(); + this.ds.toCanvasContext(ctx); + + //draw nodes + var drawn_nodes = 0; + var visible_nodes = this.computeVisibleNodes( + null, + this.visible_nodes + ); + + for (var i = 0; i < visible_nodes.length; ++i) { + var node = visible_nodes[i]; + + //transform coords system + ctx.save(); + ctx.translate(node.pos[0], node.pos[1]); + + //Draw + this.drawNode(node, ctx); + drawn_nodes += 1; + + //Restore + ctx.restore(); + } + + //on top (debug) + if (this.render_execution_order) { + this.drawExecutionOrder(ctx); + } + + //connections ontop? + if (this.graph.config.links_ontop) { + if (!this.live_mode) { + this.drawConnections(ctx); + } + } + + //current connection (the one being dragged by the mouse) + if (this.connecting_pos != null) { + ctx.lineWidth = this.connections_width; + var link_color = null; + + var connInOrOut = this.connecting_output || this.connecting_input; + + var connType = connInOrOut.type; + var connDir = connInOrOut.dir; + if(connDir == null) + { + if (this.connecting_output) { + var outputs_horizontal = this.connecting_node.outputs_horizontal !== undefined && this.connecting_node.outputs_horizontal !== null ? + this.connecting_node.outputs_horizontal : this.connecting_node.horizontal; + connDir = outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; + } else { + var inputs_horizontal = this.connecting_node.inputs_horizontal !== undefined && this.connecting_node.inputs_horizontal !== null ? + this.connecting_node.inputs_horizontal : this.connecting_node.horizontal; + connDir = inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT; + } + } + var connShape = connInOrOut.shape; + + switch (connType) { + case LiteGraph.EVENT: + link_color = LiteGraph.EVENT_LINK_COLOR; + break; + default: + link_color = LiteGraph.CONNECTING_LINK_COLOR; + } + + //the connection being dragged by the mouse + this.renderLink( + ctx, + this.connecting_pos, + [this.graph_mouse[0], this.graph_mouse[1]], + null, + false, + null, + link_color, + connDir, + LiteGraph.CENTER + ); + + ctx.beginPath(); + if ( + connType === LiteGraph.EVENT || + connShape === LiteGraph.BOX_SHAPE + ) { + ctx.rect( + this.connecting_pos[0] - 6 + 0.5, + this.connecting_pos[1] - 5 + 0.5, + 14, + 10 + ); + ctx.fill(); + ctx.beginPath(); + ctx.rect( + this.graph_mouse[0] - 6 + 0.5, + this.graph_mouse[1] - 5 + 0.5, + 14, + 10 + ); + } else if (connShape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); + ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); + ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); + ctx.closePath(); + } + else { + ctx.arc( + this.connecting_pos[0], + this.connecting_pos[1], + 4, + 0, + Math.PI * 2 + ); + ctx.fill(); + ctx.beginPath(); + ctx.arc( + this.graph_mouse[0], + this.graph_mouse[1], + 4, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + + ctx.fillStyle = "#ffcc00"; + if (this._highlight_input) { + ctx.beginPath(); + var shape = this._highlight_input_slot.shape; + if (shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); + ctx.closePath(); + } else { + ctx.arc( + this._highlight_input[0], + this._highlight_input[1], + 6, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + } + if (this._highlight_output) { + ctx.beginPath(); + if (shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); + ctx.closePath(); + } else { + ctx.arc( + this._highlight_output[0], + this._highlight_output[1], + 6, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + } + } + + //the selection rectangle + if (this.dragging_rectangle) { + ctx.strokeStyle = "#FFF"; + ctx.strokeRect( + this.dragging_rectangle[0], + this.dragging_rectangle[1], + this.dragging_rectangle[2], + this.dragging_rectangle[3] + ); + } + + //on top of link center + if(this.over_link_center && this.render_link_tooltip) + this.drawLinkTooltip( ctx, this.over_link_center ); + else + if(this.onDrawLinkTooltip) //to remove + this.onDrawLinkTooltip(ctx,null); + + //custom info + if (this.onDrawForeground) { + this.onDrawForeground(ctx, this.visible_rect); + } + + ctx.restore(); + } + + //draws panel in the corner + if (this._graph_stack && this._graph_stack.length) { + this.drawSubgraphPanel( ctx ); + } + + + if (this.onDrawOverlay) { + this.onDrawOverlay(ctx); + } + + if (area){ + ctx.restore(); + } + + if (ctx.finish2D) { + //this is a function I use in webgl renderer + ctx.finish2D(); + } + }; + + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { + var subgraph = this.graph; + var subnode = subgraph._subgraph_node; + if (!subnode) { + console.warn("subgraph without subnode"); + return; + } + this.drawSubgraphPanelLeft(subgraph, subnode, ctx) + this.drawSubgraphPanelRight(subgraph, subnode, ctx) + } + + LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { + var num = subnode.inputs ? subnode.inputs.length : 0; + var w = 200; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + ctx.fillText("Graph Inputs", 20, 34); + // var pos = this.mouse; + + if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "14px Arial"; + if (subnode.inputs) + for (var i = 0; i < subnode.inputs.length; ++i) { + var input = subnode.inputs[i]; + if (input.not_subgraph_input) + continue; + + //input button clicked + if (this.drawButton(20, y + 2, w - 20, h - 2)) { + var type = subnode.constructor.input_node_type || "graph/input"; + this.graph.beforeChange(); + var newnode = LiteGraph.createNode(type); + if (newnode) { + subgraph.add(newnode); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name", input.name); + newnode.setProperty("type", input.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + this.graph.afterChange(); + } + else + console.error("graph input node not found:", type); + } + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = "#AAA"; + ctx.fillText(input.name, 30, y + h * 0.75); + // var tw = ctx.measureText(input.name); + ctx.fillStyle = "#777"; + ctx.fillText(input.type, 130, y + h * 0.75); + y += h; + } + //add + button + if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { + this.showSubgraphPropertiesDialog(subnode); + } + } + LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { + var num = subnode.outputs ? subnode.outputs.length : 0; + var canvas_w = this.bgcanvas.width + var w = 200; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + var title_text = "Graph Outputs" + var tw = ctx.measureText(title_text).width + ctx.fillText(title_text, (canvas_w - tw) - 20, 34); + // var pos = this.mouse; + if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "14px Arial"; + if (subnode.outputs) + for (var i = 0; i < subnode.outputs.length; ++i) { + var output = subnode.outputs[i]; + if (output.not_subgraph_input) + continue; + + //output button clicked + if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { + var type = subnode.constructor.output_node_type || "graph/output"; + this.graph.beforeChange(); + var newnode = LiteGraph.createNode(type); + if (newnode) { + subgraph.add(newnode); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name", output.name); + newnode.setProperty("type", output.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + this.graph.afterChange(); + } + else + console.error("graph input node not found:", type); + } + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = "#AAA"; + ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); + // var tw = ctx.measureText(input.name); + ctx.fillStyle = "#777"; + ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); + y += h; + } + //add + button + if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { + this.showSubgraphPropertiesDialogRight(subnode); + } + } + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) + { + var ctx = this.ctx; + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; + hovercolor = hovercolor || "#555"; + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; + var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; + if(pos) { + var rect = this.canvas.getBoundingClientRect(); + pos[0] -= rect.left; + pos[1] -= rect.top; + } + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + + ctx.fillStyle = hover ? hovercolor : bgcolor; + if(clicked) + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.roundRect(x,y,w,h,[4] ); + ctx.fill(); + + if(text != null) + { + if(text.constructor == String) + { + ctx.fillStyle = textcolor; + ctx.textAlign = "center"; + ctx.font = ((h * 0.65)|0) + "px Arial"; + ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); + ctx.textAlign = "left"; + } + } + + var was_clicked = clicked && !this.block_click; + if(clicked) + this.blockClick(); + return was_clicked; + } + + LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) + { + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var was_clicked = clicked && !this.block_click; + if(clicked && hold_click) + this.blockClick(); + return was_clicked; + } + + /** + * draws some useful stats in the corner of the canvas + * @method renderInfo + **/ + LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { + x = x || 10; + y = y || this.canvas.height - 80; + + ctx.save(); + ctx.translate(x, y); + + ctx.font = "10px Arial"; + ctx.fillStyle = "#888"; + ctx.textAlign = "left"; + if (this.graph) { + ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); + ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); + ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); + ctx.fillText("V: " + this.graph._version, 5, 13 * 4); + ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); + } else { + ctx.fillText("No graph selected", 5, 13 * 1); + } + ctx.restore(); + }; + + /** + * draws the back canvas (the one containing the background and the connections) + * @method drawBackCanvas + **/ + LGraphCanvas.prototype.drawBackCanvas = function() { + var canvas = this.bgcanvas; + if ( + canvas.width != this.canvas.width || + canvas.height != this.canvas.height + ) { + canvas.width = this.canvas.width; + canvas.height = this.canvas.height; + } + + if (!this.bgctx) { + this.bgctx = this.bgcanvas.getContext("2d"); + } + var ctx = this.bgctx; + if (ctx.start) { + ctx.start(); + } + + var viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height]; + + //clear + if (this.clear_background) { + ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] ); + } + + //show subgraph stack header + if (this._graph_stack && this._graph_stack.length) { + ctx.save(); + var parent_graph = this._graph_stack[this._graph_stack.length - 1]; + var subgraph_node = this.graph._subgraph_node; + ctx.strokeStyle = subgraph_node.bgcolor; + ctx.lineWidth = 10; + ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); + ctx.lineWidth = 1; + ctx.font = "40px Arial"; + ctx.textAlign = "center"; + ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; + var title = ""; + for (var i = 1; i < this._graph_stack.length; ++i) { + title += + this._graph_stack[i]._subgraph_node.getTitle() + " >> "; + } + ctx.fillText( + title + subgraph_node.getTitle(), + canvas.width * 0.5, + 40 + ); + ctx.restore(); + } + + var bg_already_painted = false; + if (this.onRenderBackground) { + bg_already_painted = this.onRenderBackground(canvas, ctx); + } + + //reset in case of error + if ( !this.viewport ) + { + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + this.visible_links.length = 0; + + if (this.graph) { + //apply transformations + ctx.save(); + this.ds.toCanvasContext(ctx); + + //render BG + if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color ) + { + ctx.fillStyle = this.clear_background_color; + ctx.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ); + } + + if ( + this.background_image && + this.ds.scale > 0.5 && + !bg_already_painted + ) { + if (this.zoom_modify_alpha) { + ctx.globalAlpha = + (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; + } else { + ctx.globalAlpha = this.editor_alpha; + } + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = + if ( + !this._bg_img || + this._bg_img.name != this.background_image + ) { + this._bg_img = new Image(); + this._bg_img.name = this.background_image; + this._bg_img.src = this.background_image; + var that = this; + this._bg_img.onload = function() { + that.draw(true, true); + }; + } + + var pattern = null; + if (this._pattern == null && this._bg_img.width > 0) { + pattern = ctx.createPattern(this._bg_img, "repeat"); + this._pattern_img = this._bg_img; + this._pattern = pattern; + } else { + pattern = this._pattern; + } + if (pattern) { + ctx.fillStyle = pattern; + ctx.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ); + ctx.fillStyle = "transparent"; + } + + ctx.globalAlpha = 1.0; + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled + } + + //groups + if (this.graph._groups.length && !this.live_mode) { + this.drawGroups(canvas, ctx); + } + + if (this.onDrawBackground) { + this.onDrawBackground(ctx, this.visible_area); + } + if (this.onBackgroundRender) { + //LEGACY + console.error( + "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " + ); + this.onBackgroundRender = null; + } + + //DEBUG: show clipping area + //ctx.fillStyle = "red"; + //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); + + //bg + if (this.render_canvas_border) { + ctx.strokeStyle = "#235"; + ctx.strokeRect(0, 0, canvas.width, canvas.height); + } + + if (this.render_connections_shadows) { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 6; + } else { + ctx.shadowColor = "rgba(0,0,0,0)"; + } + + //draw connections + if (!this.live_mode) { + this.drawConnections(ctx); + } + + ctx.shadowColor = "rgba(0,0,0,0)"; + + //restore state + ctx.restore(); + } + + if (ctx.finish) { + ctx.finish(); + } + + this.dirty_bgcanvas = false; + this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas + }; + + var temp_vec2 = new Float32Array(2); + + /** + * draws the given node inside the canvas + * @method drawNode + **/ + LGraphCanvas.prototype.drawNode = function(node, ctx) { + var glow = false; + this.current_node = node; + + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + //shadow and glow + if (node.mouseOver) { + glow = true; + } + + var low_quality = this.ds.scale < 0.6; //zoomed out + + //only render if it forces it to do it + if (this.live_mode) { + if (!node.flags.collapsed) { + ctx.shadowColor = "transparent"; + if (node.onDrawForeground) { + node.onDrawForeground(ctx, this, this.canvas); + } + } + return; + } + + var editor_alpha = this.editor_alpha; + ctx.globalAlpha = editor_alpha; + + if (this.render_shadows && !low_quality) { + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; + ctx.shadowOffsetX = 2 * this.ds.scale; + ctx.shadowOffsetY = 2 * this.ds.scale; + ctx.shadowBlur = 3 * this.ds.scale; + } else { + ctx.shadowColor = "transparent"; + } + + //custom draw collapsed method (draw after shadows because they are affected) + if ( + node.flags.collapsed && + node.onDrawCollapsed && + node.onDrawCollapsed(ctx, this) == true + ) { + return; + } + + //clip if required (mask) + var shape = node._shape || LiteGraph.BOX_SHAPE; + var size = temp_vec2; + temp_vec2.set(node.size); + var horizontal = node.horizontal; // || node.flags.horizontal; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + + if (node.flags.collapsed) { + ctx.font = this.inner_text_font; + var title = node.getTitle ? node.getTitle() : node.title; + if (title != null) { + node._collapsed_width = Math.min( + node.size[0], + ctx.measureText(title).width + + LiteGraph.NODE_TITLE_HEIGHT * 2 + ); //LiteGraph.NODE_COLLAPSED_WIDTH; + size[0] = node._collapsed_width; + size[1] = 0; + } + } + + if (node.clip_area) { + //Start clipping + ctx.save(); + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) { + ctx.rect(0, 0, size[0], size[1]); + } else if (shape == LiteGraph.ROUND_SHAPE) { + ctx.roundRect(0, 0, size[0], size[1], [10]); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5, + 0, + Math.PI * 2 + ); + } + ctx.clip(); + } + + //draw shape + if (node.has_errors) { + bgcolor = "red"; + } + this.drawNodeShape( + node, + ctx, + size, + color, + bgcolor, + node.is_selected, + node.mouseOver + ); + ctx.shadowColor = "transparent"; + + //draw foreground + if (node.onDrawForeground) { + node.onDrawForeground(ctx, this, this.canvas); + } + + //connection slots + ctx.textAlign = inputs_horizontal ? "center" : "left"; + ctx.font = this.inner_text_font; + + var render_text = !low_quality; + + var out_slot = this.connecting_output; + var in_slot = this.connecting_input; + ctx.lineWidth = 1; + + var max_y = 0; + var slot_pos = new Float32Array(2); //to reuse + + //render inputs and outputs + if (!node.flags.collapsed) { + //input connection slots + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + + var slot_type = slot.type; + var slot_shape = slot.shape; + + ctx.globalAlpha = editor_alpha; + //change opacity of incompatible slots when dragging a connection + if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { + ctx.globalAlpha = 0.4 * editor_alpha; + } + + ctx.fillStyle = + slot.link != null + ? slot.color_on || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.input_on + : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.input_off; + + var pos = node.getConnectionPos(true, i, slot_pos); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + } + + ctx.beginPath(); + + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? + } + + var doStroke = true; + + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + if (inputs_horizontal) { + ctx.rect( + pos[0] - 5 + 0.5, + pos[1] - 8 + 0.5, + 10, + 14 + ); + } else { + ctx.rect( + pos[0] - 6 + 0.5, + pos[1] - 5 + 0.5, + 14, + 10 + ); + } + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); + ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; + } else { + if(low_quality) + ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster + else + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + ctx.fill(); + + //render name + if (render_text) { + var text = slot.label != null ? slot.label : slot.name; + if (text) { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if (inputs_horizontal || slot.dir == LiteGraph.UP) { + ctx.fillText(text, pos[0], pos[1] - 10); + } else { + ctx.fillText(text, pos[0] + 10, pos[1] + 5); + } + } + } + } + } + + //output connection slots + + ctx.textAlign = outputs_horizontal ? "center" : "right"; + ctx.strokeStyle = "black"; + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + + var slot_type = slot.type; + var slot_shape = slot.shape; + + //change opacity of incompatible slots when dragging a connection + if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { + ctx.globalAlpha = 0.4 * editor_alpha; + } + + var pos = node.getConnectionPos(false, i, slot_pos); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + } + + ctx.fillStyle = + slot.links && slot.links.length + ? slot.color_on || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.output_on + : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.output_off; + ctx.beginPath(); + //ctx.rect( node.size[0] - 14,i*14,10,10); + + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; + } + + var doStroke = true; + + if ( + slot_type === LiteGraph.EVENT || + slot_shape === LiteGraph.BOX_SHAPE + ) { + if (outputs_horizontal) { + ctx.rect( + pos[0] - 5 + 0.5, + pos[1] - 8 + 0.5, + 10, + 14 + ); + } else { + ctx.rect( + pos[0] - 6 + 0.5, + pos[1] - 5 + 0.5, + 14, + 10 + ); + } + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); + ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; + } else { + if(low_quality) + ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); + else + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + if(!low_quality && doStroke) + ctx.stroke(); + + //render output name + if (render_text) { + var text = slot.label != null ? slot.label : slot.name; + if (text) { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if (outputs_horizontal || slot.dir == LiteGraph.DOWN) { + ctx.fillText(text, pos[0], pos[1] - 8); + } else { + ctx.fillText(text, pos[0] - 10, pos[1] + 5); + } + } + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1; + + if (node.widgets) { + var widgets_y = max_y; + if (inputs_horizontal || outputs_horizontal || node.widgets_up) { + widgets_y = 2; + } + if( node.widgets_start_y != null ) + widgets_y = node.widgets_start_y; + this.drawNodeWidgets( + node, + widgets_y, + ctx, + this.node_widget && this.node_widget[0] == node + ? this.node_widget[1] + : null + ); + } + } else if (this.render_collapsed_slots) { + //if collapsed + var input_slot = null; + var output_slot = null; + + //get first connected slot to render + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + if (slot.link == null) { + continue; + } + input_slot = slot; + break; + } + } + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + if (!slot.links || !slot.links.length) { + continue; + } + output_slot = slot; + } + } + + if (input_slot) { + var x = 0; + var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + // For collapsed nodes, use inputs_horizontal if available, otherwise fall back to general horizontal + var input_is_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + if (input_is_horizontal) { + x = node._collapsed_width * 0.5; + y = -LiteGraph.NODE_TITLE_HEIGHT; + } + ctx.fillStyle = "#686"; + ctx.beginPath(); + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(x + 8, y); + ctx.lineTo(x + -4, y - 4); + ctx.lineTo(x + -4, y + 4); + ctx.closePath(); + } else { + ctx.arc(x, y, 4, 0, Math.PI * 2); + } + ctx.fill(); + } + + if (output_slot) { + var x = node._collapsed_width; + var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + // For collapsed nodes, use outputs_horizontal if available, otherwise fall back to general horizontal + var output_is_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + if (output_is_horizontal) { + x = node._collapsed_width * 0.5; + y = 0; + } + ctx.fillStyle = "#686"; + ctx.strokeStyle = "black"; + ctx.beginPath(); + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(x + 6, y); + ctx.lineTo(x - 6, y - 4); + ctx.lineTo(x - 6, y + 4); + ctx.closePath(); + } else { + ctx.arc(x, y, 4, 0, Math.PI * 2); + } + ctx.fill(); + //ctx.stroke(); + } + } + + if (node.clip_area) { + ctx.restore(); + } + + ctx.globalAlpha = 1.0; + }; + + //used by this.over_link_center + LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) + { + var pos = link._pos; + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); + ctx.fill(); + + if(link.data == null) + return; + + if(this.onDrawLinkTooltip) + if( this.onDrawLinkTooltip(ctx,link,this) == true ) + return; + + var data = link.data; + var text = null; + + if( data.constructor === Number ) + text = data.toFixed(2); + else if( data.constructor === String ) + text = "\"" + data + "\""; + else if( data.constructor === Boolean ) + text = String(data); + else if (data.toToolTip) + text = data.toToolTip(); + else + text = "[" + data.constructor.name + "]"; + + if(text == null) + return; + text = text.substr(0,30); //avoid weird + + ctx.font = "14px Courier New"; + var info = ctx.measureText(text); + var w = info.width + 20; + var h = 24; + ctx.shadowColor = "black"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 3; + ctx.fillStyle = "#454"; + ctx.beginPath(); + ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); + ctx.moveTo( pos[0] - 10, pos[1] - 15 ); + ctx.lineTo( pos[0] + 10, pos[1] - 15 ); + ctx.lineTo( pos[0], pos[1] - 5 ); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.textAlign = "center"; + ctx.fillStyle = "#CEC"; + ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); + } + + /** + * draws the shape of the given node in the canvas + * @method drawNodeShape + **/ + var tmp_area = new Float32Array(4); + + LGraphCanvas.prototype.drawNodeShape = function( + node, + ctx, + size, + fgcolor, + bgcolor, + selected, + mouse_over + ) { + //bg rect + ctx.strokeStyle = fgcolor; + ctx.fillStyle = bgcolor; + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + var low_quality = this.ds.scale < 0.5; + + //render node area depending on shape + var shape = + node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; + + var title_mode = node.constructor.title_mode; + + var render_title = true; + if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { + render_title = false; + } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { + render_title = true; + } + + var area = tmp_area; + area[0] = 0; //x + area[1] = render_title ? -title_height : 0; //y + area[2] = size[0] + 1; //w + area[3] = render_title ? size[1] + title_height : size[1]; //h + + var old_alpha = ctx.globalAlpha; + + //full node shape + //if(node.flags.collapsed) + { + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE || low_quality) { + ctx.fillRect(area[0], area[1], area[2], area[3]); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + shape == LiteGraph.CARD_SHAPE + ) { + ctx.roundRect( + area[0], + area[1], + area[2], + area[3], + shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] + ); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + + //separator + if(!node.flags.collapsed && render_title) + { + ctx.shadowColor = "transparent"; + ctx.fillStyle = "rgba(0,0,0,0.2)"; + ctx.fillRect(0, -1, area[2], 2); + } + } + ctx.shadowColor = "transparent"; + + if (node.onDrawBackground) { + node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); + } + + //title bg (remember, it is rendered ABOVE the node) + if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { + //title bar + if (node.onDrawTitleBar) { + node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); + } else if ( + title_mode != LiteGraph.TRANSPARENT_TITLE && + (node.constructor.title_color || this.render_title_colored) + ) { + var title_color = node.constructor.title_color || fgcolor; + + if (node.flags.collapsed) { + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; + } + + //* gradient test + if (this.use_gradients) { + var grad = LGraphCanvas.gradients[title_color]; + if (!grad) { + grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); + grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException + grad.addColorStop(1, "#000"); + } + ctx.fillStyle = grad; + } else { + ctx.fillStyle = title_color; + } + + //ctx.globalAlpha = 0.5 * old_alpha; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE || low_quality) { + ctx.rect(0, -title_height, size[0] + 1, title_height); + } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { + ctx.roundRect( + 0, + -title_height, + size[0] + 1, + title_height, + node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] + ); + } + ctx.fill(); + ctx.shadowColor = "transparent"; + } + + var colState = false; + if (LiteGraph.node_box_coloured_by_mode){ + if(LiteGraph.NODE_MODES_COLORS[node.mode]){ + colState = LiteGraph.NODE_MODES_COLORS[node.mode]; + } + } + if (LiteGraph.node_box_coloured_when_on){ + colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); + } + + //title box + var box_size = 10; + if (node.onDrawTitleBox) { + node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + shape == LiteGraph.CIRCLE_SHAPE || + shape == LiteGraph.CARD_SHAPE + ) { + if (low_quality) { + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.arc( + title_height * 0.5, + title_height * -0.5, + box_size * 0.5 + 1, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + if(low_quality) + ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); + else + { + ctx.beginPath(); + ctx.arc( + title_height * 0.5, + title_height * -0.5, + box_size * 0.5, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + } else { + if (low_quality) { + ctx.fillStyle = "black"; + ctx.fillRect( + (title_height - box_size) * 0.5 - 1, + (title_height + box_size) * -0.5 - 1, + box_size + 2, + box_size + 2 + ); + } + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.fillRect( + (title_height - box_size) * 0.5, + (title_height + box_size) * -0.5, + box_size, + box_size + ); + } + ctx.globalAlpha = old_alpha; + + //title text + if (node.onDrawTitleText) { + node.onDrawTitleText( + ctx, + title_height, + size, + this.ds.scale, + this.title_text_font, + selected + ); + } + if (!low_quality) { + ctx.font = this.title_text_font; + var title = String(node.getTitle()); + if (title) { + if (selected) { + ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; + } else { + ctx.fillStyle = + node.constructor.title_text_color || + this.node_title_color; + } + if (node.flags.collapsed) { + ctx.textAlign = "left"; + var measure = ctx.measureText(title); + ctx.fillText( + title.substr(0,20), //avoid urls too long + title_height,// + measure.width * 0.5, + LiteGraph.NODE_TITLE_TEXT_Y - title_height + ); + ctx.textAlign = "left"; + } else { + ctx.textAlign = "left"; + ctx.fillText( + title, + title_height, + LiteGraph.NODE_TITLE_TEXT_Y - title_height + ); + } + } + } + + //subgraph box + if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { + var w = LiteGraph.NODE_TITLE_HEIGHT; + var x = node.size[0] - w; + var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); + ctx.fillStyle = over ? "#888" : "#555"; + if( shape == LiteGraph.BOX_SHAPE || low_quality) + ctx.fillRect(x+2, -w+2, w-4, w-4); + else + { + ctx.beginPath(); + ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); + ctx.fill(); + } + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + } + + //custom title render + if (node.onDrawTitle) { + node.onDrawTitle(ctx); + } + } + + //render selection marker + if (selected) { + if (node.onBounding) { + node.onBounding(area); + } + + if (title_mode == LiteGraph.TRANSPARENT_TITLE) { + area[1] -= title_height; + area[3] += title_height; + } + ctx.lineWidth = 1; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) { + ctx.rect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3] + ); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) + ) { + ctx.roundRect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3], + [this.round_radius * 2] + ); + } else if (shape == LiteGraph.CARD_SHAPE) { + ctx.roundRect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3], + [this.round_radius * 2,2,this.round_radius * 2,2] + ); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5 + 6, + 0, + Math.PI * 2 + ); + } + ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; + ctx.stroke(); + ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; + } + + // these counter helps in conditioning drawing based on if the node has been executed or an action occurred + if (node.execute_triggered>0) node.execute_triggered--; + if (node.action_triggered>0) node.action_triggered--; + }; + + var margin_area = new Float32Array(4); + var link_bounding = new Float32Array(4); + var tempA = new Float32Array(2); + var tempB = new Float32Array(2); + + /** + * draws every connection visible in the canvas + * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time + * @method drawConnections + **/ + LGraphCanvas.prototype.drawConnections = function(ctx) { + var now = LiteGraph.getTime(); + var visible_area = this.visible_area; + margin_area[0] = visible_area[0] - 20; + margin_area[1] = visible_area[1] - 20; + margin_area[2] = visible_area[2] + 40; + margin_area[3] = visible_area[3] + 40; + + //draw connections + ctx.lineWidth = this.connections_width; + + ctx.fillStyle = "#AAA"; + ctx.strokeStyle = "#AAA"; + ctx.globalAlpha = this.editor_alpha; + //for every node + var nodes = this.graph._nodes; + for (var n = 0, l = nodes.length; n < l; ++n) { + var node = nodes[n]; + //for every input (we render just inputs because it is easier as every slot can only have one input) + if (!node.inputs || !node.inputs.length) { + continue; + } + + for (var i = 0; i < node.inputs.length; ++i) { + var input = node.inputs[i]; + if (!input || input.link == null) { + continue; + } + var link_id = input.link; + var link = this.graph.links[link_id]; + if (!link) { + continue; + } + + //find link info + var start_node = this.graph.getNodeById(link.origin_id); + if (start_node == null) { + continue; + } + var start_node_slot = link.origin_slot; + var start_node_slotpos = null; + if (start_node_slot == -1) { + start_node_slotpos = [ + start_node.pos[0] + 10, + start_node.pos[1] + 10 + ]; + } else { + start_node_slotpos = start_node.getConnectionPos( + false, + start_node_slot, + tempA + ); + } + var end_node_slotpos = node.getConnectionPos(true, i, tempB); + + //compute link bounding + link_bounding[0] = start_node_slotpos[0]; + link_bounding[1] = start_node_slotpos[1]; + link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; + link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; + if (link_bounding[2] < 0) { + link_bounding[0] += link_bounding[2]; + link_bounding[2] = Math.abs(link_bounding[2]); + } + if (link_bounding[3] < 0) { + link_bounding[1] += link_bounding[3]; + link_bounding[3] = Math.abs(link_bounding[3]); + } + + //skip links outside of the visible area of the canvas + if (!overlapBounding(link_bounding, margin_area)) { + continue; + } + + var start_slot = start_node.outputs[start_node_slot]; + var end_slot = node.inputs[i]; + if (!start_slot || !end_slot) { + continue; + } + var start_dir = + start_slot.dir || + (start_node.outputs_horizontal !== undefined && start_node.outputs_horizontal !== null ? + (start_node.outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT) : + (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT)); + var end_dir = + end_slot.dir || + (node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? + (node.inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT) : + (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT)); + + this.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + link, + false, + 0, + null, + start_dir, + end_dir + ); + + //event triggered rendered on top + if (link && link._last_time && now - link._last_time < 1000) { + var f = 2.0 - (now - link._last_time) * 0.002; + var tmp = ctx.globalAlpha; + ctx.globalAlpha = tmp * f; + this.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + link, + true, + f, + "white", + start_dir, + end_dir + ); + ctx.globalAlpha = tmp; + } + } + } + ctx.globalAlpha = 1; + }; + + /** + * draws a link between two points + * @method renderLink + * @param {vec2} a start pos + * @param {vec2} b end pos + * @param {Object} link the link object with all the link info + * @param {boolean} skip_border ignore the shadow of the link + * @param {boolean} flow show flow animation (for events) + * @param {string} color the color for the link + * @param {number} start_dir the direction enum + * @param {number} end_dir the direction enum + * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) + **/ + LGraphCanvas.prototype.renderLink = function( + ctx, + a, + b, + link, + skip_border, + flow, + color, + start_dir, + end_dir, + num_sublines + ) { + if (link) { + this.visible_links.push(link); + } + + //choose color + if (!color && link) { + color = link.color || LGraphCanvas.link_type_colors[link.type]; + } + if (!color) { + color = this.default_link_color; + } + if (link != null && this.highlighted_links[link.id]) { + color = "#FFF"; + } + + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a, b); + + // Auto-detect when to use straight lines for better visual appearance + var should_use_straight = false; + var dx = Math.abs(a[0] - b[0]); + var dy = Math.abs(a[1] - b[1]); + + // Use straight lines when connecting opposite directions and nodes are reasonably aligned + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + // Vertical to vertical connections (like mixed nodes with vertical outputs to vertical inputs) + if ((start_dir == LiteGraph.RIGHT && end_dir == LiteGraph.LEFT) || + (start_dir == LiteGraph.LEFT && end_dir == LiteGraph.RIGHT)) { + // If nodes are roughly vertically aligned (small horizontal distance relative to vertical) + if (dx < 50 && dy > 20) { + should_use_straight = true; + } + } + // Horizontal to horizontal connections + else if ((start_dir == LiteGraph.DOWN && end_dir == LiteGraph.UP) || + (start_dir == LiteGraph.UP && end_dir == LiteGraph.DOWN)) { + // If nodes are roughly horizontally aligned (small vertical distance relative to horizontal) + if (dy < 50 && dx > 20) { + should_use_straight = true; + } + } + } + + if (this.render_connections_border && this.ds.scale > 0.6) { + ctx.lineWidth = this.connections_width + 4; + } + ctx.lineJoin = "round"; + num_sublines = num_sublines || 1; + if (num_sublines > 1) { + ctx.lineWidth = 0.5; + } + + //begin line shape + ctx.beginPath(); + for (var i = 0; i < num_sublines; i += 1) { + var offsety = (i - (num_sublines - 1) * 0.5) * 5; + + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + ctx.moveTo(a[0], a[1] + offsety); + + if (should_use_straight) { + // Use straight line logic for better visual alignment + var start_x = a[0]; + var start_y = a[1] + offsety; + var end_x = b[0]; + var end_y = b[1] + offsety; + + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else if (start_dir == LiteGraph.LEFT) { + start_x -= 10; + } else if (start_dir == LiteGraph.DOWN) { + start_y += 10; + } else if (start_dir == LiteGraph.UP) { + start_y -= 10; + } + + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else if (end_dir == LiteGraph.RIGHT) { + end_x += 10; + } else if (end_dir == LiteGraph.UP) { + end_y -= 10; + } else if (end_dir == LiteGraph.DOWN) { + end_y += 10; + } + + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1] + offsety); + } else { + // Original spline logic + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + start_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + start_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + start_offset_y = dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + end_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + end_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + end_offset_y = dist * 0.25; + break; + } + ctx.bezierCurveTo( + a[0] + start_offset_x, + a[1] + start_offset_y + offsety, + b[0] + end_offset_x, + b[1] + end_offset_y + offsety, + b[0], + b[1] + offsety + ); + } + } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { + ctx.moveTo(a[0], a[1] + offsety); + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = -1; + break; + case LiteGraph.RIGHT: + start_offset_x = 1; + break; + case LiteGraph.UP: + start_offset_y = -1; + break; + case LiteGraph.DOWN: + start_offset_y = 1; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = -1; + break; + case LiteGraph.RIGHT: + end_offset_x = 1; + break; + case LiteGraph.UP: + end_offset_y = -1; + break; + case LiteGraph.DOWN: + end_offset_y = 1; + break; + } + var l = 15; + ctx.lineTo( + a[0] + start_offset_x * l, + a[1] + start_offset_y * l + offsety + ); + ctx.lineTo( + b[0] + end_offset_x * l, + b[1] + end_offset_y * l + offsety + ); + ctx.lineTo(b[0], b[1] + offsety); + } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { + ctx.moveTo(a[0], a[1]); + var start_x = a[0]; + var start_y = a[1]; + var end_x = b[0]; + var end_y = b[1]; + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else { + start_y += 10; + } + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else { + end_y -= 10; + } + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1]); + } else { + return; + } //unknown + } + + //rendering the outline of the connection can be a little bit slow + if ( + this.render_connections_border && + this.ds.scale > 0.6 && + !skip_border + ) { + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + } + + ctx.lineWidth = this.connections_width; + ctx.fillStyle = ctx.strokeStyle = color; + ctx.stroke(); + //end line shape + + var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); + if (link && link._pos) { + link._pos[0] = pos[0]; + link._pos[1] = pos[1]; + } + + //render arrow in the middle + if ( + this.ds.scale >= 0.6 && + this.highquality_render && + end_dir != LiteGraph.CENTER + ) { + //render arrow + if (this.render_connection_arrows) { + //compute two points in the connection + var posA = this.computeConnectionPoint( + a, + b, + 0.25, + start_dir, + end_dir + ); + var posB = this.computeConnectionPoint( + a, + b, + 0.26, + start_dir, + end_dir + ); + var posC = this.computeConnectionPoint( + a, + b, + 0.75, + start_dir, + end_dir + ); + var posD = this.computeConnectionPoint( + a, + b, + 0.76, + start_dir, + end_dir + ); + + //compute the angle between them so the arrow points in the right direction + var angleA = 0; + var angleB = 0; + if (this.render_curved_connections) { + angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); + angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); + } else { + angleB = angleA = b[1] > a[1] ? 0 : Math.PI; + } + + //render arrow + ctx.save(); + ctx.translate(posA[0], posA[1]); + ctx.rotate(angleA); + ctx.beginPath(); + ctx.moveTo(-5, -3); + ctx.lineTo(0, +7); + ctx.lineTo(+5, -3); + ctx.fill(); + ctx.restore(); + ctx.save(); + ctx.translate(posC[0], posC[1]); + ctx.rotate(angleB); + ctx.beginPath(); + ctx.moveTo(-5, -3); + ctx.lineTo(0, +7); + ctx.lineTo(+5, -3); + ctx.fill(); + ctx.restore(); + } + + //circle + ctx.beginPath(); + ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); + ctx.fill(); + } + + //render flowing points + if (flow) { + ctx.fillStyle = color; + for (var i = 0; i < 5; ++i) { + var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; + var pos = this.computeConnectionPoint( + a, + b, + f, + start_dir, + end_dir + ); + ctx.beginPath(); + ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); + ctx.fill(); + } + } + }; + + //returns the link center point based on curvature + LGraphCanvas.prototype.computeConnectionPoint = function( + a, + b, + t, + start_dir, + end_dir + ) { + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a, b); + var p0 = a; + var p1 = [a[0], a[1]]; + var p2 = [b[0], b[1]]; + var p3 = b; + + switch (start_dir) { + case LiteGraph.LEFT: + p1[0] += dist * -0.25; + break; + case LiteGraph.RIGHT: + p1[0] += dist * 0.25; + break; + case LiteGraph.UP: + p1[1] += dist * -0.25; + break; + case LiteGraph.DOWN: + p1[1] += dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + p2[0] += dist * -0.25; + break; + case LiteGraph.RIGHT: + p2[0] += dist * 0.25; + break; + case LiteGraph.UP: + p2[1] += dist * -0.25; + break; + case LiteGraph.DOWN: + p2[1] += dist * 0.25; + break; + } + + var c1 = (1 - t) * (1 - t) * (1 - t); + var c2 = 3 * ((1 - t) * (1 - t)) * t; + var c3 = 3 * (1 - t) * (t * t); + var c4 = t * t * t; + + var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; + var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; + return [x, y]; + }; + + LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { + ctx.shadowColor = "transparent"; + ctx.globalAlpha = 0.25; + + ctx.textAlign = "center"; + ctx.strokeStyle = "white"; + ctx.globalAlpha = 0.75; + + var visible_nodes = this.visible_nodes; + for (var i = 0; i < visible_nodes.length; ++i) { + var node = visible_nodes[i]; + ctx.fillStyle = "black"; + ctx.fillRect( + node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, + node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT + ); + if (node.order == 0) { + ctx.strokeRect( + node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, + node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, + LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT + ); + } + ctx.fillStyle = "#FFF"; + ctx.fillText( + node.order, + node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, + node.pos[1] - 6 + ); + } + ctx.globalAlpha = 1; + }; + + /** + * draws the widgets stored inside a node + * @method drawNodeWidgets + **/ + LGraphCanvas.prototype.drawNodeWidgets = function( + node, + posY, + ctx, + active_widget + ) { + if (!node.widgets || !node.widgets.length) { + return 0; + } + var width = node.size[0]; + var widgets = node.widgets; + posY += 2; + var H = LiteGraph.NODE_WIDGET_HEIGHT; + var show_text = this.ds.scale > 0.5; + ctx.save(); + ctx.globalAlpha = this.editor_alpha; + var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; + var background_color = LiteGraph.WIDGET_BGCOLOR; + var text_color = LiteGraph.WIDGET_TEXT_COLOR; + var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; + var margin = 15; + + for (var i = 0; i < widgets.length; ++i) { + var w = widgets[i]; + var y = posY; + if (w.y) { + y = w.y; + } + w.last_y = y; + ctx.strokeStyle = outline_color; + ctx.fillStyle = "#222"; + ctx.textAlign = "left"; + //ctx.lineWidth = 2; + if(w.disabled) + ctx.globalAlpha *= 0.5; + var widget_width = w.width || width; + + switch (w.type) { + case "button": + if (w.clicked) { + ctx.fillStyle = "#AAA"; + w.clicked = false; + this.dirty_canvas = true; + } + ctx.fillRect(margin, y, widget_width - margin * 2, H); + if(show_text && !w.disabled) + ctx.strokeRect( margin, y, widget_width - margin * 2, H ); + if (show_text) { + ctx.textAlign = "center"; + ctx.fillStyle = text_color; + ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); + } + break; + case "toggle": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); + else + ctx.rect(margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if(show_text && !w.disabled) + ctx.stroke(); + ctx.fillStyle = w.value ? "#89A" : "#333"; + ctx.beginPath(); + ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); + ctx.fill(); + if (show_text) { + ctx.fillStyle = secondary_text_color; + const label = w.label || w.name; + if (label != null) { + ctx.fillText(label, margin * 2, y + H * 0.7); + } + ctx.fillStyle = w.value ? text_color : secondary_text_color; + ctx.textAlign = "right"; + ctx.fillText( + w.value + ? w.options.on || "true" + : w.options.off || "false", + widget_width - 40, + y + H * 0.7 + ); + } + break; + case "slider": + ctx.fillStyle = background_color; + ctx.fillRect(margin, y, widget_width - margin * 2, H); + var range = w.options.max - w.options.min; + var nvalue = (w.value - w.options.min) / range; + if(nvalue < 0.0) nvalue = 0.0; + if(nvalue > 1.0) nvalue = 1.0; + ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); + ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); + if(show_text && !w.disabled) + ctx.strokeRect(margin, y, widget_width - margin * 2, H); + if (w.marker) { + var marker_nvalue = (w.marker - w.options.min) / range; + if(marker_nvalue < 0.0) marker_nvalue = 0.0; + if(marker_nvalue > 1.0) marker_nvalue = 1.0; + ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; + ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H ); + } + if (show_text) { + ctx.textAlign = "center"; + ctx.fillStyle = text_color; + ctx.fillText( + w.label || w.name + " " + Number(w.value).toFixed( + w.options.precision != null + ? w.options.precision + : 3 + ), + widget_width * 0.5, + y + H * 0.7 + ); + } + break; + case "number": + case "combo": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if(show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] ); + else + ctx.rect(margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if (show_text) { + if(!w.disabled) + ctx.stroke(); + ctx.fillStyle = text_color; + if(!w.disabled) + { + ctx.beginPath(); + ctx.moveTo(margin + 16, y + 5); + ctx.lineTo(margin + 6, y + H * 0.5); + ctx.lineTo(margin + 16, y + H - 5); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(widget_width - margin - 16, y + 5); + ctx.lineTo(widget_width - margin - 6, y + H * 0.5); + ctx.lineTo(widget_width - margin - 16, y + H - 5); + ctx.fill(); + } + ctx.fillStyle = secondary_text_color; + ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); + ctx.fillStyle = text_color; + ctx.textAlign = "right"; + if (w.type == "number") { + ctx.fillText( + Number(w.value).toFixed( + w.options.precision !== undefined + ? w.options.precision + : 3 + ), + widget_width - margin * 2 - 20, + y + H * 0.7 + ); + } else { + var v = w.value; + if( w.options.values ) + { + var values = w.options.values; + if( values.constructor === Function ) + values = values(); + if(values && values.constructor !== Array) + v = values[ w.value ]; + } + ctx.fillText( + v, + widget_width - margin * 2 - 20, + y + H * 0.7 + ); + } + } + break; + case "string": + case "text": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); + else + ctx.rect( margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if (show_text) { + if(!w.disabled) + ctx.stroke(); + ctx.save(); + ctx.beginPath(); + ctx.rect(margin, y, widget_width - margin * 2, H); + ctx.clip(); + + //ctx.stroke(); + ctx.fillStyle = secondary_text_color; + const label = w.label || w.name; + if (label != null) { + ctx.fillText(label, margin * 2, y + H * 0.7); + } + ctx.fillStyle = text_color; + ctx.textAlign = "right"; + ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max + ctx.restore(); + } + break; + default: + if (w.draw) { + w.draw(ctx, node, widget_width, y, H); + } + break; + } + posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; + ctx.globalAlpha = this.editor_alpha; + + } + ctx.restore(); + ctx.textAlign = "left"; + }; + + /** + * process an event on widgets + * @method processNodeWidgets + **/ + LGraphCanvas.prototype.processNodeWidgets = function( + node, + pos, + event, + active_widget + ) { + if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { + return null; + } + + var x = pos[0] - node.pos[0]; + var y = pos[1] - node.pos[1]; + var width = node.size[0]; + var deltaX = event.deltaX || event.deltax || 0; + var that = this; + var ref_window = this.getCanvasWindow(); + + for (var i = 0; i < node.widgets.length; ++i) { + var w = node.widgets[i]; + if(!w || w.disabled) + continue; + var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; + var widget_width = w.width || width; + //outside + if ( w != active_widget && + (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) + continue; + + var old_value = w.value; + + //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { + //inside widget + switch (w.type) { + case "button": + if (event.type === LiteGraph.pointerevents_method+"down") { + if (w.callback) { + setTimeout(function() { + w.callback(w, that, node, pos, event); + }, 20); + } + w.clicked = true; + this.dirty_canvas = true; + } + break; + case "slider": + var old_value = w.value; + var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); + if(w.options.read_only) break; + w.value = w.options.min + (w.options.max - w.options.min) * nvalue; + if (old_value != w.value) { + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + this.dirty_canvas = true; + break; + case "number": + case "combo": + var old_value = w.value; + if (event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") { + if(deltaX) + w.value += deltaX * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (event.type == LiteGraph.pointerevents_method+"down") { + var values = w.options.values; + if (values && values.constructor === Function) { + values = w.options.values(w, node); + } + var values_list = null; + + if( w.type != "number") + values_list = values.constructor === Array ? values : Object.keys(values); + + var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + if (w.type == "number") { + w.value += delta * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (delta) { //clicked in arrow, used for combos + var index = -1; + this.last_mouseclick = 0; //avoids dobl click event + if(values.constructor === Object) + index = values_list.indexOf( String( w.value ) ) + delta; + else + index = values_list.indexOf( w.value ) + delta; + if (index >= values_list.length) { + index = values_list.length - 1; + } + if (index < 0) { + index = 0; + } + if( values.constructor === Array ) + w.value = values[index]; + else + w.value = index; + } else { //combo clicked + var text_values = values != values_list ? Object.values(values) : values; + var menu = new LiteGraph.ContextMenu(text_values, { + scale: Math.max(1, this.ds.scale), + event: event, + className: "dark", + callback: inner_clicked.bind(w) + }, + ref_window); + function inner_clicked(v, option, event) { + if(values != values_list) + v = text_values.indexOf(v); + this.value = v; + inner_value_change(this, v); + that.dirty_canvas = true; + return false; + } + } + } //end mousedown + else if(event.type == LiteGraph.pointerevents_method+"up" && w.type == "number") + { + var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + if (event.click_time < 200 && delta == 0) { + this.prompt("Value",w.value,function(v) { + // check if v is a valid equation or a number + if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { + try {//solve the equation if possible + v = eval(v); + } catch (e) { } + } + this.value = Number(v); + inner_value_change(this, this.value); + }.bind(w), + event); + } + } + + if( old_value != w.value ) + setTimeout( + function() { + inner_value_change(this, this.value); + }.bind(w), + 20 + ); + this.dirty_canvas = true; + break; + case "toggle": + if (event.type == LiteGraph.pointerevents_method+"down") { + w.value = !w.value; + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + break; + case "string": + case "text": + if (event.type == LiteGraph.pointerevents_method+"down") { + this.prompt("Value",w.value,function(v) { + inner_value_change(this, v); + }.bind(w), + event,w.options ? w.options.multiline : false ); + } + break; + default: + if (w.mouse) { + this.dirty_canvas = w.mouse(event, [x, y], node); + } + break; + } //end switch + + //value changed + if( old_value != w.value ) + { + if(node.onWidgetChanged) + node.onWidgetChanged( w.name,w.value,old_value,w ); + node.graph._version++; + } + + return w; + }//end for + + function inner_value_change(widget, value) { + if(widget.type == "number"){ + value = Number(value); + } + widget.value = value; + if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { + node.setProperty( widget.options.property, value ); + } + if (widget.callback) { + widget.callback(widget.value, that, node, pos, event); + } + } + + return null; + }; + + /** + * draws every group area in the background + * @method drawGroups + **/ + LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { + if (!this.graph) { + return; + } + + var groups = this.graph._groups; + + ctx.save(); + ctx.globalAlpha = 0.5 * this.editor_alpha; + + for (var i = 0; i < groups.length; ++i) { + var group = groups[i]; + + if (!overlapBounding(this.visible_area, group._bounding)) { + continue; + } //out of the visible area + + ctx.fillStyle = group.color || "#335"; + ctx.strokeStyle = group.color || "#335"; + var pos = group._pos; + var size = group._size; + ctx.globalAlpha = 0.25 * this.editor_alpha; + ctx.beginPath(); + ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); + ctx.fill(); + ctx.globalAlpha = this.editor_alpha; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); + ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); + ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); + ctx.fill(); + + var font_size = + group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; + ctx.font = font_size + "px Arial"; + ctx.textAlign = "left"; + ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); + } + + ctx.restore(); + }; + + LGraphCanvas.prototype.adjustNodesSize = function() { + var nodes = this.graph._nodes; + for (var i = 0; i < nodes.length; ++i) { + nodes[i].size = nodes[i].computeSize(); + } + this.setDirty(true, true); + }; + + /** + * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode + * @method resize + **/ + LGraphCanvas.prototype.resize = function(width, height) { + if (!width && !height) { + var parent = this.canvas.parentNode; + width = parent.offsetWidth; + height = parent.offsetHeight; + } + + if (this.canvas.width == width && this.canvas.height == height) { + return; + } + + this.canvas.width = width; + this.canvas.height = height; + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + this.setDirty(true, true); + }; + + /** + * switches to live mode (node shapes are not rendered, only the content) + * this feature was designed when graphs where meant to create user interfaces + * @method switchLiveMode + **/ + LGraphCanvas.prototype.switchLiveMode = function(transition) { + if (!transition) { + this.live_mode = !this.live_mode; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + return; + } + + var self = this; + var delta = this.live_mode ? 1.1 : 0.9; + if (this.live_mode) { + this.live_mode = false; + this.editor_alpha = 0.1; + } + + var t = setInterval(function() { + self.editor_alpha *= delta; + self.dirty_canvas = true; + self.dirty_bgcanvas = true; + + if (delta < 1 && self.editor_alpha < 0.01) { + clearInterval(t); + if (delta < 1) { + self.live_mode = true; + } + } + if (delta > 1 && self.editor_alpha > 0.99) { + clearInterval(t); + self.editor_alpha = 1; + } + }, 1); + }; + + LGraphCanvas.prototype.onNodeSelectionChange = function(node) { + return; //disabled + }; + + /* this is an implementation for touch not in production and not ready + */ + /*LGraphCanvas.prototype.touchHandler = function(event) { + //alert("foo"); + var touches = event.changedTouches, + first = touches[0], + type = ""; + + switch (event.type) { + case "touchstart": + type = "mousedown"; + break; + case "touchmove": + type = "mousemove"; + break; + case "touchend": + type = "mouseup"; + break; + default: + return; + } + + //initMouseEvent(type, canBubble, cancelable, view, clickCount, + // screenX, screenY, clientX, clientY, ctrlKey, + // altKey, shiftKey, metaKey, button, relatedTarget); + + // this is eventually a Dom object, get the LGraphCanvas back + if(typeof this.getCanvasWindow == "undefined"){ + var window = this.lgraphcanvas.getCanvasWindow(); + }else{ + var window = this.getCanvasWindow(); + } + + var document = window.document; + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent( + type, + true, + true, + window, + 1, + first.screenX, + first.screenY, + first.clientX, + first.clientY, + false, + false, + false, + false, + 0, //left + null + ); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); + };*/ + + /* CONTEXT MENU ********************/ + + LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var group = new LiteGraph.LGraphGroup(); + group.pos = canvas.convertEventToCanvasOffset(mouse_event); + canvas.graph.add(group); + }; + + /** + * Determines the furthest nodes in each direction + * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.getBoundaryNodes = function(nodes) { + let top = null; + let right = null; + let bottom = null; + let left = null; + for (const nID in nodes) { + const node = nodes[nID]; + const [x, y] = node.pos; + const [width, height] = node.size; + + if (top === null || y < top.pos[1]) { + top = node; + } + if (right === null || x + width > right.pos[0] + right.size[0]) { + right = node; + } + if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { + bottom = node; + } + if (left === null || x < left.pos[0]) { + left = node; + } + } + + return { + "top": top, + "right": right, + "bottom": bottom, + "left": left + }; + } + /** + * Determines the furthest nodes in each direction for the currently selected nodes + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.prototype.boundaryNodesForSelection = function() { + return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); + } + + /** + * + * @param {LGraphNode[]} nodes a list of nodes + * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes + * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) + */ + LGraphCanvas.alignNodes = function (nodes, direction, align_to) { + if (!nodes) { + return; + } + + const canvas = LGraphCanvas.active_canvas; + let boundaryNodes = [] + if (align_to === undefined) { + boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) + } else { + boundaryNodes = { + "top": align_to, + "right": align_to, + "bottom": align_to, + "left": align_to + } + } + + for (const [_, node] of Object.entries(canvas.selected_nodes)) { + switch (direction) { + case "right": + node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; + break; + case "left": + node.pos[0] = boundaryNodes["left"].pos[0]; + break; + case "top": + node.pos[1] = boundaryNodes["top"].pos[1]; + break; + case "bottom": + node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; + break; + } + } + + canvas.dirty_canvas = true; + canvas.dirty_bgcanvas = true; + }; + + LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); + } + } + + LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); + } + } + + LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { + + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + var graph = canvas.graph; + if (!graph) + return; + + function inner_onMenuAdded(base_category ,prev_menu){ + + var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); + var entries = []; + + categories.map(function(category){ + + if (!category) + return; + + var base_category_regex = new RegExp('^(' + base_category + ')'); + var category_name = category.replace(base_category_regex,"").split('/')[0]; + var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; + + var name = category_name; + if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace + name = name.split("::")[1]; + + var index = entries.findIndex(function(entry){return entry.value === category_path}); + if (index === -1) { + entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){ + inner_onMenuAdded(value.value, contextMenu) + }}); + } + + }); + + var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); + nodes.map(function(node){ + + if (node.skip_list) + return; + + var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){ + + var first_event = contextMenu.getFirstEvent(); + canvas.graph.beforeChange(); + var node = LiteGraph.createNode(value.value); + if (node) { + node.pos = canvas.convertEventToCanvasOffset(first_event); + canvas.graph.add(node); + } + if(callback) + callback(node); + canvas.graph.afterChange(); + + } + } + + entries.push(entry); + + }); + + new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); + + } + + inner_onMenuAdded('',prev_menu); + return false; + + }; + + LGraphCanvas.onMenuCollapseAll = function() {}; + + LGraphCanvas.onMenuNodeEdit = function() {}; + + LGraphCanvas.showMenuNodeOptionalInputs = function( + v, + options, + e, + prev_menu, + node + ) { + if (!node) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_inputs; + if (node.onGetInputs) { + options = node.onGetInputs(); + } + + var entries = []; + if (options) { + for (var i=0; i < options.length; i++) { + var entry = options[i]; + if (!entry) { + entries.push(null); + continue; + } + var label = entry[0]; + if(!entry[2]) + entry[2] = {}; + + if (entry[2].label) { + label = entry[2].label; + } + + entry[2].removable = true; + var data = { content: label, value: entry }; + if (entry[1] == LiteGraph.ACTION) { + data.className = "event"; + } + entries.push(data); + } + } + + if (node.onMenuNodeInputs) { + var retEntries = node.onMenuNodeInputs(entries); + if(retEntries) entries = retEntries; + } + + if (!entries.length) { + console.log("no input entries"); + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }, + ref_window + ); + + function inner_clicked(v, e, prev) { + if (!node) { + return; + } + + if (v.callback) { + v.callback.call(that, node, v, e, prev); + } + + if (v.value) { + node.graph.beforeChange(); + node.addInput(v.value[0], v.value[1], v.value[2]); + + if (node.onNodeInputAdd) { // callback to the node when adding a slot + node.onNodeInputAdd(v.value); + } + node.setDirtyCanvas(true, true); + node.graph.afterChange(); + } + } + + return false; + }; + + LGraphCanvas.showMenuNodeOptionalOutputs = function( + v, + options, + e, + prev_menu, + node + ) { + if (!node) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_outputs; + if (node.onGetOutputs) { + options = node.onGetOutputs(); + } + + var entries = []; + if (options) { + for (var i=0; i < options.length; i++) { + var entry = options[i]; + if (!entry) { + //separator? + entries.push(null); + continue; + } + + if ( + node.flags && + node.flags.skip_repeated_outputs && + node.findOutputSlot(entry[0]) != -1 + ) { + continue; + } //skip the ones already on + var label = entry[0]; + if(!entry[2]) + entry[2] = {}; + if (entry[2].label) { + label = entry[2].label; + } + entry[2].removable = true; + var data = { content: label, value: entry }; + if (entry[1] == LiteGraph.EVENT) { + data.className = "event"; + } + entries.push(data); + } + } + + if (this.onMenuNodeOutputs) { + entries = this.onMenuNodeOutputs(entries); + } + if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted + if (node.findOutputSlot("onExecuted") == -1){ + entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} + } + } + // add callback for modifing the menu elements onMenuNodeOutputs + if (node.onMenuNodeOutputs) { + var retEntries = node.onMenuNodeOutputs(entries); + if(retEntries) entries = retEntries; + } + + if (!entries.length) { + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }, + ref_window + ); + + function inner_clicked(v, e, prev) { + if (!node) { + return; + } + + if (v.callback) { + v.callback.call(that, node, v, e, prev); + } + + if (!v.value) { + return; + } + + var value = v.value[1]; + + if ( + value && + (value.constructor === Object || value.constructor === Array) + ) { + //submenu why? + var entries = []; + for (var i in value) { + entries.push({ content: i, value: value[i] }); + } + new LiteGraph.ContextMenu(entries, { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }); + return false; + } else { + node.graph.beforeChange(); + node.addOutput(v.value[0], v.value[1], v.value[2]); + + if (node.onNodeOutputAdd) { // a callback to the node when adding a slot + node.onNodeOutputAdd(v.value); + } + node.setDirtyCanvas(true, true); + node.graph.afterChange(); + } + } + + return false; + }; + + LGraphCanvas.onShowMenuNodeProperties = function( + value, + options, + e, + prev_menu, + node + ) { + if (!node || !node.properties) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var entries = []; + for (var i in node.properties) { + var value = node.properties[i] !== undefined ? node.properties[i] : " "; + if( typeof value == "object" ) + value = JSON.stringify(value); + var info = node.getPropertyInfo(i); + if(info.type == "enum" || info.type == "combo") + value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); + + //value could contain invalid html characters, clean that + value = LGraphCanvas.decodeHTML(value); + entries.push({ + content: + "" + + (info.label ? info.label : i) + + "" + + "" + + value + + "", + value: i + }); + } + if (!entries.length) { + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + allow_html: true, + node: node + }, + ref_window + ); + + function inner_clicked(v, options, e, prev) { + if (!node) { + return; + } + var rect = this.getBoundingClientRect(); + canvas.showEditPropertyValue(node, v.value, { + position: [rect.left, rect.top] + }); + } + + return false; + }; + + LGraphCanvas.decodeHTML = function(str) { + var e = document.createElement("div"); + e.innerText = str; + return e.innerHTML; + }; + + LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { + if (!node) { + return; + } + + var fApplyMultiNode = function(node){ + node.size = node.computeSize(); + if (node.onResize) + node.onResize(node.size); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.prototype.showLinkMenu = function(link, e) { + var that = this; + // console.log(link); + var node_left = that.graph.getNodeById( link.origin_id ); + var node_right = that.graph.getNodeById( link.target_id ); + var fromType = false; + if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; + var destType = false; + if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; + + var options = ["Add Node",null,"Delete",null]; + + + var menu = new LiteGraph.ContextMenu(options, { + event: e, + title: link.data != null ? link.data.constructor.name : null, + callback: inner_clicked + }); + + function inner_clicked(v,options,e) { + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + // console.debug("node autoconnect"); + if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ + return; + } + // leave the connection type checking inside connectByType + if (node_left.connectByType( link.origin_slot, node, fromType )){ + node.connectByType( link.target_slot, node_right, destType ); + node.pos[0] -= node.size[0] * 0.5; + } + }); + break; + + case "Delete": + that.graph.removeLink(link.id); + break; + default: + /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left + ,slotFrom: link.origin_slot + ,nodeTo: node + ,slotTo: link.target_slot + ,e: e + ,nodeType: "AUTO" + }); + if(nodeCreated) console.log("new node in beetween "+v+" created");*/ + } + } + + return false; + }; + + LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,position: [] // pass the event coords + ,nodeType: null // choose a nodetype to add, AUTO to set at first good + ,posAdd:[0,0] // adjust x,y + ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom!==null; + var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; + + if (!isFrom && !isTo){ + console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); + return false; + } + if (!opts.nodeType){ + console.warn("No type to createDefaultNodeForSlot"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "undefined": + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + if (slotX===false || iSlotConn===false){ + console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); + } + + // check for defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + nodeNewType = false; + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ + nodeNewType = slotTypesDefault[fromSlotType][typeX]; + // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); + break; // -------- + } + } + }else{ + if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; + } + if (nodeNewType) { + var nodeNewOpts = false; + if (typeof nodeNewType == "object" && nodeNewType.node){ + nodeNewOpts = nodeNewType; + nodeNewType = nodeNewType.node; + } + + //that.graph.beforeChange(); + + var newNode = LiteGraph.createNode(nodeNewType); + if(newNode){ + // if is object pass options + if (nodeNewOpts){ + if (nodeNewOpts.properties) { + for (var i in nodeNewOpts.properties) { + newNode.addProperty( i, nodeNewOpts.properties[i] ); + } + } + if (nodeNewOpts.inputs) { + newNode.inputs = []; + for (var i in nodeNewOpts.inputs) { + newNode.addOutput( + nodeNewOpts.inputs[i][0], + nodeNewOpts.inputs[i][1] + ); + } + } + if (nodeNewOpts.outputs) { + newNode.outputs = []; + for (var i in nodeNewOpts.outputs) { + newNode.addOutput( + nodeNewOpts.outputs[i][0], + nodeNewOpts.outputs[i][1] + ); + } + } + if (nodeNewOpts.title) { + newNode.title = nodeNewOpts.title; + } + if (nodeNewOpts.json) { + newNode.configure(nodeNewOpts.json); + } + + } + + // add the node + that.graph.add(newNode); + newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) + ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ + + //that.graph.afterChange(); + + // connect the two! + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); + } + + // if connecting in between + if (isFrom && isTo){ + // TODO + } + + return true; + + }else{ + console.log("failed creating "+nodeNewType); + } + } + } + return false; + } + + LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,e: null + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom; + var isTo = !isFrom && opts.nodeTo && opts.slotTo; + + if (!isFrom && !isTo){ + console.warn("No data passed to showConnectionMenu"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + var options = ["Add Node",null]; + + if (that.allow_searchbox){ + options.push("Search"); + options.push(null); + } + + // get defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + options.push(slotTypesDefault[fromSlotType][typeX]); + } + }else{ + options.push(slotTypesDefault[fromSlotType]); + } + } + + // build menu + var menu = new LiteGraph.ContextMenu(options, { + event: opts.e, + title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), + callback: inner_clicked + }); + + // callback + function inner_clicked(v,options,e) { + //console.log("Process showConnectionMenu selection"); + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); + } + }); + break; + case "Search": + if(isFrom){ + that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); + }else{ + that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); + } + break; + default: + // check for defaults nodes for this slottype + var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] + ,nodeType: v + })); + if (nodeCreated){ + // new node created + //console.log("node "+v+" created") + }else{ + // failed or v is not in defaults + } + break; + } + } + + return false; + }; + + // TODO refactor :: this is used fot title but not for properties! + LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { + var input_html = ""; + var property = item.property || "title"; + var value = node[property]; + + // TODO refactor :: use createDialog ? + + var dialog = document.createElement("div"); + dialog.is_modified = false; + dialog.className = "graphdialog"; + dialog.innerHTML = + ""; + dialog.close = function() { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + var title = dialog.querySelector(".name"); + title.innerText = property; + var input = dialog.querySelector(".value"); + if (input) { + input.value = value; + input.addEventListener("blur", function(e) { + this.focus(); + }); + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + } + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner); + canvas.parentNode.appendChild(dialog); + + if(input) input.focus(); + + var dialogCloseTimer = null; + dialog.addEventListener("mouseleave", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + dialog.addEventListener("mouseenter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + + function inner() { + if(input) setValue(input.value); + } + + function setValue(value) { + if (item.type == "Number") { + value = Number(value); + } else if (item.type == "Boolean") { + value = Boolean(value); + } + node[property] = value; + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + node.setDirtyCanvas(true, true); + } + }; + + // refactor: there are different dialogs, some uses createDialog some dont + LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { + var that = this; + var input_html = ""; + title = title || ""; + + var dialog = document.createElement("div"); + dialog.is_modified = false; + dialog.className = "graphdialog rounded"; + if(multiline) + dialog.innerHTML = " "; + else + dialog.innerHTML = " "; + dialog.close = function() { + that.prompt_box = null; + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + canvas.parentNode.appendChild(dialog); + + if (this.ds.scale > 1) { + dialog.style.transform = "scale(" + this.ds.scale + ")"; + } + + var dialogCloseTimer = null; + var prevent_timeout = false; + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout) + return; + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + var selInDia = dialog.querySelectorAll("select"); + if (selInDia){ + // if filtering, check focus changed to comboboxes and prevent closing + selInDia.forEach(function(selIn) { + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + }); + } + + if (that.prompt_box) { + that.prompt_box.close(); + } + that.prompt_box = dialog; + + var first = null; + var timeout = null; + var selected = null; + + var name_element = dialog.querySelector(".name"); + name_element.innerText = title; + var value_element = dialog.querySelector(".value"); + value_element.value = value; + + var input = value_element; + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13 && e.target.localName != "textarea") { + if (callback) { + callback(this.value); + } + dialog.close(); + } else { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + + var button = dialog.querySelector("button"); + button.addEventListener("click", function(e) { + if (callback) { + callback(input.value); + } + that.setDirty(true); + dialog.close(); + }); + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + + setTimeout(function() { + input.focus(); + }, 10); + + return dialog; + }; + + LGraphCanvas.search_limit = -1; + LGraphCanvas.prototype.showSearchBox = function(event, options) { + // proposed defaults + var def_options = { slot_from: null + ,node_from: null + ,node_to: null + ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out + ,type_filter_in: false // these are default: pass to set initially set values + ,type_filter_out: false + ,show_general_if_none_on_typefilter: true + ,show_general_after_typefiltered: true + ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave + ,show_all_if_empty: true + ,show_all_on_open: LiteGraph.search_show_all_on_open + }; + options = Object.assign(def_options, options || {}); + + //console.log(options); + + var that = this; + var input_html = ""; + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + var root_document = canvas.ownerDocument || document; + + var dialog = document.createElement("div"); + dialog.className = "litegraph litesearchbox graphdialog rounded"; + dialog.innerHTML = "Search "; + if (options.do_type_filter){ + dialog.innerHTML += ""; + dialog.innerHTML += ""; + } + dialog.innerHTML += "
"; + + if( root_document.fullscreenElement ) + root_document.fullscreenElement.appendChild(dialog); + else + { + root_document.body.appendChild(dialog); + root_document.body.style.overflow = "hidden"; + } + // dialog element has been appended + + if (options.do_type_filter){ + var selIn = dialog.querySelector(".slot_in_type_filter"); + var selOut = dialog.querySelector(".slot_out_type_filter"); + } + + dialog.close = function() { + that.search_box = null; + this.blur(); + canvas.focus(); + root_document.body.style.overflow = ""; + + setTimeout(function() { + that.canvas.focus(); + }, 20); //important, if canvas loses focus keys wont be captured + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + if (this.ds.scale > 1) { + dialog.style.transform = "scale(" + this.ds.scale + ")"; + } + + // hide on mouse leave + if(options.hide_on_mouse_leave){ + var prevent_timeout = false; + var timeout_close = null; + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if (timeout_close) { + clearTimeout(timeout_close); + timeout_close = null; + } + }); + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout){ + return; + } + timeout_close = setTimeout(function() { + dialog.close(); + }, 500); + }); + // if filtering, check focus changed to comboboxes and prevent closing + if (options.do_type_filter){ + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + selOut.addEventListener("click", function(e) { + prevent_timeout++; + }); + selOut.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selOut.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + } + } + + if (that.search_box) { + that.search_box.close(); + } + that.search_box = dialog; + + var helper = dialog.querySelector(".helper"); + + var first = null; + var timeout = null; + var selected = null; + + var input = dialog.querySelector("input"); + if (input) { + input.addEventListener("blur", function(e) { + if(that.search_box) + this.focus(); + }); + input.addEventListener("keydown", function(e) { + if (e.keyCode == 38) { + //UP + changeSelection(false); + } else if (e.keyCode == 40) { + //DOWN + changeSelection(true); + } else if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + refreshHelper(); + if (selected) { + select(selected.innerHTML); + } else if (first) { + select(first); + } else { + dialog.close(); + } + } else { + if (timeout) { + clearInterval(timeout); + } + timeout = setTimeout(refreshHelper, 250); + return; + } + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + return true; + }); + } + + // if should filter on type, load and fill selected and choose elements if passed + if (options.do_type_filter){ + if (selIn){ + var aSlots = LiteGraph.slot_types_in; + var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; + + if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) + options.type_filter_in = "_event_"; + /* this will filter on * .. but better do it manually in case + else if(options.type_filter_in === "" || options.type_filter_in === 0) + options.type_filter_in = "*";*/ + + for (var iK=0; iK (rect.height - 200)) + helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; + + /* + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + canvas.parentNode.appendChild(dialog); + */ + + input.focus(); + if (options.show_all_on_open) refreshHelper(); + + function select(name) { + if (name) { + if (that.onSearchBoxSelection) { + that.onSearchBoxSelection(name, event, graphcanvas); + } else { + var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; + if (extra) { + name = extra.type; + } + + graphcanvas.graph.beforeChange(); + var node = LiteGraph.createNode(name); + if (node) { + node.pos = graphcanvas.convertEventToCanvasOffset( + event + ); + graphcanvas.graph.add(node, false); + } + + if (extra && extra.data) { + if (extra.data.properties) { + for (var i in extra.data.properties) { + node.addProperty( i, extra.data.properties[i] ); + } + } + if (extra.data.inputs) { + node.inputs = []; + for (var i in extra.data.inputs) { + node.addOutput( + extra.data.inputs[i][0], + extra.data.inputs[i][1] + ); + } + } + if (extra.data.outputs) { + node.outputs = []; + for (var i in extra.data.outputs) { + node.addOutput( + extra.data.outputs[i][0], + extra.data.outputs[i][1] + ); + } + } + if (extra.data.title) { + node.title = extra.data.title; + } + if (extra.data.json) { + node.configure(extra.data.json); + } + + } + + // join node after inserting + if (options.node_from){ + var iS = false; + switch (typeof options.slot_from){ + case "string": + iS = options.node_from.findOutputSlot(options.slot_from); + break; + case "object": + if (options.slot_from.name){ + iS = options.node_from.findOutputSlot(options.slot_from.name); + }else{ + iS = -1; + } + if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; + break; + case "number": + iS = options.slot_from; + break; + default: + iS = 0; // try with first if no name set + } + if (typeof options.node_from.outputs[iS] !== "undefined"){ + if (iS!==false && iS>-1){ + options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); + } + }else{ + // console.warn("cant find slot " + options.slot_from); + } + } + if (options.node_to){ + var iS = false; + switch (typeof options.slot_from){ + case "string": + iS = options.node_to.findInputSlot(options.slot_from); + break; + case "object": + if (options.slot_from.name){ + iS = options.node_to.findInputSlot(options.slot_from.name); + }else{ + iS = -1; + } + if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; + break; + case "number": + iS = options.slot_from; + break; + default: + iS = 0; // try with first if no name set + } + if (typeof options.node_to.inputs[iS] !== "undefined"){ + if (iS!==false && iS>-1){ + // try connection + options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); + } + }else{ + // console.warn("cant find slot_nodeTO " + options.slot_from); + } + } + + graphcanvas.graph.afterChange(); + } + } + + dialog.close(); + } + + function changeSelection(forward) { + var prev = selected; + if (selected) { + selected.classList.remove("selected"); + } + if (!selected) { + selected = forward + ? helper.childNodes[0] + : helper.childNodes[helper.childNodes.length]; + } else { + selected = forward + ? selected.nextSibling + : selected.previousSibling; + if (!selected) { + selected = prev; + } + } + if (!selected) { + return; + } + selected.classList.add("selected"); + selected.scrollIntoView({block: "end", behavior: "smooth"}); + } + + function refreshHelper() { + timeout = null; + var str = input.value; + first = null; + helper.innerHTML = ""; + if (!str && !options.show_all_if_empty) { + return; + } + + if (that.onSearchBox) { + var list = that.onSearchBox(helper, str, graphcanvas); + if (list) { + for (var i = 0; i < list.length; ++i) { + addResult(list[i]); + } + } + } else { + var c = 0; + str = str.toLowerCase(); + var filter = graphcanvas.filter || graphcanvas.graph.filter; + + // filter by type preprocess + if(options.do_type_filter && that.search_box){ + var sIn = that.search_box.querySelector(".slot_in_type_filter"); + var sOut = that.search_box.querySelector(".slot_out_type_filter"); + }else{ + var sIn = false; + var sOut = false; + } + + //extras + for (var i in LiteGraph.searchbox_extras) { + var extra = LiteGraph.searchbox_extras[i]; + if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { + continue; + } + var ctor = LiteGraph.registered_node_types[ extra.type ]; + if( ctor && ctor.filter != filter ) + continue; + if( ! inner_test_filter(extra.type) ) + continue; + addResult( extra.desc, "searchbox_extra" ); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + + var filtered = null; + if (Array.prototype.filter) { //filter supported + var keys = Object.keys( LiteGraph.registered_node_types ); //types + var filtered = keys.filter( inner_test_filter ); + } else { + filtered = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i) ) + filtered.push(i); + } + } + + for (var i = 0; i < filtered.length; i++) { + addResult(filtered[i]); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + + // add general type if filtering + if (options.show_general_after_typefiltered + && (sIn.value || sOut.value) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "generic_type"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + // check il filtering gave no results + if ((sIn.value || sOut.value) && + ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {skipFilter: true}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "not_in_filter"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + function inner_test_filter( type, optsIn ) + { + var optsIn = optsIn || {}; + var optsDef = { skipFilter: false + ,inTypeOverride: false + ,outTypeOverride: false + }; + var opts = Object.assign(optsDef,optsIn); + var ctor = LiteGraph.registered_node_types[ type ]; + if(filter && ctor.filter != filter ) + return false; + if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) + return false; + + // filter by slot IN, OUT types + if(options.do_type_filter && !opts.skipFilter){ + var sType = type; + + var sV = sIn.value; + if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sIn && sV){ + //console.log("will check filter against "+sV); + if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_in_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + + var sV = sOut.value; + if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sOut && sV){ + //console.log("search will check filter against "+sV); + if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_out_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + } + return true; + } + } + + function addResult(type, className) { + var help = document.createElement("div"); + if (!first) { + first = type; + } + help.innerText = type; + help.dataset["type"] = escape(type); + help.className = "litegraph lite-search-item"; + if (className) { + help.className += " " + className; + } + help.addEventListener("click", function(e) { + select(unescape(this.dataset["type"])); + }); + helper.appendChild(help); + } + } + + return dialog; + }; + + LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { + if (!node || node.properties[property] === undefined) { + return; + } + + options = options || {}; + var that = this; + + var info = node.getPropertyInfo(property); + var type = info.type; + + var input_html = ""; + + if (type == "string" || type == "number" || type == "array" || type == "object") { + input_html = ""; + } else if ( (type == "enum" || type == "combo") && info.values) { + input_html = ""; + } else if (type == "boolean" || type == "toggle") { + input_html = + ""; + } else { + console.warn("unknown type: " + type); + return; + } + + var dialog = this.createDialog( + "" + + (info.label ? info.label : property) + + "" + + input_html + + "", + options + ); + + var input = false; + if ((type == "enum" || type == "combo") && info.values) { + input = dialog.querySelector("select"); + input.addEventListener("change", function(e) { + dialog.modified(); + setValue(e.target.value); + //var index = e.target.value; + //setValue( e.options[e.selectedIndex].value ); + }); + } else if (type == "boolean" || type == "toggle") { + input = dialog.querySelector("input"); + if (input) { + input.addEventListener("click", function(e) { + dialog.modified(); + setValue(!!input.checked); + }); + } + } else { + input = dialog.querySelector("input"); + if (input) { + input.addEventListener("blur", function(e) { + this.focus(); + }); + + var v = node.properties[property] !== undefined ? node.properties[property] : ""; + if (type !== 'string') { + v = JSON.stringify(v); + } + + input.value = v; + input.addEventListener("keydown", function(e) { + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + // ENTER + inner(); // save + } else if (e.keyCode != 13) { + dialog.modified(); + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + } + } + if (input) input.focus(); + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner); + + function inner() { + setValue(input.value); + } + + function setValue(value) { + + if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) + value = info.values[value]; + + if (typeof node.properties[property] == "number") { + value = Number(value); + } + if (type == "array" || type == "object") { + value = JSON.parse(value); + } + node.properties[property] = value; + if (node.graph) { + node.graph._version++; + } + if (node.onPropertyChanged) { + node.onPropertyChanged(property, value); + } + if(options.onclose) + options.onclose(); + dialog.close(); + node.setDirtyCanvas(true, true); + } + + return dialog; + }; + + // TODO refactor, theer are different dialog, some uses createDialog, some dont + LGraphCanvas.prototype.createDialog = function(html, options) { + var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; + options = Object.assign(def_options, options || {}); + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = html; + dialog.is_modified = false; + + var rect = this.canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (options.position) { + offsetx += options.position[0]; + offsety += options.position[1]; + } else if (options.event) { + offsetx += options.event.clientX; + offsety += options.event.clientY; + } //centered + else { + offsetx += this.canvas.width * 0.5; + offsety += this.canvas.height * 0.5; + } + + dialog.style.left = offsetx + "px"; + dialog.style.top = offsety + "px"; + + this.canvas.parentNode.appendChild(dialog); + + // acheck for input and use default behaviour: save on enter, close on esc + if (options.checkForInput){ + var aI = []; + var focused = false; + if (aI = dialog.querySelectorAll("input")){ + aI.forEach(function(iX) { + iX.addEventListener("keydown",function(e){ + dialog.modified(); + if (e.keyCode == 27) { + dialog.close(); + } else if (e.keyCode != 13) { + return; + } + // set value ? + e.preventDefault(); + e.stopPropagation(); + }); + if (!focused) iX.focus(); + }); + } + } + + dialog.modified = function(){ + dialog.is_modified = true; + } + dialog.close = function() { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + var dialogCloseTimer = null; + var prevent_timeout = false; + dialog.addEventListener("mouseleave", function(e) { + if (prevent_timeout) + return; + if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + dialog.addEventListener("mouseenter", function(e) { + if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + var selInDia = dialog.querySelectorAll("select"); + if (selInDia){ + // if filtering, check focus changed to comboboxes and prevent closing + selInDia.forEach(function(selIn) { + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + }); + } + + return dialog; + }; + + LGraphCanvas.prototype.createPanel = function(title, options) { + options = options || {}; + + var ref_window = options.window || window; + var root = document.createElement("div"); + root.className = "litegraph dialog"; + root.innerHTML = "
"; + root.header = root.querySelector(".dialog-header"); + + if(options.width) + root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); + if(options.height) + root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); + if(options.closable) + { + var close = document.createElement("span"); + close.innerHTML = "✕"; + close.classList.add("close"); + close.addEventListener("click",function(){ + root.close(); + }); + root.header.appendChild(close); + } + root.title_element = root.querySelector(".dialog-title"); + root.title_element.innerText = title; + root.content = root.querySelector(".dialog-content"); + root.alt_content = root.querySelector(".dialog-alt-content"); + root.footer = root.querySelector(".dialog-footer"); + + root.close = function() + { + if (root.onClose && typeof root.onClose == "function"){ + root.onClose(); + } + if(root.parentNode) + root.parentNode.removeChild(root); + /* XXX CHECK THIS */ + if(this.parentNode){ + this.parentNode.removeChild(this); + } + /* XXX this was not working, was fixed with an IF, check this */ + } + + // function to swap panel content + root.toggleAltContent = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + var vAlt = force ? "none" : "block"; + }else{ + var vTo = root.alt_content.style.display != "block" ? "block" : "none"; + var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; + } + root.alt_content.style.display = vTo; + root.content.style.display = vAlt; + } + + root.toggleFooterVisibility = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + }else{ + var vTo = root.footer.style.display != "block" ? "block" : "none"; + } + root.footer.style.display = vTo; + } + + root.clear = function() + { + this.content.innerHTML = ""; + } + + root.addHTML = function(code, classname, on_footer) + { + var elem = document.createElement("div"); + if(classname) + elem.className = classname; + elem.innerHTML = code; + if(on_footer) + root.footer.appendChild(elem); + else + root.content.appendChild(elem); + return elem; + } + + root.addButton = function( name, callback, options ) + { + var elem = document.createElement("button"); + elem.innerText = name; + elem.options = options; + elem.classList.add("btn"); + elem.addEventListener("click",callback); + root.footer.appendChild(elem); + return elem; + } + + root.addSeparator = function() + { + var elem = document.createElement("div"); + elem.className = "separator"; + root.content.appendChild(elem); + } + + root.addWidget = function( type, name, value, options, callback ) + { + options = options || {}; + var str_value = String(value); + type = type.toLowerCase(); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = options.label || name; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = name; + elem.dataset["type"] = options.type || type; + elem.options = options; + elem.value = value; + + if( type == "code" ) + elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); + else if (type == "boolean") + { + elem.classList.add("boolean"); + if(value) + elem.classList.add("bool-on"); + elem.addEventListener("click", function(){ + //var v = node.properties[this.dataset["property"]]; + //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + var propname = this.dataset["property"]; + this.value = !this.value; + this.classList.toggle("bool-on"); + this.querySelector(".property_value").innerText = this.value ? "true" : "false"; + innerChange(propname, this.value ); + }); + } + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("keydown", function(e){ + if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline + { + e.preventDefault(); + this.blur(); + } + }); + value_element.addEventListener("blur", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + var proptype = this.parentNode.dataset["type"]; + if( proptype == "number") + v = Number(v); + innerChange(propname, v); + }); + } + else if (type == "enum" || type == "combo") { + var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); + value_element.innerText = str_value; + + value_element.addEventListener("click", function(event){ + var values = options.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + //node.setProperty(propname,v); + //graphcanvas.dirty_canvas = true; + elem_that.innerText = v; + innerChange(propname,v); + return false; + } + }); + } + + root.content.appendChild(elem); + + function innerChange(name, value) + { + //console.log("change",name,value); + //that.dirty_canvas = true; + if(options.callback) + options.callback(name,value,options); + if(callback) + callback(name,value,options); + } + + return elem; + } + + if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); + + return root; + }; + + LGraphCanvas.getPropertyPrintableValue = function(value, values) + { + if(!values) + return String(value); + + if(values.constructor === Array) + { + return String(value); + } + + if(values.constructor === Object) + { + var desc_value = ""; + for(var k in values) + { + if(values[k] != value) + continue; + desc_value = k; + break; + } + return String(value) + " ("+desc_value+")"; + } + } + + LGraphCanvas.prototype.closePanels = function(){ + var panel = document.querySelector("#node-panel"); + if(panel) + panel.close(); + var panel = document.querySelector("#option-panel"); + if(panel) + panel.close(); + } + + LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ + if(this.constructor && this.constructor.name == "HTMLDivElement"){ + // assume coming from the menu event click + if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ + console.warn("Canvas not found"); // need a ref to canvas obj + /*console.debug(event); + console.debug(event.target);*/ + return; + } + var graphcanvas = obEv.event.target.lgraphcanvas; + }else{ + // assume called internally + var graphcanvas = this; + } + graphcanvas.closePanels(); + var ref_window = graphcanvas.getCanvasWindow(); + panel = graphcanvas.createPanel("Options",{ + closable: true + ,window: ref_window + ,onOpen: function(){ + graphcanvas.OPTIONPANEL_IS_OPEN = true; + } + ,onClose: function(){ + graphcanvas.OPTIONPANEL_IS_OPEN = false; + graphcanvas.options_panel = null; + } + }); + graphcanvas.options_panel = panel; + panel.id = "option-panel"; + panel.classList.add("settings"); + + function inner_refresh(){ + + panel.content.innerHTML = ""; //clear + + var fUpdate = function(name, value, options){ + switch(name){ + /*case "Render mode": + // Case "".. + if (options.values && options.key){ + var kV = Object.values(options.values).indexOf(value); + if (kV>=0 && options.values[kV]){ + console.debug("update graph options: "+options.key+": "+kV); + graphcanvas[options.key] = kV; + //console.debug(graphcanvas); + break; + } + } + console.warn("unexpected options"); + console.debug(options); + break;*/ + default: + //console.debug("want to update graph options: "+name+": "+value); + if (options && options.key){ + name = options.key; + } + if (options.values){ + value = Object.values(options.values).indexOf(value); + } + //console.debug("update graph option: "+name+": "+value); + graphcanvas[name] = value; + break; + } + }; + + // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement + + var aProps = LiteGraph.availableCanvasOptions; + aProps.sort(); + for(var pI in aProps){ + var pX = aProps[pI]; + panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); + } + + var aLinks = [ graphcanvas.links_render_mode ]; + panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); + + panel.addSeparator(); + + panel.footer.innerHTML = ""; // clear + + } + inner_refresh(); + + graphcanvas.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showShowNodePanel = function( node ) + { + this.SELECTED_NODE = node; + this.closePanels(); + var ref_window = this.getCanvasWindow(); + var that = this; + var graphcanvas = this; + var panel = this.createPanel(node.title || "",{ + closable: true + ,window: ref_window + ,onOpen: function(){ + graphcanvas.NODEPANEL_IS_OPEN = true; + } + ,onClose: function(){ + graphcanvas.NODEPANEL_IS_OPEN = false; + graphcanvas.node_panel = null; + } + }); + graphcanvas.node_panel = panel; + panel.id = "node-panel"; + panel.node = node; + panel.classList.add("settings"); + + function inner_refresh() + { + panel.content.innerHTML = ""; //clear + panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); + + panel.addHTML("

Properties

"); + + var fUpdate = function(name,value){ + graphcanvas.graph.beforeChange(node); + switch(name){ + case "Title": + node.title = value; + break; + case "Mode": + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); + if (kV>=0 && LiteGraph.NODE_MODES[kV]){ + node.changeMode(kV); + }else{ + console.warn("unexpected mode: "+value); + } + break; + case "Color": + if (LGraphCanvas.node_colors[value]){ + node.color = LGraphCanvas.node_colors[value].color; + node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; + }else{ + console.warn("unexpected color: "+value); + } + break; + default: + node.setProperty(name,value); + break; + } + graphcanvas.graph.afterChange(); + graphcanvas.dirty_canvas = true; + }; + + panel.addWidget( "string", "Title", node.title, {}, fUpdate); + + panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); + + var nodeCol = ""; + if (node.color !== undefined){ + nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); + } + + panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); + + for(var pName in node.properties) + { + var value = node.properties[pName]; + var info = node.getPropertyInfo(pName); + var type = info.type || "string"; + + //in case the user wants control over the side panel widget + if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) + continue; + + panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); + } + + panel.addSeparator(); + + if(node.onShowCustomPanelInfo) + node.onShowCustomPanelInfo(panel); + + panel.footer.innerHTML = ""; // clear + panel.addButton("Delete",function(){ + if(node.block_delete) + return; + node.graph.remove(node); + panel.close(); + }).classList.add("delete"); + } + + panel.inner_showCodePad = function( propname ) + { + panel.classList.remove("settings"); + panel.classList.add("centered"); + + + /*if(window.CodeFlask) //disabled for now + { + panel.content.innerHTML = "
"; + var flask = new CodeFlask( "div.code", { language: 'js' }); + flask.updateCode(node.properties[propname]); + flask.onUpdate( function(code) { + node.setProperty(propname, code); + }); + } + else + {*/ + panel.alt_content.innerHTML = ""; + var textarea = panel.alt_content.querySelector("textarea"); + var fDoneWith = function(){ + panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); + panel.toggleFooterVisibility(true); + textarea.parentNode.removeChild(textarea); + panel.classList.add("settings"); + panel.classList.remove("centered"); + inner_refresh(); + } + textarea.value = node.properties[propname]; + textarea.addEventListener("keydown", function(e){ + if(e.code == "Enter" && e.ctrlKey ) + { + node.setProperty(propname, textarea.value); + fDoneWith(); + } + }); + panel.toggleAltContent(true); + panel.toggleFooterVisibility(false); + textarea.style.height = "calc(100% - 40px)"; + /*}*/ + var assign = panel.addButton( "Assign", function(){ + node.setProperty(propname, textarea.value); + fDoneWith(); + }); + panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); + var button = panel.addButton( "Close", fDoneWith); + button.style.float = "right"; + panel.alt_content.appendChild(button); // panel.content.appendChild(button); + } + + inner_refresh(); + + this.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) + { + console.log("showing subgraph properties dialog"); + + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if(old_panel) + old_panel.close(); + + var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() + { + panel.clear(); + + //show currents + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + if(input.not_subgraph_input) + continue; + var html = " "; + var elem = panel.addHTML(html,"subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click",function(e){ + node.removeInput( Number( this.parentNode.dataset["slot"] ) ); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html,"subgraph_property extra", true); + elem.querySelector("button").addEventListener("click", function(e){ + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if(!name || node.findInputSlot(name) != -1) + return; + node.addInput(name,type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + }); + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { + + // console.log("showing subgraph properties dialog"); + var that = this; + // old_panel if old_panel is exist close it + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if (old_panel) + old_panel.close(); + // new panel + var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() { + panel.clear(); + //show currents + if (node.outputs) + for (var i = 0; i < node.outputs.length; ++i) { + var input = node.outputs[i]; + if (input.not_subgraph_output) + continue; + var html = " "; + var elem = panel.addHTML(html, "subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click", function (e) { + node.removeOutput(Number(this.parentNode.dataset["slot"])); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html, "subgraph_property extra", true); + elem.querySelector(".name").addEventListener("keydown", function (e) { + if (e.keyCode == 13) { + addOutput.apply(this) + } + }) + elem.querySelector("button").addEventListener("click", function (e) { + addOutput.apply(this) + }); + function addOutput() { + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if (!name || node.findOutputSlot(name) != -1) + return; + node.addOutput(name, type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + } + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + LGraphCanvas.prototype.checkPanels = function() + { + if(!this.canvas) + return; + var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); + for(var i = 0; i < panels.length; ++i) + { + var panel = panels[i]; + if( !panel.node ) + continue; + if( !panel.node.graph || panel.graph != this.graph ) + panel.close(); + } + } + + LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { + node.graph.beforeChange(/*?*/); + + var fApplyMultiNode = function(node){ + node.collapse(); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); + }; + + LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { + node.pin(); + }; + + LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { + new LiteGraph.ContextMenu( + LiteGraph.NODE_MODES, + { event: e, callback: inner_clicked, parentMenu: menu, node: node } + ); + + function inner_clicked(v) { + if (!node) { + return; + } + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); + var fApplyMultiNode = function(node){ + if (kV>=0 && LiteGraph.NODE_MODES[kV]) + node.changeMode(kV); + else{ + console.warn("unexpected mode: "+v); + node.changeMode(LiteGraph.ALWAYS); + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + } + + return false; + }; + + LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { + if (!node) { + throw "no node for color"; + } + + var values = []; + values.push({ + value: null, + content: + "No color" + }); + + for (var i in LGraphCanvas.node_colors) { + var color = LGraphCanvas.node_colors[i]; + var value = { + value: i, + content: + "" + + i + + "" + }; + values.push(value); + } + new LiteGraph.ContextMenu(values, { + event: e, + callback: inner_clicked, + parentMenu: menu, + node: node + }); + + function inner_clicked(v) { + if (!node) { + return; + } + + var color = v.value ? LGraphCanvas.node_colors[v.value] : null; + + var fApplyColor = function(node){ + if (color) { + if (node.constructor === LiteGraph.LGraphGroup) { + node.color = color.groupcolor; + } else { + node.color = color.color; + node.bgcolor = color.bgcolor; + } + } else { + delete node.color; + delete node.bgcolor; + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyColor(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyColor(graphcanvas.selected_nodes[i]); + } + } + node.setDirtyCanvas(true, true); + } + + return false; + }; + + LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { + if (!node) { + throw "no node passed"; + } + + new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { + event: e, + callback: inner_clicked, + parentMenu: menu, + node: node + }); + + function inner_clicked(v) { + if (!node) { + return; + } + node.graph.beforeChange(/*?*/); //node + + var fApplyMultiNode = function(node){ + node.shape = v; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); //node + node.setDirtyCanvas(true); + } + + return false; + }; + + LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { + if (!node) { + throw "no node passed"; + } + + var graph = node.graph; + graph.beforeChange(); + + + var fApplyMultiNode = function(node){ + if (node.removable === false) { + return; + } + graph.remove(node); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + graph.afterChange(); + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { + var graph = node.graph; + var graphcanvas = LGraphCanvas.active_canvas; + if(!graphcanvas) //?? + return; + + var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); + if( !nodes_list.length ) + nodes_list = [ node ]; + + var subgraph_node = LiteGraph.createNode("graph/subgraph"); + subgraph_node.pos = node.pos.concat(); + graph.add(subgraph_node); + + subgraph_node.buildFromNodes( nodes_list ); + + graphcanvas.deselectAllNodes(); + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { + + node.graph.beforeChange(); + + var newSelected = {}; + + var fApplyMultiNode = function(node){ + if (node.clonable === false) { + return; + } + var newnode = node.clone(); + if (!newnode) { + return; + } + newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; + node.graph.add(newnode); + newSelected[newnode.id] = newnode; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + if(Object.keys(newSelected).length){ + graphcanvas.selectNodes(newSelected); + } + + node.graph.afterChange(); + + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.node_colors = { + red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, + brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, + green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, + blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, + pale_blue: { + color: "#2a363b", + bgcolor: "#3f5159", + groupcolor: "#3f789e" + }, + cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, + purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, + yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, + black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } + }; + + LGraphCanvas.prototype.getCanvasMenuOptions = function() { + var options = null; + var that = this; + if (this.getMenuOptions) { + options = this.getMenuOptions(); + } else { + options = [ + { + content: "Add Node", + has_submenu: true, + callback: LGraphCanvas.onMenuAdd + }, + { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, + //{ content: "Arrange", callback: that.graph.arrange }, + //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + ]; + /*if (LiteGraph.showCanvasOptions){ + options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); + }*/ + + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align", + has_submenu: true, + callback: LGraphCanvas.onGroupAlign, + }) + } + + if (this._graph_stack && this._graph_stack.length > 0) { + options.push(null, { + content: "Close subgraph", + callback: this.closeSubgraph.bind(this) + }); + } + } + + if (this.getExtraMenuOptions) { + var extra = this.getExtraMenuOptions(this, options); + if (extra) { + options = options.concat(extra); + } + } + + return options; + }; + + //called by processContextMenu to extract the menu list + LGraphCanvas.prototype.getNodeMenuOptions = function(node) { + var options = null; + + if (node.getMenuOptions) { + options = node.getMenuOptions(this); + } else { + options = [ + { + content: "Inputs", + has_submenu: true, + disabled: true, + callback: LGraphCanvas.showMenuNodeOptionalInputs + }, + { + content: "Outputs", + has_submenu: true, + disabled: true, + callback: LGraphCanvas.showMenuNodeOptionalOutputs + }, + null, + { + content: "Properties", + has_submenu: true, + callback: LGraphCanvas.onShowMenuNodeProperties + }, + null, + { + content: "Title", + callback: LGraphCanvas.onShowPropertyEditor + }, + { + content: "Mode", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeMode + }]; + if(node.resizable !== false){ + options.push({ + content: "Resize", callback: LGraphCanvas.onMenuResizeNode + }); + } + options.push( + { + content: "Collapse", + callback: LGraphCanvas.onMenuNodeCollapse + }, + { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, + { + content: "Colors", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeColors + }, + { + content: "Shapes", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeShapes + }, + null + ); + } + + if (node.onGetInputs) { + var inputs = node.onGetInputs(); + if (inputs && inputs.length) { + options[0].disabled = false; + } + } + + if (node.onGetOutputs) { + var outputs = node.onGetOutputs(); + if (outputs && outputs.length) { + options[1].disabled = false; + } + } + + if (node.getExtraMenuOptions) { + var extra = node.getExtraMenuOptions(this, options); + if (extra) { + extra.push(null); + options = extra.concat(options); + } + } + + if (node.clonable !== false) { + options.push({ + content: "Clone", + callback: LGraphCanvas.onMenuNodeClone + }); + } + + if(0) //TODO + options.push({ + content: "To Subgraph", + callback: LGraphCanvas.onMenuNodeToSubgraph + }); + + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align Selected To", + has_submenu: true, + callback: LGraphCanvas.onNodeAlign, + }) + } + + options.push(null, { + content: "Remove", + disabled: !(node.removable !== false && !node.block_delete ), + callback: LGraphCanvas.onMenuNodeRemove + }); + + if (node.graph && node.graph.onGetNodeMenuOptions) { + node.graph.onGetNodeMenuOptions(options, node); + } + + return options; + }; + + LGraphCanvas.prototype.getGroupMenuOptions = function(node) { + var o = [ + { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, + { + content: "Color", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeColors + }, + { + content: "Font size", + property: "font_size", + type: "Number", + callback: LGraphCanvas.onShowPropertyEditor + }, + null, + { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } + ]; + + return o; + }; + + LGraphCanvas.prototype.processContextMenu = function(node, event) { + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var menu_info = null; + var options = { + event: event, + callback: inner_option_clicked, + extra: node + }; + + if(node) + options.title = node.type; + + //check if mouse is in input + var slot = null; + if (node) { + slot = node.getSlotInPosition(event.canvasX, event.canvasY); + LGraphCanvas.active_node = node; + } + + if (slot) { + //on slot + menu_info = []; + if (node.getSlotMenuOptions) { + menu_info = node.getSlotMenuOptions(slot); + } else { + if ( + slot && + slot.output && + slot.output.links && + slot.output.links.length + ) { + menu_info.push({ content: "Disconnect Links", slot: slot }); + } + var _slot = slot.input || slot.output; + if (_slot.removable){ + menu_info.push( + _slot.locked + ? "Cannot remove" + : { content: "Remove Slot", slot: slot } + ); + } + if (!_slot.nameLocked){ + menu_info.push({ content: "Rename Slot", slot: slot }); + } + + } + options.title = + (slot.input ? slot.input.type : slot.output.type) || "*"; + if (slot.input && slot.input.type == LiteGraph.ACTION) { + options.title = "Action"; + } + if (slot.output && slot.output.type == LiteGraph.EVENT) { + options.title = "Event"; + } + } else { + if (node) { + //on node + menu_info = this.getNodeMenuOptions(node); + } else { + menu_info = this.getCanvasMenuOptions(); + var group = this.graph.getGroupOnPos( + event.canvasX, + event.canvasY + ); + if (group) { + //on group + menu_info.push(null, { + content: "Edit Group", + has_submenu: true, + submenu: { + title: "Group", + extra: group, + options: this.getGroupMenuOptions(group) + } + }); + } + } + } + + //show menu + if (!menu_info) { + return; + } + + var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); + + function inner_option_clicked(v, options, e) { + if (!v) { + return; + } + + if (v.content == "Remove Slot") { + var info = v.slot; + node.graph.beforeChange(); + if (info.input) { + node.removeInput(info.slot); + } else if (info.output) { + node.removeOutput(info.slot); + } + node.graph.afterChange(); + return; + } else if (v.content == "Disconnect Links") { + var info = v.slot; + node.graph.beforeChange(); + if (info.output) { + node.disconnectOutput(info.slot); + } else if (info.input) { + node.disconnectInput(info.slot); + } + node.graph.afterChange(); + return; + } else if (v.content == "Rename Slot") { + var info = v.slot; + var slot_info = info.input + ? node.getInputInfo(info.slot) + : node.getOutputInfo(info.slot); + var dialog = that.createDialog( + "Name", + options + ); + var input = dialog.querySelector("input"); + if (input && slot_info) { + input.value = slot_info.label || ""; + } + var inner = function(){ + node.graph.beforeChange(); + if (input.value) { + if (slot_info) { + slot_info.label = input.value; + } + that.setDirty(true); + } + dialog.close(); + node.graph.afterChange(); + } + dialog.querySelector("button").addEventListener("click", inner); + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + input.focus(); + } + + //if(v.callback) + // return v.callback.call(that, node, options, e, menu, that, event ); + } + }; + + //API ************************************************* + function compareObjects(a, b) { + for (var i in a) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + LiteGraph.compareObjects = compareObjects; + + function distance(a, b) { + return Math.sqrt( + (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) + ); + } + LiteGraph.distance = distance; + + function colorToString(c) { + return ( + "rgba(" + + Math.round(c[0] * 255).toFixed() + + "," + + Math.round(c[1] * 255).toFixed() + + "," + + Math.round(c[2] * 255).toFixed() + + "," + + (c.length == 4 ? c[3].toFixed(2) : "1.0") + + ")" + ); + } + LiteGraph.colorToString = colorToString; + + function isInsideRectangle(x, y, left, top, width, height) { + if (left < x && left + width > x && top < y && top + height > y) { + return true; + } + return false; + } + LiteGraph.isInsideRectangle = isInsideRectangle; + + //[minx,miny,maxx,maxy] + function growBounding(bounding, x, y) { + if (x < bounding[0]) { + bounding[0] = x; + } else if (x > bounding[2]) { + bounding[2] = x; + } + + if (y < bounding[1]) { + bounding[1] = y; + } else if (y > bounding[3]) { + bounding[3] = y; + } + } + LiteGraph.growBounding = growBounding; + + //point inside bounding box + function isInsideBounding(p, bb) { + if ( + p[0] < bb[0][0] || + p[1] < bb[0][1] || + p[0] > bb[1][0] || + p[1] > bb[1][1] + ) { + return false; + } + return true; + } + LiteGraph.isInsideBounding = isInsideBounding; + + //bounding overlap, format: [ startx, starty, width, height ] + function overlapBounding(a, b) { + var A_end_x = a[0] + a[2]; + var A_end_y = a[1] + a[3]; + var B_end_x = b[0] + b[2]; + var B_end_y = b[1] + b[3]; + + if ( + a[0] > B_end_x || + a[1] > B_end_y || + A_end_x < b[0] || + A_end_y < b[1] + ) { + return false; + } + return true; + } + LiteGraph.overlapBounding = overlapBounding; + + //Convert a hex value to its decimal value - the inputted hex must be in the + // format of a hex triplet - the kind we use for HTML colours. The function + // will return an array with three values. + function hex2num(hex) { + if (hex.charAt(0) == "#") { + hex = hex.slice(1); + } //Remove the '#' char - if there is one. + hex = hex.toUpperCase(); + var hex_alphabets = "0123456789ABCDEF"; + var value = new Array(3); + var k = 0; + var int1, int2; + for (var i = 0; i < 6; i += 2) { + int1 = hex_alphabets.indexOf(hex.charAt(i)); + int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); + value[k] = int1 * 16 + int2; + k++; + } + return value; + } + + LiteGraph.hex2num = hex2num; + + //Give a array with three values as the argument and the function will return + // the corresponding hex triplet. + function num2hex(triplet) { + var hex_alphabets = "0123456789ABCDEF"; + var hex = "#"; + var int1, int2; + for (var i = 0; i < 3; i++) { + int1 = triplet[i] / 16; + int2 = triplet[i] % 16; + + hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); + } + return hex; + } + + LiteGraph.num2hex = num2hex; + + /* LiteGraph GUI elements used for canvas editing *************************************/ + + /** + * ContextMenu from LiteGUI + * + * @class ContextMenu + * @constructor + * @param {Array} values (allows object { title: "Nice text", callback: function ... }) + * @param {Object} options [optional] Some options:\ + * - title: title to show on top of the menu + * - callback: function to call when an option is clicked, it receives the item information + * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback + * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position + */ + function ContextMenu(values, options) { + options = options || {}; + this.options = options; + var that = this; + + //to link a menu with its parent + if (options.parentMenu) { + if (options.parentMenu.constructor !== this.constructor) { + console.error( + "parentMenu must be of class ContextMenu, ignoring it" + ); + options.parentMenu = null; + } else { + this.parentMenu = options.parentMenu; + this.parentMenu.lock = true; + this.parentMenu.current_submenu = this; + } + } + + var eventClass = null; + if(options.event) //use strings because comparing classes between windows doesnt work + eventClass = options.event.constructor.name; + if ( eventClass !== "MouseEvent" && + eventClass !== "CustomEvent" && + eventClass !== "PointerEvent" + ) { + console.error( + "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" + ); + options.event = null; + } + + var root = document.createElement("div"); + root.className = "litegraph litecontextmenu litemenubar-panel"; + if (options.className) { + root.className += " " + options.className; + } + root.style.minWidth = 100; + root.style.minHeight = 100; + root.style.pointerEvents = "none"; + setTimeout(function() { + root.style.pointerEvents = "auto"; + }, 100); //delay so the mouse up event is not caught by this element + + //this prevents the default context browser menu to open in case this menu was created when pressing right button + LiteGraph.pointerListenerAdd(root,"up", + function(e) { + //console.log("pointerevents: ContextMenu up root prevent"); + e.preventDefault(); + return true; + }, + true + ); + root.addEventListener( + "contextmenu", + function(e) { + if (e.button != 2) { + //right button + return false; + } + e.preventDefault(); + return false; + }, + true + ); + + LiteGraph.pointerListenerAdd(root,"down", + function(e) { + //console.log("pointerevents: ContextMenu down"); + if (e.button == 2) { + that.close(); + e.preventDefault(); + return true; + } + }, + true + ); + + function on_mouse_wheel(e) { + var pos = parseInt(root.style.top); + root.style.top = + (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; + e.preventDefault(); + return true; + } + + if (!options.scroll_speed) { + options.scroll_speed = 0.1; + } + + root.addEventListener("wheel", on_mouse_wheel, true); + root.addEventListener("mousewheel", on_mouse_wheel, true); + + this.root = root; + + //title + if (options.title) { + var element = document.createElement("div"); + element.className = "litemenu-title"; + element.innerHTML = options.title; + root.appendChild(element); + } + + //entries + var num = 0; + for (var i=0; i < values.length; i++) { + var name = values.constructor == Array ? values[i] : i; + if (name != null && name.constructor !== String) { + name = name.content === undefined ? String(name) : name.content; + } + var value = values[i]; + this.addItem(name, value, options); + num++; + } + + //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that + /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { + console.log("pointerevents: ContextMenu leave"); + if (that.lock) { + return; + } + if (root.closing_timer) { + clearTimeout(root.closing_timer); + } + root.closing_timer = setTimeout(that.close.bind(that, e), 500); + //that.close(e); + });*/ + + LiteGraph.pointerListenerAdd(root,"enter", function(e) { + //console.log("pointerevents: ContextMenu enter"); + if (root.closing_timer) { + clearTimeout(root.closing_timer); + } + }); + + //insert before checking position + var root_document = document; + if (options.event) { + root_document = options.event.target.ownerDocument; + } + + if (!root_document) { + root_document = document; + } + + if( root_document.fullscreenElement ) + root_document.fullscreenElement.appendChild(root); + else + root_document.body.appendChild(root); + + //compute best position + var left = options.left || 0; + var top = options.top || 0; + if (options.event) { + left = options.event.clientX - 10; + top = options.event.clientY - 10; + if (options.title) { + top -= 20; + } + + if (options.parentMenu) { + var rect = options.parentMenu.root.getBoundingClientRect(); + left = rect.left + rect.width; + } + + var body_rect = document.body.getBoundingClientRect(); + var root_rect = root.getBoundingClientRect(); + if(body_rect.height == 0) + console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); + + if (body_rect.width && left > body_rect.width - root_rect.width - 10) { + left = body_rect.width - root_rect.width - 10; + } + if (body_rect.height && top > body_rect.height - root_rect.height - 10) { + top = body_rect.height - root_rect.height - 10; + } + } + + root.style.left = left + "px"; + root.style.top = top + "px"; + + if (options.scale) { + root.style.transform = "scale(" + options.scale + ")"; + } + } + + ContextMenu.prototype.addItem = function(name, value, options) { + var that = this; + options = options || {}; + + var element = document.createElement("div"); + element.className = "litemenu-entry submenu"; + + var disabled = false; + + if (value === null) { + element.classList.add("separator"); + //element.innerHTML = "
" + //continue; + } else { + element.innerHTML = value && value.title ? value.title : name; + element.value = value; + + if (value) { + if (value.disabled) { + disabled = true; + element.classList.add("disabled"); + } + if (value.submenu || value.has_submenu) { + element.classList.add("has_submenu"); + } + } + + if (typeof value == "function") { + element.dataset["value"] = name; + element.onclick_callback = value; + } else { + element.dataset["value"] = value; + } + + if (value.className) { + element.className += " " + value.className; + } + } + + this.root.appendChild(element); + if (!disabled) { + element.addEventListener("click", inner_onclick); + } + if (!disabled && options.autoopen) { + LiteGraph.pointerListenerAdd(element,"enter",inner_over); + } + + function inner_over(e) { + var value = this.value; + if (!value || !value.has_submenu) { + return; + } + //if it is a submenu, autoopen like the item was clicked + inner_onclick.call(this, e); + } + + //menu option clicked + function inner_onclick(e) { + var value = this.value; + var close_parent = true; + + if (that.current_submenu) { + that.current_submenu.close(e); + } + + //global callback + if (options.callback) { + var r = options.callback.call( + this, + value, + options, + e, + that, + options.node + ); + if (r === true) { + close_parent = false; + } + } + + //special cases + if (value) { + if ( + value.callback && + !options.ignore_item_callbacks && + value.disabled !== true + ) { + //item callback + var r = value.callback.call( + this, + value, + options, + e, + that, + options.extra + ); + if (r === true) { + close_parent = false; + } + } + if (value.submenu) { + if (!value.submenu.options) { + throw "ContextMenu submenu needs options"; + } + var submenu = new that.constructor(value.submenu.options, { + callback: value.submenu.callback, + event: e, + parentMenu: that, + ignore_item_callbacks: + value.submenu.ignore_item_callbacks, + title: value.submenu.title, + extra: value.submenu.extra, + autoopen: options.autoopen + }); + close_parent = false; + } + } + + if (close_parent && !that.lock) { + that.close(); + } + } + + return element; + }; + + ContextMenu.prototype.close = function(e, ignore_parent_menu) { + if (this.root.parentNode) { + this.root.parentNode.removeChild(this.root); + } + if (this.parentMenu && !ignore_parent_menu) { + this.parentMenu.lock = false; + this.parentMenu.current_submenu = null; + if (e === undefined) { + this.parentMenu.close(); + } else if ( + e && + !ContextMenu.isCursorOverElement(e, this.parentMenu.root) + ) { + ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); + } + } + if (this.current_submenu) { + this.current_submenu.close(e, true); + } + + if (this.root.closing_timer) { + clearTimeout(this.root.closing_timer); + } + + // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu + // on key press, allow filtering/selecting the context menu elements + }; + + //this code is used to trigger events easily (used in the context menu mouseleave + ContextMenu.trigger = function(element, event_name, params, origin) { + var evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail + evt.srcElement = origin; + if (element.dispatchEvent) { + element.dispatchEvent(evt); + } else if (element.__events) { + element.__events.dispatchEvent(evt); + } + //else nothing seems binded here so nothing to do + return evt; + }; + + //returns the top most menu + ContextMenu.prototype.getTopMenu = function() { + if (this.options.parentMenu) { + return this.options.parentMenu.getTopMenu(); + } + return this; + }; + + ContextMenu.prototype.getFirstEvent = function() { + if (this.options.parentMenu) { + return this.options.parentMenu.getFirstEvent(); + } + return this.options.event; + }; + + ContextMenu.isCursorOverElement = function(event, element) { + var left = event.clientX; + var top = event.clientY; + var rect = element.getBoundingClientRect(); + if (!rect) { + return false; + } + if ( + top > rect.top && + top < rect.top + rect.height && + left > rect.left && + left < rect.left + rect.width + ) { + return true; + } + return false; + }; + + LiteGraph.ContextMenu = ContextMenu; + + LiteGraph.closeAllContextMenus = function(ref_window) { + ref_window = ref_window || window; + + var elements = ref_window.document.querySelectorAll(".litecontextmenu"); + if (!elements.length) { + return; + } + + var result = []; + for (var i = 0; i < elements.length; i++) { + result.push(elements[i]); + } + + for (var i=0; i < result.length; i++) { + if (result[i].close) { + result[i].close(); + } else if (result[i].parentNode) { + result[i].parentNode.removeChild(result[i]); + } + } + }; + + LiteGraph.extendClass = function(target, origin) { + for (var i in origin) { + //copy class properties + if (target.hasOwnProperty(i)) { + continue; + } + target[i] = origin[i]; + } + + if (origin.prototype) { + //copy prototype properties + for (var i in origin.prototype) { + //only enumerable + if (!origin.prototype.hasOwnProperty(i)) { + continue; + } + + if (target.prototype.hasOwnProperty(i)) { + //avoid overwriting existing ones + continue; + } + + //copy getters + if (origin.prototype.__lookupGetter__(i)) { + target.prototype.__defineGetter__( + i, + origin.prototype.__lookupGetter__(i) + ); + } else { + target.prototype[i] = origin.prototype[i]; + } + + //and setters + if (origin.prototype.__lookupSetter__(i)) { + target.prototype.__defineSetter__( + i, + origin.prototype.__lookupSetter__(i) + ); + } + } + } + }; + + //used by some widgets to render a curve editor + function CurveEditor( points ) + { + this.points = points; + this.selected = -1; + this.nearest = -1; + this.size = null; //stores last size used + this.must_update = true; + this.margin = 5; + } + + CurveEditor.sampleCurve = function(f,points) + { + if(!points) + return; + for(var i = 0; i < points.length - 1; ++i) + { + var p = points[i]; + var pn = points[i+1]; + if(pn[0] < f) + continue; + var r = (pn[0] - p[0]); + if( Math.abs(r) < 0.00001 ) + return p[1]; + var local_f = (f - p[0]) / r; + return p[1] * (1.0 - local_f) + pn[1] * local_f; + } + return 0; + } + + CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) + { + var points = this.points; + if(!points) + return; + this.size = size; + var w = size[0] - this.margin * 2; + var h = size[1] - this.margin * 2; + + line_color = line_color || "#666"; + + ctx.save(); + ctx.translate(this.margin,this.margin); + + if(background_color) + { + ctx.fillStyle = "#111"; + ctx.fillRect(0,0,w,h); + ctx.fillStyle = "#222"; + ctx.fillRect(w*0.5,0,1,h); + ctx.strokeStyle = "#333"; + ctx.strokeRect(0,0,w,h); + } + ctx.strokeStyle = line_color; + if(inactive) + ctx.globalAlpha = 0.5; + ctx.beginPath(); + for(var i = 0; i < points.length; ++i) + { + var p = points[i]; + ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); + } + ctx.stroke(); + ctx.globalAlpha = 1; + if(!inactive) + for(var i = 0; i < points.length; ++i) + { + var p = points[i]; + ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); + ctx.beginPath(); + ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); + ctx.fill(); + } + ctx.restore(); + } + + //localpos is mouse in curve editor space + CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) + { + var points = this.points; + if(!points) + return; + if( localpos[1] < 0 ) + return; + + //this.captureInput(true); + var w = this.size[0] - this.margin * 2; + var h = this.size[1] - this.margin * 2; + var x = localpos[0] - this.margin; + var y = localpos[1] - this.margin; + var pos = [x,y]; + var max_dist = 30 / graphcanvas.ds.scale; + //search closer one + this.selected = this.getCloserPoint(pos, max_dist); + //create one + if(this.selected == -1) + { + var point = [x / w, 1 - y / h]; + points.push(point); + points.sort(function(a,b){ return a[0] - b[0]; }); + this.selected = points.indexOf(point); + this.must_update = true; + } + if(this.selected != -1) + return true; + } + + CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) + { + var points = this.points; + if(!points) + return; + var s = this.selected; + if(s < 0) + return; + var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); + var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); + var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; + var max_dist = 30 / graphcanvas.ds.scale; + this._nearest = this.getCloserPoint(curvepos, max_dist); + var point = points[s]; + if(point) + { + var is_edge_point = s == 0 || s == points.length - 1; + if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) + { + points.splice(s,1); + this.selected = -1; + return; + } + if( !is_edge_point ) //not edges + point[0] = clamp(x, 0, 1); + else + point[0] = s == 0 ? 0 : 1; + point[1] = 1.0 - clamp(y, 0, 1); + points.sort(function(a,b){ return a[0] - b[0]; }); + this.selected = points.indexOf(point); + this.must_update = true; + } + } + + CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) + { + this.selected = -1; + return false; + } + + CurveEditor.prototype.getCloserPoint = function(pos, max_dist) + { + var points = this.points; + if(!points) + return -1; + max_dist = max_dist || 30; + var w = (this.size[0] - this.margin * 2); + var h = (this.size[1] - this.margin * 2); + var num = points.length; + var p2 = [0,0]; + var min_dist = 1000000; + var closest = -1; + var last_valid = -1; + for(var i = 0; i < num; ++i) + { + var p = points[i]; + p2[0] = p[0] * w; + p2[1] = (1.0 - p[1]) * h; + if(p2[0] < pos[0]) + last_valid = i; + var dist = vec2.distance(pos,p2); + if(dist > min_dist || dist > max_dist) + continue; + closest = i; + min_dist = dist; + } + return closest; + } + + LiteGraph.CurveEditor = CurveEditor; + + //used to create nodes from wrapping functions + LiteGraph.getParameterNames = function(func) { + return (func + "") + .replace(/[/][/].*$/gm, "") // strip single-line comments + .replace(/\s+/g, "") // strip white space + .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ + .split("){", 1)[0] + .replace(/^[^(]*[(]/, "") // extract the parameters + .replace(/=[^,]+/g, "") // strip any ES6 defaults + .split(",") + .filter(Boolean); // split & filter [""] + }; + + /* helper for interaction: pointer, touch, mouse Listeners + used by LGraphCanvas DragAndScale ContextMenu*/ + LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { + if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ + //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); + return; // -- break -- + } + + var sMethod = LiteGraph.pointerevents_method; + var sEvent = sEvIn; + + // UNDER CONSTRUCTION + // convert pointerevents to touch event when not available + if (sMethod=="pointer" && !window.PointerEvent){ + console.warn("sMethod=='pointer' && !window.PointerEvent"); + console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); + switch(sEvent){ + case "down":{ + sMethod = "touch"; + sEvent = "start"; + break; + } + case "move":{ + sMethod = "touch"; + //sEvent = "move"; + break; + } + case "up":{ + sMethod = "touch"; + sEvent = "end"; + break; + } + case "cancel":{ + sMethod = "touch"; + //sEvent = "cancel"; + break; + } + case "enter":{ + console.log("debug: Should I send a move event?"); // ??? + break; + } + // case "over": case "out": not used at now + default:{ + console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); + } + } + } + + switch(sEvent){ + //both pointer and move events + case "down": case "up": case "move": case "over": case "out": case "enter": + { + oDOM.addEventListener(sMethod+sEvent, fCall, capture); + } + // only pointerevents + case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": + { + if (sMethod!="mouse"){ + return oDOM.addEventListener(sMethod+sEvent, fCall, capture); + } + } + // not "pointer" || "mouse" + default: + return oDOM.addEventListener(sEvent, fCall, capture); + } + } + LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { + if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ + //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); + return; // -- break -- + } + switch(sEvent){ + //both pointer and move events + case "down": case "up": case "move": case "over": case "out": case "enter": + { + if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ + oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + } + } + // only pointerevents + case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": + { + if (LiteGraph.pointerevents_method=="pointer"){ + return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + } + } + // not "pointer" || "mouse" + default: + return oDOM.removeEventListener(sEvent, fCall, capture); + } + } + + function clamp(v, a, b) { + return a > v ? a : b < v ? b : v; + }; + global.clamp = clamp; + + if (typeof window != "undefined" && !window["requestAnimationFrame"]) { + window.requestAnimationFrame = + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + } +})(this); + +if (typeof exports != "undefined") { + exports.LiteGraph = this.LiteGraph; + exports.LGraph = this.LGraph; + exports.LLink = this.LLink; + exports.LGraphNode = this.LGraphNode; + exports.LGraphGroup = this.LGraphGroup; + exports.DragAndScale = this.DragAndScale; + exports.LGraphCanvas = this.LGraphCanvas; + exports.ContextMenu = this.ContextMenu; +} + + +//basic nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + //Constant + function Time() { + this.addOutput("in ms", "number"); + this.addOutput("in sec", "number"); + } + + Time.title = "Time"; + Time.desc = "Time"; + + Time.prototype.onExecute = function() { + this.setOutputData(0, this.graph.globaltime * 1000); + this.setOutputData(1, this.graph.globaltime); + }; + + LiteGraph.registerNodeType("basic/time", Time); + + //Subgraph: a node that contains a graph + function Subgraph() { + var that = this; + this.size = [140, 80]; + this.properties = { enabled: true }; + this.enabled = true; + + //create inner graph + this.subgraph = new LiteGraph.LGraph(); + this.subgraph._subgraph_node = this; + this.subgraph._is_subgraph = true; + + this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); + + //nodes input node added inside + this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); + this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); + this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); + this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); + + this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); + this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); + this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); + this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); + } + + Subgraph.title = "Subgraph"; + Subgraph.desc = "Graph inside a node"; + Subgraph.title_color = "#334"; + + Subgraph.prototype.onGetInputs = function() { + return [["enabled", "boolean"]]; + }; + + /* + Subgraph.prototype.onDrawTitle = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.fillStyle = "#555"; + var w = LiteGraph.NODE_TITLE_HEIGHT; + var x = this.size[0] - w; + ctx.fillRect(x, -w, w, w); + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + }; + */ + + Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { + var that = this; + setTimeout(function() { + graphcanvas.openSubgraph(that.subgraph); + }, 10); + }; + + /* + Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { + if ( + !this.flags.collapsed && + pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT && + pos[1] < 0 + ) { + var that = this; + setTimeout(function() { + graphcanvas.openSubgraph(that.subgraph); + }, 10); + } + }; + */ + + Subgraph.prototype.onAction = function(action, param) { + this.subgraph.onAction(action, param); + }; + + Subgraph.prototype.onExecute = function() { + this.enabled = this.getInputOrProperty("enabled"); + if (!this.enabled) { + return; + } + + //send inputs to subgraph global inputs + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var value = this.getInputData(i); + this.subgraph.setInputData(input.name, value); + } + } + + //execute + this.subgraph.runStep(); + + //send subgraph global outputs to outputs + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + var value = this.subgraph.getOutputData(output.name); + this.setOutputData(i, value); + } + } + }; + + Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) { + if (this.enabled) { + this.subgraph.sendEventToAllNodes(eventname, param, mode); + } + }; + + Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) { + if (this.flags.collapsed) + return; + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // button + var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT); + let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT) + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + if (this._shape == LiteGraph.BOX_SHAPE) { + if (overleft) { + ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + } else { + ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + } + } + else { + if (overleft) { + ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + } else { + ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + } + } + if (over) { + ctx.fill(); + } else { + ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT); + } + // button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText("+", this.size[0] * 0.25, y + 24); + ctx.fillText("+", this.size[0] * 0.75, y + 24); + } + + // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + // { + // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // if(localpos[1] > y) + // { + // graphcanvas.showSubgraphPropertiesDialog(this); + // } + // } + Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + console.log(0) + if (localpos[1] > y) { + if (localpos[0] < this.size[0] / 2) { + console.log(1) + graphcanvas.showSubgraphPropertiesDialog(this); + } else { + console.log(2) + graphcanvas.showSubgraphPropertiesDialogRight(this); + } + } + } + Subgraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; + } + + //**** INPUTS *********************************** + Subgraph.prototype.onSubgraphTrigger = function(event, param) { + var slot = this.findOutputSlot(event); + if (slot != -1) { + this.triggerSlot(slot); + } + }; + + Subgraph.prototype.onSubgraphNewInput = function(name, type) { + var slot = this.findInputSlot(name); + if (slot == -1) { + //add input to the node + this.addInput(name, type); + } + }; + + Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) { + var slot = this.findInputSlot(oldname); + if (slot == -1) { + return; + } + var info = this.getInputInfo(slot); + info.name = name; + }; + + Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) { + var slot = this.findInputSlot(name); + if (slot == -1) { + return; + } + var info = this.getInputInfo(slot); + info.type = type; + }; + + Subgraph.prototype.onSubgraphRemovedInput = function(name) { + var slot = this.findInputSlot(name); + if (slot == -1) { + return; + } + this.removeInput(slot); + }; + + //**** OUTPUTS *********************************** + Subgraph.prototype.onSubgraphNewOutput = function(name, type) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + this.addOutput(name, type); + } + }; + + Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) { + var slot = this.findOutputSlot(oldname); + if (slot == -1) { + return; + } + var info = this.getOutputInfo(slot); + info.name = name; + }; + + Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + return; + } + var info = this.getOutputInfo(slot); + info.type = type; + }; + + Subgraph.prototype.onSubgraphRemovedOutput = function(name) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + return; + } + this.removeOutput(slot); + }; + // ***************************************************** + + Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) { + var that = this; + return [ + { + content: "Open", + callback: function() { + graphcanvas.openSubgraph(that.subgraph); + } + } + ]; + }; + + Subgraph.prototype.onResize = function(size) { + size[1] += 20; + }; + + Subgraph.prototype.serialize = function() { + var data = LiteGraph.LGraphNode.prototype.serialize.call(this); + data.subgraph = this.subgraph.serialize(); + return data; + }; + //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() + + Subgraph.prototype.reassignSubgraphUUIDs = function(graph) { + const idMap = { nodeIDs: {}, linkIDs: {} } + + for (const node of graph.nodes) { + const oldID = node.id + const newID = LiteGraph.uuidv4() + node.id = newID + + if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) { + throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`) + } + + idMap.nodeIDs[oldID] = newID + idMap.nodeIDs[newID] = oldID + } + + for (const link of graph.links) { + const oldID = link[0] + const newID = LiteGraph.uuidv4(); + link[0] = newID + + if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) { + throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`) + } + + idMap.linkIDs[oldID] = newID + idMap.linkIDs[newID] = oldID + + const nodeFrom = link[1] + const nodeTo = link[3] + + if (!idMap.nodeIDs[nodeFrom]) { + throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`) + } + + link[1] = idMap.nodeIDs[nodeFrom] + + if (!idMap.nodeIDs[nodeTo]) { + throw new Error(`Old node UUID not found in mapping! ${nodeTo}`) + } + + link[3] = idMap.nodeIDs[nodeTo] + } + + // Reconnect links + for (const node of graph.nodes) { + if (node.inputs) { + for (const input of node.inputs) { + if (input.link) { + input.link = idMap.linkIDs[input.link] + } + } + } + if (node.outputs) { + for (const output of node.outputs) { + if (output.links) { + output.links = output.links.map(l => idMap.linkIDs[l]); + } + } + } + } + + // Recurse! + for (const node of graph.nodes) { + if (node.type === "graph/subgraph") { + const merge = reassignGraphUUIDs(node.subgraph); + idMap.nodeIDs.assign(merge.nodeIDs) + idMap.linkIDs.assign(merge.linkIDs) + } + } + }; + + Subgraph.prototype.clone = function() { + var node = LiteGraph.createNode(this.type); + var data = this.serialize(); + + if (LiteGraph.use_uuids) { + // LGraph.serialize() seems to reuse objects in the original graph. But we + // need to change node IDs here, so clone it first. + const subgraph = LiteGraph.cloneObject(data.subgraph) + + this.reassignSubgraphUUIDs(subgraph); + + data.subgraph = subgraph; + } + + delete data["id"]; + delete data["inputs"]; + delete data["outputs"]; + node.configure(data); + return node; + }; + + Subgraph.prototype.buildFromNodes = function(nodes) + { + //clear all? + //TODO + + //nodes that connect data between parent graph and subgraph + var subgraph_inputs = []; + var subgraph_outputs = []; + + //mark inner nodes + var ids = {}; + var min_x = 0; + var max_x = 0; + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + ids[ node.id ] = node; + min_x = Math.min( node.pos[0], min_x ); + max_x = Math.max( node.pos[0], min_x ); + } + + var last_input_y = 0; + var last_output_y = 0; + + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + //check inputs + if( node.inputs ) + for(var j = 0; j < node.inputs.length; ++j) + { + var input = node.inputs[j]; + if( !input || !input.link ) + continue; + var link = node.graph.links[ input.link ]; + if(!link) + continue; + if( ids[ link.origin_id ] ) + continue; + //this.addInput(input.name,link.type); + this.subgraph.addInput(input.name,link.type); + /* + var input_node = LiteGraph.createNode("graph/input"); + this.subgraph.add( input_node ); + input_node.pos = [min_x - 200, last_input_y ]; + last_input_y += 100; + */ + } + + //check outputs + if( node.outputs ) + for(var j = 0; j < node.outputs.length; ++j) + { + var output = node.outputs[j]; + if( !output || !output.links || !output.links.length ) + continue; + var is_external = false; + for(var k = 0; k < output.links.length; ++k) + { + var link = node.graph.links[ output.links[k] ]; + if(!link) + continue; + if( ids[ link.target_id ] ) + continue; + is_external = true; + break; + } + if(!is_external) + continue; + //this.addOutput(output.name,output.type); + /* + var output_node = LiteGraph.createNode("graph/output"); + this.subgraph.add( output_node ); + output_node.pos = [max_x + 50, last_output_y ]; + last_output_y += 100; + */ + } + } + + //detect inputs and outputs + //split every connection in two data_connection nodes + //keep track of internal connections + //connect external connections + + //clone nodes inside subgraph and try to reconnect them + + //connect edge subgraph nodes to extarnal connections nodes + } + + LiteGraph.Subgraph = Subgraph; + LiteGraph.registerNodeType("graph/subgraph", Subgraph); + + //Input for a subgraph + function GraphInput() { + this.addOutput("", "number"); + + this.name_in_graph = ""; + this.properties = { + name: "", + type: "number", + value: 0 + }; + + var that = this; + + this.name_widget = this.addWidget( + "text", + "Name", + this.properties.name, + function(v) { + if (!v) { + return; + } + that.setProperty("name",v); + } + ); + this.type_widget = this.addWidget( + "text", + "Type", + this.properties.type, + function(v) { + that.setProperty("type",v); + } + ); + + this.value_widget = this.addWidget( + "number", + "Value", + this.properties.value, + function(v) { + that.setProperty("value",v); + } + ); + + this.widgets_up = true; + this.size = [180, 90]; + } + + GraphInput.title = "Input"; + GraphInput.desc = "Input of the graph"; + + GraphInput.prototype.onConfigure = function() + + { + this.updateType(); + } + + //ensures the type in the node output and the type in the associated graph input are the same + GraphInput.prototype.updateType = function() + { + var type = this.properties.type; + this.type_widget.value = type; + + //update output + if(this.outputs[0].type != type) + { + if (!LiteGraph.isValidConnection(this.outputs[0].type,type)) + this.disconnectOutput(0); + this.outputs[0].type = type; + } + + //update widget + if(type == "number") + { + this.value_widget.type = "number"; + this.value_widget.value = 0; + } + else if(type == "boolean") + { + this.value_widget.type = "toggle"; + this.value_widget.value = true; + } + else if(type == "string") + { + this.value_widget.type = "text"; + this.value_widget.value = ""; + } + else + { + this.value_widget.type = null; + this.value_widget.value = null; + } + this.properties.value = this.value_widget.value; + + //update graph + if (this.graph && this.name_in_graph) { + this.graph.changeInputType(this.name_in_graph, type); + } + } + + //this is executed AFTER the property has changed + GraphInput.prototype.onPropertyChanged = function(name,v) + { + if( name == "name" ) + { + if (v == "" || v == this.name_in_graph || v == "enabled") { + return false; + } + if(this.graph) + { + if (this.name_in_graph) { + //already added + this.graph.renameInput( this.name_in_graph, v ); + } else { + this.graph.addInput( v, this.properties.type ); + } + } //what if not?! + this.name_widget.value = v; + this.name_in_graph = v; + } + else if( name == "type" ) + { + this.updateType(); + } + else if( name == "value" ) + { + } + } + + GraphInput.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.name; + } + return this.title; + }; + + GraphInput.prototype.onAction = function(action, param) { + if (this.properties.type == LiteGraph.EVENT) { + this.triggerSlot(0, param); + } + }; + + GraphInput.prototype.onExecute = function() { + var name = this.properties.name; + //read from global input + var data = this.graph.inputs[name]; + if (!data) { + this.setOutputData(0, this.properties.value ); + return; + } + + this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value ); + }; + + GraphInput.prototype.onRemoved = function() { + if (this.name_in_graph) { + this.graph.removeInput(this.name_in_graph); + } + }; + + LiteGraph.GraphInput = GraphInput; + LiteGraph.registerNodeType("graph/input", GraphInput); + + //Output for a subgraph + function GraphOutput() { + this.addInput("", ""); + + this.name_in_graph = ""; + this.properties = { name: "", type: "" }; + var that = this; + + // Object.defineProperty(this.properties, "name", { + // get: function() { + // return that.name_in_graph; + // }, + // set: function(v) { + // if (v == "" || v == that.name_in_graph) { + // return; + // } + // if (that.name_in_graph) { + // //already added + // that.graph.renameOutput(that.name_in_graph, v); + // } else { + // that.graph.addOutput(v, that.properties.type); + // } + // that.name_widget.value = v; + // that.name_in_graph = v; + // }, + // enumerable: true + // }); + + // Object.defineProperty(this.properties, "type", { + // get: function() { + // return that.inputs[0].type; + // }, + // set: function(v) { + // if (v == "action" || v == "event") { + // v = LiteGraph.ACTION; + // } + // if (!LiteGraph.isValidConnection(that.inputs[0].type,v)) + // that.disconnectInput(0); + // that.inputs[0].type = v; + // if (that.name_in_graph) { + // //already added + // that.graph.changeOutputType( + // that.name_in_graph, + // that.inputs[0].type + // ); + // } + // that.type_widget.value = v || ""; + // }, + // enumerable: true + // }); + + this.name_widget = this.addWidget("text","Name",this.properties.name,"name"); + this.type_widget = this.addWidget("text","Type",this.properties.type,"type"); + this.widgets_up = true; + this.size = [180, 60]; + } + + GraphOutput.title = "Output"; + GraphOutput.desc = "Output of the graph"; + + GraphOutput.prototype.onPropertyChanged = function (name, v) { + if (name == "name") { + if (v == "" || v == this.name_in_graph || v == "enabled") { + return false; + } + if (this.graph) { + if (this.name_in_graph) { + //already added + this.graph.renameOutput(this.name_in_graph, v); + } else { + this.graph.addOutput(v, this.properties.type); + } + } //what if not?! + this.name_widget.value = v; + this.name_in_graph = v; + } + else if (name == "type") { + this.updateType(); + } + else if (name == "value") { + } + } + + GraphOutput.prototype.updateType = function () { + var type = this.properties.type; + if (this.type_widget) + this.type_widget.value = type; + + //update output + if (this.inputs[0].type != type) { + + if ( type == "action" || type == "event") + type = LiteGraph.EVENT; + if (!LiteGraph.isValidConnection(this.inputs[0].type, type)) + this.disconnectInput(0); + this.inputs[0].type = type; + } + + //update graph + if (this.graph && this.name_in_graph) { + this.graph.changeOutputType(this.name_in_graph, type); + } + } + + + + GraphOutput.prototype.onExecute = function() { + this._value = this.getInputData(0); + this.graph.setOutputData(this.properties.name, this._value); + }; + + GraphOutput.prototype.onAction = function(action, param) { + if (this.properties.type == LiteGraph.ACTION) { + this.graph.trigger( this.properties.name, param ); + } + }; + + GraphOutput.prototype.onRemoved = function() { + if (this.name_in_graph) { + this.graph.removeOutput(this.name_in_graph); + } + }; + + GraphOutput.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.name; + } + return this.title; + }; + + LiteGraph.GraphOutput = GraphOutput; + LiteGraph.registerNodeType("graph/output", GraphOutput); + + //Constant + function ConstantNumber() { + this.addOutput("value", "number"); + this.addProperty("value", 1.0); + this.widget = this.addWidget("number","value",1,"value"); + this.widgets_up = true; + this.size = [180, 30]; + } + + ConstantNumber.title = "Const Number"; + ConstantNumber.desc = "Constant number"; + + ConstantNumber.prototype.onExecute = function() { + this.setOutputData(0, parseFloat(this.properties["value"])); + }; + + ConstantNumber.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.value; + } + return this.title; + }; + + ConstantNumber.prototype.setValue = function(v) + { + this.setProperty("value",v); + } + + ConstantNumber.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = this.properties["value"].toFixed(3); + }; + + LiteGraph.registerNodeType("basic/const", ConstantNumber); + + function ConstantBoolean() { + this.addOutput("bool", "boolean"); + this.addProperty("value", true); + this.widget = this.addWidget("toggle","value",true,"value"); + this.serialize_widgets = true; + this.widgets_up = true; + this.size = [140, 30]; + } + + ConstantBoolean.title = "Const Boolean"; + ConstantBoolean.desc = "Constant boolean"; + ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle; + + ConstantBoolean.prototype.onExecute = function() { + this.setOutputData(0, this.properties["value"]); + }; + + ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantBoolean.prototype.onGetInputs = function() { + return [["toggle", LiteGraph.ACTION]]; + }; + + ConstantBoolean.prototype.onAction = function(action) + { + this.setValue( !this.properties.value ); + } + + LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + + function ConstantString() { + this.addOutput("string", "string"); + this.addProperty("value", ""); + this.widget = this.addWidget("text","value","","value"); //link to property value + this.widgets_up = true; + this.size = [180, 30]; + } + + ConstantString.title = "Const String"; + ConstantString.desc = "Constant string"; + + ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; + + ConstantString.prototype.onExecute = function() { + this.setOutputData(0, this.properties["value"]); + }; + + ConstantString.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantString.prototype.onDropFile = function(file) + { + var that = this; + var reader = new FileReader(); + reader.onload = function(e) + { + that.setProperty("value",e.target.result); + } + reader.readAsText(file); + } + + LiteGraph.registerNodeType("basic/string", ConstantString); + + function ConstantObject() { + this.addOutput("obj", "object"); + this.size = [120, 30]; + this._object = {}; + } + + ConstantObject.title = "Const Object"; + ConstantObject.desc = "Constant Object"; + + ConstantObject.prototype.onExecute = function() { + this.setOutputData(0, this._object); + }; + + LiteGraph.registerNodeType( "basic/object", ConstantObject ); + + function ConstantFile() { + this.addInput("url", "string"); + this.addOutput("file", "string"); + this.addProperty("url", ""); + this.addProperty("type", "text"); + this.widget = this.addWidget("text","url","","url"); + this._data = null; + } + + ConstantFile.title = "Const File"; + ConstantFile.desc = "Fetches a file from an url"; + ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] }; + + ConstantFile.prototype.onPropertyChanged = function(name, value) { + if (name == "url") + { + if( value == null || value == "") + this._data = null; + else + { + this.fetchFile(value); + } + } + } + + ConstantFile.prototype.onExecute = function() { + var url = this.getInputData(0) || this.properties.url; + if(url && (url != this._url || this._type != this.properties.type)) + this.fetchFile(url); + this.setOutputData(0, this._data ); + }; + + ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantFile.prototype.fetchFile = function(url) { + var that = this; + if(!url || url.constructor !== String) + { + that._data = null; + that.boxcolor = null; + return; + } + + this._url = url; + this._type = this.properties.type; + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); + + if(that.properties.type == "arraybuffer") + return response.arrayBuffer(); + else if(that.properties.type == "text") + return response.text(); + else if(that.properties.type == "json") + return response.json(); + else if(that.properties.type == "blob") + return response.blob(); + }) + .then(function(data) { + that._data = data; + that.boxcolor = "#AEA"; + }) + .catch(function(error) { + that._data = null; + that.boxcolor = "red"; + console.error("error fetching file:",url); + }); + }; + + ConstantFile.prototype.onDropFile = function(file) + { + var that = this; + this._url = file.name; + this._type = this.properties.type; + this.properties.url = file.name; + var reader = new FileReader(); + reader.onload = function(e) + { + that.boxcolor = "#AEA"; + var v = e.target.result; + if( that.properties.type == "json" ) + v = JSON.parse(v); + that._data = v; + } + if(that.properties.type == "arraybuffer") + reader.readAsArrayBuffer(file); + else if(that.properties.type == "text" || that.properties.type == "json") + reader.readAsText(file); + else if(that.properties.type == "blob") + return reader.readAsBinaryString(file); + } + + LiteGraph.registerNodeType("basic/file", ConstantFile); + + +//to store json objects +function JSONParse() { + this.addInput("parse", LiteGraph.ACTION); + this.addInput("json", "string"); + this.addOutput("done", LiteGraph.EVENT); + this.addOutput("object", "object"); + this.widget = this.addWidget("button","parse","",this.parse.bind(this)); + this._str = null; + this._obj = null; +} + +JSONParse.title = "JSON Parse"; +JSONParse.desc = "Parses JSON String into object"; + +JSONParse.prototype.parse = function() +{ + if(!this._str) + return; + + try { + this._str = this.getInputData(1); + this._obj = JSON.parse(this._str); + this.boxcolor = "#AEA"; + this.triggerSlot(0); + } catch (err) { + this.boxcolor = "red"; + } +} + +JSONParse.prototype.onExecute = function() { + this._str = this.getInputData(1); + this.setOutputData(1, this._obj); +}; + +JSONParse.prototype.onAction = function(name) { + if(name == "parse") + this.parse(); +} + +LiteGraph.registerNodeType("basic/jsonparse", JSONParse); + + //to store json objects + function ConstantData() { + this.addOutput("data", "object"); + this.addProperty("value", ""); + this.widget = this.addWidget("text","json","","value"); + this.widgets_up = true; + this.size = [140, 30]; + this._value = null; + } + + ConstantData.title = "Const Data"; + ConstantData.desc = "Constant Data"; + + ConstantData.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + if (value == null || value == "") { + return; + } + + try { + this._value = JSON.parse(value); + this.boxcolor = "#AEA"; + } catch (err) { + this.boxcolor = "red"; + } + }; + + ConstantData.prototype.onExecute = function() { + this.setOutputData(0, this._value); + }; + + ConstantData.prototype.setValue = ConstantNumber.prototype.setValue; + + LiteGraph.registerNodeType("basic/data", ConstantData); + + //to store json objects + function ConstantArray() { + this._value = []; + this.addInput("json", ""); + this.addOutput("arrayOut", "array"); + this.addOutput("length", "number"); + this.addProperty("value", "[]"); + this.widget = this.addWidget("text","array",this.properties.value,"value"); + this.widgets_up = true; + this.size = [140, 50]; + } + + ConstantArray.title = "Const Array"; + ConstantArray.desc = "Constant Array"; + + ConstantArray.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + if (value == null || value == "") { + return; + } + + try { + if(value[0] != "[") + this._value = JSON.parse("[" + value + "]"); + else + this._value = JSON.parse(value); + this.boxcolor = "#AEA"; + } catch (err) { + this.boxcolor = "red"; + } + }; + + ConstantArray.prototype.onExecute = function() { + var v = this.getInputData(0); + if(v && v.length) //clone + { + if(!this._value) + this._value = new Array(); + this._value.length = v.length; + for(var i = 0; i < v.length; ++i) + this._value[i] = v[i]; + } + this.setOutputData(0, this._value); + this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); + }; + + ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue; + + LiteGraph.registerNodeType("basic/array", ConstantArray); + + function SetArray() + { + this.addInput("arr", "array"); + this.addInput("value", ""); + this.addOutput("arr", "array"); + this.properties = { index: 0 }; + this.widget = this.addWidget("number","i",this.properties.index,"index",{precision: 0, step: 10, min: 0}); + } + + SetArray.title = "Set Array"; + SetArray.desc = "Sets index of array"; + + SetArray.prototype.onExecute = function() { + var arr = this.getInputData(0); + if(!arr) + return; + var v = this.getInputData(1); + if(v === undefined ) + return; + if(this.properties.index) + arr[ Math.floor(this.properties.index) ] = v; + this.setOutputData(0,arr); + }; + + LiteGraph.registerNodeType("basic/set_array", SetArray ); + + function ArrayElement() { + this.addInput("array", "array,table,string"); + this.addInput("index", "number"); + this.addOutput("value", ""); + this.addProperty("index",0); + } + + ArrayElement.title = "Array[i]"; + ArrayElement.desc = "Returns an element from an array"; + + ArrayElement.prototype.onExecute = function() { + var array = this.getInputData(0); + var index = this.getInputData(1); + if(index == null) + index = this.properties.index; + if(array == null || index == null ) + return; + this.setOutputData(0, array[Math.floor(Number(index))] ); + }; + + LiteGraph.registerNodeType("basic/array[]", ArrayElement); + + function TableElement() { + this.addInput("table", "table"); + this.addInput("row", "number"); + this.addInput("col", "number"); + this.addOutput("value", ""); + this.addProperty("row",0); + this.addProperty("column",0); + } + + TableElement.title = "Table[row][col]"; + TableElement.desc = "Returns an element from a table"; + + TableElement.prototype.onExecute = function() { + var table = this.getInputData(0); + var row = this.getInputData(1); + var col = this.getInputData(2); + if(row == null) + row = this.properties.row; + if(col == null) + col = this.properties.column; + if(table == null || row == null || col == null) + return; + var row = table[Math.floor(Number(row))]; + if(row) + this.setOutputData(0, row[Math.floor(Number(col))] ); + else + this.setOutputData(0, null ); + }; + + LiteGraph.registerNodeType("basic/table[][]", TableElement); + + function ObjectProperty() { + this.addInput("obj", "object"); + this.addOutput("property", 0); + this.addProperty("value", 0); + this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); + this.widgets_up = true; + this.size = [140, 30]; + this._value = null; + } + + ObjectProperty.title = "Object property"; + ObjectProperty.desc = "Outputs the property of an object"; + + ObjectProperty.prototype.setValue = function(v) { + this.properties.value = v; + this.widget.value = v; + }; + + ObjectProperty.prototype.getTitle = function() { + if (this.flags.collapsed) { + return "in." + this.properties.value; + } + return this.title; + }; + + ObjectProperty.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + }; + + ObjectProperty.prototype.onExecute = function() { + var data = this.getInputData(0); + if (data != null) { + this.setOutputData(0, data[this.properties.value]); + } + }; + + LiteGraph.registerNodeType("basic/object_property", ObjectProperty); + + function ObjectKeys() { + this.addInput("obj", ""); + this.addOutput("keys", "array"); + this.size = [140, 30]; + } + + ObjectKeys.title = "Object keys"; + ObjectKeys.desc = "Outputs an array with the keys of an object"; + + ObjectKeys.prototype.onExecute = function() { + var data = this.getInputData(0); + if (data != null) { + this.setOutputData(0, Object.keys(data) ); + } + }; + + LiteGraph.registerNodeType("basic/object_keys", ObjectKeys); + + + function SetObject() + { + this.addInput("obj", ""); + this.addInput("value", ""); + this.addOutput("obj", ""); + this.properties = { property: "" }; + this.name_widget = this.addWidget("text","prop.",this.properties.property,"property"); + } + + SetObject.title = "Set Object"; + SetObject.desc = "Adds propertiesrty to object"; + + SetObject.prototype.onExecute = function() { + var obj = this.getInputData(0); + if(!obj) + return; + var v = this.getInputData(1); + if(v === undefined ) + return; + if(this.properties.property) + obj[ this.properties.property ] = v; + this.setOutputData(0,obj); + }; + + LiteGraph.registerNodeType("basic/set_object", SetObject ); + + + function MergeObjects() { + this.addInput("A", "object"); + this.addInput("B", "object"); + this.addOutput("out", "object"); + this._result = {}; + var that = this; + this.addWidget("button","clear","",function(){ + that._result = {}; + }); + this.size = this.computeSize(); + } + + MergeObjects.title = "Merge Objects"; + MergeObjects.desc = "Creates an object copying properties from others"; + + MergeObjects.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + var C = this._result; + if(A) + for(var i in A) + C[i] = A[i]; + if(B) + for(var i in B) + C[i] = B[i]; + this.setOutputData(0,C); + }; + + LiteGraph.registerNodeType("basic/merge_objects", MergeObjects ); + + //Store as variable + function Variable() { + this.size = [60, 30]; + this.addInput("in"); + this.addOutput("out"); + this.properties = { varname: "myname", container: Variable.LITEGRAPH }; + this.value = null; + } + + Variable.title = "Variable"; + Variable.desc = "store/read variable value"; + + Variable.LITEGRAPH = 0; //between all graphs + Variable.GRAPH = 1; //only inside this graph + Variable.GLOBALSCOPE = 2; //attached to Window + + Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} }; + + Variable.prototype.onExecute = function() { + var container = this.getContainer(); + + if(this.isInputConnected(0)) + { + this.value = this.getInputData(0); + container[ this.properties.varname ] = this.value; + this.setOutputData(0, this.value ); + return; + } + + this.setOutputData( 0, container[ this.properties.varname ] ); + }; + + Variable.prototype.getContainer = function() + { + switch(this.properties.container) + { + case Variable.GRAPH: + if(this.graph) + return this.graph.vars; + return {}; + break; + case Variable.GLOBALSCOPE: + return global; + break; + case Variable.LITEGRAPH: + default: + return LiteGraph.Globals; + break; + } + } + + Variable.prototype.getTitle = function() { + return this.properties.varname; + }; + + LiteGraph.registerNodeType("basic/variable", Variable); + + function length(v) { + if(v && v.length != null) + return Number(v.length); + return 0; + } + + LiteGraph.wrapFunctionAsNode( + "basic/length", + length, + [""], + "number" + ); + + function length(v) { + if(v && v.length != null) + return Number(v.length); + return 0; + } + + LiteGraph.wrapFunctionAsNode( + "basic/not", + function(a){ return !a; }, + [""], + "boolean" + ); + + function DownloadData() { + this.size = [60, 30]; + this.addInput("data", 0 ); + this.addInput("download", LiteGraph.ACTION ); + this.properties = { filename: "data.json" }; + this.value = null; + var that = this; + this.addWidget("button","Download","", function(v){ + if(!that.value) + return; + that.downloadAsFile(); + }); + } + + DownloadData.title = "Download"; + DownloadData.desc = "Download some data"; + + DownloadData.prototype.downloadAsFile = function() + { + if(this.value == null) + return; + + var str = null; + if(this.value.constructor === String) + str = this.value; + else + str = JSON.stringify(this.value); + + var file = new Blob([str]); + var url = URL.createObjectURL( file ); + var element = document.createElement("a"); + element.setAttribute('href', url); + element.setAttribute('download', this.properties.filename ); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url + } + + DownloadData.prototype.onAction = function(action, param) { + var that = this; + setTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup + } + + DownloadData.prototype.onExecute = function() { + if (this.inputs[0]) { + this.value = this.getInputData(0); + } + }; + + DownloadData.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.filename; + } + return this.title; + }; + + LiteGraph.registerNodeType("basic/download", DownloadData); + + + + //Watch a value in the editor + function Watch() { + this.size = [60, 30]; + this.addInput("value", 0, { label: "" }); + this.value = 0; + } + + Watch.title = "Watch"; + Watch.desc = "Show value of input"; + + Watch.prototype.onExecute = function() { + if (this.inputs[0]) { + this.value = this.getInputData(0); + } + }; + + Watch.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.inputs[0].label; + } + return this.title; + }; + + Watch.toString = function(o) { + if (o == null) { + return "null"; + } else if (o.constructor === Number) { + return o.toFixed(3); + } else if (o.constructor === Array) { + var str = "["; + for (var i = 0; i < o.length; ++i) { + str += Watch.toString(o[i]) + (i + 1 != o.length ? "," : ""); + } + str += "]"; + return str; + } else { + return String(o); + } + }; + + Watch.prototype.onDrawBackground = function(ctx) { + //show the current value + this.inputs[0].label = Watch.toString(this.value); + }; + + LiteGraph.registerNodeType("basic/watch", Watch); + + //in case one type doesnt match other type but you want to connect them anyway + function Cast() { + this.addInput("in", 0); + this.addOutput("out", 0); + this.size = [40, 30]; + } + + Cast.title = "Cast"; + Cast.desc = "Allows to connect different types"; + + Cast.prototype.onExecute = function() { + this.setOutputData(0, this.getInputData(0)); + }; + + LiteGraph.registerNodeType("basic/cast", Cast); + + //Show value inside the debug console + function Console() { + this.mode = LiteGraph.ON_EVENT; + this.size = [80, 30]; + this.addProperty("msg", ""); + this.addInput("log", LiteGraph.EVENT); + this.addInput("msg", 0); + } + + Console.title = "Console"; + Console.desc = "Show value inside the console"; + + Console.prototype.onAction = function(action, param) { + // param is the action + var msg = this.getInputData(1); //getInputDataByName("msg"); + //if (msg == null || typeof msg == "undefined") return; + if (!msg) msg = this.properties.msg; + if (!msg) msg = "Event: "+param; // msg is undefined if the slot is lost? + if (action == "log") { + console.log(msg); + } else if (action == "warn") { + console.warn(msg); + } else if (action == "error") { + console.error(msg); + } + }; + + Console.prototype.onExecute = function() { + var msg = this.getInputData(1); //getInputDataByName("msg"); + if (!msg) msg = this.properties.msg; + if (msg != null && typeof msg != "undefined") { + this.properties.msg = msg; + console.log(msg); + } + }; + + Console.prototype.onGetInputs = function() { + return [ + ["log", LiteGraph.ACTION], + ["warn", LiteGraph.ACTION], + ["error", LiteGraph.ACTION] + ]; + }; + + LiteGraph.registerNodeType("basic/console", Console); + + //Show value inside the debug console + function Alert() { + this.mode = LiteGraph.ON_EVENT; + this.addProperty("msg", ""); + this.addInput("", LiteGraph.EVENT); + var that = this; + this.widget = this.addWidget("text", "Text", "", "msg"); + this.widgets_up = true; + this.size = [200, 30]; + } + + Alert.title = "Alert"; + Alert.desc = "Show an alert window"; + Alert.color = "#510"; + + Alert.prototype.onConfigure = function(o) { + this.widget.value = o.properties.msg; + }; + + Alert.prototype.onAction = function(action, param) { + var msg = this.properties.msg; + setTimeout(function() { + alert(msg); + }, 10); + }; + + LiteGraph.registerNodeType("basic/alert", Alert); + + //Execites simple code + function NodeScript() { + this.size = [60, 30]; + this.addProperty("onExecute", "return A;"); + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("out", 0); + + this._func = null; + this.data = {}; + } + + NodeScript.prototype.onConfigure = function(o) { + if (o.properties.onExecute && LiteGraph.allow_scripts) + this.compileCode(o.properties.onExecute); + else + console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + }; + + NodeScript.title = "Script"; + NodeScript.desc = "executes a code (max 256 characters)"; + + NodeScript.widgets_info = { + onExecute: { type: "code" } + }; + + NodeScript.prototype.onPropertyChanged = function(name, value) { + if (name == "onExecute" && LiteGraph.allow_scripts) + this.compileCode(value); + else + console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + }; + + NodeScript.prototype.compileCode = function(code) { + this._func = null; + if (code.length > 256) { + console.warn("Script too long, max 256 chars"); + } else { + var code_low = code.toLowerCase(); + var forbidden_words = [ + "script", + "body", + "document", + "eval", + "nodescript", + "function" + ]; //bad security solution + for (var i = 0; i < forbidden_words.length; ++i) { + if (code_low.indexOf(forbidden_words[i]) != -1) { + console.warn("invalid script"); + return; + } + } + try { + this._func = new Function("A", "B", "C", "DATA", "node", code); + } catch (err) { + console.error("Error parsing script"); + console.error(err); + } + } + }; + + NodeScript.prototype.onExecute = function() { + if (!this._func) { + return; + } + + try { + var A = this.getInputData(0); + var B = this.getInputData(1); + var C = this.getInputData(2); + this.setOutputData(0, this._func(A, B, C, this.data, this)); + } catch (err) { + console.error("Error in script"); + console.error(err); + } + }; + + NodeScript.prototype.onGetOutputs = function() { + return [["C", ""]]; + }; + + LiteGraph.registerNodeType("basic/script", NodeScript); + + + function GenericCompare() { + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "==", "enum", { values: GenericCompare.values }); + this.addWidget("combo","Op.",this.properties.OP,{ property: "OP", values: GenericCompare.values } ); + + this.size = [80, 60]; + } + + GenericCompare.values = ["==", "!="]; //[">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + GenericCompare["@OP"] = { + type: "enum", + title: "operation", + values: GenericCompare.values + }; + + GenericCompare.title = "Compare *"; + GenericCompare.desc = "evaluates condition between A and B"; + + GenericCompare.prototype.getTitle = function() { + return "*A " + this.properties.OP + " *B"; + }; + + GenericCompare.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A === undefined) { + A = this.properties.A; + } else { + this.properties.A = A; + } + + var B = this.getInputData(1); + if (B === undefined) { + B = this.properties.B; + } else { + this.properties.B = B; + } + + var result = false; + if (typeof A == typeof B){ + switch (this.properties.OP) { + case "==": + case "!=": + // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual() + result = true; + switch(typeof A){ + case "object": + var aProps = Object.getOwnPropertyNames(A); + var bProps = Object.getOwnPropertyNames(B); + if (aProps.length != bProps.length){ + result = false; + break; + } + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + if (A[propName] !== B[propName]) { + result = false; + break; + } + } + break; + default: + result = A == B; + } + if (this.properties.OP == "!=") result = !result; + break; + /*case ">": + result = A > B; + break; + case "<": + result = A < B; + break; + case "<=": + result = A <= B; + break; + case ">=": + result = A >= B; + break; + case "||": + result = A || B; + break; + case "&&": + result = A && B; + break;*/ + } + } + this.setOutputData(0, result); + this.setOutputData(1, !result); + }; + + LiteGraph.registerNodeType("basic/CompareValues", GenericCompare); + +})(this); + +//event related nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + //Show value inside the debug console + function LogEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + } + + LogEvent.title = "Log Event"; + LogEvent.desc = "Log event in console"; + + LogEvent.prototype.onAction = function(action, param, options) { + console.log(action, param); + }; + + LiteGraph.registerNodeType("events/log", LogEvent); + + //convert to Event if the value is true + function TriggerEvent() { + this.size = [60, 30]; + this.addInput("if", ""); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.properties = { only_on_change: true }; + this.prev = 0; + } + + TriggerEvent.title = "TriggerEvent"; + TriggerEvent.desc = "Triggers event if input evaluates to true"; + + TriggerEvent.prototype.onExecute = function( param, options) { + var v = this.getInputData(0); + var changed = (v != this.prev); + if(this.prev === 0) + changed = false; + var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); + if(v && must_resend ) + this.triggerSlot(0, param, null, options); + if(!v && must_resend) + this.triggerSlot(2, param, null, options); + if(changed) + this.triggerSlot(1, param, null, options); + this.prev = v; + }; + + LiteGraph.registerNodeType("events/trigger", TriggerEvent); + + //Sequence of events + function Sequence() { + var that = this; + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addWidget("button","+",null,function(){ + that.addInput("", LiteGraph.ACTION); + that.addOutput("", LiteGraph.EVENT); + }); + this.size = [90, 70]; + this.flags = { horizontal: true, render_box: false }; + } + + Sequence.title = "Sequence"; + Sequence.desc = "Triggers a sequence of events when an event arrives"; + + Sequence.prototype.getTitle = function() { + return ""; + }; + + Sequence.prototype.onAction = function(action, param, options) { + if (this.outputs) { + options = options || {}; + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + //needs more info about this... + if( options.action_call ) // CREATE A NEW ID FOR THE ACTION + options.action_call = options.action_call + "_seq_" + i; + else + options.action_call = this.id + "_" + (action ? action : "action")+"_seq_"+i+"_"+Math.floor(Math.random()*9999); + this.triggerSlot(i, param, null, options); + } + } + }; + + LiteGraph.registerNodeType("events/sequence", Sequence); + + + //Sequence of events + function WaitAll() { + var that = this; + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addOutput("", LiteGraph.EVENT); + this.addWidget("button","+",null,function(){ + that.addInput("", LiteGraph.ACTION); + that.size[0] = 90; + }); + this.size = [90, 70]; + this.ready = []; +} + +WaitAll.title = "WaitAll"; +WaitAll.desc = "Wait until all input events arrive then triggers output"; + +WaitAll.prototype.getTitle = function() { + return ""; +}; + +WaitAll.prototype.onDrawBackground = function(ctx) +{ + if (this.flags.collapsed) { + return; + } + for(var i = 0; i < this.inputs.length; ++i) + { + var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10; + ctx.fillStyle = this.ready[i] ? "#AFB" : "#000"; + ctx.fillRect(20, y, 10, 10); + } +} + +WaitAll.prototype.onAction = function(action, param, options, slot_index) { + if(slot_index == null) + return; + + //check all + this.ready.length = this.outputs.length; + this.ready[slot_index] = true; + for(var i = 0; i < this.ready.length;++i) + if(!this.ready[i]) + return; + //pass + this.reset(); + this.triggerSlot(0); +}; + +WaitAll.prototype.reset = function() +{ + this.ready.length = 0; +} + +LiteGraph.registerNodeType("events/waitAll", WaitAll); + + + //Sequencer for events + function Stepper() { + var that = this; + this.properties = { index: 0 }; + this.addInput("index", "number"); + this.addInput("step", LiteGraph.ACTION); + this.addInput("reset", LiteGraph.ACTION); + this.addOutput("index", "number"); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT,{removable:true}); + this.addWidget("button","+",null,function(){ + that.addOutput("", LiteGraph.EVENT, {removable:true}); + }); + this.size = [120, 120]; + this.flags = { render_box: false }; + } + + Stepper.title = "Stepper"; + Stepper.desc = "Trigger events sequentially when an tick arrives"; + + Stepper.prototype.onDrawBackground = function(ctx) + { + if (this.flags.collapsed) { + return; + } + var index = this.properties.index || 0; + ctx.fillStyle = "#AFB"; + var w = this.size[0]; + var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4; + ctx.beginPath(); + ctx.moveTo(w - 30, y); + ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + } + + Stepper.prototype.onExecute = function() + { + var index = this.getInputData(0); + if(index != null) + { + index = Math.floor(index); + index = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 ); + if( index != this.properties.index ) + { + this.properties.index = index; + this.triggerSlot( index+1 ); + } + } + + this.setOutputData(0, this.properties.index ); + } + + Stepper.prototype.onAction = function(action, param) { + if(action == "reset") + this.properties.index = 0; + else if(action == "step") + { + this.triggerSlot(this.properties.index+1, param); + var n = this.outputs ? this.outputs.length - 1 : 0; + this.properties.index = (this.properties.index + 1) % n; + } + }; + + LiteGraph.registerNodeType("events/stepper", Stepper); + + //Filter events + function FilterEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + this.addOutput("event", LiteGraph.EVENT); + this.properties = { + equal_to: "", + has_property: "", + property_equal_to: "" + }; + } + + FilterEvent.title = "Filter Event"; + FilterEvent.desc = "Blocks events that do not match the filter"; + + FilterEvent.prototype.onAction = function(action, param, options) { + if (param == null) { + return; + } + + if (this.properties.equal_to && this.properties.equal_to != param) { + return; + } + + if (this.properties.has_property) { + var prop = param[this.properties.has_property]; + if (prop == null) { + return; + } + + if ( + this.properties.property_equal_to && + this.properties.property_equal_to != prop + ) { + return; + } + } + + this.triggerSlot(0, param, null, options); + }; + + LiteGraph.registerNodeType("events/filter", FilterEvent); + + + function EventBranch() { + this.addInput("in", LiteGraph.ACTION); + this.addInput("cond", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.size = [120, 60]; + this._value = false; + } + + EventBranch.title = "Branch"; + EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + + EventBranch.prototype.onExecute = function() { + this._value = this.getInputData(1); + } + + EventBranch.prototype.onAction = function(action, param, options) { + this._value = this.getInputData(1); + this.triggerSlot(this._value ? 0 : 1, param, null, options); + } + + LiteGraph.registerNodeType("events/branch", EventBranch); + + //Show value inside the debug console + function EventCounter() { + this.addInput("inc", LiteGraph.ACTION); + this.addInput("dec", LiteGraph.ACTION); + this.addInput("reset", LiteGraph.ACTION); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("num", "number"); + this.addProperty("doCountExecution", false, "boolean", {name: "Count Executions"}); + this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"); + this.num = 0; + } + + EventCounter.title = "Counter"; + EventCounter.desc = "Counts events"; + + EventCounter.prototype.getTitle = function() { + if (this.flags.collapsed) { + return String(this.num); + } + return this.title; + }; + + EventCounter.prototype.onAction = function(action, param, options) { + var v = this.num; + if (action == "inc") { + this.num += 1; + } else if (action == "dec") { + this.num -= 1; + } else if (action == "reset") { + this.num = 0; + } + if (this.num != v) { + this.trigger("change", this.num); + } + }; + + EventCounter.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + ctx.fillStyle = "#AAA"; + ctx.font = "20px Arial"; + ctx.textAlign = "center"; + ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); + }; + + EventCounter.prototype.onExecute = function() { + if(this.properties.doCountExecution){ + this.num += 1; + } + this.setOutputData(1, this.num); + }; + + LiteGraph.registerNodeType("events/counter", EventCounter); + + //Show value inside the debug console + function DelayEvent() { + this.size = [60, 30]; + this.addProperty("time_in_ms", 1000); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; + } + + DelayEvent.title = "Delay"; + DelayEvent.desc = "Delays one event"; + + DelayEvent.prototype.onAction = function(action, param, options) { + var time = this.properties.time_in_ms; + if (time <= 0) { + this.trigger(null, param, options); + } else { + this._pending.push([time, param]); + } + }; + + DelayEvent.prototype.onExecute = function(param, options) { + var dt = this.graph.elapsed_time * 1000; //in ms + + if (this.isInputConnected(1)) { + this.properties.time_in_ms = this.getInputData(1); + } + + for (var i = 0; i < this._pending.length; ++i) { + var actionPass = this._pending[i]; + actionPass[0] -= dt; + if (actionPass[0] > 0) { + continue; + } + + //remove + this._pending.splice(i, 1); + --i; + + //trigger + this.trigger(null, actionPass[1], options); + } + }; + + DelayEvent.prototype.onGetInputs = function() { + return [["event", LiteGraph.ACTION], ["time_in_ms", "number"]]; + }; + + LiteGraph.registerNodeType("events/delay", DelayEvent); + + //Show value inside the debug console + function TimerEvent() { + this.addProperty("interval", 1000); + this.addProperty("event", "tick"); + this.addOutput("on_tick", LiteGraph.EVENT); + this.time = 0; + this.last_interval = 1000; + this.triggered = false; + } + + TimerEvent.title = "Timer"; + TimerEvent.desc = "Sends an event every N milliseconds"; + + TimerEvent.prototype.onStart = function() { + this.time = 0; + }; + + TimerEvent.prototype.getTitle = function() { + return "Timer: " + this.last_interval.toString() + "ms"; + }; + + TimerEvent.on_color = "#AAA"; + TimerEvent.off_color = "#222"; + + TimerEvent.prototype.onDrawBackground = function() { + this.boxcolor = this.triggered + ? TimerEvent.on_color + : TimerEvent.off_color; + this.triggered = false; + }; + + TimerEvent.prototype.onExecute = function() { + var dt = this.graph.elapsed_time * 1000; //in ms + + var trigger = this.time == 0; + + this.time += dt; + this.last_interval = Math.max( + 1, + this.getInputOrProperty("interval") | 0 + ); + + if ( + !trigger && + (this.time < this.last_interval || isNaN(this.last_interval)) + ) { + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, false); + } + return; + } + + this.triggered = true; + this.time = this.time % this.last_interval; + this.trigger("on_tick", this.properties.event); + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, true); + } + }; + + TimerEvent.prototype.onGetInputs = function() { + return [["interval", "number"]]; + }; + + TimerEvent.prototype.onGetOutputs = function() { + return [["tick", "boolean"]]; + }; + + LiteGraph.registerNodeType("events/timer", TimerEvent); + + + + function SemaphoreEvent() { + this.addInput("go", LiteGraph.ACTION ); + this.addInput("green", LiteGraph.ACTION ); + this.addInput("red", LiteGraph.ACTION ); + this.addOutput("continue", LiteGraph.EVENT ); + this.addOutput("blocked", LiteGraph.EVENT ); + this.addOutput("is_green", "boolean" ); + this._ready = false; + this.properties = {}; + var that = this; + this.addWidget("button","reset","",function(){ + that._ready = false; + }); + } + + SemaphoreEvent.title = "Semaphore Event"; + SemaphoreEvent.desc = "Until both events are not triggered, it doesnt continue."; + + SemaphoreEvent.prototype.onExecute = function() + { + this.setOutputData(1,this._ready); + this.boxcolor = this._ready ? "#9F9" : "#FA5"; + } + + SemaphoreEvent.prototype.onAction = function(action, param) { + if( action == "go" ) + this.triggerSlot( this._ready ? 0 : 1 ); + else if( action == "green" ) + this._ready = true; + else if( action == "red" ) + this._ready = false; + }; + + LiteGraph.registerNodeType("events/semaphore", SemaphoreEvent); + + function OnceEvent() { + this.addInput("in", LiteGraph.ACTION ); + this.addInput("reset", LiteGraph.ACTION ); + this.addOutput("out", LiteGraph.EVENT ); + this._once = false; + this.properties = {}; + var that = this; + this.addWidget("button","reset","",function(){ + that._once = false; + }); + } + + OnceEvent.title = "Once"; + OnceEvent.desc = "Only passes an event once, then gets locked"; + + OnceEvent.prototype.onAction = function(action, param) { + if( action == "in" && !this._once ) + { + this._once = true; + this.triggerSlot( 0, param ); + } + else if( action == "reset" ) + this._once = false; + }; + + LiteGraph.registerNodeType("events/once", OnceEvent); + + function DataStore() { + this.addInput("data", 0); + this.addInput("assign", LiteGraph.ACTION); + this.addOutput("data", 0); + this._last_value = null; + this.properties = { data: null, serialize: true }; + var that = this; + this.addWidget("button","store","",function(){ + that.properties.data = that._last_value; + }); + } + + DataStore.title = "Data Store"; + DataStore.desc = "Stores data and only changes when event is received"; + + DataStore.prototype.onExecute = function() + { + this._last_value = this.getInputData(0); + this.setOutputData(0, this.properties.data ); + } + + DataStore.prototype.onAction = function(action, param, options) { + this.properties.data = this._last_value; + }; + + DataStore.prototype.onSerialize = function(o) + { + if(o.data == null) + return; + if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) + o.data = null; + } + + LiteGraph.registerNodeType("basic/data_store", DataStore); + + + +})(this); + +//widgets +(function(global) { + var LiteGraph = global.LiteGraph; + + /* Button ****************/ + + function WidgetButton() { + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", "boolean"); + this.addProperty("text", "click me"); + this.addProperty("font_size", 30); + this.addProperty("message", ""); + this.size = [164, 84]; + this.clicked = false; + } + + WidgetButton.title = "Button"; + WidgetButton.desc = "Triggers an event"; + + WidgetButton.font = "Arial"; + WidgetButton.prototype.onDrawForeground = function(ctx) { + if (this.flags.collapsed) { + return; + } + var margin = 10; + ctx.fillStyle = "black"; + ctx.fillRect( + margin + 1, + margin + 1, + this.size[0] - margin * 2, + this.size[1] - margin * 2 + ); + ctx.fillStyle = "#AAF"; + ctx.fillRect( + margin - 1, + margin - 1, + this.size[0] - margin * 2, + this.size[1] - margin * 2 + ); + ctx.fillStyle = this.clicked + ? "white" + : this.mouseOver + ? "#668" + : "#334"; + ctx.fillRect( + margin, + margin, + this.size[0] - margin * 2, + this.size[1] - margin * 2 + ); + + if (this.properties.text || this.properties.text === 0) { + var font_size = this.properties.font_size || 30; + ctx.textAlign = "center"; + ctx.fillStyle = this.clicked ? "black" : "white"; + ctx.font = font_size + "px " + WidgetButton.font; + ctx.fillText( + this.properties.text, + this.size[0] * 0.5, + this.size[1] * 0.5 + font_size * 0.3 + ); + ctx.textAlign = "left"; + } + }; + + WidgetButton.prototype.onMouseDown = function(e, local_pos) { + if ( + local_pos[0] > 1 && + local_pos[1] > 1 && + local_pos[0] < this.size[0] - 2 && + local_pos[1] < this.size[1] - 2 + ) { + this.clicked = true; + this.setOutputData(1, this.clicked); + this.triggerSlot(0, this.properties.message); + return true; + } + }; + + WidgetButton.prototype.onExecute = function() { + this.setOutputData(1, this.clicked); + }; + + WidgetButton.prototype.onMouseUp = function(e) { + this.clicked = false; + }; + + LiteGraph.registerNodeType("widget/button", WidgetButton); + + function WidgetToggle() { + this.addInput("", "boolean"); + this.addInput("e", LiteGraph.ACTION); + this.addOutput("v", "boolean"); + this.addOutput("e", LiteGraph.EVENT); + this.properties = { font: "", value: false }; + this.size = [160, 44]; + } + + WidgetToggle.title = "Toggle"; + WidgetToggle.desc = "Toggles between true or false"; + + WidgetToggle.prototype.onDrawForeground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + var size = this.size[1] * 0.5; + var margin = 0.25; + var h = this.size[1] * 0.8; + ctx.font = this.properties.font || (size * 0.8).toFixed(0) + "px Arial"; + var w = ctx.measureText(this.title).width; + var x = (this.size[0] - (w + size)) * 0.5; + + ctx.fillStyle = "#AAA"; + ctx.fillRect(x, h - size, size, size); + + ctx.fillStyle = this.properties.value ? "#AEF" : "#000"; + ctx.fillRect( + x + size * margin, + h - size + size * margin, + size * (1 - margin * 2), + size * (1 - margin * 2) + ); + + ctx.textAlign = "left"; + ctx.fillStyle = "#AAA"; + ctx.fillText(this.title, size * 1.2 + x, h * 0.85); + ctx.textAlign = "left"; + }; + + WidgetToggle.prototype.onAction = function(action) { + this.properties.value = !this.properties.value; + this.trigger("e", this.properties.value); + }; + + WidgetToggle.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v != null) { + this.properties.value = v; + } + this.setOutputData(0, this.properties.value); + }; + + WidgetToggle.prototype.onMouseDown = function(e, local_pos) { + if ( + local_pos[0] > 1 && + local_pos[1] > 1 && + local_pos[0] < this.size[0] - 2 && + local_pos[1] < this.size[1] - 2 + ) { + this.properties.value = !this.properties.value; + this.graph._version++; + this.trigger("e", this.properties.value); + return true; + } + }; + + LiteGraph.registerNodeType("widget/toggle", WidgetToggle); + + /* Number ****************/ + + function WidgetNumber() { + this.addOutput("", "number"); + this.size = [80, 60]; + this.properties = { min: -1000, max: 1000, value: 1, step: 1 }; + this.old_y = -1; + this._remainder = 0; + this._precision = 0; + this.mouse_captured = false; + } + + WidgetNumber.title = "Number"; + WidgetNumber.desc = "Widget to select number value"; + + WidgetNumber.pixels_threshold = 10; + WidgetNumber.markers_color = "#666"; + + WidgetNumber.prototype.onDrawForeground = function(ctx) { + var x = this.size[0] * 0.5; + var h = this.size[1]; + if (h > 30) { + ctx.fillStyle = WidgetNumber.markers_color; + ctx.beginPath(); + ctx.moveTo(x, h * 0.1); + ctx.lineTo(x + h * 0.1, h * 0.2); + ctx.lineTo(x + h * -0.1, h * 0.2); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(x, h * 0.9); + ctx.lineTo(x + h * 0.1, h * 0.8); + ctx.lineTo(x + h * -0.1, h * 0.8); + ctx.fill(); + ctx.font = (h * 0.7).toFixed(1) + "px Arial"; + } else { + ctx.font = (h * 0.8).toFixed(1) + "px Arial"; + } + + ctx.textAlign = "center"; + ctx.font = (h * 0.7).toFixed(1) + "px Arial"; + ctx.fillStyle = "#EEE"; + ctx.fillText( + this.properties.value.toFixed(this._precision), + x, + h * 0.75 + ); + }; + + WidgetNumber.prototype.onExecute = function() { + this.setOutputData(0, this.properties.value); + }; + + WidgetNumber.prototype.onPropertyChanged = function(name, value) { + var t = (this.properties.step + "").split("."); + this._precision = t.length > 1 ? t[1].length : 0; + }; + + WidgetNumber.prototype.onMouseDown = function(e, pos) { + if (pos[1] < 0) { + return; + } + + this.old_y = e.canvasY; + this.captureInput(true); + this.mouse_captured = true; + + return true; + }; + + WidgetNumber.prototype.onMouseMove = function(e) { + if (!this.mouse_captured) { + return; + } + + var delta = this.old_y - e.canvasY; + if (e.shiftKey) { + delta *= 10; + } + if (e.metaKey || e.altKey) { + delta *= 0.1; + } + this.old_y = e.canvasY; + + var steps = this._remainder + delta / WidgetNumber.pixels_threshold; + this._remainder = steps % 1; + steps = steps | 0; + + var v = clamp( + this.properties.value + steps * this.properties.step, + this.properties.min, + this.properties.max + ); + this.properties.value = v; + this.graph._version++; + this.setDirtyCanvas(true); + }; + + WidgetNumber.prototype.onMouseUp = function(e, pos) { + if (e.click_time < 200) { + var steps = pos[1] > this.size[1] * 0.5 ? -1 : 1; + this.properties.value = clamp( + this.properties.value + steps * this.properties.step, + this.properties.min, + this.properties.max + ); + this.graph._version++; + this.setDirtyCanvas(true); + } + + if (this.mouse_captured) { + this.mouse_captured = false; + this.captureInput(false); + } + }; + + LiteGraph.registerNodeType("widget/number", WidgetNumber); + + + /* Combo ****************/ + + function WidgetCombo() { + this.addOutput("", "string"); + this.addOutput("change", LiteGraph.EVENT); + this.size = [80, 60]; + this.properties = { value: "A", values:"A;B;C" }; + this.old_y = -1; + this.mouse_captured = false; + this._values = this.properties.values.split(";"); + var that = this; + this.widgets_up = true; + this.widget = this.addWidget("combo","", this.properties.value, function(v){ + that.properties.value = v; + that.triggerSlot(1, v); + }, { property: "value", values: this._values } ); + } + + WidgetCombo.title = "Combo"; + WidgetCombo.desc = "Widget to select from a list"; + + WidgetCombo.prototype.onExecute = function() { + this.setOutputData( 0, this.properties.value ); + }; + + WidgetCombo.prototype.onPropertyChanged = function(name, value) { + if(name == "values") + { + this._values = value.split(";"); + this.widget.options.values = this._values; + } + else if(name == "value") + { + this.widget.value = value; + } + }; + + LiteGraph.registerNodeType("widget/combo", WidgetCombo); + + + /* Knob ****************/ + + function WidgetKnob() { + this.addOutput("", "number"); + this.size = [64, 84]; + this.properties = { + min: 0, + max: 1, + value: 0.5, + color: "#7AF", + precision: 2 + }; + this.value = -1; + } + + WidgetKnob.title = "Knob"; + WidgetKnob.desc = "Circular controller"; + WidgetKnob.size = [80, 100]; + + WidgetKnob.prototype.onDrawForeground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + if (this.value == -1) { + this.value = + (this.properties.value - this.properties.min) / + (this.properties.max - this.properties.min); + } + + var center_x = this.size[0] * 0.5; + var center_y = this.size[1] * 0.5; + var radius = Math.min(this.size[0], this.size[1]) * 0.5 - 5; + var w = Math.floor(radius * 0.05); + + ctx.globalAlpha = 1; + ctx.save(); + ctx.translate(center_x, center_y); + ctx.rotate(Math.PI * 0.75); + + //bg + ctx.fillStyle = "rgba(0,0,0,0.5)"; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.arc(0, 0, radius, 0, Math.PI * 1.5); + ctx.fill(); + + //value + ctx.strokeStyle = "black"; + ctx.fillStyle = this.properties.color; + ctx.lineWidth = 2; + ctx.beginPath(); + ctx.moveTo(0, 0); + ctx.arc( + 0, + 0, + radius - 4, + 0, + Math.PI * 1.5 * Math.max(0.01, this.value) + ); + ctx.closePath(); + ctx.fill(); + //ctx.stroke(); + ctx.lineWidth = 1; + ctx.globalAlpha = 1; + ctx.restore(); + + //inner + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.arc(center_x, center_y, radius * 0.75, 0, Math.PI * 2, true); + ctx.fill(); + + //miniball + ctx.fillStyle = this.mouseOver ? "white" : this.properties.color; + ctx.beginPath(); + var angle = this.value * Math.PI * 1.5 + Math.PI * 0.75; + ctx.arc( + center_x + Math.cos(angle) * radius * 0.65, + center_y + Math.sin(angle) * radius * 0.65, + radius * 0.05, + 0, + Math.PI * 2, + true + ); + ctx.fill(); + + //text + ctx.fillStyle = this.mouseOver ? "white" : "#AAA"; + ctx.font = Math.floor(radius * 0.5) + "px Arial"; + ctx.textAlign = "center"; + ctx.fillText( + this.properties.value.toFixed(this.properties.precision), + center_x, + center_y + radius * 0.15 + ); + }; + + WidgetKnob.prototype.onExecute = function() { + this.setOutputData(0, this.properties.value); + this.boxcolor = LiteGraph.colorToString([ + this.value, + this.value, + this.value + ]); + }; + + WidgetKnob.prototype.onMouseDown = function(e) { + this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; + this.radius = this.size[0] * 0.5; + if ( + e.canvasY - this.pos[1] < 20 || + LiteGraph.distance( + [e.canvasX, e.canvasY], + [this.pos[0] + this.center[0], this.pos[1] + this.center[1]] + ) > this.radius + ) { + return false; + } + this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; + this.captureInput(true); + return true; + }; + + WidgetKnob.prototype.onMouseMove = function(e) { + if (!this.oldmouse) { + return; + } + + var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; + + var v = this.value; + v -= (m[1] - this.oldmouse[1]) * 0.01; + if (v > 1.0) { + v = 1.0; + } else if (v < 0.0) { + v = 0.0; + } + this.value = v; + this.properties.value = + this.properties.min + + (this.properties.max - this.properties.min) * this.value; + this.oldmouse = m; + this.setDirtyCanvas(true); + }; + + WidgetKnob.prototype.onMouseUp = function(e) { + if (this.oldmouse) { + this.oldmouse = null; + this.captureInput(false); + } + }; + + WidgetKnob.prototype.onPropertyChanged = function(name, value) { + if (name == "min" || name == "max" || name == "value") { + this.properties[name] = parseFloat(value); + return true; //block + } + }; + + LiteGraph.registerNodeType("widget/knob", WidgetKnob); + + //Show value inside the debug console + function WidgetSliderGUI() { + this.addOutput("", "number"); + this.properties = { + value: 0.5, + min: 0, + max: 1, + text: "V" + }; + var that = this; + this.size = [140, 40]; + this.slider = this.addWidget( + "slider", + "V", + this.properties.value, + function(v) { + that.properties.value = v; + }, + this.properties + ); + this.widgets_up = true; + } + + WidgetSliderGUI.title = "Inner Slider"; + + WidgetSliderGUI.prototype.onPropertyChanged = function(name, value) { + if (name == "value") { + this.slider.value = value; + } + }; + + WidgetSliderGUI.prototype.onExecute = function() { + this.setOutputData(0, this.properties.value); + }; + + LiteGraph.registerNodeType("widget/internal_slider", WidgetSliderGUI); + + //Widget H SLIDER + function WidgetHSlider() { + this.size = [160, 26]; + this.addOutput("", "number"); + this.properties = { color: "#7AF", min: 0, max: 1, value: 0.5 }; + this.value = -1; + } + + WidgetHSlider.title = "H.Slider"; + WidgetHSlider.desc = "Linear slider controller"; + + WidgetHSlider.prototype.onDrawForeground = function(ctx) { + if (this.value == -1) { + this.value = + (this.properties.value - this.properties.min) / + (this.properties.max - this.properties.min); + } + + //border + ctx.globalAlpha = 1; + ctx.lineWidth = 1; + ctx.fillStyle = "#000"; + ctx.fillRect(2, 2, this.size[0] - 4, this.size[1] - 4); + + ctx.fillStyle = this.properties.color; + ctx.beginPath(); + ctx.rect(4, 4, (this.size[0] - 8) * this.value, this.size[1] - 8); + ctx.fill(); + }; + + WidgetHSlider.prototype.onExecute = function() { + this.properties.value = + this.properties.min + + (this.properties.max - this.properties.min) * this.value; + this.setOutputData(0, this.properties.value); + this.boxcolor = LiteGraph.colorToString([ + this.value, + this.value, + this.value + ]); + }; + + WidgetHSlider.prototype.onMouseDown = function(e) { + if (e.canvasY - this.pos[1] < 0) { + return false; + } + + this.oldmouse = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; + this.captureInput(true); + return true; + }; + + WidgetHSlider.prototype.onMouseMove = function(e) { + if (!this.oldmouse) { + return; + } + + var m = [e.canvasX - this.pos[0], e.canvasY - this.pos[1]]; + + var v = this.value; + var delta = m[0] - this.oldmouse[0]; + v += delta / this.size[0]; + if (v > 1.0) { + v = 1.0; + } else if (v < 0.0) { + v = 0.0; + } + + this.value = v; + + this.oldmouse = m; + this.setDirtyCanvas(true); + }; + + WidgetHSlider.prototype.onMouseUp = function(e) { + this.oldmouse = null; + this.captureInput(false); + }; + + WidgetHSlider.prototype.onMouseLeave = function(e) { + //this.oldmouse = null; + }; + + LiteGraph.registerNodeType("widget/hslider", WidgetHSlider); + + function WidgetProgress() { + this.size = [160, 26]; + this.addInput("", "number"); + this.properties = { min: 0, max: 1, value: 0, color: "#AAF" }; + } + + WidgetProgress.title = "Progress"; + WidgetProgress.desc = "Shows data in linear progress"; + + WidgetProgress.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v != undefined) { + this.properties["value"] = v; + } + }; + + WidgetProgress.prototype.onDrawForeground = function(ctx) { + //border + ctx.lineWidth = 1; + ctx.fillStyle = this.properties.color; + var v = + (this.properties.value - this.properties.min) / + (this.properties.max - this.properties.min); + v = Math.min(1, v); + v = Math.max(0, v); + ctx.fillRect(2, 2, (this.size[0] - 4) * v, this.size[1] - 4); + }; + + LiteGraph.registerNodeType("widget/progress", WidgetProgress); + + function WidgetText() { + this.addInputs("", 0); + this.properties = { + value: "...", + font: "Arial", + fontsize: 18, + color: "#AAA", + align: "left", + glowSize: 0, + decimals: 1 + }; + } + + WidgetText.title = "Text"; + WidgetText.desc = "Shows the input value"; + WidgetText.widgets = [ + { name: "resize", text: "Resize box", type: "button" }, + { name: "led_text", text: "LED", type: "minibutton" }, + { name: "normal_text", text: "Normal", type: "minibutton" } + ]; + + WidgetText.prototype.onDrawForeground = function(ctx) { + //ctx.fillStyle="#000"; + //ctx.fillRect(0,0,100,60); + ctx.fillStyle = this.properties["color"]; + var v = this.properties["value"]; + + if (this.properties["glowSize"]) { + ctx.shadowColor = this.properties.color; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["glowSize"]; + } else { + ctx.shadowColor = "transparent"; + } + + var fontsize = this.properties["fontsize"]; + + ctx.textAlign = this.properties["align"]; + ctx.font = fontsize.toString() + "px " + this.properties["font"]; + this.str = + typeof v == "number" ? v.toFixed(this.properties["decimals"]) : v; + + if (typeof this.str == "string") { + var lines = this.str.replace(/[\r\n]/g, "\\n").split("\\n"); + for (var i=0; i < lines.length; i++) { + ctx.fillText( + lines[i], + this.properties["align"] == "left" ? 15 : this.size[0] - 15, + fontsize * -0.15 + fontsize * (parseInt(i) + 1) + ); + } + } + + ctx.shadowColor = "transparent"; + this.last_ctx = ctx; + ctx.textAlign = "left"; + }; + + WidgetText.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v != null) { + this.properties["value"] = v; + } + //this.setDirtyCanvas(true); + }; + + WidgetText.prototype.resize = function() { + if (!this.last_ctx) { + return; + } + + var lines = this.str.split("\\n"); + this.last_ctx.font = + this.properties["fontsize"] + "px " + this.properties["font"]; + var max = 0; + for (var i=0; i < lines.length; i++) { + var w = this.last_ctx.measureText(lines[i]).width; + if (max < w) { + max = w; + } + } + this.size[0] = max + 20; + this.size[1] = 4 + lines.length * this.properties["fontsize"]; + + this.setDirtyCanvas(true); + }; + + WidgetText.prototype.onPropertyChanged = function(name, value) { + this.properties[name] = value; + this.str = typeof value == "number" ? value.toFixed(3) : value; + //this.resize(); + return true; + }; + + LiteGraph.registerNodeType("widget/text", WidgetText); + + function WidgetPanel() { + this.size = [200, 100]; + this.properties = { + borderColor: "#ffffff", + bgcolorTop: "#f0f0f0", + bgcolorBottom: "#e0e0e0", + shadowSize: 2, + borderRadius: 3 + }; + } + + WidgetPanel.title = "Panel"; + WidgetPanel.desc = "Non interactive panel"; + WidgetPanel.widgets = [{ name: "update", text: "Update", type: "button" }]; + + WidgetPanel.prototype.createGradient = function(ctx) { + if ( + this.properties["bgcolorTop"] == "" || + this.properties["bgcolorBottom"] == "" + ) { + this.lineargradient = 0; + return; + } + + this.lineargradient = ctx.createLinearGradient(0, 0, 0, this.size[1]); + this.lineargradient.addColorStop(0, this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1, this.properties["bgcolorBottom"]); + }; + + WidgetPanel.prototype.onDrawForeground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + if (this.lineargradient == null) { + this.createGradient(ctx); + } + + if (!this.lineargradient) { + return; + } + + ctx.lineWidth = 1; + ctx.strokeStyle = this.properties["borderColor"]; + //ctx.fillStyle = "#ebebeb"; + ctx.fillStyle = this.lineargradient; + + if (this.properties["shadowSize"]) { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + } else { + ctx.shadowColor = "transparent"; + } + + ctx.roundRect( + 0, + 0, + this.size[0] - 1, + this.size[1] - 1, + this.properties["shadowSize"] + ); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.stroke(); + }; + + LiteGraph.registerNodeType("widget/panel", WidgetPanel); +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + function GamepadInput() { + this.addOutput("left_x_axis", "number"); + this.addOutput("left_y_axis", "number"); + this.addOutput("button_pressed", LiteGraph.EVENT); + this.properties = { gamepad_index: 0, threshold: 0.1 }; + + this._left_axis = new Float32Array(2); + this._right_axis = new Float32Array(2); + this._triggers = new Float32Array(2); + this._previous_buttons = new Uint8Array(17); + this._current_buttons = new Uint8Array(17); + } + + GamepadInput.title = "Gamepad"; + GamepadInput.desc = "gets the input of the gamepad"; + + GamepadInput.CENTER = 0; + GamepadInput.LEFT = 1; + GamepadInput.RIGHT = 2; + GamepadInput.UP = 4; + GamepadInput.DOWN = 8; + + GamepadInput.zero = new Float32Array(2); + GamepadInput.buttons = [ + "a", + "b", + "x", + "y", + "lb", + "rb", + "lt", + "rt", + "back", + "start", + "ls", + "rs", + "home" + ]; + + GamepadInput.prototype.onExecute = function() { + //get gamepad + var gamepad = this.getGamepad(); + var threshold = this.properties.threshold || 0.0; + + if (gamepad) { + this._left_axis[0] = + Math.abs(gamepad.xbox.axes["lx"]) > threshold + ? gamepad.xbox.axes["lx"] + : 0; + this._left_axis[1] = + Math.abs(gamepad.xbox.axes["ly"]) > threshold + ? gamepad.xbox.axes["ly"] + : 0; + this._right_axis[0] = + Math.abs(gamepad.xbox.axes["rx"]) > threshold + ? gamepad.xbox.axes["rx"] + : 0; + this._right_axis[1] = + Math.abs(gamepad.xbox.axes["ry"]) > threshold + ? gamepad.xbox.axes["ry"] + : 0; + this._triggers[0] = + Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold + ? gamepad.xbox.axes["ltrigger"] + : 0; + this._triggers[1] = + Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold + ? gamepad.xbox.axes["rtrigger"] + : 0; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var v = null; + + if (gamepad) { + switch (output.name) { + case "left_axis": + v = this._left_axis; + break; + case "right_axis": + v = this._right_axis; + break; + case "left_x_axis": + v = this._left_axis[0]; + break; + case "left_y_axis": + v = this._left_axis[1]; + break; + case "right_x_axis": + v = this._right_axis[0]; + break; + case "right_y_axis": + v = this._right_axis[1]; + break; + case "trigger_left": + v = this._triggers[0]; + break; + case "trigger_right": + v = this._triggers[1]; + break; + case "a_button": + v = gamepad.xbox.buttons["a"] ? 1 : 0; + break; + case "b_button": + v = gamepad.xbox.buttons["b"] ? 1 : 0; + break; + case "x_button": + v = gamepad.xbox.buttons["x"] ? 1 : 0; + break; + case "y_button": + v = gamepad.xbox.buttons["y"] ? 1 : 0; + break; + case "lb_button": + v = gamepad.xbox.buttons["lb"] ? 1 : 0; + break; + case "rb_button": + v = gamepad.xbox.buttons["rb"] ? 1 : 0; + break; + case "ls_button": + v = gamepad.xbox.buttons["ls"] ? 1 : 0; + break; + case "rs_button": + v = gamepad.xbox.buttons["rs"] ? 1 : 0; + break; + case "hat_left": + v = gamepad.xbox.hatmap & GamepadInput.LEFT; + break; + case "hat_right": + v = gamepad.xbox.hatmap & GamepadInput.RIGHT; + break; + case "hat_up": + v = gamepad.xbox.hatmap & GamepadInput.UP; + break; + case "hat_down": + v = gamepad.xbox.hatmap & GamepadInput.DOWN; + break; + case "hat": + v = gamepad.xbox.hatmap; + break; + case "start_button": + v = gamepad.xbox.buttons["start"] ? 1 : 0; + break; + case "back_button": + v = gamepad.xbox.buttons["back"] ? 1 : 0; + break; + case "button_pressed": + for ( + var j = 0; + j < this._current_buttons.length; + ++j + ) { + if ( + this._current_buttons[j] && + !this._previous_buttons[j] + ) { + this.triggerSlot( + i, + GamepadInput.buttons[j] + ); + } + } + break; + default: + break; + } + } else { + //if no gamepad is connected, output 0 + switch (output.name) { + case "button_pressed": + break; + case "left_axis": + case "right_axis": + v = GamepadInput.zero; + break; + default: + v = 0; + } + } + this.setOutputData(i, v); + } + } + }; + + GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; + GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; + + GamepadInput.prototype.getGamepad = function() { + var getGamepads = + navigator.getGamepads || + navigator.webkitGetGamepads || + navigator.mozGetGamepads; + if (!getGamepads) { + return null; + } + var gamepads = getGamepads.call(navigator); + var gamepad = null; + + this._previous_buttons.set(this._current_buttons); + + //pick the first connected + for (var i = this.properties.gamepad_index; i < 4; i++) { + if (!gamepads[i]) { + continue; + } + gamepad = gamepads[i]; + + //xbox controller mapping + var xbox = this.xbox_mapping; + if (!xbox) { + xbox = this.xbox_mapping = { + axes: [], + buttons: {}, + hat: "", + hatmap: GamepadInput.CENTER + }; + } + + xbox.axes["lx"] = gamepad.axes[0]; + xbox.axes["ly"] = gamepad.axes[1]; + xbox.axes["rx"] = gamepad.axes[2]; + xbox.axes["ry"] = gamepad.axes[3]; + xbox.axes["ltrigger"] = gamepad.buttons[6].value; + xbox.axes["rtrigger"] = gamepad.buttons[7].value; + xbox.hat = ""; + xbox.hatmap = GamepadInput.CENTER; + + for (var j = 0; j < gamepad.buttons.length; j++) { + this._current_buttons[j] = gamepad.buttons[j].pressed; + + if(j < 12) + { + xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; + if(gamepad.buttons[j].was_pressed) + this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); + } + else //mapping of XBOX + switch ( j ) //I use a switch to ensure that a player with another gamepad could play + { + case 12: + if (gamepad.buttons[j].pressed) { + xbox.hat += "up"; + xbox.hatmap |= GamepadInput.UP; + } + break; + case 13: + if (gamepad.buttons[j].pressed) { + xbox.hat += "down"; + xbox.hatmap |= GamepadInput.DOWN; + } + break; + case 14: + if (gamepad.buttons[j].pressed) { + xbox.hat += "left"; + xbox.hatmap |= GamepadInput.LEFT; + } + break; + case 15: + if (gamepad.buttons[j].pressed) { + xbox.hat += "right"; + xbox.hatmap |= GamepadInput.RIGHT; + } + break; + case 16: + xbox.buttons["home"] = gamepad.buttons[j].pressed; + break; + default: + } + } + gamepad.xbox = xbox; + return gamepad; + } + }; + + GamepadInput.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + //render gamepad state? + var la = this._left_axis; + var ra = this._right_axis; + ctx.strokeStyle = "#88A"; + ctx.strokeRect( + (la[0] + 1) * 0.5 * this.size[0] - 4, + (la[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + ctx.strokeStyle = "#8A8"; + ctx.strokeRect( + (ra[0] + 1) * 0.5 * this.size[0] - 4, + (ra[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + var h = this.size[1] / this._current_buttons.length; + ctx.fillStyle = "#AEB"; + for (var i = 0; i < this._current_buttons.length; ++i) { + if (this._current_buttons[i]) { + ctx.fillRect(0, h * i, 6, h); + } + } + }; + + GamepadInput.prototype.onGetOutputs = function() { + return [ + ["left_axis", "vec2"], + ["right_axis", "vec2"], + ["left_x_axis", "number"], + ["left_y_axis", "number"], + ["right_x_axis", "number"], + ["right_y_axis", "number"], + ["trigger_left", "number"], + ["trigger_right", "number"], + ["a_button", "number"], + ["b_button", "number"], + ["x_button", "number"], + ["y_button", "number"], + ["lb_button", "number"], + ["rb_button", "number"], + ["ls_button", "number"], + ["rs_button", "number"], + ["start_button", "number"], + ["back_button", "number"], + ["a_button_event", LiteGraph.EVENT ], + ["b_button_event", LiteGraph.EVENT ], + ["x_button_event", LiteGraph.EVENT ], + ["y_button_event", LiteGraph.EVENT ], + ["lb_button_event", LiteGraph.EVENT ], + ["rb_button_event", LiteGraph.EVENT ], + ["ls_button_event", LiteGraph.EVENT ], + ["rs_button_event", LiteGraph.EVENT ], + ["start_button_event", LiteGraph.EVENT ], + ["back_button_event", LiteGraph.EVENT ], + ["hat_left", "number"], + ["hat_right", "number"], + ["hat_up", "number"], + ["hat_down", "number"], + ["hat", "number"], + ["button_pressed", LiteGraph.EVENT] + ]; + }; + + LiteGraph.registerNodeType("input/gamepad", GamepadInput); + +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + //Converter + function Converter() { + this.addInput("in", 0); + this.addOutput("out", 0); + this.size = [80, 30]; + } + + Converter.title = "Converter"; + Converter.desc = "type A to type B"; + + Converter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + + var result = null; + switch (output.name) { + case "number": + result = v.length ? v[0] : parseFloat(v); + break; + case "vec2": + case "vec3": + case "vec4": + var result = null; + var count = 1; + switch (output.name) { + case "vec2": + count = 2; + break; + case "vec3": + count = 3; + break; + case "vec4": + count = 4; + break; + } + + var result = new Float32Array(count); + if (v.length) { + for ( + var j = 0; + j < v.length && j < result.length; + j++ + ) { + result[j] = v[j]; + } + } else { + result[0] = parseFloat(v); + } + break; + } + this.setOutputData(i, result); + } + } + }; + + Converter.prototype.onGetOutputs = function() { + return [ + ["number", "number"], + ["vec2", "vec2"], + ["vec3", "vec3"], + ["vec4", "vec4"] + ]; + }; + + LiteGraph.registerNodeType("math/converter", Converter); + + //Bypass + function Bypass() { + this.addInput("in"); + this.addOutput("out"); + this.size = [80, 30]; + } + + Bypass.title = "Bypass"; + Bypass.desc = "removes the type"; + + Bypass.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/bypass", Bypass); + + function ToNumber() { + this.addInput("in"); + this.addOutput("out"); + } + + ToNumber.title = "to Number"; + ToNumber.desc = "Cast to number"; + + ToNumber.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, Number(v)); + }; + + LiteGraph.registerNodeType("math/to_number", ToNumber); + + function MathRange() { + this.addInput("in", "number", { locked: true }); + this.addOutput("out", "number", { locked: true }); + this.addOutput("clamped", "number", { locked: true }); + + this.addProperty("in", 0); + this.addProperty("in_min", 0); + this.addProperty("in_max", 1); + this.addProperty("out_min", 0); + this.addProperty("out_max", 1); + + this.size = [120, 50]; + } + + MathRange.title = "Range"; + MathRange.desc = "Convert a number from one range to another"; + + MathRange.prototype.getTitle = function() { + if (this.flags.collapsed) { + return (this._last_v || 0).toFixed(2); + } + return this.title; + }; + + MathRange.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var v = this.properties["in"]; + if (v === undefined || v === null || v.constructor !== Number) { + v = 0; + } + + var in_min = this.properties.in_min; + var in_max = this.properties.in_max; + var out_min = this.properties.out_min; + var out_max = this.properties.out_max; + /* + if( in_min > in_max ) + { + in_min = in_max; + in_max = this.properties.in_min; + } + if( out_min > out_max ) + { + out_min = out_max; + out_max = this.properties.out_min; + } + */ + + this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; + this.setOutputData(0, this._last_v); + this.setOutputData(1, clamp( this._last_v, out_min, out_max )); + }; + + MathRange.prototype.onDrawBackground = function(ctx) { + //show the current value + if (this._last_v) { + this.outputs[0].label = this._last_v.toFixed(3); + } else { + this.outputs[0].label = "?"; + } + }; + + MathRange.prototype.onGetInputs = function() { + return [ + ["in_min", "number"], + ["in_max", "number"], + ["out_min", "number"], + ["out_max", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/range", MathRange); + + function MathRand() { + this.addOutput("value", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.size = [80, 30]; + } + + MathRand.title = "Rand"; + MathRand.desc = "Random number"; + + MathRand.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var min = this.properties.min; + var max = this.properties.max; + this._last_v = Math.random() * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathRand.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + MathRand.prototype.onGetInputs = function() { + return [["min", "number"], ["max", "number"]]; + }; + + LiteGraph.registerNodeType("math/rand", MathRand); + + //basic continuous noise + function MathNoise() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.addProperty("smooth", true); + this.addProperty("seed", 0); + this.addProperty("octaves", 1); + this.addProperty("persistence", 0.8); + this.addProperty("speed", 1); + this.size = [90, 30]; + } + + MathNoise.title = "Noise"; + MathNoise.desc = "Random number with temporal continuity"; + MathNoise.data = null; + + MathNoise.getValue = function(f, smooth) { + if (!MathNoise.data) { + MathNoise.data = new Float32Array(1024); + for (var i = 0; i < MathNoise.data.length; ++i) { + MathNoise.data[i] = Math.random(); + } + } + f = f % 1024; + if (f < 0) { + f += 1024; + } + var f_min = Math.floor(f); + var f = f - f_min; + var r1 = MathNoise.data[f_min]; + var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; + if (smooth) { + f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); + } + return r1 * (1 - f) + r2 * f; + }; + + MathNoise.prototype.onExecute = function() { + var f = this.getInputData(0) || 0; + var iterations = this.properties.octaves || 1; + var r = 0; + var amp = 1; + var seed = this.properties.seed || 0; + f += seed; + var speed = this.properties.speed || 1; + var total_amp = 0; + for(var i = 0; i < iterations; ++i) + { + r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; + total_amp += amp; + amp *= this.properties.persistence; + if(amp < 0.001) + break; + } + r /= total_amp; + var min = this.properties.min; + var max = this.properties.max; + this._last_v = r * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathNoise.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + LiteGraph.registerNodeType("math/noise", MathNoise); + + //generates spikes every random time + function MathSpikes() { + this.addOutput("out", "number"); + this.addProperty("min_time", 1); + this.addProperty("max_time", 2); + this.addProperty("duration", 0.2); + this.size = [90, 30]; + this._remaining_time = 0; + this._blink_time = 0; + } + + MathSpikes.title = "Spikes"; + MathSpikes.desc = "spike every random time"; + + MathSpikes.prototype.onExecute = function() { + var dt = this.graph.elapsed_time; //in secs + + this._remaining_time -= dt; + this._blink_time -= dt; + + var v = 0; + if (this._blink_time > 0) { + var f = this._blink_time / this.properties.duration; + v = 1 / (Math.pow(f * 8 - 4, 4) + 1); + } + + if (this._remaining_time < 0) { + this._remaining_time = + Math.random() * + (this.properties.max_time - this.properties.min_time) + + this.properties.min_time; + this._blink_time = this.properties.duration; + this.boxcolor = "#FFF"; + } else { + this.boxcolor = "#000"; + } + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/spikes", MathSpikes); + + //Math clamp + function MathClamp() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("min", 0); + this.addProperty("max", 1); + } + + MathClamp.title = "Clamp"; + MathClamp.desc = "Clamp number between min and max"; + //MathClamp.filter = "shader"; + + MathClamp.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + v = Math.max(this.properties.min, v); + v = Math.min(this.properties.max, v); + this.setOutputData(0, v); + }; + + MathClamp.prototype.getCode = function(lang) { + var code = ""; + if (this.isInputConnected(0)) { + code += + "clamp({{0}}," + + this.properties.min + + "," + + this.properties.max + + ")"; + } + return code; + }; + + LiteGraph.registerNodeType("math/clamp", MathClamp); + + //Math ABS + function MathLerp() { + this.properties = { f: 0.5 }; + this.addInput("A", "number"); + this.addInput("B", "number"); + + this.addOutput("out", "number"); + } + + MathLerp.title = "Lerp"; + MathLerp.desc = "Linear Interpolation"; + + MathLerp.prototype.onExecute = function() { + var v1 = this.getInputData(0); + if (v1 == null) { + v1 = 0; + } + var v2 = this.getInputData(1); + if (v2 == null) { + v2 = 0; + } + + var f = this.properties.f; + + var _f = this.getInputData(2); + if (_f !== undefined) { + f = _f; + } + + this.setOutputData(0, v1 * (1 - f) + v2 * f); + }; + + MathLerp.prototype.onGetInputs = function() { + return [["f", "number"]]; + }; + + LiteGraph.registerNodeType("math/lerp", MathLerp); + + //Math ABS + function MathAbs() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathAbs.title = "Abs"; + MathAbs.desc = "Absolute"; + + MathAbs.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.abs(v)); + }; + + LiteGraph.registerNodeType("math/abs", MathAbs); + + //Math Floor + function MathFloor() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFloor.title = "Floor"; + MathFloor.desc = "Floor number to remove fractional part"; + + MathFloor.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.floor(v)); + }; + + LiteGraph.registerNodeType("math/floor", MathFloor); + + //Math frac + function MathFrac() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFrac.title = "Frac"; + MathFrac.desc = "Returns fractional part"; + + MathFrac.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, v % 1); + }; + + LiteGraph.registerNodeType("math/frac", MathFrac); + + //Math Floor + function MathSmoothStep() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.properties = { A: 0, B: 1 }; + } + + MathSmoothStep.title = "Smoothstep"; + MathSmoothStep.desc = "Smoothstep"; + + MathSmoothStep.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v === undefined) { + return; + } + + var edge0 = this.properties.A; + var edge1 = this.properties.B; + + // Scale, bias and saturate x to 0..1 range + v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0); + // Evaluate polynomial + v = v * v * (3 - 2 * v); + + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep); + + //Math scale + function MathScale() { + this.addInput("in", "number", { label: "" }); + this.addOutput("out", "number", { label: "" }); + this.size = [80, 30]; + this.addProperty("factor", 1); + } + + MathScale.title = "Scale"; + MathScale.desc = "v * factor"; + + MathScale.prototype.onExecute = function() { + var value = this.getInputData(0); + if (value != null) { + this.setOutputData(0, value * this.properties.factor); + } + }; + + LiteGraph.registerNodeType("math/scale", MathScale); + + //Gate + function Gate() { + this.addInput("v","boolean"); + this.addInput("A"); + this.addInput("B"); + this.addOutput("out"); + } + + Gate.title = "Gate"; + Gate.desc = "if v is true, then outputs A, otherwise B"; + + Gate.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, this.getInputData( v ? 1 : 2 )); + }; + + LiteGraph.registerNodeType("math/gate", Gate); + + + //Math Average + function MathAverageFilter() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("samples", 10); + this._values = new Float32Array(10); + this._current = 0; + } + + MathAverageFilter.title = "Average"; + MathAverageFilter.desc = "Average Filter"; + + MathAverageFilter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + + var num_samples = this._values.length; + + this._values[this._current % num_samples] = v; + this._current += 1; + if (this._current > num_samples) { + this._current = 0; + } + + var avr = 0; + for (var i = 0; i < num_samples; ++i) { + avr += this._values[i]; + } + + this.setOutputData(0, avr / num_samples); + }; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (value < 1) { + value = 1; + } + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array(this.properties.samples); + if (old.length <= this._values.length) { + this._values.set(old); + } else { + this._values.set(old.subarray(0, this._values.length)); + } + }; + + LiteGraph.registerNodeType("math/average", MathAverageFilter); + + //Math + function MathTendTo() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("factor", 0.1); + this.size = [80, 30]; + this._value = null; + } + + MathTendTo.title = "TendTo"; + MathTendTo.desc = "moves the output value always closer to the input"; + + MathTendTo.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var f = this.properties.factor; + if (this._value == null) { + this._value = v; + } else { + this._value = this._value * (1 - f) + v * f; + } + this.setOutputData(0, this._value); + }; + + LiteGraph.registerNodeType("math/tendTo", MathTendTo); + + //Math operation + function MathOperation() { + this.addInput("A", "number,array,object"); + this.addInput("B", "number"); + this.addOutput("=", "number"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "+", "enum", { values: MathOperation.values }); + this._func = MathOperation.funcs[this.properties.OP]; + this._result = []; //only used for arrays + } + + MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; + MathOperation.funcs = { + "+": function(A,B) { return A + B; }, + "-": function(A,B) { return A - B; }, + "x": function(A,B) { return A * B; }, + "X": function(A,B) { return A * B; }, + "*": function(A,B) { return A * B; }, + "/": function(A,B) { return A / B; }, + "%": function(A,B) { return A % B; }, + "^": function(A,B) { return Math.pow(A, B); }, + "max": function(A,B) { return Math.max(A, B); }, + "min": function(A,B) { return Math.min(A, B); } + }; + + MathOperation.title = "Operation"; + MathOperation.desc = "Easy math operators"; + MathOperation["@OP"] = { + type: "enum", + title: "operation", + values: MathOperation.values + }; + MathOperation.size = [100, 60]; + + MathOperation.prototype.getTitle = function() { + if(this.properties.OP == "max" || this.properties.OP == "min") + return this.properties.OP + "(A,B)"; + return "A " + this.properties.OP + " B"; + }; + + MathOperation.prototype.setValue = function(v) { + if (typeof v == "string") { + v = parseFloat(v); + } + this.properties["value"] = v; + }; + + MathOperation.prototype.onPropertyChanged = function(name, value) + { + if (name != "OP") + return; + this._func = MathOperation.funcs[this.properties.OP]; + if(!this._func) + { + console.warn("Unknown operation: " + this.properties.OP); + this._func = function(A) { return A; }; + } + } + + MathOperation.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if ( A != null ) { + if( A.constructor === Number ) + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B != null) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + var func = MathOperation.funcs[this.properties.OP]; + + var result; + if(A.constructor === Number) + { + result = 0; + result = func(A,B); + } + else if(A.constructor === Array) + { + result = this._result; + result.length = A.length; + for(var i = 0; i < A.length; ++i) + result[i] = func(A[i],B); + } + else + { + result = {}; + for(var i in A) + result[i] = func(A[i],B); + } + this.setOutputData(0, result); + }; + + MathOperation.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.font = "40px Arial"; + ctx.fillStyle = "#666"; + ctx.textAlign = "center"; + ctx.fillText( + this.properties.OP, + this.size[0] * 0.5, + (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 + ); + ctx.textAlign = "left"; + }; + + LiteGraph.registerNodeType("math/operation", MathOperation); + + LiteGraph.registerSearchboxExtra("math/operation", "MAX", { + properties: {OP:"max"}, + title: "MAX()" + }); + + LiteGraph.registerSearchboxExtra("math/operation", "MIN", { + properties: {OP:"min"}, + title: "MIN()" + }); + + + //Math compare + function MathCompare() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("A==B", "boolean"); + this.addOutput("A!=B", "boolean"); + this.addProperty("A", 0); + this.addProperty("B", 0); + } + + MathCompare.title = "Compare"; + MathCompare.desc = "compares between two values"; + + MathCompare.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if (A !== undefined) { + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B !== undefined) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var value; + switch (output.name) { + case "A==B": + value = A == B; + break; + case "A!=B": + value = A != B; + break; + case "A>B": + value = A > B; + break; + case "A=B": + value = A >= B; + break; + } + this.setOutputData(i, value); + } + }; + + MathCompare.prototype.onGetOutputs = function() { + return [ + ["A==B", "boolean"], + ["A!=B", "boolean"], + ["A>B", "boolean"], + ["A=B", "boolean"], + ["A<=B", "boolean"] + ]; + }; + + LiteGraph.registerNodeType("math/compare", MathCompare); + + LiteGraph.registerSearchboxExtra("math/compare", "==", { + outputs: [["A==B", "boolean"]], + title: "A==B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "!=", { + outputs: [["A!=B", "boolean"]], + title: "A!=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", ">", { + outputs: [["A>B", "boolean"]], + title: "A>B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<", { + outputs: [["A=", { + outputs: [["A>=B", "boolean"]], + title: "A>=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<=", { + outputs: [["A<=B", "boolean"]], + title: "A<=B" + }); + + function MathCondition() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", ">", "enum", { values: MathCondition.values }); + this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); + + this.size = [80, 60]; + } + + MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + MathCondition["@OP"] = { + type: "enum", + title: "operation", + values: MathCondition.values + }; + + MathCondition.title = "Condition"; + MathCondition.desc = "evaluates condition between A and B"; + + MathCondition.prototype.getTitle = function() { + return "A " + this.properties.OP + " B"; + }; + + MathCondition.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A === undefined) { + A = this.properties.A; + } else { + this.properties.A = A; + } + + var B = this.getInputData(1); + if (B === undefined) { + B = this.properties.B; + } else { + this.properties.B = B; + } + + var result = true; + switch (this.properties.OP) { + case ">": + result = A > B; + break; + case "<": + result = A < B; + break; + case "==": + result = A == B; + break; + case "!=": + result = A != B; + break; + case "<=": + result = A <= B; + break; + case ">=": + result = A >= B; + break; + case "||": + result = A || B; + break; + case "&&": + result = A && B; + break; + } + + this.setOutputData(0, result); + this.setOutputData(1, !result); + }; + + LiteGraph.registerNodeType("math/condition", MathCondition); + + + function MathBranch() { + this.addInput("in", 0); + this.addInput("cond", "boolean"); + this.addOutput("true", 0); + this.addOutput("false", 0); + this.size = [80, 60]; + } + + MathBranch.title = "Branch"; + MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; + + MathBranch.prototype.onExecute = function() { + var V = this.getInputData(0); + var cond = this.getInputData(1); + + if(cond) + { + this.setOutputData(0, V); + this.setOutputData(1, null); + } + else + { + this.setOutputData(0, null); + this.setOutputData(1, V); + } + } + + LiteGraph.registerNodeType("math/branch", MathBranch); + + + function MathAccumulate() { + this.addInput("inc", "number"); + this.addOutput("total", "number"); + this.addProperty("increment", 1); + this.addProperty("value", 0); + } + + MathAccumulate.title = "Accumulate"; + MathAccumulate.desc = "Increments a value every time"; + + MathAccumulate.prototype.onExecute = function() { + if (this.properties.value === null) { + this.properties.value = 0; + } + + var inc = this.getInputData(0); + if (inc !== null) { + this.properties.value += inc; + } else { + this.properties.value += this.properties.increment; + } + this.setOutputData(0, this.properties.value); + }; + + LiteGraph.registerNodeType("math/accumulate", MathAccumulate); + + //Math Trigonometry + function MathTrigonometry() { + this.addInput("v", "number"); + this.addOutput("sin", "number"); + + this.addProperty("amplitude", 1); + this.addProperty("offset", 0); + this.bgImageUrl = "nodes/imgs/icon-sin.png"; + } + + MathTrigonometry.title = "Trigonometry"; + MathTrigonometry.desc = "Sin Cos Tan"; + //MathTrigonometry.filter = "shader"; + + MathTrigonometry.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var amplitude = this.properties["amplitude"]; + var slot = this.findInputSlot("amplitude"); + if (slot != -1) { + amplitude = this.getInputData(slot); + } + var offset = this.properties["offset"]; + slot = this.findInputSlot("offset"); + if (slot != -1) { + offset = this.getInputData(slot); + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + var value; + switch (output.name) { + case "sin": + value = Math.sin(v); + break; + case "cos": + value = Math.cos(v); + break; + case "tan": + value = Math.tan(v); + break; + case "asin": + value = Math.asin(v); + break; + case "acos": + value = Math.acos(v); + break; + case "atan": + value = Math.atan(v); + break; + } + this.setOutputData(i, amplitude * value + offset); + } + }; + + MathTrigonometry.prototype.onGetInputs = function() { + return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; + }; + + MathTrigonometry.prototype.onGetOutputs = function() { + return [ + ["sin", "number"], + ["cos", "number"], + ["tan", "number"], + ["asin", "number"], + ["acos", "number"], + ["atan", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry); + + LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { + outputs: [["sin", "number"]], + title: "SIN()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { + outputs: [["cos", "number"]], + title: "COS()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { + outputs: [["tan", "number"]], + title: "TAN()" + }); + + //math library for safe math operations without eval + function MathFormula() { + this.addInput("x", "number"); + this.addInput("y", "number"); + this.addOutput("", "number"); + this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; + this.code_widget = this.addWidget( + "text", + "F(x,y)", + this.properties.formula, + function(v, canvas, node) { + node.properties.formula = v; + } + ); + this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { + LiteGraph.allow_scripts = v; + }); + this._func = null; + } + + MathFormula.title = "Formula"; + MathFormula.desc = "Compute formula"; + MathFormula.size = [160, 100]; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (name == "formula") { + this.code_widget.value = value; + } + }; + + MathFormula.prototype.onExecute = function() { + if (!LiteGraph.allow_scripts) { + return; + } + + var x = this.getInputData(0); + var y = this.getInputData(1); + if (x != null) { + this.properties["x"] = x; + } else { + x = this.properties["x"]; + } + + if (y != null) { + this.properties["y"] = y; + } else { + y = this.properties["y"]; + } + + var f = this.properties["formula"]; + + var value; + try { + if (!this._func || this._func_code != this.properties.formula) { + this._func = new Function( + "x", + "y", + "TIME", + "return " + this.properties.formula + ); + this._func_code = this.properties.formula; + } + value = this._func(x, y, this.graph.globaltime); + this.boxcolor = null; + } catch (err) { + this.boxcolor = "red"; + } + this.setOutputData(0, value); + }; + + MathFormula.prototype.getTitle = function() { + return this._func_code || "Formula"; + }; + + MathFormula.prototype.onDrawBackground = function() { + var f = this.properties["formula"]; + if (this.outputs && this.outputs.length) { + this.outputs[0].label = f; + } + }; + + LiteGraph.registerNodeType("math/formula", MathFormula); + + function Math3DVec2ToXY() { + this.addInput("vec2", "vec2"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + } + + Math3DVec2ToXY.title = "Vec2->XY"; + Math3DVec2ToXY.desc = "vector 2 to components"; + + Math3DVec2ToXY.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + }; + + LiteGraph.registerNodeType("math3d/vec2-to-xy", Math3DVec2ToXY); + + function Math3DXYToVec2() { + this.addInputs([["x", "number"], ["y", "number"]]); + this.addOutput("vec2", "vec2"); + this.properties = { x: 0, y: 0 }; + this._data = new Float32Array(2); + } + + Math3DXYToVec2.title = "XY->Vec2"; + Math3DXYToVec2.desc = "components to vector2"; + + Math3DXYToVec2.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + + var data = this._data; + data[0] = x; + data[1] = y; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); + + function Math3DVec3ToXYZ() { + this.addInput("vec3", "vec3"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + } + + Math3DVec3ToXYZ.title = "Vec3->XYZ"; + Math3DVec3ToXYZ.desc = "vector 3 to components"; + + Math3DVec3ToXYZ.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + }; + + LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); + + function Math3DXYZToVec3() { + this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); + this.addOutput("vec3", "vec3"); + this.properties = { x: 0, y: 0, z: 0 }; + this._data = new Float32Array(3); + } + + Math3DXYZToVec3.title = "XYZ->Vec3"; + Math3DXYZToVec3.desc = "components to vector3"; + + Math3DXYZToVec3.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); + + function Math3DVec4ToXYZW() { + this.addInput("vec4", "vec4"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + this.addOutput("w", "number"); + } + + Math3DVec4ToXYZW.title = "Vec4->XYZW"; + Math3DVec4ToXYZW.desc = "vector 4 to components"; + + Math3DVec4ToXYZW.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + this.setOutputData(3, v[3]); + }; + + LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); + + function Math3DXYZWToVec4() { + this.addInputs([ + ["x", "number"], + ["y", "number"], + ["z", "number"], + ["w", "number"] + ]); + this.addOutput("vec4", "vec4"); + this.properties = { x: 0, y: 0, z: 0, w: 0 }; + this._data = new Float32Array(4); + } + + Math3DXYZWToVec4.title = "XYZW->Vec4"; + Math3DXYZWToVec4.desc = "components to vector4"; + + Math3DXYZWToVec4.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + var w = this.getInputData(3); + if (w == null) { + w = this.properties.w; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); + +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + + function Math3DMat4() + { + this.addInput("T", "vec3"); + this.addInput("R", "vec3"); + this.addInput("S", "vec3"); + this.addOutput("mat4", "mat4"); + this.properties = { + "T":[0,0,0], + "R":[0,0,0], + "S":[1,1,1], + R_in_degrees: true + }; + this._result = mat4.create(); + this._must_update = true; + } + + Math3DMat4.title = "mat4"; + Math3DMat4.temp_quat = new Float32Array([0,0,0,1]); + Math3DMat4.temp_mat4 = new Float32Array(16); + Math3DMat4.temp_vec3 = new Float32Array(3); + + Math3DMat4.prototype.onPropertyChanged = function(name, value) + { + this._must_update = true; + } + + Math3DMat4.prototype.onExecute = function() + { + var M = this._result; + var Q = Math3DMat4.temp_quat; + var temp_mat4 = Math3DMat4.temp_mat4; + var temp_vec3 = Math3DMat4.temp_vec3; + + var T = this.getInputData(0); + var R = this.getInputData(1); + var S = this.getInputData(2); + + if( this._must_update || T || R || S ) + { + T = T || this.properties.T; + R = R || this.properties.R; + S = S || this.properties.S; + mat4.identity( M ); + mat4.translate( M, M, T ); + if(this.properties.R_in_degrees) + { + temp_vec3.set( R ); + vec3.scale(temp_vec3,temp_vec3,DEG2RAD); + quat.fromEuler( Q, temp_vec3 ); + } + else + quat.fromEuler( Q, R ); + mat4.fromQuat( temp_mat4, Q ); + mat4.multiply( M, M, temp_mat4 ); + mat4.scale( M, M, S ); + } + + this.setOutputData(0, M); + } + + LiteGraph.registerNodeType("math3d/mat4", Math3DMat4); + + //Math 3D operation + function Math3DOperation() { + this.addInput("A", "number,vec3"); + this.addInput("B", "number,vec3"); + this.addOutput("=", "number,vec3"); + this.addProperty("OP", "+", "enum", { values: Math3DOperation.values }); + this._result = vec3.create(); + } + + Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min","dot","cross"]; + + LiteGraph.registerSearchboxExtra("math3d/operation", "CROSS()", { + properties: {"OP":"cross"}, + title: "CROSS()" + }); + + LiteGraph.registerSearchboxExtra("math3d/operation", "DOT()", { + properties: {"OP":"dot"}, + title: "DOT()" + }); + + Math3DOperation.title = "Operation"; + Math3DOperation.desc = "Easy math 3D operators"; + Math3DOperation["@OP"] = { + type: "enum", + title: "operation", + values: Math3DOperation.values + }; + Math3DOperation.size = [100, 60]; + + Math3DOperation.prototype.getTitle = function() { + if(this.properties.OP == "max" || this.properties.OP == "min" ) + return this.properties.OP + "(A,B)"; + return "A " + this.properties.OP + " B"; + }; + + Math3DOperation.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if(A == null || B == null) + return; + if(A.constructor === Number) + A = [A,A,A]; + if(B.constructor === Number) + B = [B,B,B]; + + var result = this._result; + switch (this.properties.OP) { + case "+": + result = vec3.add(result,A,B); + break; + case "-": + result = vec3.sub(result,A,B); + break; + case "x": + case "X": + case "*": + result = vec3.mul(result,A,B); + break; + case "/": + result = vec3.div(result,A,B); + break; + case "%": + result[0] = A[0]%B[0]; + result[1] = A[1]%B[1]; + result[2] = A[2]%B[2]; + break; + case "^": + result[0] = Math.pow(A[0],B[0]); + result[1] = Math.pow(A[1],B[1]); + result[2] = Math.pow(A[2],B[2]); + break; + case "max": + result[0] = Math.max(A[0],B[0]); + result[1] = Math.max(A[1],B[1]); + result[2] = Math.max(A[2],B[2]); + break; + case "min": + result[0] = Math.min(A[0],B[0]); + result[1] = Math.min(A[1],B[1]); + result[2] = Math.min(A[2],B[2]); + case "dot": + result = vec3.dot(A,B); + break; + case "cross": + vec3.cross(result,A,B); + break; + default: + console.warn("Unknown operation: " + this.properties.OP); + } + this.setOutputData(0, result); + }; + + Math3DOperation.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.font = "40px Arial"; + ctx.fillStyle = "#666"; + ctx.textAlign = "center"; + ctx.fillText( + this.properties.OP, + this.size[0] * 0.5, + (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 + ); + ctx.textAlign = "left"; + }; + + LiteGraph.registerNodeType("math3d/operation", Math3DOperation); + + function Math3DVec3Scale() { + this.addInput("in", "vec3"); + this.addInput("f", "number"); + this.addOutput("out", "vec3"); + this.properties = { f: 1 }; + this._data = new Float32Array(3); + } + + Math3DVec3Scale.title = "vec3_scale"; + Math3DVec3Scale.desc = "scales the components of a vec3"; + + Math3DVec3Scale.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + var f = this.getInputData(1); + if (f == null) { + f = this.properties.f; + } + + var data = this._data; + data[0] = v[0] * f; + data[1] = v[1] * f; + data[2] = v[2] * f; + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/vec3-scale", Math3DVec3Scale); + + function Math3DVec3Length() { + this.addInput("in", "vec3"); + this.addOutput("out", "number"); + } + + Math3DVec3Length.title = "vec3_length"; + Math3DVec3Length.desc = "returns the module of a vector"; + + Math3DVec3Length.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + this.setOutputData(0, dist); + }; + + LiteGraph.registerNodeType("math3d/vec3-length", Math3DVec3Length); + + function Math3DVec3Normalize() { + this.addInput("in", "vec3"); + this.addOutput("out", "vec3"); + this._data = new Float32Array(3); + } + + Math3DVec3Normalize.title = "vec3_normalize"; + Math3DVec3Normalize.desc = "returns the vector normalized"; + + Math3DVec3Normalize.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + var dist = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + var data = this._data; + data[0] = v[0] / dist; + data[1] = v[1] / dist; + data[2] = v[2] / dist; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/vec3-normalize", Math3DVec3Normalize); + + function Math3DVec3Lerp() { + this.addInput("A", "vec3"); + this.addInput("B", "vec3"); + this.addInput("f", "vec3"); + this.addOutput("out", "vec3"); + this.properties = { f: 0.5 }; + this._data = new Float32Array(3); + } + + Math3DVec3Lerp.title = "vec3_lerp"; + Math3DVec3Lerp.desc = "returns the interpolated vector"; + + Math3DVec3Lerp.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A == null) { + return; + } + var B = this.getInputData(1); + if (B == null) { + return; + } + var f = this.getInputOrProperty("f"); + + var data = this._data; + data[0] = A[0] * (1 - f) + B[0] * f; + data[1] = A[1] * (1 - f) + B[1] * f; + data[2] = A[2] * (1 - f) + B[2] * f; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/vec3-lerp", Math3DVec3Lerp); + + function Math3DVec3Dot() { + this.addInput("A", "vec3"); + this.addInput("B", "vec3"); + this.addOutput("out", "number"); + } + + Math3DVec3Dot.title = "vec3_dot"; + Math3DVec3Dot.desc = "returns the dot product"; + + Math3DVec3Dot.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A == null) { + return; + } + var B = this.getInputData(1); + if (B == null) { + return; + } + + var dot = A[0] * B[0] + A[1] * B[1] + A[2] * B[2]; + this.setOutputData(0, dot); + }; + + LiteGraph.registerNodeType("math3d/vec3-dot", Math3DVec3Dot); + + //if glMatrix is installed... + if (global.glMatrix) { + function Math3DQuaternion() { + this.addOutput("quat", "quat"); + this.properties = { x: 0, y: 0, z: 0, w: 1, normalize: false }; + this._value = quat.create(); + } + + Math3DQuaternion.title = "Quaternion"; + Math3DQuaternion.desc = "quaternion"; + + Math3DQuaternion.prototype.onExecute = function() { + this._value[0] = this.getInputOrProperty("x"); + this._value[1] = this.getInputOrProperty("y"); + this._value[2] = this.getInputOrProperty("z"); + this._value[3] = this.getInputOrProperty("w"); + if (this.properties.normalize) { + quat.normalize(this._value, this._value); + } + this.setOutputData(0, this._value); + }; + + Math3DQuaternion.prototype.onGetInputs = function() { + return [ + ["x", "number"], + ["y", "number"], + ["z", "number"], + ["w", "number"] + ]; + }; + + LiteGraph.registerNodeType("math3d/quaternion", Math3DQuaternion); + + function Math3DRotation() { + this.addInputs([["degrees", "number"], ["axis", "vec3"]]); + this.addOutput("quat", "quat"); + this.properties = { angle: 90.0, axis: vec3.fromValues(0, 1, 0) }; + + this._value = quat.create(); + } + + Math3DRotation.title = "Rotation"; + Math3DRotation.desc = "quaternion rotation"; + + Math3DRotation.prototype.onExecute = function() { + var angle = this.getInputData(0); + if (angle == null) { + angle = this.properties.angle; + } + var axis = this.getInputData(1); + if (axis == null) { + axis = this.properties.axis; + } + + var R = quat.setAxisAngle(this._value, axis, angle * 0.0174532925); + this.setOutputData(0, R); + }; + + LiteGraph.registerNodeType("math3d/rotation", Math3DRotation); + + + function MathEulerToQuat() { + this.addInput("euler", "vec3"); + this.addOutput("quat", "quat"); + this.properties = { euler:[0,0,0], use_yaw_pitch_roll: false }; + this._degs = vec3.create(); + this._value = quat.create(); + } + + MathEulerToQuat.title = "Euler->Quat"; + MathEulerToQuat.desc = "Converts euler angles (in degrees) to quaternion"; + + MathEulerToQuat.prototype.onExecute = function() { + var euler = this.getInputData(0); + if (euler == null) { + euler = this.properties.euler; + } + vec3.scale( this._degs, euler, DEG2RAD ); + if(this.properties.use_yaw_pitch_roll) + this._degs = [this._degs[2],this._degs[0],this._degs[1]]; + var R = quat.fromEuler(this._value, this._degs); + this.setOutputData(0, R); + }; + + LiteGraph.registerNodeType("math3d/euler_to_quat", MathEulerToQuat); + + function MathQuatToEuler() { + this.addInput(["quat", "quat"]); + this.addOutput("euler", "vec3"); + this._value = vec3.create(); + } + + MathQuatToEuler.title = "Euler->Quat"; + MathQuatToEuler.desc = "Converts rotX,rotY,rotZ in degrees to quat"; + + MathQuatToEuler.prototype.onExecute = function() { + var q = this.getInputData(0); + if(!q) + return; + var R = quat.toEuler(this._value, q); + vec3.scale( this._value, this._value, DEG2RAD ); + this.setOutputData(0, this._value); + }; + + LiteGraph.registerNodeType("math3d/quat_to_euler", MathQuatToEuler); + + + //Math3D rotate vec3 + function Math3DRotateVec3() { + this.addInputs([["vec3", "vec3"], ["quat", "quat"]]); + this.addOutput("result", "vec3"); + this.properties = { vec: [0, 0, 1] }; + } + + Math3DRotateVec3.title = "Rot. Vec3"; + Math3DRotateVec3.desc = "rotate a point"; + + Math3DRotateVec3.prototype.onExecute = function() { + var vec = this.getInputData(0); + if (vec == null) { + vec = this.properties.vec; + } + var quat = this.getInputData(1); + if (quat == null) { + this.setOutputData(vec); + } else { + this.setOutputData( + 0, + vec3.transformQuat(vec3.create(), vec, quat) + ); + } + }; + + LiteGraph.registerNodeType("math3d/rotate_vec3", Math3DRotateVec3); + + function Math3DMultQuat() { + this.addInputs([["A", "quat"], ["B", "quat"]]); + this.addOutput("A*B", "quat"); + + this._value = quat.create(); + } + + Math3DMultQuat.title = "Mult. Quat"; + Math3DMultQuat.desc = "rotate quaternion"; + + Math3DMultQuat.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A == null) { + return; + } + var B = this.getInputData(1); + if (B == null) { + return; + } + + var R = quat.multiply(this._value, A, B); + this.setOutputData(0, R); + }; + + LiteGraph.registerNodeType("math3d/mult-quat", Math3DMultQuat); + + function Math3DQuatSlerp() { + this.addInputs([ + ["A", "quat"], + ["B", "quat"], + ["factor", "number"] + ]); + this.addOutput("slerp", "quat"); + this.addProperty("factor", 0.5); + + this._value = quat.create(); + } + + Math3DQuatSlerp.title = "Quat Slerp"; + Math3DQuatSlerp.desc = "quaternion spherical interpolation"; + + Math3DQuatSlerp.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A == null) { + return; + } + var B = this.getInputData(1); + if (B == null) { + return; + } + var factor = this.properties.factor; + if (this.getInputData(2) != null) { + factor = this.getInputData(2); + } + + var R = quat.slerp(this._value, A, B, factor); + this.setOutputData(0, R); + }; + + LiteGraph.registerNodeType("math3d/quat-slerp", Math3DQuatSlerp); + + + //Math3D rotate vec3 + function Math3DRemapRange() { + this.addInput("vec3", "vec3"); + this.addOutput("remap", "vec3"); + this.addOutput("clamped", "vec3"); + this.properties = { clamp: true, range_min: [-1, -1, 0], range_max: [1, 1, 0], target_min: [-1,-1,0], target_max:[1,1,0] }; + this._value = vec3.create(); + this._clamped = vec3.create(); + } + + Math3DRemapRange.title = "Remap Range"; + Math3DRemapRange.desc = "remap a 3D range"; + + Math3DRemapRange.prototype.onExecute = function() { + var vec = this.getInputData(0); + if(vec) + this._value.set(vec); + var range_min = this.properties.range_min; + var range_max = this.properties.range_max; + var target_min = this.properties.target_min; + var target_max = this.properties.target_max; + + //swap to avoid errors + /* + if(range_min > range_max) + { + range_min = range_max; + range_max = this.properties.range_min; + } + + if(target_min > target_max) + { + target_min = target_max; + target_max = this.properties.target_min; + } + */ + + for(var i = 0; i < 3; ++i) + { + var r = range_max[i] - range_min[i]; + this._clamped[i] = clamp( this._value[i], range_min[i], range_max[i] ); + if(r == 0) + { + this._value[i] = (target_min[i] + target_max[i]) * 0.5; + continue; + } + + var n = (this._value[i] - range_min[i]) / r; + if(this.properties.clamp) + n = clamp(n,0,1); + var t = target_max[i] - target_min[i]; + this._value[i] = target_min[i] + n * t; + } + + this.setOutputData(0,this._value); + this.setOutputData(1,this._clamped); + }; + + LiteGraph.registerNodeType("math3d/remap_range", Math3DRemapRange); + + + + } //glMatrix + else if (LiteGraph.debug) + console.warn("No glmatrix found, some Math3D nodes may not work"); + +})(this); + +//basic nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + function toString(a) { + if(a && a.constructor === Object) + { + try + { + return JSON.stringify(a); + } + catch (err) + { + return String(a); + } + } + return String(a); + } + + LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "string"); + + function compare(a, b) { + return a == b; + } + + LiteGraph.wrapFunctionAsNode( + "string/compare", + compare, + ["string", "string"], + "boolean" + ); + + function concatenate(a, b) { + if (a === undefined) { + return b; + } + if (b === undefined) { + return a; + } + return a + b; + } + + LiteGraph.wrapFunctionAsNode( + "string/concatenate", + concatenate, + ["string", "string"], + "string" + ); + + function contains(a, b) { + if (a === undefined || b === undefined) { + return false; + } + return a.indexOf(b) != -1; + } + + LiteGraph.wrapFunctionAsNode( + "string/contains", + contains, + ["string", "string"], + "boolean" + ); + + function toUpperCase(a) { + if (a != null && a.constructor === String) { + return a.toUpperCase(); + } + return a; + } + + LiteGraph.wrapFunctionAsNode( + "string/toUpperCase", + toUpperCase, + ["string"], + "string" + ); + + function split(str, separator) { + if(separator == null) + separator = this.properties.separator; + if (str == null ) + return []; + if( str.constructor === String ) + return str.split(separator || " "); + else if( str.constructor === Array ) + { + var r = []; + for(var i = 0; i < str.length; ++i){ + if (typeof str[i] == "string") + r[i] = str[i].split(separator || " "); + } + return r; + } + return null; + } + + LiteGraph.wrapFunctionAsNode( + "string/split", + split, + ["string,array", "string"], + "array", + { separator: "," } + ); + + function toFixed(a) { + if (a != null && a.constructor === Number) { + return a.toFixed(this.properties.precision); + } + return a; + } + + LiteGraph.wrapFunctionAsNode( + "string/toFixed", + toFixed, + ["number"], + "string", + { precision: 0 } + ); + + + function StringToTable() { + this.addInput("", "string"); + this.addOutput("table", "table"); + this.addOutput("rows", "number"); + this.addProperty("value", ""); + this.addProperty("separator", ","); + this._table = null; + } + + StringToTable.title = "toTable"; + StringToTable.desc = "Splits a string to table"; + + StringToTable.prototype.onExecute = function() { + var input = this.getInputData(0); + if(!input) + return; + var separator = this.properties.separator || ","; + if(input != this._str || separator != this._last_separator ) + { + this._last_separator = separator; + this._str = input; + this._table = input.split("\n").map(function(a){ return a.trim().split(separator)}); + } + this.setOutputData(0, this._table ); + this.setOutputData(1, this._table ? this._table.length : 0 ); + }; + + LiteGraph.registerNodeType("string/toTable", StringToTable); + +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + function Selector() { + this.addInput("sel", "number"); + this.addInput("A"); + this.addInput("B"); + this.addInput("C"); + this.addInput("D"); + this.addOutput("out"); + + this.selected = 0; + } + + Selector.title = "Selector"; + Selector.desc = "selects an output"; + + Selector.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + ctx.fillStyle = "#AFB"; + var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6; + ctx.beginPath(); + ctx.moveTo(50, y); + ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + }; + + Selector.prototype.onExecute = function() { + var sel = this.getInputData(0); + if (sel == null || sel.constructor !== Number) + sel = 0; + this.selected = sel = Math.round(sel) % (this.inputs.length - 1); + var v = this.getInputData(sel + 1); + if (v !== undefined) { + this.setOutputData(0, v); + } + }; + + Selector.prototype.onGetInputs = function() { + return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; + }; + + LiteGraph.registerNodeType("logic/selector", Selector); + + function Sequence() { + this.properties = { + sequence: "A,B,C" + }; + this.addInput("index", "number"); + this.addInput("seq"); + this.addOutput("out"); + + this.index = 0; + this.values = this.properties.sequence.split(","); + } + + Sequence.title = "Sequence"; + Sequence.desc = "select one element from a sequence from a string"; + + Sequence.prototype.onPropertyChanged = function(name, value) { + if (name == "sequence") { + this.values = value.split(","); + } + }; + + Sequence.prototype.onExecute = function() { + var seq = this.getInputData(1); + if (seq && seq != this.current_sequence) { + this.values = seq.split(","); + this.current_sequence = seq; + } + var index = this.getInputData(0); + if (index == null) { + index = 0; + } + this.index = index = Math.round(index) % this.values.length; + + this.setOutputData(0, this.values[index]); + }; + + LiteGraph.registerNodeType("logic/sequence", Sequence); + + + function logicAnd(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicAnd.title = "AND"; + logicAnd.desc = "Return true if all inputs are true"; + logicAnd.prototype.onExecute = function() { + var ret = true; + for (var inX in this.inputs){ + if (!this.getInputData(inX)){ + var ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicAnd.prototype.onGetInputs = function() { + return [ + ["and", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/AND", logicAnd); + + + function logicOr(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicOr.title = "OR"; + logicOr.desc = "Return true if at least one input is true"; + logicOr.prototype.onExecute = function() { + var ret = false; + for (var inX in this.inputs){ + if (this.getInputData(inX)){ + ret = true; + break; + } + } + this.setOutputData(0, ret); + }; + logicOr.prototype.onGetInputs = function() { + return [ + ["or", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/OR", logicOr); + + + function logicNot(){ + this.properties = { }; + this.addInput("in", "boolean"); + this.addOutput("out", "boolean"); + } + logicNot.title = "NOT"; + logicNot.desc = "Return the logical negation"; + logicNot.prototype.onExecute = function() { + var ret = !this.getInputData(0); + this.setOutputData(0, ret); + }; + LiteGraph.registerNodeType("logic/NOT", logicNot); + + + function logicCompare(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicCompare.title = "bool == bool"; + logicCompare.desc = "Compare for logical equality"; + logicCompare.prototype.onExecute = function() { + var last = null; + var ret = true; + for (var inX in this.inputs){ + if (last === null) last = this.getInputData(inX); + else + if (last != this.getInputData(inX)){ + ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicCompare.prototype.onGetInputs = function() { + return [ + ["bool", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/CompareBool", logicCompare); + + + function logicBranch(){ + this.properties = { }; + this.addInput("onTrigger", LiteGraph.ACTION); + this.addInput("condition", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.mode = LiteGraph.ON_TRIGGER; + } + logicBranch.title = "Branch"; + logicBranch.desc = "Branch execution on condition"; + logicBranch.prototype.onExecute = function(param, options) { + var condtition = this.getInputData(1); + if (condtition){ + this.triggerSlot(0); + }else{ + this.triggerSlot(1); + } + }; + LiteGraph.registerNodeType("logic/IF", logicBranch); +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + function GraphicsPlot() { + this.addInput("A", "Number"); + this.addInput("B", "Number"); + this.addInput("C", "Number"); + this.addInput("D", "Number"); + + this.values = [[], [], [], []]; + this.properties = { scale: 2 }; + } + + GraphicsPlot.title = "Plot"; + GraphicsPlot.desc = "Plots data over time"; + GraphicsPlot.colors = ["#FFF", "#F99", "#9F9", "#99F"]; + + GraphicsPlot.prototype.onExecute = function(ctx) { + if (this.flags.collapsed) { + return; + } + + var size = this.size; + + for (var i = 0; i < 4; ++i) { + var v = this.getInputData(i); + if (v == null) { + continue; + } + var values = this.values[i]; + values.push(v); + if (values.length > size[0]) { + values.shift(); + } + } + }; + + GraphicsPlot.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + var size = this.size; + + var scale = (0.5 * size[1]) / this.properties.scale; + var colors = GraphicsPlot.colors; + var offset = size[1] * 0.5; + + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, size[0], size[1]); + ctx.strokeStyle = "#555"; + ctx.beginPath(); + ctx.moveTo(0, offset); + ctx.lineTo(size[0], offset); + ctx.stroke(); + + if (this.inputs) { + for (var i = 0; i < 4; ++i) { + var values = this.values[i]; + if (!this.inputs[i] || !this.inputs[i].link) { + continue; + } + ctx.strokeStyle = colors[i]; + ctx.beginPath(); + var v = values[0] * scale * -1 + offset; + ctx.moveTo(0, clamp(v, 0, size[1])); + for (var j = 1; j < values.length && j < size[0]; ++j) { + var v = values[j] * scale * -1 + offset; + ctx.lineTo(j, clamp(v, 0, size[1])); + } + ctx.stroke(); + } + } + }; + + LiteGraph.registerNodeType("graphics/plot", GraphicsPlot); + + function GraphicsImage() { + this.addOutput("frame", "image"); + this.properties = { url: "" }; + } + + GraphicsImage.title = "Image"; + GraphicsImage.desc = "Image loader"; + GraphicsImage.widgets = [{ name: "load", text: "Load", type: "button" }]; + + GraphicsImage.supported_extensions = ["jpg", "jpeg", "png", "gif"]; + + GraphicsImage.prototype.onAdded = function() { + if (this.properties["url"] != "" && this.img == null) { + this.loadImage(this.properties["url"]); + } + }; + + GraphicsImage.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + if (this.img && this.size[0] > 5 && this.size[1] > 5 && this.img.width) { + ctx.drawImage(this.img, 0, 0, this.size[0], this.size[1]); + } + }; + + GraphicsImage.prototype.onExecute = function() { + if (!this.img) { + this.boxcolor = "#000"; + } + if (this.img && this.img.width) { + this.setOutputData(0, this.img); + } else { + this.setOutputData(0, null); + } + if (this.img && this.img.dirty) { + this.img.dirty = false; + } + }; + + GraphicsImage.prototype.onPropertyChanged = function(name, value) { + this.properties[name] = value; + if (name == "url" && value != "") { + this.loadImage(value); + } + + return true; + }; + + GraphicsImage.prototype.loadImage = function(url, callback) { + if (url == "") { + this.img = null; + return; + } + + this.img = document.createElement("img"); + + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + + this.img.src = url; + this.boxcolor = "#F95"; + var that = this; + this.img.onload = function() { + if (callback) { + callback(this); + } + console.log( "Image loaded, size: " + that.img.width + "x" + that.img.height ); + this.dirty = true; + that.boxcolor = "#9F9"; + that.setDirtyCanvas(true); + }; + this.img.onerror = function() { + console.log("error loading the image:" + url); + } + }; + + GraphicsImage.prototype.onWidget = function(e, widget) { + if (widget.name == "load") { + this.loadImage(this.properties["url"]); + } + }; + + GraphicsImage.prototype.onDropFile = function(file) { + var that = this; + if (this._url) { + URL.revokeObjectURL(this._url); + } + this._url = URL.createObjectURL(file); + this.properties.url = this._url; + this.loadImage(this._url, function(img) { + that.size[1] = (img.height / img.width) * that.size[0]; + }); + }; + + LiteGraph.registerNodeType("graphics/image", GraphicsImage); + + function ColorPalette() { + this.addInput("f", "number"); + this.addOutput("Color", "color"); + this.properties = { + colorA: "#444444", + colorB: "#44AAFF", + colorC: "#44FFAA", + colorD: "#FFFFFF" + }; + } + + ColorPalette.title = "Palette"; + ColorPalette.desc = "Generates a color"; + + ColorPalette.prototype.onExecute = function() { + var c = []; + + if (this.properties.colorA != null) { + c.push(hex2num(this.properties.colorA)); + } + if (this.properties.colorB != null) { + c.push(hex2num(this.properties.colorB)); + } + if (this.properties.colorC != null) { + c.push(hex2num(this.properties.colorC)); + } + if (this.properties.colorD != null) { + c.push(hex2num(this.properties.colorD)); + } + + var f = this.getInputData(0); + if (f == null) { + f = 0.5; + } + if (f > 1.0) { + f = 1.0; + } else if (f < 0.0) { + f = 0.0; + } + + if (c.length == 0) { + return; + } + + var result = [0, 0, 0]; + if (f == 0) { + result = c[0]; + } else if (f == 1) { + result = c[c.length - 1]; + } else { + var pos = (c.length - 1) * f; + var c1 = c[Math.floor(pos)]; + var c2 = c[Math.floor(pos) + 1]; + var t = pos - Math.floor(pos); + result[0] = c1[0] * (1 - t) + c2[0] * t; + result[1] = c1[1] * (1 - t) + c2[1] * t; + result[2] = c1[2] * (1 - t) + c2[2] * t; + } + + /* + c[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) ); + c[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) ); + c[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) ); + */ + + for (var i=0; i < result.length; i++) { + result[i] /= 255; + } + + this.boxcolor = colorToString(result); + this.setOutputData(0, result); + }; + + LiteGraph.registerNodeType("color/palette", ColorPalette); + + function ImageFrame() { + this.addInput("", "image,canvas"); + this.size = [200, 200]; + } + + ImageFrame.title = "Frame"; + ImageFrame.desc = "Frame viewerew"; + ImageFrame.widgets = [ + { name: "resize", text: "Resize box", type: "button" }, + { name: "view", text: "View Image", type: "button" } + ]; + + ImageFrame.prototype.onDrawBackground = function(ctx) { + if (this.frame && !this.flags.collapsed) { + ctx.drawImage(this.frame, 0, 0, this.size[0], this.size[1]); + } + }; + + ImageFrame.prototype.onExecute = function() { + this.frame = this.getInputData(0); + this.setDirtyCanvas(true); + }; + + ImageFrame.prototype.onWidget = function(e, widget) { + if (widget.name == "resize" && this.frame) { + var width = this.frame.width; + var height = this.frame.height; + + if (!width && this.frame.videoWidth != null) { + width = this.frame.videoWidth; + height = this.frame.videoHeight; + } + + if (width && height) { + this.size = [width, height]; + } + this.setDirtyCanvas(true, true); + } else if (widget.name == "view") { + this.show(); + } + }; + + ImageFrame.prototype.show = function() { + //var str = this.canvas.toDataURL("image/png"); + if (showElement && this.frame) { + showElement(this.frame); + } + }; + + LiteGraph.registerNodeType("graphics/frame", ImageFrame); + + function ImageFade() { + this.addInputs([ + ["img1", "image"], + ["img2", "image"], + ["fade", "number"] + ]); + this.addOutput("", "image"); + this.properties = { fade: 0.5, width: 512, height: 512 }; + } + + ImageFade.title = "Image fade"; + ImageFade.desc = "Fades between images"; + ImageFade.widgets = [ + { name: "resizeA", text: "Resize to A", type: "button" }, + { name: "resizeB", text: "Resize to B", type: "button" } + ]; + + ImageFade.prototype.onAdded = function() { + this.createCanvas(); + var ctx = this.canvas.getContext("2d"); + ctx.fillStyle = "#000"; + ctx.fillRect(0, 0, this.properties["width"], this.properties["height"]); + }; + + ImageFade.prototype.createCanvas = function() { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; + }; + + ImageFade.prototype.onExecute = function() { + var ctx = this.canvas.getContext("2d"); + this.canvas.width = this.canvas.width; + + var A = this.getInputData(0); + if (A != null) { + ctx.drawImage(A, 0, 0, this.canvas.width, this.canvas.height); + } + + var fade = this.getInputData(2); + if (fade == null) { + fade = this.properties["fade"]; + } else { + this.properties["fade"] = fade; + } + + ctx.globalAlpha = fade; + var B = this.getInputData(1); + if (B != null) { + ctx.drawImage(B, 0, 0, this.canvas.width, this.canvas.height); + } + ctx.globalAlpha = 1.0; + + this.setOutputData(0, this.canvas); + this.setDirtyCanvas(true); + }; + + LiteGraph.registerNodeType("graphics/imagefade", ImageFade); + + function ImageCrop() { + this.addInput("", "image"); + this.addOutput("", "image"); + this.properties = { width: 256, height: 256, x: 0, y: 0, scale: 1.0 }; + this.size = [50, 20]; + } + + ImageCrop.title = "Crop"; + ImageCrop.desc = "Crop Image"; + + ImageCrop.prototype.onAdded = function() { + this.createCanvas(); + }; + + ImageCrop.prototype.createCanvas = function() { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; + }; + + ImageCrop.prototype.onExecute = function() { + var input = this.getInputData(0); + if (!input) { + return; + } + + if (input.width) { + var ctx = this.canvas.getContext("2d"); + + ctx.drawImage( + input, + -this.properties["x"], + -this.properties["y"], + input.width * this.properties["scale"], + input.height * this.properties["scale"] + ); + this.setOutputData(0, this.canvas); + } else { + this.setOutputData(0, null); + } + }; + + ImageCrop.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + if (this.canvas) { + ctx.drawImage( + this.canvas, + 0, + 0, + this.canvas.width, + this.canvas.height, + 0, + 0, + this.size[0], + this.size[1] + ); + } + }; + + ImageCrop.prototype.onPropertyChanged = function(name, value) { + this.properties[name] = value; + + if (name == "scale") { + this.properties[name] = parseFloat(value); + if (this.properties[name] == 0) { + console.error("Error in scale"); + this.properties[name] = 1.0; + } + } else { + this.properties[name] = parseInt(value); + } + + this.createCanvas(); + + return true; + }; + + LiteGraph.registerNodeType("graphics/cropImage", ImageCrop); + + //CANVAS stuff + + function CanvasNode() { + this.addInput("clear", LiteGraph.ACTION); + this.addOutput("", "canvas"); + this.properties = { width: 512, height: 512, autoclear: true }; + + this.canvas = document.createElement("canvas"); + this.ctx = this.canvas.getContext("2d"); + } + + CanvasNode.title = "Canvas"; + CanvasNode.desc = "Canvas to render stuff"; + + CanvasNode.prototype.onExecute = function() { + var canvas = this.canvas; + var w = this.properties.width | 0; + var h = this.properties.height | 0; + if (canvas.width != w) { + canvas.width = w; + } + if (canvas.height != h) { + canvas.height = h; + } + + if (this.properties.autoclear) { + this.ctx.clearRect(0, 0, canvas.width, canvas.height); + } + this.setOutputData(0, canvas); + }; + + CanvasNode.prototype.onAction = function(action, param) { + if (action == "clear") { + this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); + } + }; + + LiteGraph.registerNodeType("graphics/canvas", CanvasNode); + + function DrawImageNode() { + this.addInput("canvas", "canvas"); + this.addInput("img", "image,canvas"); + this.addInput("x", "number"); + this.addInput("y", "number"); + this.properties = { x: 0, y: 0, opacity: 1 }; + } + + DrawImageNode.title = "DrawImage"; + DrawImageNode.desc = "Draws image into a canvas"; + + DrawImageNode.prototype.onExecute = function() { + var canvas = this.getInputData(0); + if (!canvas) { + return; + } + + var img = this.getInputOrProperty("img"); + if (!img) { + return; + } + + var x = this.getInputOrProperty("x"); + var y = this.getInputOrProperty("y"); + var ctx = canvas.getContext("2d"); + ctx.drawImage(img, x, y); + }; + + LiteGraph.registerNodeType("graphics/drawImage", DrawImageNode); + + function DrawRectangleNode() { + this.addInput("canvas", "canvas"); + this.addInput("x", "number"); + this.addInput("y", "number"); + this.addInput("w", "number"); + this.addInput("h", "number"); + this.properties = { + x: 0, + y: 0, + w: 10, + h: 10, + color: "white", + opacity: 1 + }; + } + + DrawRectangleNode.title = "DrawRectangle"; + DrawRectangleNode.desc = "Draws rectangle in canvas"; + + DrawRectangleNode.prototype.onExecute = function() { + var canvas = this.getInputData(0); + if (!canvas) { + return; + } + + var x = this.getInputOrProperty("x"); + var y = this.getInputOrProperty("y"); + var w = this.getInputOrProperty("w"); + var h = this.getInputOrProperty("h"); + var ctx = canvas.getContext("2d"); + ctx.fillRect(x, y, w, h); + }; + + LiteGraph.registerNodeType("graphics/drawRectangle", DrawRectangleNode); + + function ImageVideo() { + this.addInput("t", "number"); + this.addOutputs([["frame", "image"], ["t", "number"], ["d", "number"]]); + this.properties = { url: "", use_proxy: true }; + } + + ImageVideo.title = "Video"; + ImageVideo.desc = "Video playback"; + ImageVideo.widgets = [ + { name: "play", text: "PLAY", type: "minibutton" }, + { name: "stop", text: "STOP", type: "minibutton" }, + { name: "demo", text: "Demo video", type: "button" }, + { name: "mute", text: "Mute video", type: "button" } + ]; + + ImageVideo.prototype.onExecute = function() { + if (!this.properties.url) { + return; + } + + if (this.properties.url != this._video_url) { + this.loadVideo(this.properties.url); + } + + if (!this._video || this._video.width == 0) { + return; + } + + var t = this.getInputData(0); + if (t && t >= 0 && t <= 1.0) { + this._video.currentTime = t * this._video.duration; + this._video.pause(); + } + + this._video.dirty = true; + this.setOutputData(0, this._video); + this.setOutputData(1, this._video.currentTime); + this.setOutputData(2, this._video.duration); + this.setDirtyCanvas(true); + }; + + ImageVideo.prototype.onStart = function() { + this.play(); + }; + + ImageVideo.prototype.onStop = function() { + this.stop(); + }; + + ImageVideo.prototype.loadVideo = function(url) { + this._video_url = url; + + var pos = url.substr(0,10).indexOf(":"); + var protocol = ""; + if(pos != -1) + protocol = url.substr(0,pos); + + var host = ""; + if(protocol) + { + host = url.substr(0,url.indexOf("/",protocol.length + 3)); + host = host.substr(protocol.length+3); + } + + if ( + this.properties.use_proxy && + protocol && + LiteGraph.proxy && + host != location.host + ) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + + this._video = document.createElement("video"); + this._video.src = url; + this._video.type = "type=video/mp4"; + + this._video.muted = true; + this._video.autoplay = true; + + var that = this; + this._video.addEventListener("loadedmetadata", function(e) { + //onload + console.log("Duration: " + this.duration + " seconds"); + console.log("Size: " + this.videoWidth + "," + this.videoHeight); + that.setDirtyCanvas(true); + this.width = this.videoWidth; + this.height = this.videoHeight; + }); + this._video.addEventListener("progress", function(e) { + //onload + console.log("video loading..."); + }); + this._video.addEventListener("error", function(e) { + console.error("Error loading video: " + this.src); + if (this.error) { + switch (this.error.code) { + case this.error.MEDIA_ERR_ABORTED: + console.error("You stopped the video."); + break; + case this.error.MEDIA_ERR_NETWORK: + console.error("Network error - please try again later."); + break; + case this.error.MEDIA_ERR_DECODE: + console.error("Video is broken.."); + break; + case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: + console.error("Sorry, your browser can't play this video."); + break; + } + } + }); + + this._video.addEventListener("ended", function(e) { + console.log("Video Ended."); + this.play(); //loop + }); + + //document.body.appendChild(this.video); + }; + + ImageVideo.prototype.onPropertyChanged = function(name, value) { + this.properties[name] = value; + if (name == "url" && value != "") { + this.loadVideo(value); + } + + return true; + }; + + ImageVideo.prototype.play = function() { + if (this._video && this._video.videoWidth ) { //is loaded + this._video.play(); + } + }; + + ImageVideo.prototype.playPause = function() { + if (!this._video) { + return; + } + if (this._video.paused) { + this.play(); + } else { + this.pause(); + } + }; + + ImageVideo.prototype.stop = function() { + if (!this._video) { + return; + } + this._video.pause(); + this._video.currentTime = 0; + }; + + ImageVideo.prototype.pause = function() { + if (!this._video) { + return; + } + console.log("Video paused"); + this._video.pause(); + }; + + ImageVideo.prototype.onWidget = function(e, widget) { + /* + if(widget.name == "demo") + { + this.loadVideo(); + } + else if(widget.name == "play") + { + if(this._video) + this.playPause(); + } + if(widget.name == "stop") + { + this.stop(); + } + else if(widget.name == "mute") + { + if(this._video) + this._video.muted = !this._video.muted; + } + */ + }; + + LiteGraph.registerNodeType("graphics/video", ImageVideo); + + // Texture Webcam ***************************************** + function ImageWebcam() { + this.addOutput("Webcam", "image"); + this.properties = { filterFacingMode: false, facingMode: "user" }; + this.boxcolor = "black"; + this.frame = 0; + } + + ImageWebcam.title = "Webcam"; + ImageWebcam.desc = "Webcam image"; + ImageWebcam.is_webcam_open = false; + + ImageWebcam.prototype.openStream = function() { + if (!navigator.mediaDevices.getUserMedia) { + console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); + return; + } + + this._waiting_confirmation = true; + + // Not showing vendor prefixes. + var constraints = { + audio: false, + video: !this.properties.filterFacingMode ? true : { facingMode: this.properties.facingMode } + }; + navigator.mediaDevices + .getUserMedia(constraints) + .then(this.streamReady.bind(this)) + .catch(onFailSoHard); + + var that = this; + function onFailSoHard(e) { + console.log("Webcam rejected", e); + that._webcam_stream = false; + ImageWebcam.is_webcam_open = false; + that.boxcolor = "red"; + that.trigger("stream_error"); + } + }; + + ImageWebcam.prototype.closeStream = function() { + if (this._webcam_stream) { + var tracks = this._webcam_stream.getTracks(); + if (tracks.length) { + for (var i = 0; i < tracks.length; ++i) { + tracks[i].stop(); + } + } + ImageWebcam.is_webcam_open = false; + this._webcam_stream = null; + this._video = null; + this.boxcolor = "black"; + this.trigger("stream_closed"); + } + }; + + ImageWebcam.prototype.onPropertyChanged = function(name, value) { + if (name == "facingMode") { + this.properties.facingMode = value; + this.closeStream(); + this.openStream(); + } + }; + + ImageWebcam.prototype.onRemoved = function() { + this.closeStream(); + }; + + ImageWebcam.prototype.streamReady = function(localMediaStream) { + this._webcam_stream = localMediaStream; + //this._waiting_confirmation = false; + this.boxcolor = "green"; + + var video = this._video; + if (!video) { + video = document.createElement("video"); + video.autoplay = true; + video.srcObject = localMediaStream; + this._video = video; + //document.body.appendChild( video ); //debug + //when video info is loaded (size and so) + video.onloadedmetadata = function(e) { + // Ready to go. Do some stuff. + console.log(e); + ImageWebcam.is_webcam_open = true; + }; + } + + this.trigger("stream_ready", video); + }; + + ImageWebcam.prototype.onExecute = function() { + if (this._webcam_stream == null && !this._waiting_confirmation) { + this.openStream(); + } + + if (!this._video || !this._video.videoWidth) { + return; + } + + this._video.frame = ++this.frame; + this._video.width = this._video.videoWidth; + this._video.height = this._video.videoHeight; + this.setOutputData(0, this._video); + for (var i = 1; i < this.outputs.length; ++i) { + if (!this.outputs[i]) { + continue; + } + switch (this.outputs[i].name) { + case "width": + this.setOutputData(i, this._video.videoWidth); + break; + case "height": + this.setOutputData(i, this._video.videoHeight); + break; + } + } + }; + + ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) { + var that = this; + var txt = !that.properties.show ? "Show Frame" : "Hide Frame"; + return [ + { + content: txt, + callback: function() { + that.properties.show = !that.properties.show; + } + } + ]; + }; + + ImageWebcam.prototype.onDrawBackground = function(ctx) { + if ( + this.flags.collapsed || + this.size[1] <= 20 || + !this.properties.show + ) { + return; + } + + if (!this._video) { + return; + } + + //render to graph canvas + ctx.save(); + ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); + ctx.restore(); + }; + + ImageWebcam.prototype.onGetOutputs = function() { + return [ + ["width", "number"], + ["height", "number"], + ["stream_ready", LiteGraph.EVENT], + ["stream_closed", LiteGraph.EVENT], + ["stream_error", LiteGraph.EVENT] + ]; + }; + + LiteGraph.registerNodeType("graphics/webcam", ImageWebcam); +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + var LGraphCanvas = global.LGraphCanvas; + + //Works with Litegl.js to create WebGL nodes + global.LGraphTexture = null; + + if (typeof GL == "undefined") + return; + + LGraphCanvas.link_type_colors["Texture"] = "#987"; + + function LGraphTexture() { + this.addOutput("tex", "Texture"); + this.addOutput("name", "string"); + this.properties = { name: "", filter: true }; + this.size = [ + LGraphTexture.image_preview_size, + LGraphTexture.image_preview_size + ]; + } + + global.LGraphTexture = LGraphTexture; + + LGraphTexture.title = "Texture"; + LGraphTexture.desc = "Texture"; + LGraphTexture.widgets_info = { + name: { widget: "texture" }, + filter: { widget: "checkbox" } + }; + + //REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK + LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container + LGraphTexture.image_preview_size = 256; + + //flags to choose output texture type + LGraphTexture.UNDEFINED = 0; //not specified + LGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out) + LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture + LGraphTexture.LOW = 3; //create new texture with low precision (byte) + LGraphTexture.HIGH = 4; //create new texture with high precision (half-float) + LGraphTexture.REUSE = 5; //reuse input texture + LGraphTexture.DEFAULT = 2; //use the default + + LGraphTexture.MODE_VALUES = { + "undefined": LGraphTexture.UNDEFINED, + "pass through": LGraphTexture.PASS_THROUGH, + copy: LGraphTexture.COPY, + low: LGraphTexture.LOW, + high: LGraphTexture.HIGH, + reuse: LGraphTexture.REUSE, + default: LGraphTexture.DEFAULT + }; + + //returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager) + LGraphTexture.getTexturesContainer = function() { + return gl.textures; + }; + + //process the loading of a texture (overwrite it if you have a Resources Manager) + LGraphTexture.loadTexture = function(name, options) { + options = options || {}; + var url = name; + if (url.substr(0, 7) == "http://") { + if (LiteGraph.proxy) { + //proxy external files + url = LiteGraph.proxy + url.substr(7); + } + } + + var container = LGraphTexture.getTexturesContainer(); + var tex = (container[name] = GL.Texture.fromURL(url, options)); + return tex; + }; + + LGraphTexture.getTexture = function(name) { + var container = this.getTexturesContainer(); + + if (!container) { + throw "Cannot load texture, container of textures not found"; + } + + var tex = container[name]; + if (!tex && name && name[0] != ":") { + return this.loadTexture(name); + } + + return tex; + }; + + //used to compute the appropiate output texture + LGraphTexture.getTargetTexture = function(origin, target, mode) { + if (!origin) { + throw "LGraphTexture.getTargetTexture expects a reference texture"; + } + + var tex_type = null; + + switch (mode) { + case LGraphTexture.LOW: + tex_type = gl.UNSIGNED_BYTE; + break; + case LGraphTexture.HIGH: + tex_type = gl.HIGH_PRECISION_FORMAT; + break; + case LGraphTexture.REUSE: + return origin; + break; + case LGraphTexture.COPY: + default: + tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; + break; + } + + if ( + !target || + target.width != origin.width || + target.height != origin.height || + target.type != tex_type || + target.format != origin.format + ) { + target = new GL.Texture(origin.width, origin.height, { + type: tex_type, + format: origin.format, + filter: gl.LINEAR + }); + } + + return target; + }; + + LGraphTexture.getTextureType = function(precision, ref_texture) { + var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE; + switch (precision) { + case LGraphTexture.HIGH: + type = gl.HIGH_PRECISION_FORMAT; + break; + case LGraphTexture.LOW: + type = gl.UNSIGNED_BYTE; + break; + //no default + } + return type; + }; + + LGraphTexture.getWhiteTexture = function() { + if (this._white_texture) { + return this._white_texture; + } + var texture = (this._white_texture = GL.Texture.fromMemory( + 1, + 1, + [255, 255, 255, 255], + { format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST } + )); + return texture; + }; + + LGraphTexture.getNoiseTexture = function() { + if (this._noise_texture) { + return this._noise_texture; + } + + var noise = new Uint8Array(512 * 512 * 4); + for (var i = 0; i < 512 * 512 * 4; ++i) { + noise[i] = Math.random() * 255; + } + + var texture = GL.Texture.fromMemory(512, 512, noise, { + format: gl.RGBA, + wrap: gl.REPEAT, + filter: gl.NEAREST + }); + this._noise_texture = texture; + return texture; + }; + + LGraphTexture.prototype.onDropFile = function(data, filename, file) { + if (!data) { + this._drop_texture = null; + this.properties.name = ""; + } else { + var texture = null; + if (typeof data == "string") { + texture = GL.Texture.fromURL(data); + } else if (filename.toLowerCase().indexOf(".dds") != -1) { + texture = GL.Texture.fromDDSInMemory(data); + } else { + var blob = new Blob([file]); + var url = URL.createObjectURL(blob); + texture = GL.Texture.fromURL(url); + } + + this._drop_texture = texture; + this.properties.name = filename; + } + }; + + LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) { + var that = this; + if (!this._drop_texture) { + return; + } + return [ + { + content: "Clear", + callback: function() { + that._drop_texture = null; + that.properties.name = ""; + } + } + ]; + }; + + LGraphTexture.prototype.onExecute = function() { + var tex = null; + if (this.isOutputConnected(1)) { + tex = this.getInputData(0); + } + + if (!tex && this._drop_texture) { + tex = this._drop_texture; + } + + if (!tex && this.properties.name) { + tex = LGraphTexture.getTexture(this.properties.name); + } + + if (!tex) { + this.setOutputData( 0, null ); + this.setOutputData( 1, "" ); + return; + } + + this._last_tex = tex; + + if (this.properties.filter === false) { + tex.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST); + } else { + tex.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR); + } + + this.setOutputData( 0, tex ); + this.setOutputData( 1, tex.fullpath || tex.filename ); + + for (var i = 2; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output) { + continue; + } + var v = null; + if (output.name == "width") { + v = tex.width; + } else if (output.name == "height") { + v = tex.height; + } else if (output.name == "aspect") { + v = tex.width / tex.height; + } + this.setOutputData(i, v); + } + }; + + LGraphTexture.prototype.onResourceRenamed = function( + old_name, + new_name + ) { + if (this.properties.name == old_name) { + this.properties.name = new_name; + } + }; + + LGraphTexture.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed || this.size[1] <= 20) { + return; + } + + if (this._drop_texture && ctx.webgl) { + ctx.drawImage( + this._drop_texture, + 0, + 0, + this.size[0], + this.size[1] + ); + //this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]); + return; + } + + //Different texture? then get it from the GPU + if (this._last_preview_tex != this._last_tex) { + if (ctx.webgl) { + this._canvas = this._last_tex; + } else { + var tex_canvas = LGraphTexture.generateLowResTexturePreview( + this._last_tex + ); + if (!tex_canvas) { + return; + } + + this._last_preview_tex = this._last_tex; + this._canvas = cloneCanvas(tex_canvas); + } + } + + if (!this._canvas) { + return; + } + + //render to graph canvas + ctx.save(); + if (!ctx.webgl) { + //reverse image + ctx.translate(0, this.size[1]); + ctx.scale(1, -1); + } + ctx.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]); + ctx.restore(); + }; + + //very slow, used at your own risk + LGraphTexture.generateLowResTexturePreview = function(tex) { + if (!tex) { + return null; + } + + var size = LGraphTexture.image_preview_size; + var temp_tex = tex; + + if (tex.format == gl.DEPTH_COMPONENT) { + return null; + } //cannot generate from depth + + //Generate low-level version in the GPU to speed up + if (tex.width > size || tex.height > size) { + temp_tex = this._preview_temp_tex; + if (!this._preview_temp_tex) { + temp_tex = new GL.Texture(size, size, { + minFilter: gl.NEAREST + }); + this._preview_temp_tex = temp_tex; + } + + //copy + tex.copyTo(temp_tex); + tex = temp_tex; + } + + //create intermediate canvas with lowquality version + var tex_canvas = this._preview_canvas; + if (!tex_canvas) { + tex_canvas = createCanvas(size, size); + this._preview_canvas = tex_canvas; + } + + if (temp_tex) { + temp_tex.toCanvas(tex_canvas); + } + return tex_canvas; + }; + + LGraphTexture.prototype.getResources = function(res) { + if(this.properties.name) + res[this.properties.name] = GL.Texture; + return res; + }; + + LGraphTexture.prototype.onGetInputs = function() { + return [["in", "Texture"]]; + }; + + LGraphTexture.prototype.onGetOutputs = function() { + return [ + ["width", "number"], + ["height", "number"], + ["aspect", "number"] + ]; + }; + + //used to replace shader code + LGraphTexture.replaceCode = function( code, context ) + { + return code.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){ + v = v.replace( /[\{\}]/g, "" ); + return context[v] || ""; + }); + } + + LiteGraph.registerNodeType("texture/texture", LGraphTexture); + + //************************** + function LGraphTexturePreview() { + this.addInput("Texture", "Texture"); + this.properties = { flipY: false }; + this.size = [ + LGraphTexture.image_preview_size, + LGraphTexture.image_preview_size + ]; + } + + LGraphTexturePreview.title = "Preview"; + LGraphTexturePreview.desc = "Show a texture in the graph canvas"; + LGraphTexturePreview.allow_preview = false; + + LGraphTexturePreview.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + if (!ctx.webgl && !LGraphTexturePreview.allow_preview) { + return; + } //not working well + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + var tex_canvas = null; + + if (!tex.handle && ctx.webgl) { + tex_canvas = tex; + } else { + tex_canvas = LGraphTexture.generateLowResTexturePreview(tex); + } + + //render to graph canvas + ctx.save(); + if (this.properties.flipY) { + ctx.translate(0, this.size[1]); + ctx.scale(1, -1); + } + ctx.drawImage(tex_canvas, 0, 0, this.size[0], this.size[1]); + ctx.restore(); + }; + + LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview); + + //************************************** + + function LGraphTextureSave() { + this.addInput("Texture", "Texture"); + this.addOutput("tex", "Texture"); + this.addOutput("name", "string"); + this.properties = { name: "", generate_mipmaps: false }; + } + + LGraphTextureSave.title = "Save"; + LGraphTextureSave.desc = "Save a texture in the repository"; + + LGraphTextureSave.prototype.getPreviewTexture = function() + { + return this._texture; + } + + LGraphTextureSave.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (this.properties.generate_mipmaps) { + tex.bind(0); + tex.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR ); + gl.generateMipmap(tex.texture_type); + tex.unbind(0); + } + + if (this.properties.name) { + //for cases where we want to perform something when storing it + if (LGraphTexture.storeTexture) { + LGraphTexture.storeTexture(this.properties.name, tex); + } else { + var container = LGraphTexture.getTexturesContainer(); + container[this.properties.name] = tex; + } + } + + this._texture = tex; + this.setOutputData(0, tex); + this.setOutputData(1, this.properties.name); + }; + + LiteGraph.registerNodeType("texture/save", LGraphTextureSave); + + //**************************************************** + + function LGraphTextureOperation() { + this.addInput("Texture", "Texture"); + this.addInput("TextureB", "Texture"); + this.addInput("value", "number"); + this.addOutput("Texture", "Texture"); + this.help = "

pixelcode must be vec3, uvcode must be vec2, is optional

\ +

uv: tex. coords

color: texture colorB: textureB

time: scene time value: input value

For multiline you must type: result = ...

"; + + this.properties = { + value: 1, + pixelcode: "color + colorB * value", + uvcode: "", + precision: LGraphTexture.DEFAULT + }; + + this.has_error = false; + } + + LGraphTextureOperation.widgets_info = { + uvcode: { widget: "code" }, + pixelcode: { widget: "code" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureOperation.title = "Operation"; + LGraphTextureOperation.desc = "Texture shader operation"; + + LGraphTextureOperation.presets = {}; + + LGraphTextureOperation.prototype.getExtraMenuOptions = function( + graphcanvas + ) { + var that = this; + var txt = !that.properties.show ? "Show Texture" : "Hide Texture"; + return [ + { + content: txt, + callback: function() { + that.properties.show = !that.properties.show; + } + } + ]; + }; + + LGraphTextureOperation.prototype.onPropertyChanged = function() + { + this.has_error = false; + } + + LGraphTextureOperation.prototype.onDrawBackground = function(ctx) { + if ( + this.flags.collapsed || + this.size[1] <= 20 || + !this.properties.show + ) { + return; + } + + if (!this._tex) { + return; + } + + //only works if using a webgl renderer + if (this._tex.gl != ctx) { + return; + } + + //render to graph canvas + ctx.save(); + ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]); + ctx.restore(); + }; + + LGraphTextureOperation.prototype.onExecute = function() { + var tex = this.getInputData(0); + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, tex); + return; + } + + var texB = this.getInputData(1); + + if (!this.properties.uvcode && !this.properties.pixelcode) { + return; + } + + var width = 512; + var height = 512; + if (tex) { + width = tex.width; + height = tex.height; + } else if (texB) { + width = texB.width; + height = texB.height; + } + + if(!texB) + texB = GL.Texture.getWhiteTexture(); + + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + + if (!tex && !this._tex) { + this._tex = new GL.Texture(width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR }); + } else { + this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision ); + } + + var uvcode = ""; + if (this.properties.uvcode) { + uvcode = "uv = " + this.properties.uvcode; + if (this.properties.uvcode.indexOf(";") != -1) { + //there are line breaks, means multiline code + uvcode = this.properties.uvcode; + } + } + + var pixelcode = ""; + if (this.properties.pixelcode) { + pixelcode = "result = " + this.properties.pixelcode; + if (this.properties.pixelcode.indexOf(";") != -1) { + //there are line breaks, means multiline code + pixelcode = this.properties.pixelcode; + } + } + + var shader = this._shader; + + if ( !this.has_error && (!shader || this._shader_code != uvcode + "|" + pixelcode) ) { + + var final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode }); + + try { + shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code ); + this.boxcolor = "#00FF00"; + } catch (err) { + //console.log("Error compiling shader: ", err, final_pixel_code ); + GL.Shader.dumpErrorToConsole(err,Shader.SCREEN_VERTEX_SHADER, final_pixel_code); + this.boxcolor = "#FF0000"; + this.has_error = true; + return; + } + this._shader = shader; + this._shader_code = uvcode + "|" + pixelcode; + } + + if(!this._shader) + return; + + var value = this.getInputData(2); + if (value != null) { + this.properties.value = value; + } else { + value = parseFloat(this.properties.value); + } + + var time = this.graph.getTime(); + + this._tex.drawTo(function() { + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.CULL_FACE); + gl.disable(gl.BLEND); + if (tex) { + tex.bind(0); + } + if (texB) { + texB.bind(1); + } + var mesh = Mesh.getScreenQuad(); + shader + .uniforms({ + u_texture: 0, + u_textureB: 1, + value: value, + texSize: [width, height,1/width,1/height], + time: time + }) + .draw(mesh); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureOperation.pixel_shader = + "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform vec4 texSize;\n\ + uniform float time;\n\ + uniform float value;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + {{UV_CODE}};\n\ + vec4 color4 = texture2D(u_texture, uv);\n\ + vec3 color = color4.rgb;\n\ + vec4 color4B = texture2D(u_textureB, uv);\n\ + vec3 colorB = color4B.rgb;\n\ + vec3 result = color;\n\ + float alpha = 1.0;\n\ + {{PIXEL_CODE}};\n\ + gl_FragColor = vec4(result, alpha);\n\ + }\n\ + "; + + LGraphTextureOperation.registerPreset = function ( name, code ) + { + LGraphTextureOperation.presets[name] = code; + } + + LGraphTextureOperation.registerPreset("",""); + LGraphTextureOperation.registerPreset("bypass","color"); + LGraphTextureOperation.registerPreset("add","color + colorB * value"); + LGraphTextureOperation.registerPreset("substract","(color - colorB) * value"); + LGraphTextureOperation.registerPreset("mate","mix( color, colorB, color4B.a * value)"); + LGraphTextureOperation.registerPreset("invert","vec3(1.0) - color"); + LGraphTextureOperation.registerPreset("multiply","color * colorB * value"); + LGraphTextureOperation.registerPreset("divide","(color / colorB) / value"); + LGraphTextureOperation.registerPreset("difference","abs(color - colorB) * value"); + LGraphTextureOperation.registerPreset("max","max(color, colorB) * value"); + LGraphTextureOperation.registerPreset("min","min(color, colorB) * value"); + LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); + LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); + LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); + LGraphTextureOperation.registerPreset("normalmap","\n\ + float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\ + float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\ + float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\ + float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\ + float z4 = color.x;\n\ + float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\ + float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\ + float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\ + float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\ + vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\ + normal.xy *= value;\n\ + result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\ + "); + LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); + + //webglstudio stuff... + LGraphTextureOperation.prototype.onInspect = function(widgets) + { + var that = this; + widgets.addCombo("Presets","",{ values: Object.keys(LGraphTextureOperation.presets), callback: function(v){ + var code = LGraphTextureOperation.presets[v]; + if(!code) + return; + that.setProperty("pixelcode",code); + that.title = v; + widgets.refresh(); + }}); + } + + LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation); + + //**************************************************** + + function LGraphTextureShader() { + this.addOutput("out", "Texture"); + this.properties = { + code: "", + u_value: 1, + u_color: [1,1,1,1], + width: 512, + height: 512, + precision: LGraphTexture.DEFAULT + }; + + this.properties.code = LGraphTextureShader.pixel_shader; + this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 }; + } + + LGraphTextureShader.title = "Shader"; + LGraphTextureShader.desc = "Texture shader"; + LGraphTextureShader.widgets_info = { + code: { type: "code", lang: "glsl" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureShader.prototype.onPropertyChanged = function( + name, + value + ) { + if (name != "code") { + return; + } + + var shader = this.getShader(); + if (!shader) { + return; + } + + //update connections + var uniforms = shader.uniformInfo; + + //remove deprecated slots + if (this.inputs) { + var already = {}; + for (var i = 0; i < this.inputs.length; ++i) { + var info = this.getInputInfo(i); + if (!info) { + continue; + } + + if (uniforms[info.name] && !already[info.name]) { + already[info.name] = true; + continue; + } + this.removeInput(i); + i--; + } + } + + //update existing ones + for (var i in uniforms) { + var info = shader.uniformInfo[i]; + if (info.loc === null) { + continue; + } //is an attribute, not a uniform + if (i == "time") { + //default one + continue; + } + + var type = "number"; + if (this._shader.samplers[i]) { + type = "texture"; + } else { + switch (info.size) { + case 1: + type = "number"; + break; + case 2: + type = "vec2"; + break; + case 3: + type = "vec3"; + break; + case 4: + type = "vec4"; + break; + case 9: + type = "mat3"; + break; + case 16: + type = "mat4"; + break; + default: + continue; + } + } + + var slot = this.findInputSlot(i); + if (slot == -1) { + this.addInput(i, type); + continue; + } + + var input_info = this.getInputInfo(slot); + if (!input_info) { + this.addInput(i, type); + } else { + if (input_info.type == type) { + continue; + } + this.removeInput(slot, type); + this.addInput(i, type); + } + } + }; + + LGraphTextureShader.prototype.getShader = function() { + //replug + if (this._shader && this._shader_code == this.properties.code) { + return this._shader; + } + + this._shader_code = this.properties.code; + this._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code ); + if (!this._shader) { + this.boxcolor = "red"; + return null; + } else { + this.boxcolor = "green"; + } + return this._shader; + }; + + LGraphTextureShader.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var shader = this.getShader(); + if (!shader) { + return; + } + + var tex_slot = 0; + var in_tex = null; + + //set uniforms + if(this.inputs) + for (var i = 0; i < this.inputs.length; ++i) { + var info = this.getInputInfo(i); + var data = this.getInputData(i); + if (data == null) { + continue; + } + + if (data.constructor === GL.Texture) { + data.bind(tex_slot); + if (!in_tex) { + in_tex = data; + } + data = tex_slot; + tex_slot++; + } + shader.setUniform(info.name, data); //data is tex_slot + } + + var uniforms = this._uniforms; + var type = LGraphTexture.getTextureType( this.properties.precision, in_tex ); + + //render to texture + var w = this.properties.width | 0; + var h = this.properties.height | 0; + if (w == 0) { + w = in_tex ? in_tex.width : gl.canvas.width; + } + if (h == 0) { + h = in_tex ? in_tex.height : gl.canvas.height; + } + uniforms.texSize[0] = w; + uniforms.texSize[1] = h; + uniforms.texSize[2] = 1/w; + uniforms.texSize[3] = 1/h; + uniforms.time = this.graph.getTime(); + uniforms.u_value = this.properties.u_value; + uniforms.u_color.set( this.properties.u_color ); + + if ( !this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h ) { + this._tex = new GL.Texture(w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR }); + } + var tex = this._tex; + tex.drawTo(function() { + shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad()); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureShader.pixel_shader = +"precision highp float;\n\ +\n\ +varying vec2 v_coord;\n\ +uniform float time; //time in seconds\n\ +uniform vec4 texSize; //tex resolution\n\ +uniform float u_value;\n\ +uniform vec4 u_color;\n\n\ +void main() {\n\ + vec2 uv = v_coord;\n\ + vec3 color = vec3(0.0);\n\ + //your code here\n\ + color.xy=uv;\n\n\ + gl_FragColor = vec4(color, 1.0);\n\ +}\n\ +"; + + LiteGraph.registerNodeType("texture/shader", LGraphTextureShader); + + // Texture Scale Offset + + function LGraphTextureScaleOffset() { + this.addInput("in", "Texture"); + this.addInput("scale", "vec2"); + this.addInput("offset", "vec2"); + this.addOutput("out", "Texture"); + this.properties = { + offset: vec2.fromValues(0, 0), + scale: vec2.fromValues(1, 1), + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureScaleOffset.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureScaleOffset.title = "Scale/Offset"; + LGraphTextureScaleOffset.desc = "Applies an scaling and offseting"; + + LGraphTextureScaleOffset.prototype.onExecute = function() { + var tex = this.getInputData(0); + + if (!this.isOutputConnected(0) || !tex) { + return; + } //saves work + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, tex); + return; + } + + var width = tex.width; + var height = tex.height; + var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; + if (this.precision === LGraphTexture.DEFAULT) { + type = tex.type; + } + + if ( + !this._tex || + this._tex.width != width || + this._tex.height != height || + this._tex.type != type + ) { + this._tex = new GL.Texture(width, height, { + type: type, + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + var shader = this._shader; + + if (!shader) { + shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureScaleOffset.pixel_shader + ); + } + + var scale = this.getInputData(1); + if (scale) { + this.properties.scale[0] = scale[0]; + this.properties.scale[1] = scale[1]; + } else { + scale = this.properties.scale; + } + + var offset = this.getInputData(2); + if (offset) { + this.properties.offset[0] = offset[0]; + this.properties.offset[1] = offset[1]; + } else { + offset = this.properties.offset; + } + + this._tex.drawTo(function() { + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.CULL_FACE); + gl.disable(gl.BLEND); + tex.bind(0); + var mesh = Mesh.getScreenQuad(); + shader + .uniforms({ + u_texture: 0, + u_scale: scale, + u_offset: offset + }) + .draw(mesh); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureScaleOffset.pixel_shader = + "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform vec2 u_scale;\n\ + uniform vec2 u_offset;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + uv = uv / u_scale - u_offset;\n\ + gl_FragColor = texture2D(u_texture, uv);\n\ + }\n\ + "; + + LiteGraph.registerNodeType( + "texture/scaleOffset", + LGraphTextureScaleOffset + ); + + // Warp (distort a texture) ************************* + + function LGraphTextureWarp() { + this.addInput("in", "Texture"); + this.addInput("warp", "Texture"); + this.addInput("factor", "number"); + this.addOutput("out", "Texture"); + this.properties = { + factor: 0.01, + scale: [1,1], + offset: [0,0], + precision: LGraphTexture.DEFAULT + }; + + this._uniforms = { + u_texture: 0, + u_textureB: 1, + u_factor: 1, + u_scale: vec2.create(), + u_offset: vec2.create() + }; + } + + LGraphTextureWarp.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureWarp.title = "Warp"; + LGraphTextureWarp.desc = "Texture warp operation"; + + LGraphTextureWarp.prototype.onExecute = function() { + var tex = this.getInputData(0); + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, tex); + return; + } + + var texB = this.getInputData(1); + + var width = 512; + var height = 512; + var type = gl.UNSIGNED_BYTE; + if (tex) { + width = tex.width; + height = tex.height; + type = tex.type; + } else if (texB) { + width = texB.width; + height = texB.height; + type = texB.type; + } + + if (!tex && !this._tex) { + this._tex = new GL.Texture(width, height, { + type: + this.precision === LGraphTexture.LOW + ? gl.UNSIGNED_BYTE + : gl.HIGH_PRECISION_FORMAT, + format: gl.RGBA, + filter: gl.LINEAR + }); + } else { + this._tex = LGraphTexture.getTargetTexture( + tex || this._tex, + this._tex, + this.properties.precision + ); + } + + var shader = this._shader; + + if (!shader) { + shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureWarp.pixel_shader + ); + } + + var factor = this.getInputData(2); + if (factor != null) { + this.properties.factor = factor; + } else { + factor = parseFloat(this.properties.factor); + } + var uniforms = this._uniforms; + uniforms.u_factor = factor; + uniforms.u_scale.set( this.properties.scale ); + uniforms.u_offset.set( this.properties.offset ); + + this._tex.drawTo(function() { + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.CULL_FACE); + gl.disable(gl.BLEND); + if (tex) { + tex.bind(0); + } + if (texB) { + texB.bind(1); + } + var mesh = Mesh.getScreenQuad(); + shader + .uniforms( uniforms ) + .draw(mesh); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureWarp.pixel_shader = + "precision highp float;\n\ + \n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + varying vec2 v_coord;\n\ + uniform float u_factor;\n\ + uniform vec2 u_scale;\n\ + uniform vec2 u_offset;\n\ + \n\ + void main() {\n\ + vec2 uv = v_coord;\n\ + uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\n\ + gl_FragColor = texture2D(u_texture, uv);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp); + + //**************************************************** + + // Texture to Viewport ***************************************** + function LGraphTextureToViewport() { + this.addInput("Texture", "Texture"); + this.properties = { + additive: false, + antialiasing: false, + filter: true, + disable_alpha: false, + gamma: 1.0, + viewport: [0,0,1,1] + }; + this.size[0] = 130; + } + + LGraphTextureToViewport.title = "to Viewport"; + LGraphTextureToViewport.desc = "Texture to viewport"; + + LGraphTextureToViewport._prev_viewport = new Float32Array(4); + + LGraphTextureToViewport.prototype.onDrawBackground = function( ctx ) + { + if ( this.flags.collapsed || this.size[1] <= 40 ) + return; + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40); + } + + LGraphTextureToViewport.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (this.properties.disable_alpha) { + gl.disable(gl.BLEND); + } else { + gl.enable(gl.BLEND); + if (this.properties.additive) { + gl.blendFunc(gl.SRC_ALPHA, gl.ONE); + } else { + gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); + } + } + + gl.disable(gl.DEPTH_TEST); + var gamma = this.properties.gamma || 1.0; + if (this.isInputConnected(1)) { + gamma = this.getInputData(1); + } + + tex.setParameter( + gl.TEXTURE_MAG_FILTER, + this.properties.filter ? gl.LINEAR : gl.NEAREST + ); + + var old_viewport = LGraphTextureToViewport._prev_viewport; + old_viewport.set( gl.viewport_data ); + var new_view = this.properties.viewport; + gl.viewport( old_viewport[0] + old_viewport[2] * new_view[0], old_viewport[1] + old_viewport[3] * new_view[1], old_viewport[2] * new_view[2], old_viewport[3] * new_view[3] ); + var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT); + + if (this.properties.antialiasing) { + if (!LGraphTextureToViewport._shader) { + LGraphTextureToViewport._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureToViewport.aa_pixel_shader + ); + } + + var mesh = Mesh.getScreenQuad(); + tex.bind(0); + LGraphTextureToViewport._shader + .uniforms({ + u_texture: 0, + uViewportSize: [tex.width, tex.height], + u_igamma: 1 / gamma, + inverseVP: [1 / tex.width, 1 / tex.height] + }) + .draw(mesh); + } else { + if (gamma != 1.0) { + if (!LGraphTextureToViewport._gamma_shader) { + LGraphTextureToViewport._gamma_shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureToViewport.gamma_pixel_shader + ); + } + tex.toViewport(LGraphTextureToViewport._gamma_shader, { + u_texture: 0, + u_igamma: 1 / gamma + }); + } else { + tex.toViewport(); + } + } + + gl.viewport( old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3] ); + }; + + LGraphTextureToViewport.prototype.onGetInputs = function() { + return [["gamma", "number"]]; + }; + + LGraphTextureToViewport.aa_pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 uViewportSize;\n\ + uniform vec2 inverseVP;\n\ + uniform float u_igamma;\n\ + #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\ + #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\ + #define FXAA_SPAN_MAX 8.0\n\ + \n\ + /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\ + vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\ + {\n\ + vec4 color = vec4(0.0);\n\ + /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\ + vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\ + vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\ + vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\ + vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\ + vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\ + vec3 luma = vec3(0.299, 0.587, 0.114);\n\ + float lumaNW = dot(rgbNW, luma);\n\ + float lumaNE = dot(rgbNE, luma);\n\ + float lumaSW = dot(rgbSW, luma);\n\ + float lumaSE = dot(rgbSE, luma);\n\ + float lumaM = dot(rgbM, luma);\n\ + float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\ + float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\ + \n\ + vec2 dir;\n\ + dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\ + dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\ + \n\ + float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\ + \n\ + float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\ + dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\ + \n\ + vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\ + texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\ + vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\ + texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\ + \n\ + //return vec4(rgbA,1.0);\n\ + float lumaB = dot(rgbB, luma);\n\ + if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\ + color = vec4(rgbA, 1.0);\n\ + else\n\ + color = vec4(rgbB, 1.0);\n\ + if(u_igamma != 1.0)\n\ + color.xyz = pow( color.xyz, vec3(u_igamma) );\n\ + return color;\n\ + }\n\ + \n\ + void main() {\n\ + gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\ + }\n\ + "; + + LGraphTextureToViewport.gamma_pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_igamma;\n\ + void main() {\n\ + vec4 color = texture2D( u_texture, v_coord);\n\ + color.xyz = pow(color.xyz, vec3(u_igamma) );\n\ + gl_FragColor = color;\n\ + }\n\ + "; + + LiteGraph.registerNodeType( + "texture/toviewport", + LGraphTextureToViewport + ); + + // Texture Copy ***************************************** + function LGraphTextureCopy() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + size: 0, + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureCopy.title = "Copy"; + LGraphTextureCopy.desc = "Copy Texture"; + LGraphTextureCopy.widgets_info = { + size: { + widget: "combo", + values: [0, 32, 64, 128, 256, 512, 1024, 2048] + }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureCopy.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex && !this._temp_texture) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + //copy the texture + if (tex) { + var width = tex.width; + var height = tex.height; + + if (this.properties.size != 0) { + width = this.properties.size; + height = this.properties.size; + } + + var temp = this._temp_texture; + + var type = tex.type; + if (this.properties.precision === LGraphTexture.LOW) { + type = gl.UNSIGNED_BYTE; + } else if (this.properties.precision === LGraphTexture.HIGH) { + type = gl.HIGH_PRECISION_FORMAT; + } + + if ( + !temp || + temp.width != width || + temp.height != height || + temp.type != type + ) { + var minFilter = gl.LINEAR; + if ( + this.properties.generate_mipmaps && + isPowerOfTwo(width) && + isPowerOfTwo(height) + ) { + minFilter = gl.LINEAR_MIPMAP_LINEAR; + } + this._temp_texture = new GL.Texture(width, height, { + type: type, + format: gl.RGBA, + minFilter: minFilter, + magFilter: gl.LINEAR + }); + } + tex.copyTo(this._temp_texture); + + if (this.properties.generate_mipmaps) { + this._temp_texture.bind(0); + gl.generateMipmap(this._temp_texture.texture_type); + this._temp_texture.unbind(0); + } + } + + this.setOutputData(0, this._temp_texture); + }; + + LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy); + + // Texture Downsample ***************************************** + function LGraphTextureDownsample() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + iterations: 1, + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureDownsample.title = "Downsample"; + LGraphTextureDownsample.desc = "Downsample Texture"; + LGraphTextureDownsample.widgets_info = { + iterations: { type: "number", step: 1, precision: 0, min: 0 }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureDownsample.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex && !this._temp_texture) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + //we do not allow any texture different than texture 2D + if (!tex || tex.texture_type !== GL.TEXTURE_2D) { + return; + } + + if (this.properties.iterations < 1) { + this.setOutputData(0, tex); + return; + } + + var shader = LGraphTextureDownsample._shader; + if (!shader) { + LGraphTextureDownsample._shader = shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureDownsample.pixel_shader + ); + } + + var width = tex.width | 0; + var height = tex.height | 0; + var type = tex.type; + if (this.properties.precision === LGraphTexture.LOW) { + type = gl.UNSIGNED_BYTE; + } else if (this.properties.precision === LGraphTexture.HIGH) { + type = gl.HIGH_PRECISION_FORMAT; + } + var iterations = this.properties.iterations || 1; + + var origin = tex; + var target = null; + + var temp = []; + var options = { + type: type, + format: tex.format + }; + + var offset = vec2.create(); + var uniforms = { + u_offset: offset + }; + + if (this._texture) { + GL.Texture.releaseTemporary(this._texture); + } + + for (var i = 0; i < iterations; ++i) { + offset[0] = 1 / width; + offset[1] = 1 / height; + width = width >> 1 || 0; + height = height >> 1 || 0; + target = GL.Texture.getTemporary(width, height, options); + temp.push(target); + origin.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST); + origin.copyTo(target, shader, uniforms); + if (width == 1 && height == 1) { + break; + } //nothing else to do + origin = target; + } + + //keep the last texture used + this._texture = temp.pop(); + + //free the rest + for (var i = 0; i < temp.length; ++i) { + GL.Texture.releaseTemporary(temp[i]); + } + + if (this.properties.generate_mipmaps) { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0, this._texture); + }; + + LGraphTextureDownsample.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_offset;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = texture2D(u_texture, v_coord );\n\ + color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\ + color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\ + color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\ + gl_FragColor = color * 0.25;\n\ + }\n\ + "; + + LiteGraph.registerNodeType( + "texture/downsample", + LGraphTextureDownsample + ); + + + + function LGraphTextureResize() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + size: [512,512], + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureResize.title = "Resize"; + LGraphTextureResize.desc = "Resize Texture"; + LGraphTextureResize.widgets_info = { + iterations: { type: "number", step: 1, precision: 0, min: 0 }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureResize.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex && !this._temp_texture) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + //we do not allow any texture different than texture 2D + if (!tex || tex.texture_type !== GL.TEXTURE_2D) { + return; + } + + var width = this.properties.size[0] | 0; + var height = this.properties.size[1] | 0; + if(width == 0) + width = tex.width; + if(height == 0) + height = tex.height; + var type = tex.type; + if (this.properties.precision === LGraphTexture.LOW) { + type = gl.UNSIGNED_BYTE; + } else if (this.properties.precision === LGraphTexture.HIGH) { + type = gl.HIGH_PRECISION_FORMAT; + } + + if( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type ) + this._texture = new GL.Texture( width, height, { type: type } ); + + tex.copyTo( this._texture ); + + if (this.properties.generate_mipmaps) { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0, this._texture); + }; + + LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize ); + + // Texture Average ***************************************** + function LGraphTextureAverage() { + this.addInput("Texture", "Texture"); + this.addOutput("tex", "Texture"); + this.addOutput("avg", "vec4"); + this.addOutput("lum", "number"); + this.properties = { + use_previous_frame: true, //to avoid stalls + high_quality: false //to use as much pixels as possible + }; + + this._uniforms = { + u_texture: 0, + u_mipmap_offset: 0 + }; + this._luminance = new Float32Array(4); + } + + LGraphTextureAverage.title = "Average"; + LGraphTextureAverage.desc = + "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture.\n If high_quality is true, then it generates the mipmaps first and reads from the lower one."; + + LGraphTextureAverage.prototype.onExecute = function() { + if (!this.properties.use_previous_frame) { + this.updateAverage(); + } + + var v = this._luminance; + this.setOutputData(0, this._temp_texture); + this.setOutputData(1, v); + this.setOutputData(2, (v[0] + v[1] + v[2]) / 3); + }; + + //executed before rendering the frame + LGraphTextureAverage.prototype.onPreRenderExecute = function() { + this.updateAverage(); + }; + + LGraphTextureAverage.prototype.updateAverage = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if ( + !this.isOutputConnected(0) && + !this.isOutputConnected(1) && + !this.isOutputConnected(2) + ) { + return; + } //saves work + + if (!LGraphTextureAverage._shader) { + LGraphTextureAverage._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureAverage.pixel_shader + ); + //creates 256 random numbers and stores them in two mat4 + var samples = new Float32Array(16); + for (var i = 0; i < samples.length; ++i) { + samples[i] = Math.random(); //poorly distributed samples + } + //upload only once + LGraphTextureAverage._shader.uniforms({ + u_samples_a: samples.subarray(0, 16), + u_samples_b: samples.subarray(16, 32) + }); + } + + var temp = this._temp_texture; + var type = gl.UNSIGNED_BYTE; + if (tex.type != type) { + //force floats, half floats cannot be read with gl.readPixels + type = gl.FLOAT; + } + + if (!temp || temp.type != type) { + this._temp_texture = new GL.Texture(1, 1, { + type: type, + format: gl.RGBA, + filter: gl.NEAREST + }); + } + + this._uniforms.u_mipmap_offset = 0; + + if(this.properties.high_quality) + { + if( !this._temp_pot2_texture || this._temp_pot2_texture.type != type ) + this._temp_pot2_texture = new GL.Texture(512, 512, { + type: type, + format: gl.RGBA, + minFilter: gl.LINEAR_MIPMAP_LINEAR, + magFilter: gl.LINEAR + }); + + tex.copyTo( this._temp_pot2_texture ); + tex = this._temp_pot2_texture; + tex.bind(0); + gl.generateMipmap(GL.TEXTURE_2D); + this._uniforms.u_mipmap_offset = 9; + } + + var shader = LGraphTextureAverage._shader; + var uniforms = this._uniforms; + uniforms.u_mipmap_offset = this.properties.mipmap_offset; + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + this._temp_texture.drawTo(function() { + tex.toViewport(shader, uniforms); + }); + + if (this.isOutputConnected(1) || this.isOutputConnected(2)) { + var pixel = this._temp_texture.getPixels(); + if (pixel) { + var v = this._luminance; + var type = this._temp_texture.type; + v.set(pixel); + if (type == gl.UNSIGNED_BYTE) { + vec4.scale(v, v, 1 / 255); + } else if ( + type == GL.HALF_FLOAT || + type == GL.HALF_FLOAT_OES + ) { + //no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT + } + } + } + }; + + LGraphTextureAverage.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + uniform mat4 u_samples_a;\n\ + uniform mat4 u_samples_b;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_mipmap_offset;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = vec4(0.0);\n\ + //random average\n\ + for(int i = 0; i < 4; ++i)\n\ + for(int j = 0; j < 4; ++j)\n\ + {\n\ + color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ + color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ + }\n\ + gl_FragColor = color * 0.03125;\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/average", LGraphTextureAverage); + + + + // Computes operation between pixels (max, min) ***************************************** + function LGraphTextureMinMax() { + this.addInput("Texture", "Texture"); + this.addOutput("min_t", "Texture"); + this.addOutput("max_t", "Texture"); + this.addOutput("min", "vec4"); + this.addOutput("max", "vec4"); + this.properties = { + mode: "max", + use_previous_frame: true //to avoid stalls + }; + + this._uniforms = { + u_texture: 0 + }; + + this._max = new Float32Array(4); + this._min = new Float32Array(4); + + this._textures_chain = []; + } + + LGraphTextureMinMax.widgets_info = { + mode: { widget: "combo", values: ["min","max","avg"] } + }; + + LGraphTextureMinMax.title = "MinMax"; + LGraphTextureMinMax.desc = "Compute the scene min max"; + + LGraphTextureMinMax.prototype.onExecute = function() { + if (!this.properties.use_previous_frame) { + this.update(); + } + + this.setOutputData(0, this._temp_texture); + this.setOutputData(1, this._luminance); + }; + + //executed before rendering the frame + LGraphTextureMinMax.prototype.onPreRenderExecute = function() { + this.update(); + }; + + LGraphTextureMinMax.prototype.update = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if ( !this.isOutputConnected(0) && !this.isOutputConnected(1) ) { + return; + } //saves work + + if (!LGraphTextureMinMax._shader) { + LGraphTextureMinMax._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMinMax.pixel_shader ); + } + + var temp = this._temp_texture; + var type = gl.UNSIGNED_BYTE; + if (tex.type != type) { + //force floats, half floats cannot be read with gl.readPixels + type = gl.FLOAT; + } + + var size = 512; + + if( !this._textures_chain.length || this._textures_chain[0].type != type ) + { + var index = 0; + while(i) + { + this._textures_chain[i] = new GL.Texture( size, size, { + type: type, + format: gl.RGBA, + filter: gl.NEAREST + }); + size = size >> 2; + i++; + if(size == 1) + break; + } + } + + tex.copyTo( this._textures_chain[0] ); + var prev = this._textures_chain[0]; + for(var i = 1; i <= this._textures_chain.length; ++i) + { + var tex = this._textures_chain[i]; + + prev = tex; + } + + var shader = LGraphTextureMinMax._shader; + var uniforms = this._uniforms; + uniforms.u_mipmap_offset = this.properties.mipmap_offset; + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + this._temp_texture.drawTo(function() { + tex.toViewport(shader, uniforms); + }); + }; + + LGraphTextureMinMax.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + uniform mat4 u_samples_a;\n\ + uniform mat4 u_samples_b;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_mipmap_offset;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = vec4(0.0);\n\ + //random average\n\ + for(int i = 0; i < 4; ++i)\n\ + for(int j = 0; j < 4; ++j)\n\ + {\n\ + color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ + color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ + }\n\ + gl_FragColor = color * 0.03125;\n\ + }\n\ + "; + + //LiteGraph.registerNodeType("texture/clustered_operation", LGraphTextureClusteredOperation); + + + function LGraphTextureTemporalSmooth() { + this.addInput("in", "Texture"); + this.addInput("factor", "Number"); + this.addOutput("out", "Texture"); + this.properties = { factor: 0.5 }; + this._uniforms = { + u_texture: 0, + u_textureB: 1, + u_factor: this.properties.factor + }; + } + + LGraphTextureTemporalSmooth.title = "Smooth"; + LGraphTextureTemporalSmooth.desc = "Smooth texture over time"; + + LGraphTextureTemporalSmooth.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex || !this.isOutputConnected(0)) { + return; + } + + if (!LGraphTextureTemporalSmooth._shader) { + LGraphTextureTemporalSmooth._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureTemporalSmooth.pixel_shader + ); + } + + var temp = this._temp_texture; + if ( + !temp || + temp.type != tex.type || + temp.width != tex.width || + temp.height != tex.height + ) { + var options = { + type: tex.type, + format: gl.RGBA, + filter: gl.NEAREST + }; + this._temp_texture = new GL.Texture(tex.width, tex.height, options ); + this._temp_texture2 = new GL.Texture(tex.width, tex.height, options ); + tex.copyTo(this._temp_texture2); + } + + var tempA = this._temp_texture; + var tempB = this._temp_texture2; + + var shader = LGraphTextureTemporalSmooth._shader; + var uniforms = this._uniforms; + uniforms.u_factor = 1.0 - this.getInputOrProperty("factor"); + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + tempA.drawTo(function() { + tempB.bind(1); + tex.toViewport(shader, uniforms); + }); + + this.setOutputData(0, tempA); + + //swap + this._temp_texture = tempB; + this._temp_texture2 = tempA; + }; + + LGraphTextureTemporalSmooth.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + uniform float u_factor;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + gl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\ + }\n\ + "; + + LiteGraph.registerNodeType( "texture/temporal_smooth", LGraphTextureTemporalSmooth ); + + + function LGraphTextureLinearAvgSmooth() { + this.addInput("in", "Texture"); + this.addOutput("avg", "Texture"); + this.addOutput("array", "Texture"); + this.properties = { samples: 64, frames_interval: 1 }; + this._uniforms = { + u_texture: 0, + u_textureB: 1, + u_samples: this.properties.samples, + u_isamples: 1/this.properties.samples + }; + this.frame = 0; + } + + LGraphTextureLinearAvgSmooth.title = "Lineal Avg Smooth"; + LGraphTextureLinearAvgSmooth.desc = "Smooth texture linearly over time"; + + LGraphTextureLinearAvgSmooth["@samples"] = { type: "number", min: 1, max: 64, step: 1, precision: 1 }; + + LGraphTextureLinearAvgSmooth.prototype.getPreviewTexture = function() + { + return this._temp_texture2; + } + + LGraphTextureLinearAvgSmooth.prototype.onExecute = function() { + + var tex = this.getInputData(0); + if (!tex || !this.isOutputConnected(0)) { + return; + } + + if (!LGraphTextureLinearAvgSmooth._shader) { + LGraphTextureLinearAvgSmooth._shader_copy = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_copy ); + LGraphTextureLinearAvgSmooth._shader_avg = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_avg ); + } + + var samples = clamp(this.properties.samples,0,64); + var frame = this.frame; + var interval = this.properties.frames_interval; + + if( interval == 0 || frame % interval == 0 ) + { + var temp = this._temp_texture; + if ( !temp || temp.type != tex.type || temp.width != samples ) { + var options = { + type: tex.type, + format: gl.RGBA, + filter: gl.NEAREST + }; + this._temp_texture = new GL.Texture( samples, 1, options ); + this._temp_texture2 = new GL.Texture( samples, 1, options ); + this._temp_texture_out = new GL.Texture( 1, 1, options ); + } + + var tempA = this._temp_texture; + var tempB = this._temp_texture2; + + var shader_copy = LGraphTextureLinearAvgSmooth._shader_copy; + var shader_avg = LGraphTextureLinearAvgSmooth._shader_avg; + var uniforms = this._uniforms; + uniforms.u_samples = samples; + uniforms.u_isamples = 1.0 / samples; + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + tempA.drawTo(function() { + tempB.bind(1); + tex.toViewport( shader_copy, uniforms ); + }); + + this._temp_texture_out.drawTo(function() { + tempA.toViewport( shader_avg, uniforms ); + }); + + this.setOutputData( 0, this._temp_texture_out ); + + //swap + this._temp_texture = tempB; + this._temp_texture2 = tempA; + } + else + this.setOutputData(0, this._temp_texture_out); + this.setOutputData(1, this._temp_texture2); + this.frame++; + }; + + LGraphTextureLinearAvgSmooth.pixel_shader_copy = + "precision highp float;\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + uniform float u_isamples;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + if( v_coord.x <= u_isamples )\n\ + gl_FragColor = texture2D( u_texture, vec2(0.5) );\n\ + else\n\ + gl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\n\ + }\n\ + "; + + LGraphTextureLinearAvgSmooth.pixel_shader_avg = + "precision highp float;\n\ + precision highp float;\n\ + uniform sampler2D u_texture;\n\ + uniform int u_samples;\n\ + uniform float u_isamples;\n\ + varying vec2 v_coord;\n\ + \n\ + void main() {\n\ + vec4 color = vec4(0.0);\n\ + for(int i = 0; i < 64; ++i)\n\ + {\n\ + color += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\n\ + if(i == (u_samples - 1))\n\ + break;\n\ + }\n\ + gl_FragColor = color * u_isamples;\n\ + }\n\ + "; + + + LiteGraph.registerNodeType( "texture/linear_avg_smooth", LGraphTextureLinearAvgSmooth ); + + // Image To Texture ***************************************** + function LGraphImageToTexture() { + this.addInput("Image", "image"); + this.addOutput("", "Texture"); + this.properties = {}; + } + + LGraphImageToTexture.title = "Image to Texture"; + LGraphImageToTexture.desc = "Uploads an image to the GPU"; + //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} }; + + LGraphImageToTexture.prototype.onExecute = function() { + var img = this.getInputData(0); + if (!img) { + return; + } + + var width = img.videoWidth || img.width; + var height = img.videoHeight || img.height; + + //this is in case we are using a webgl canvas already, no need to reupload it + if (img.gltexture) { + this.setOutputData(0, img.gltexture); + return; + } + + var temp = this._temp_texture; + if (!temp || temp.width != width || temp.height != height) { + this._temp_texture = new GL.Texture(width, height, { + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + try { + this._temp_texture.uploadImage(img); + } catch (err) { + console.error( + "image comes from an unsafe location, cannot be uploaded to webgl: " + + err + ); + return; + } + + this.setOutputData(0, this._temp_texture); + }; + + LiteGraph.registerNodeType( + "texture/imageToTexture", + LGraphImageToTexture + ); + + // Texture LUT ***************************************** + function LGraphTextureLUT() { + this.addInput("Texture", "Texture"); + this.addInput("LUT", "Texture"); + this.addInput("Intensity", "number"); + this.addOutput("", "Texture"); + this.properties = { enabled: true, intensity: 1, precision: LGraphTexture.DEFAULT, texture: null }; + + if (!LGraphTextureLUT._shader) { + LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader ); + } + } + + LGraphTextureLUT.widgets_info = { + texture: { widget: "texture" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureLUT.title = "LUT"; + LGraphTextureLUT.desc = "Apply LUT to Texture"; + + LGraphTextureLUT.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + + if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) { + this.setOutputData(0, tex); + return; + } + + if (!tex) { + return; + } + + var lut_tex = this.getInputData(1); + + if (!lut_tex) { + lut_tex = LGraphTexture.getTexture(this.properties.texture); + } + + if (!lut_tex) { + this.setOutputData(0, tex); + return; + } + + lut_tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_S, + gl.CLAMP_TO_EDGE + ); + gl.texParameteri( + gl.TEXTURE_2D, + gl.TEXTURE_WRAP_T, + gl.CLAMP_TO_EDGE + ); + gl.bindTexture(gl.TEXTURE_2D, null); + + var intensity = this.properties.intensity; + if (this.isInputConnected(2)) { + this.properties.intensity = intensity = this.getInputData(2); + } + + this._tex = LGraphTexture.getTargetTexture( + tex, + this._tex, + this.properties.precision + ); + + //var mesh = Mesh.getScreenQuad(); + + this._tex.drawTo(function() { + lut_tex.bind(1); + tex.toViewport(LGraphTextureLUT._shader, { + u_texture: 0, + u_textureB: 1, + u_amount: intensity + }); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureLUT.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + uniform float u_amount;\n\ + \n\ + void main() {\n\ + lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\ + mediump float blueColor = textureColor.b * 63.0;\n\ + mediump vec2 quad1;\n\ + quad1.y = floor(floor(blueColor) / 8.0);\n\ + quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\ + mediump vec2 quad2;\n\ + quad2.y = floor(ceil(blueColor) / 8.0);\n\ + quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\ + highp vec2 texPos1;\n\ + texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ + texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ + highp vec2 texPos2;\n\ + texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ + texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ + lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\ + lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\ + lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\ + gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT); + + + // Texture LUT ***************************************** + function LGraphTextureEncode() { + this.addInput("Texture", "Texture"); + this.addInput("Atlas", "Texture"); + this.addOutput("", "Texture"); + this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null }; + + if (!LGraphTextureEncode._shader) { + LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader ); + } + + this._uniforms = { + u_texture: 0, + u_textureB: 1, + u_row_simbols: 4, + u_simbol_size: 16, + u_res: vec2.create() + }; + } + + LGraphTextureEncode.widgets_info = { + texture: { widget: "texture" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureEncode.title = "Encode"; + LGraphTextureEncode.desc = "Apply a texture atlas to encode a texture"; + + LGraphTextureEncode.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + + if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) { + this.setOutputData(0, tex); + return; + } + + if (!tex) { + return; + } + + var symbols_tex = this.getInputData(1); + + if (!symbols_tex) { + symbols_tex = LGraphTexture.getTexture(this.properties.texture); + } + + if (!symbols_tex) { + this.setOutputData(0, tex); + return; + } + + symbols_tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); + gl.bindTexture(gl.TEXTURE_2D, null); + + var uniforms = this._uniforms; + uniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols); + uniforms.u_symbol_size = this.properties.symbol_size; + uniforms.u_brightness = this.properties.brightness; + uniforms.u_invert = this.properties.invert ? 1 : 0; + uniforms.u_colorize = this.properties.colorize ? 1 : 0; + + this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); + uniforms.u_res[0] = this._tex.width; + uniforms.u_res[1] = this._tex.height; + this._tex.bind(0); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); + gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); + + this._tex.drawTo(function() { + symbols_tex.bind(1); + tex.toViewport(LGraphTextureEncode._shader, uniforms); + }); + + if (this.properties.generate_mipmaps) { + this._tex.bind(0); + gl.generateMipmap(this._tex.texture_type); + this._tex.unbind(0); + } + + this.setOutputData(0, this._tex); + }; + + LGraphTextureEncode.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_textureB;\n\ + uniform float u_row_simbols;\n\ + uniform float u_symbol_size;\n\ + uniform float u_brightness;\n\ + uniform float u_invert;\n\ + uniform float u_colorize;\n\ + uniform vec2 u_res;\n\ + \n\ + void main() {\n\ + vec2 total_symbols = u_res / u_symbol_size;\n\ + vec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \n\ + vec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\n\ + lowp vec4 textureColor = texture2D(u_texture, uv );\n\ + float lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\n\ + if( u_invert == 1.0 ) lum = 1.0 - lum;\n\ + float index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\n\ + float col = mod( index, u_row_simbols );\n\ + float row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\n\ + vec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\n\ + vec4 color = texture2D( u_textureB, simbol_uv );\n\ + if(u_colorize == 1.0)\n\ + color *= textureColor;\n\ + gl_FragColor = color;\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/encode", LGraphTextureEncode); + + // Texture Channels ***************************************** + function LGraphTextureChannels() { + this.addInput("Texture", "Texture"); + + this.addOutput("R", "Texture"); + this.addOutput("G", "Texture"); + this.addOutput("B", "Texture"); + this.addOutput("A", "Texture"); + + //this.properties = { use_single_channel: true }; + if (!LGraphTextureChannels._shader) { + LGraphTextureChannels._shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureChannels.pixel_shader + ); + } + } + + LGraphTextureChannels.title = "Texture to Channels"; + LGraphTextureChannels.desc = "Split texture channels"; + + LGraphTextureChannels.prototype.onExecute = function() { + var texA = this.getInputData(0); + if (!texA) { + return; + } + + if (!this._channels) { + this._channels = Array(4); + } + + //var format = this.properties.use_single_channel ? gl.LUMINANCE : gl.RGBA; //not supported by WebGL1 + var format = gl.RGB; + var connections = 0; + for (var i = 0; i < 4; i++) { + if (this.isOutputConnected(i)) { + if ( + !this._channels[i] || + this._channels[i].width != texA.width || + this._channels[i].height != texA.height || + this._channels[i].type != texA.type || + this._channels[i].format != format + ) { + this._channels[i] = new GL.Texture( + texA.width, + texA.height, + { + type: texA.type, + format: format, + filter: gl.LINEAR + } + ); + } + connections++; + } else { + this._channels[i] = null; + } + } + + if (!connections) { + return; + } + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + var mesh = Mesh.getScreenQuad(); + var shader = LGraphTextureChannels._shader; + var masks = [ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 1, 0], + [0, 0, 0, 1] + ]; + + for (var i = 0; i < 4; i++) { + if (!this._channels[i]) { + continue; + } + + this._channels[i].drawTo(function() { + texA.bind(0); + shader + .uniforms({ u_texture: 0, u_mask: masks[i] }) + .draw(mesh); + }); + this.setOutputData(i, this._channels[i]); + } + }; + + LGraphTextureChannels.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec4 u_mask;\n\ + \n\ + void main() {\n\ + gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\ + }\n\ + "; + + LiteGraph.registerNodeType( + "texture/textureChannels", + LGraphTextureChannels + ); + + // Texture Channels to Texture ***************************************** + function LGraphChannelsTexture() { + this.addInput("R", "Texture"); + this.addInput("G", "Texture"); + this.addInput("B", "Texture"); + this.addInput("A", "Texture"); + + this.addOutput("Texture", "Texture"); + + this.properties = { + precision: LGraphTexture.DEFAULT, + R: 1, + G: 1, + B: 1, + A: 1 + }; + this._color = vec4.create(); + this._uniforms = { + u_textureR: 0, + u_textureG: 1, + u_textureB: 2, + u_textureA: 3, + u_color: this._color + }; + } + + LGraphChannelsTexture.title = "Channels to Texture"; + LGraphChannelsTexture.desc = "Split texture channels"; + LGraphChannelsTexture.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphChannelsTexture.prototype.onExecute = function() { + var white = LGraphTexture.getWhiteTexture(); + var texR = this.getInputData(0) || white; + var texG = this.getInputData(1) || white; + var texB = this.getInputData(2) || white; + var texA = this.getInputData(3) || white; + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + var mesh = Mesh.getScreenQuad(); + if (!LGraphChannelsTexture._shader) { + LGraphChannelsTexture._shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphChannelsTexture.pixel_shader + ); + } + var shader = LGraphChannelsTexture._shader; + + var w = Math.max(texR.width, texG.width, texB.width, texA.width); + var h = Math.max( + texR.height, + texG.height, + texB.height, + texA.height + ); + var type = + this.properties.precision == LGraphTexture.HIGH + ? LGraphTexture.HIGH_PRECISION_FORMAT + : gl.UNSIGNED_BYTE; + + if ( + !this._texture || + this._texture.width != w || + this._texture.height != h || + this._texture.type != type + ) { + this._texture = new GL.Texture(w, h, { + type: type, + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + var color = this._color; + color[0] = this.properties.R; + color[1] = this.properties.G; + color[2] = this.properties.B; + color[3] = this.properties.A; + var uniforms = this._uniforms; + + this._texture.drawTo(function() { + texR.bind(0); + texG.bind(1); + texB.bind(2); + texA.bind(3); + shader.uniforms(uniforms).draw(mesh); + }); + this.setOutputData(0, this._texture); + }; + + LGraphChannelsTexture.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_textureR;\n\ + uniform sampler2D u_textureG;\n\ + uniform sampler2D u_textureB;\n\ + uniform sampler2D u_textureA;\n\ + uniform vec4 u_color;\n\ + \n\ + void main() {\n\ + gl_FragColor = u_color * vec4( \ + texture2D(u_textureR, v_coord).r,\ + texture2D(u_textureG, v_coord).r,\ + texture2D(u_textureB, v_coord).r,\ + texture2D(u_textureA, v_coord).r);\n\ + }\n\ + "; + + LiteGraph.registerNodeType( + "texture/channelsTexture", + LGraphChannelsTexture + ); + + // Texture Color ***************************************** + function LGraphTextureColor() { + this.addOutput("Texture", "Texture"); + + this._tex_color = vec4.create(); + this.properties = { + color: vec4.create(), + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureColor.title = "Color"; + LGraphTextureColor.desc = + "Generates a 1x1 texture with a constant color"; + + LGraphTextureColor.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureColor.prototype.onDrawBackground = function(ctx) { + var c = this.properties.color; + ctx.fillStyle = + "rgb(" + + Math.floor(clamp(c[0], 0, 1) * 255) + + "," + + Math.floor(clamp(c[1], 0, 1) * 255) + + "," + + Math.floor(clamp(c[2], 0, 1) * 255) + + ")"; + if (this.flags.collapsed) { + this.boxcolor = ctx.fillStyle; + } else { + ctx.fillRect(0, 0, this.size[0], this.size[1]); + } + }; + + LGraphTextureColor.prototype.onExecute = function() { + var type = + this.properties.precision == LGraphTexture.HIGH + ? LGraphTexture.HIGH_PRECISION_FORMAT + : gl.UNSIGNED_BYTE; + + if (!this._tex || this._tex.type != type) { + this._tex = new GL.Texture(1, 1, { + format: gl.RGBA, + type: type, + minFilter: gl.NEAREST + }); + } + var color = this.properties.color; + + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + switch (input.name) { + case "RGB": + case "RGBA": + color.set(v); + break; + case "R": + color[0] = v; + break; + case "G": + color[1] = v; + break; + case "B": + color[2] = v; + break; + case "A": + color[3] = v; + break; + } + } + } + + if (vec4.sqrDist(this._tex_color, color) > 0.001) { + this._tex_color.set(color); + this._tex.fill(color); + } + this.setOutputData(0, this._tex); + }; + + LGraphTextureColor.prototype.onGetInputs = function() { + return [ + ["RGB", "vec3"], + ["RGBA", "vec4"], + ["R", "number"], + ["G", "number"], + ["B", "number"], + ["A", "number"] + ]; + }; + + LiteGraph.registerNodeType("texture/color", LGraphTextureColor); + + // Texture Channels to Texture ***************************************** + function LGraphTextureGradient() { + this.addInput("A", "color"); + this.addInput("B", "color"); + this.addOutput("Texture", "Texture"); + + this.properties = { + angle: 0, + scale: 1, + A: [0, 0, 0], + B: [1, 1, 1], + texture_size: 32 + }; + if (!LGraphTextureGradient._shader) { + LGraphTextureGradient._shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureGradient.pixel_shader + ); + } + + this._uniforms = { + u_angle: 0, + u_colorA: vec3.create(), + u_colorB: vec3.create() + }; + } + + LGraphTextureGradient.title = "Gradient"; + LGraphTextureGradient.desc = "Generates a gradient"; + LGraphTextureGradient["@A"] = { type: "color" }; + LGraphTextureGradient["@B"] = { type: "color" }; + LGraphTextureGradient["@texture_size"] = { + type: "enum", + values: [32, 64, 128, 256, 512] + }; + + LGraphTextureGradient.prototype.onExecute = function() { + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + var mesh = GL.Mesh.getScreenQuad(); + var shader = LGraphTextureGradient._shader; + + var A = this.getInputData(0); + if (!A) { + A = this.properties.A; + } + var B = this.getInputData(1); + if (!B) { + B = this.properties.B; + } + + //angle and scale + for (var i = 2; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + + var uniforms = this._uniforms; + this._uniforms.u_angle = this.properties.angle * DEG2RAD; + this._uniforms.u_scale = this.properties.scale; + vec3.copy(uniforms.u_colorA, A); + vec3.copy(uniforms.u_colorB, B); + + var size = parseInt(this.properties.texture_size); + if (!this._tex || this._tex.width != size) { + this._tex = new GL.Texture(size, size, { + format: gl.RGB, + filter: gl.LINEAR + }); + } + + this._tex.drawTo(function() { + shader.uniforms(uniforms).draw(mesh); + }); + this.setOutputData(0, this._tex); + }; + + LGraphTextureGradient.prototype.onGetInputs = function() { + return [["angle", "number"], ["scale", "number"]]; + }; + + LGraphTextureGradient.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform float u_angle;\n\ + uniform float u_scale;\n\ + uniform vec3 u_colorA;\n\ + uniform vec3 u_colorB;\n\ + \n\ + vec2 rotate(vec2 v, float angle)\n\ + {\n\ + vec2 result;\n\ + float _cos = cos(angle);\n\ + float _sin = sin(angle);\n\ + result.x = v.x * _cos - v.y * _sin;\n\ + result.y = v.x * _sin + v.y * _cos;\n\ + return result;\n\ + }\n\ + void main() {\n\ + float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\ + vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\ + gl_FragColor = vec4(color,1.0);\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient); + + // Texture Mix ***************************************** + function LGraphTextureMix() { + this.addInput("A", "Texture"); + this.addInput("B", "Texture"); + this.addInput("Mixer", "Texture"); + + this.addOutput("Texture", "Texture"); + this.properties = { factor: 0.5, size_from_biggest: true, invert: false, precision: LGraphTexture.DEFAULT }; + this._uniforms = { + u_textureA: 0, + u_textureB: 1, + u_textureMix: 2, + u_mix: vec4.create() + }; + } + + LGraphTextureMix.title = "Mix"; + LGraphTextureMix.desc = "Generates a texture mixing two textures"; + + LGraphTextureMix.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureMix.prototype.onExecute = function() { + var texA = this.getInputData(0); + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, texA); + return; + } + + var texB = this.getInputData(1); + if (!texA || !texB) { + return; + } + + var texMix = this.getInputData(2); + + var factor = this.getInputData(3); + + this._tex = LGraphTexture.getTargetTexture( + this.properties.size_from_biggest && texB.width > texA.width ? texB : texA, + this._tex, + this.properties.precision + ); + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + var mesh = Mesh.getScreenQuad(); + var shader = null; + var uniforms = this._uniforms; + if (texMix) { + shader = LGraphTextureMix._shader_tex; + if (!shader) { + shader = LGraphTextureMix._shader_tex = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureMix.pixel_shader, + { MIX_TEX: "" } + ); + } + } else { + shader = LGraphTextureMix._shader_factor; + if (!shader) { + shader = LGraphTextureMix._shader_factor = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureMix.pixel_shader + ); + } + var f = factor == null ? this.properties.factor : factor; + uniforms.u_mix.set([f, f, f, f]); + } + + var invert = this.properties.invert; + + this._tex.drawTo(function() { + texA.bind( invert ? 1 : 0 ); + texB.bind( invert ? 0 : 1 ); + if (texMix) { + texMix.bind(2); + } + shader.uniforms(uniforms).draw(mesh); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureMix.prototype.onGetInputs = function() { + return [["factor", "number"]]; + }; + + LGraphTextureMix.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_textureA;\n\ + uniform sampler2D u_textureB;\n\ + #ifdef MIX_TEX\n\ + uniform sampler2D u_textureMix;\n\ + #else\n\ + uniform vec4 u_mix;\n\ + #endif\n\ + \n\ + void main() {\n\ + #ifdef MIX_TEX\n\ + vec4 f = texture2D(u_textureMix, v_coord);\n\ + #else\n\ + vec4 f = u_mix;\n\ + #endif\n\ + gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/mix", LGraphTextureMix); + + // Texture Edges detection ***************************************** + function LGraphTextureEdges() { + this.addInput("Tex.", "Texture"); + + this.addOutput("Edges", "Texture"); + this.properties = { + invert: true, + threshold: false, + factor: 1, + precision: LGraphTexture.DEFAULT + }; + + if (!LGraphTextureEdges._shader) { + LGraphTextureEdges._shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureEdges.pixel_shader + ); + } + } + + LGraphTextureEdges.title = "Edges"; + LGraphTextureEdges.desc = "Detects edges"; + + LGraphTextureEdges.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureEdges.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, tex); + return; + } + + if (!tex) { + return; + } + + this._tex = LGraphTexture.getTargetTexture( + tex, + this._tex, + this.properties.precision + ); + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + var mesh = Mesh.getScreenQuad(); + var shader = LGraphTextureEdges._shader; + var invert = this.properties.invert; + var factor = this.properties.factor; + var threshold = this.properties.threshold ? 1 : 0; + + this._tex.drawTo(function() { + tex.bind(0); + shader + .uniforms({ + u_texture: 0, + u_isize: [1 / tex.width, 1 / tex.height], + u_factor: factor, + u_threshold: threshold, + u_invert: invert ? 1 : 0 + }) + .draw(mesh); + }); + + this.setOutputData(0, this._tex); + }; + + LGraphTextureEdges.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_isize;\n\ + uniform int u_invert;\n\ + uniform float u_factor;\n\ + uniform float u_threshold;\n\ + \n\ + void main() {\n\ + vec4 center = texture2D(u_texture, v_coord);\n\ + vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\ + vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\ + vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\ + vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\ + vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\ + diff *= u_factor;\n\ + if(u_invert == 1)\n\ + diff.xyz = vec3(1.0) - diff.xyz;\n\ + if( u_threshold == 0.0 )\n\ + gl_FragColor = vec4( diff.xyz, center.a );\n\ + else\n\ + gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\ + }\n\ + "; + + LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges); + + // Texture Depth ***************************************** + function LGraphTextureDepthRange() { + this.addInput("Texture", "Texture"); + this.addInput("Distance", "number"); + this.addInput("Range", "number"); + this.addOutput("Texture", "Texture"); + this.properties = { + distance: 100, + range: 50, + only_depth: false, + high_precision: false + }; + this._uniforms = { + u_texture: 0, + u_distance: 100, + u_range: 50, + u_camera_planes: null + }; + } + + LGraphTextureDepthRange.title = "Depth Range"; + LGraphTextureDepthRange.desc = "Generates a texture with a depth range"; + + LGraphTextureDepthRange.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + var precision = gl.UNSIGNED_BYTE; + if (this.properties.high_precision) { + precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT; + } + + if ( + !this._temp_texture || + this._temp_texture.type != precision || + this._temp_texture.width != tex.width || + this._temp_texture.height != tex.height + ) { + this._temp_texture = new GL.Texture(tex.width, tex.height, { + type: precision, + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + var uniforms = this._uniforms; + + //iterations + var distance = this.properties.distance; + if (this.isInputConnected(1)) { + distance = this.getInputData(1); + this.properties.distance = distance; + } + + var range = this.properties.range; + if (this.isInputConnected(2)) { + range = this.getInputData(2); + this.properties.range = range; + } + + uniforms.u_distance = distance; + uniforms.u_range = range; + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + var mesh = Mesh.getScreenQuad(); + if (!LGraphTextureDepthRange._shader) { + LGraphTextureDepthRange._shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureDepthRange.pixel_shader + ); + LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureDepthRange.pixel_shader, + { ONLY_DEPTH: "" } + ); + } + var shader = this.properties.only_depth + ? LGraphTextureDepthRange._shader_onlydepth + : LGraphTextureDepthRange._shader; + + //NEAR AND FAR PLANES + var planes = null; + if (tex.near_far_planes) { + planes = tex.near_far_planes; + } else if (window.LS && LS.Renderer._main_camera) { + planes = LS.Renderer._main_camera._uniforms.u_camera_planes; + } else { + planes = [0.1, 1000]; + } //hardcoded + uniforms.u_camera_planes = planes; + + this._temp_texture.drawTo(function() { + tex.bind(0); + shader.uniforms(uniforms).draw(mesh); + }); + + this._temp_texture.near_far_planes = planes; + this.setOutputData(0, this._temp_texture); + }; + + LGraphTextureDepthRange.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_camera_planes;\n\ + uniform float u_distance;\n\ + uniform float u_range;\n\ + \n\ + float LinearDepth()\n\ + {\n\ + float zNear = u_camera_planes.x;\n\ + float zFar = u_camera_planes.y;\n\ + float depth = texture2D(u_texture, v_coord).x;\n\ + depth = depth * 2.0 - 1.0;\n\ + return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ + }\n\ + \n\ + void main() {\n\ + float depth = LinearDepth();\n\ + #ifdef ONLY_DEPTH\n\ + gl_FragColor = vec4(depth);\n\ + #else\n\ + float diff = abs(depth * u_camera_planes.y - u_distance);\n\ + float dof = 1.0;\n\ + if(diff <= u_range)\n\ + dof = diff / u_range;\n\ + gl_FragColor = vec4(dof);\n\ + #endif\n\ + }\n\ + "; + + LiteGraph.registerNodeType( "texture/depth_range", LGraphTextureDepthRange ); + + + // Texture Depth ***************************************** + function LGraphTextureLinearDepth() { + this.addInput("Texture", "Texture"); + this.addOutput("Texture", "Texture"); + this.properties = { + precision: LGraphTexture.DEFAULT, + invert: false + }; + this._uniforms = { + u_texture: 0, + u_camera_planes: null, //filled later + u_ires: vec2.create() + }; + } + + LGraphTextureLinearDepth.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureLinearDepth.title = "Linear Depth"; + LGraphTextureLinearDepth.desc = "Creates a color texture with linear depth"; + + LGraphTextureLinearDepth.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + if (!tex || (tex.format != gl.DEPTH_COMPONENT && tex.format != gl.DEPTH_STENCIL) ) { + return; + } + + var precision = this.properties.precision == LGraphTexture.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; + + if ( !this._temp_texture || this._temp_texture.type != precision || this._temp_texture.width != tex.width || this._temp_texture.height != tex.height ) { + this._temp_texture = new GL.Texture(tex.width, tex.height, { + type: precision, + format: gl.RGB, + filter: gl.LINEAR + }); + } + + var uniforms = this._uniforms; + uniforms.u_invert = this.properties.invert ? 1 : 0; + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + var mesh = Mesh.getScreenQuad(); + if(!LGraphTextureLinearDepth._shader) + LGraphTextureLinearDepth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearDepth.pixel_shader); + var shader = LGraphTextureLinearDepth._shader; + + //NEAR AND FAR PLANES + var planes = null; + if (tex.near_far_planes) { + planes = tex.near_far_planes; + } else if (window.LS && LS.Renderer._main_camera) { + planes = LS.Renderer._main_camera._uniforms.u_camera_planes; + } else { + planes = [0.1, 1000]; + } //hardcoded + uniforms.u_camera_planes = planes; + //uniforms.u_ires.set([1/tex.width, 1/tex.height]); + uniforms.u_ires.set([0,0]); + + this._temp_texture.drawTo(function() { + tex.bind(0); + shader.uniforms(uniforms).draw(mesh); + }); + + this._temp_texture.near_far_planes = planes; + this.setOutputData(0, this._temp_texture); + }; + + LGraphTextureLinearDepth.pixel_shader = + "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_camera_planes;\n\ + uniform int u_invert;\n\ + uniform vec2 u_ires;\n\ + \n\ + void main() {\n\ + float zNear = u_camera_planes.x;\n\ + float zFar = u_camera_planes.y;\n\ + float depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\ + float f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ + if( u_invert == 1 )\n\ + f = 1.0 - f;\n\ + gl_FragColor = vec4(vec3(f),1.0);\n\ + }\n\ + "; + + LiteGraph.registerNodeType( "texture/linear_depth", LGraphTextureLinearDepth ); + + // Texture Blur ***************************************** + function LGraphTextureBlur() { + this.addInput("Texture", "Texture"); + this.addInput("Iterations", "number"); + this.addInput("Intensity", "number"); + this.addOutput("Blurred", "Texture"); + this.properties = { + intensity: 1, + iterations: 1, + preserve_aspect: false, + scale: [1, 1], + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureBlur.title = "Blur"; + LGraphTextureBlur.desc = "Blur a texture"; + + LGraphTextureBlur.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureBlur.max_iterations = 20; + + LGraphTextureBlur.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var temp = this._final_texture; + + if ( + !temp || + temp.width != tex.width || + temp.height != tex.height || + temp.type != tex.type + ) { + //we need two textures to do the blurring + //this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); + temp = this._final_texture = new GL.Texture( + tex.width, + tex.height, + { type: tex.type, format: gl.RGBA, filter: gl.LINEAR } + ); + } + + //iterations + var iterations = this.properties.iterations; + if (this.isInputConnected(1)) { + iterations = this.getInputData(1); + this.properties.iterations = iterations; + } + iterations = Math.min( + Math.floor(iterations), + LGraphTextureBlur.max_iterations + ); + if (iterations == 0) { + //skip blurring + this.setOutputData(0, tex); + return; + } + + var intensity = this.properties.intensity; + if (this.isInputConnected(2)) { + intensity = this.getInputData(2); + this.properties.intensity = intensity; + } + + //blur sometimes needs an aspect correction + var aspect = LiteGraph.camera_aspect; + if (!aspect && window.gl !== undefined) { + aspect = gl.canvas.height / gl.canvas.width; + } + if (!aspect) { + aspect = 1; + } + aspect = this.properties.preserve_aspect ? aspect : 1; + + var scale = this.properties.scale || [1, 1]; + tex.applyBlur(aspect * scale[0], scale[1], intensity, temp); + for (var i = 1; i < iterations; ++i) { + temp.applyBlur( + aspect * scale[0] * (i + 1), + scale[1] * (i + 1), + intensity + ); + } + + this.setOutputData(0, temp); + }; + + /* +LGraphTextureBlur.pixel_shader = "precision highp float;\n\ + precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_offset;\n\ + uniform float u_intensity;\n\ + void main() {\n\ + vec4 sum = vec4(0.0);\n\ + vec4 center = texture2D(u_texture, v_coord);\n\ + sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\n\ + sum += center * 0.16/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\ + sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\ + gl_FragColor = u_intensity * sum;\n\ + }\n\ + "; +*/ + + LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur); + + //Independent glow FX + //based on https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/ + function FXGlow() + { + this.intensity = 0.5; + this.persistence = 0.6; + this.iterations = 8; + this.threshold = 0.8; + this.scale = 1; + + this.dirt_texture = null; + this.dirt_factor = 0.5; + + this._textures = []; + this._uniforms = { + u_intensity: 1, + u_texture: 0, + u_glow_texture: 1, + u_threshold: 0, + u_texel_size: vec2.create() + }; + } + + FXGlow.prototype.applyFX = function( tex, output_texture, glow_texture, average_texture ) { + + var width = tex.width; + var height = tex.height; + + var texture_info = { + format: tex.format, + type: tex.type, + minFilter: GL.LINEAR, + magFilter: GL.LINEAR, + wrap: gl.CLAMP_TO_EDGE + }; + + var uniforms = this._uniforms; + var textures = this._textures; + + //cut + var shader = FXGlow._cut_shader; + if (!shader) { + shader = FXGlow._cut_shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + FXGlow.cut_pixel_shader + ); + } + + gl.disable(gl.DEPTH_TEST); + gl.disable(gl.BLEND); + + uniforms.u_threshold = this.threshold; + var currentDestination = (textures[0] = GL.Texture.getTemporary( + width, + height, + texture_info + )); + tex.blit( currentDestination, shader.uniforms(uniforms) ); + var currentSource = currentDestination; + + var iterations = this.iterations; + iterations = clamp(iterations, 1, 16) | 0; + var texel_size = uniforms.u_texel_size; + var intensity = this.intensity; + + uniforms.u_intensity = 1; + uniforms.u_delta = this.scale; //1 + + //downscale/upscale shader + var shader = FXGlow._shader; + if (!shader) { + shader = FXGlow._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + FXGlow.scale_pixel_shader + ); + } + + var i = 1; + //downscale + for (; i < iterations; i++) { + width = width >> 1; + if ((height | 0) > 1) { + height = height >> 1; + } + if (width < 2) { + break; + } + currentDestination = textures[i] = GL.Texture.getTemporary( + width, + height, + texture_info + ); + texel_size[0] = 1 / currentSource.width; + texel_size[1] = 1 / currentSource.height; + currentSource.blit( + currentDestination, + shader.uniforms(uniforms) + ); + currentSource = currentDestination; + } + + //average + if (average_texture) { + texel_size[0] = 1 / currentSource.width; + texel_size[1] = 1 / currentSource.height; + uniforms.u_intensity = intensity; + uniforms.u_delta = 1; + currentSource.blit(average_texture, shader.uniforms(uniforms)); + } + + //upscale and blend + gl.enable(gl.BLEND); + gl.blendFunc(gl.ONE, gl.ONE); + uniforms.u_intensity = this.persistence; + uniforms.u_delta = 0.5; + + // i-=2 => -1 to point to last element in array, -1 to go to texture above + for ( i -= 2; i >= 0; i-- ) + { + currentDestination = textures[i]; + textures[i] = null; + texel_size[0] = 1 / currentSource.width; + texel_size[1] = 1 / currentSource.height; + currentSource.blit( + currentDestination, + shader.uniforms(uniforms) + ); + GL.Texture.releaseTemporary(currentSource); + currentSource = currentDestination; + } + gl.disable(gl.BLEND); + + //glow + if (glow_texture) { + currentSource.blit(glow_texture); + } + + //final composition + if ( output_texture ) { + var final_texture = output_texture; + var dirt_texture = this.dirt_texture; + var dirt_factor = this.dirt_factor; + uniforms.u_intensity = intensity; + + shader = dirt_texture + ? FXGlow._dirt_final_shader + : FXGlow._final_shader; + if (!shader) { + if (dirt_texture) { + shader = FXGlow._dirt_final_shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + FXGlow.final_pixel_shader, + { USE_DIRT: "" } + ); + } else { + shader = FXGlow._final_shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + FXGlow.final_pixel_shader + ); + } + } + + final_texture.drawTo(function() { + tex.bind(0); + currentSource.bind(1); + if (dirt_texture) { + shader.setUniform("u_dirt_factor", dirt_factor); + shader.setUniform( + "u_dirt_texture", + dirt_texture.bind(2) + ); + } + shader.toViewport(uniforms); + }); + } + + GL.Texture.releaseTemporary(currentSource); + }; + + FXGlow.cut_pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_threshold;\n\ + void main() {\n\ + gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\ + }"; + + FXGlow.scale_pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_texel_size;\n\ + uniform float u_delta;\n\ + uniform float u_intensity;\n\ + \n\ + vec4 sampleBox(vec2 uv) {\n\ + vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ + vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\ + return s * 0.25;\n\ + }\n\ + void main() {\n\ + gl_FragColor = u_intensity * sampleBox( v_coord );\n\ + }"; + + FXGlow.final_pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_glow_texture;\n\ + #ifdef USE_DIRT\n\ + uniform sampler2D u_dirt_texture;\n\ + #endif\n\ + uniform vec2 u_texel_size;\n\ + uniform float u_delta;\n\ + uniform float u_intensity;\n\ + uniform float u_dirt_factor;\n\ + \n\ + vec4 sampleBox(vec2 uv) {\n\ + vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ + vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\ + return s * 0.25;\n\ + }\n\ + void main() {\n\ + vec4 glow = sampleBox( v_coord );\n\ + #ifdef USE_DIRT\n\ + glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\ + #endif\n\ + gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\ + }"; + + + // Texture Glow ***************************************** + function LGraphTextureGlow() { + this.addInput("in", "Texture"); + this.addInput("dirt", "Texture"); + this.addOutput("out", "Texture"); + this.addOutput("glow", "Texture"); + this.properties = { + enabled: true, + intensity: 1, + persistence: 0.99, + iterations: 16, + threshold: 0, + scale: 1, + dirt_factor: 0.5, + precision: LGraphTexture.DEFAULT + }; + + this.fx = new FXGlow(); + } + + LGraphTextureGlow.title = "Glow"; + LGraphTextureGlow.desc = "Filters a texture giving it a glow effect"; + + LGraphTextureGlow.widgets_info = { + iterations: { + type: "number", + min: 0, + max: 16, + step: 1, + precision: 0 + }, + threshold: { + type: "number", + min: 0, + max: 10, + step: 0.01, + precision: 2 + }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureGlow.prototype.onGetInputs = function() { + return [ + ["enabled", "boolean"], + ["threshold", "number"], + ["intensity", "number"], + ["persistence", "number"], + ["iterations", "number"], + ["dirt_factor", "number"] + ]; + }; + + LGraphTextureGlow.prototype.onGetOutputs = function() { + return [["average", "Texture"]]; + }; + + LGraphTextureGlow.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (!this.isAnyOutputConnected()) { + return; + } //saves work + + if ( + this.properties.precision === LGraphTexture.PASS_THROUGH || + this.getInputOrProperty("enabled") === false + ) { + this.setOutputData(0, tex); + return; + } + + var width = tex.width; + var height = tex.height; + + var fx = this.fx; + fx.threshold = this.getInputOrProperty("threshold"); + fx.iterations = this.getInputOrProperty("iterations"); + fx.intensity = this.getInputOrProperty("intensity"); + fx.persistence = this.getInputOrProperty("persistence"); + fx.dirt_texture = this.getInputData(1); + fx.dirt_factor = this.getInputOrProperty("dirt_factor"); + fx.scale = this.properties.scale; + + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + + var average_texture = null; + if (this.isOutputConnected(2)) { + average_texture = this._average_texture; + if ( + !average_texture || + average_texture.type != tex.type || + average_texture.format != tex.format + ) { + average_texture = this._average_texture = new GL.Texture( + 1, + 1, + { + type: tex.type, + format: tex.format, + filter: gl.LINEAR + } + ); + } + } + + var glow_texture = null; + if (this.isOutputConnected(1)) { + glow_texture = this._glow_texture; + if ( + !glow_texture || + glow_texture.width != tex.width || + glow_texture.height != tex.height || + glow_texture.type != type || + glow_texture.format != tex.format + ) { + glow_texture = this._glow_texture = new GL.Texture( + tex.width, + tex.height, + { type: type, format: tex.format, filter: gl.LINEAR } + ); + } + } + + var final_texture = null; + if (this.isOutputConnected(0)) { + final_texture = this._final_texture; + if ( + !final_texture || + final_texture.width != tex.width || + final_texture.height != tex.height || + final_texture.type != type || + final_texture.format != tex.format + ) { + final_texture = this._final_texture = new GL.Texture( + tex.width, + tex.height, + { type: type, format: tex.format, filter: gl.LINEAR } + ); + } + + } + + //apply FX + fx.applyFX(tex, final_texture, glow_texture, average_texture ); + + if (this.isOutputConnected(0)) + this.setOutputData(0, final_texture); + + if (this.isOutputConnected(1)) + this.setOutputData(1, average_texture); + + if (this.isOutputConnected(2)) + this.setOutputData(2, glow_texture); + }; + + LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow); + + // Texture Filter ***************************************** + function LGraphTextureKuwaharaFilter() { + this.addInput("Texture", "Texture"); + this.addOutput("Filtered", "Texture"); + this.properties = { intensity: 1, radius: 5 }; + } + + LGraphTextureKuwaharaFilter.title = "Kuwahara Filter"; + LGraphTextureKuwaharaFilter.desc = + "Filters a texture giving an artistic oil canvas painting"; + + LGraphTextureKuwaharaFilter.max_radius = 10; + LGraphTextureKuwaharaFilter._shaders = []; + + LGraphTextureKuwaharaFilter.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var temp = this._temp_texture; + + if ( + !temp || + temp.width != tex.width || + temp.height != tex.height || + temp.type != tex.type + ) { + this._temp_texture = new GL.Texture(tex.width, tex.height, { + type: tex.type, + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + //iterations + var radius = this.properties.radius; + radius = Math.min( + Math.floor(radius), + LGraphTextureKuwaharaFilter.max_radius + ); + if (radius == 0) { + //skip blurring + this.setOutputData(0, tex); + return; + } + + var intensity = this.properties.intensity; + + //blur sometimes needs an aspect correction + var aspect = LiteGraph.camera_aspect; + if (!aspect && window.gl !== undefined) { + aspect = gl.canvas.height / gl.canvas.width; + } + if (!aspect) { + aspect = 1; + } + aspect = this.properties.preserve_aspect ? aspect : 1; + + if (!LGraphTextureKuwaharaFilter._shaders[radius]) { + LGraphTextureKuwaharaFilter._shaders[radius] = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureKuwaharaFilter.pixel_shader, + { RADIUS: radius.toFixed(0) } + ); + } + + var shader = LGraphTextureKuwaharaFilter._shaders[radius]; + var mesh = GL.Mesh.getScreenQuad(); + tex.bind(0); + + this._temp_texture.drawTo(function() { + shader + .uniforms({ + u_texture: 0, + u_intensity: intensity, + u_resolution: [tex.width, tex.height], + u_iResolution: [1 / tex.width, 1 / tex.height] + }) + .draw(mesh); + }); + + this.setOutputData(0, this._temp_texture); + }; + + //from https://www.shadertoy.com/view/MsXSz4 + LGraphTextureKuwaharaFilter.pixel_shader = + "\n\ +precision highp float;\n\ +varying vec2 v_coord;\n\ +uniform sampler2D u_texture;\n\ +uniform float u_intensity;\n\ +uniform vec2 u_resolution;\n\ +uniform vec2 u_iResolution;\n\ +#ifndef RADIUS\n\ + #define RADIUS 7\n\ +#endif\n\ +void main() {\n\ +\n\ + const int radius = RADIUS;\n\ + vec2 fragCoord = v_coord;\n\ + vec2 src_size = u_iResolution;\n\ + vec2 uv = v_coord;\n\ + float n = float((radius + 1) * (radius + 1));\n\ + int i;\n\ + int j;\n\ + vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\ + vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\ + vec3 c;\n\ + \n\ + for (int j = -radius; j <= 0; ++j) {\n\ + for (int i = -radius; i <= 0; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m0 += c;\n\ + s0 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = -radius; j <= 0; ++j) {\n\ + for (int i = 0; i <= radius; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m1 += c;\n\ + s1 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = 0; j <= radius; ++j) {\n\ + for (int i = 0; i <= radius; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m2 += c;\n\ + s2 += c * c;\n\ + }\n\ + }\n\ + \n\ + for (int j = 0; j <= radius; ++j) {\n\ + for (int i = -radius; i <= 0; ++i) {\n\ + c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ + m3 += c;\n\ + s3 += c * c;\n\ + }\n\ + }\n\ + \n\ + float min_sigma2 = 1e+2;\n\ + m0 /= n;\n\ + s0 = abs(s0 / n - m0 * m0);\n\ + \n\ + float sigma2 = s0.r + s0.g + s0.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m0, 1.0);\n\ + }\n\ + \n\ + m1 /= n;\n\ + s1 = abs(s1 / n - m1 * m1);\n\ + \n\ + sigma2 = s1.r + s1.g + s1.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m1, 1.0);\n\ + }\n\ + \n\ + m2 /= n;\n\ + s2 = abs(s2 / n - m2 * m2);\n\ + \n\ + sigma2 = s2.r + s2.g + s2.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m2, 1.0);\n\ + }\n\ + \n\ + m3 /= n;\n\ + s3 = abs(s3 / n - m3 * m3);\n\ + \n\ + sigma2 = s3.r + s3.g + s3.b;\n\ + if (sigma2 < min_sigma2) {\n\ + min_sigma2 = sigma2;\n\ + gl_FragColor = vec4(m3, 1.0);\n\ + }\n\ +}\n\ +"; + + LiteGraph.registerNodeType( + "texture/kuwahara", + LGraphTextureKuwaharaFilter + ); + + // Texture ***************************************** + function LGraphTextureXDoGFilter() { + this.addInput("Texture", "Texture"); + this.addOutput("Filtered", "Texture"); + this.properties = { + sigma: 1.4, + k: 1.6, + p: 21.7, + epsilon: 79, + phi: 0.017 + }; + } + + LGraphTextureXDoGFilter.title = "XDoG Filter"; + LGraphTextureXDoGFilter.desc = + "Filters a texture giving an artistic ink style"; + + LGraphTextureXDoGFilter.max_radius = 10; + LGraphTextureXDoGFilter._shaders = []; + + LGraphTextureXDoGFilter.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var temp = this._temp_texture; + if ( + !temp || + temp.width != tex.width || + temp.height != tex.height || + temp.type != tex.type + ) { + this._temp_texture = new GL.Texture(tex.width, tex.height, { + type: tex.type, + format: gl.RGBA, + filter: gl.LINEAR + }); + } + + if (!LGraphTextureXDoGFilter._xdog_shader) { + LGraphTextureXDoGFilter._xdog_shader = new GL.Shader( + Shader.SCREEN_VERTEX_SHADER, + LGraphTextureXDoGFilter.xdog_pixel_shader + ); + } + var shader = LGraphTextureXDoGFilter._xdog_shader; + var mesh = GL.Mesh.getScreenQuad(); + + var sigma = this.properties.sigma; + var k = this.properties.k; + var p = this.properties.p; + var epsilon = this.properties.epsilon; + var phi = this.properties.phi; + tex.bind(0); + this._temp_texture.drawTo(function() { + shader + .uniforms({ + src: 0, + sigma: sigma, + k: k, + p: p, + epsilon: epsilon, + phi: phi, + cvsWidth: tex.width, + cvsHeight: tex.height + }) + .draw(mesh); + }); + + this.setOutputData(0, this._temp_texture); + }; + + //from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js + LGraphTextureXDoGFilter.xdog_pixel_shader = + "\n\ +precision highp float;\n\ +uniform sampler2D src;\n\n\ +uniform float cvsHeight;\n\ +uniform float cvsWidth;\n\n\ +uniform float sigma;\n\ +uniform float k;\n\ +uniform float p;\n\ +uniform float epsilon;\n\ +uniform float phi;\n\ +varying vec2 v_coord;\n\n\ +float cosh(float val)\n\ +{\n\ + float tmp = exp(val);\n\ + float cosH = (tmp + 1.0 / tmp) / 2.0;\n\ + return cosH;\n\ +}\n\n\ +float tanh(float val)\n\ +{\n\ + float tmp = exp(val);\n\ + float tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\ + return tanH;\n\ +}\n\n\ +float sinh(float val)\n\ +{\n\ + float tmp = exp(val);\n\ + float sinH = (tmp - 1.0 / tmp) / 2.0;\n\ + return sinH;\n\ +}\n\n\ +void main(void){\n\ + vec3 destColor = vec3(0.0);\n\ + float tFrag = 1.0 / cvsHeight;\n\ + float sFrag = 1.0 / cvsWidth;\n\ + vec2 Frag = vec2(sFrag,tFrag);\n\ + vec2 uv = gl_FragCoord.st;\n\ + float twoSigmaESquared = 2.0 * sigma * sigma;\n\ + float twoSigmaRSquared = twoSigmaESquared * k * k;\n\ + int halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\ + const int MAX_NUM_ITERATION = 99999;\n\ + vec2 sum = vec2(0.0);\n\ + vec2 norm = vec2(0.0);\n\n\ + for(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\ + int i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\ + int j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\ + float d = length(vec2(i,j));\n\ + vec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\ + exp( -d * d / twoSigmaRSquared ));\n\n\ + vec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\ + norm += kernel;\n\ + sum += kernel * L;\n\ + }\n\n\ + sum /= norm;\n\n\ + float H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\ + float edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\ + destColor = vec3(edge);\n\ + gl_FragColor = vec4(destColor, 1.0);\n\ +}"; + + LiteGraph.registerNodeType("texture/xDoG", LGraphTextureXDoGFilter); + + // Texture Webcam ***************************************** + function LGraphTextureWebcam() { + this.addOutput("Webcam", "Texture"); + this.properties = { texture_name: "", facingMode: "user" }; + this.boxcolor = "black"; + this.version = 0; + } + + LGraphTextureWebcam.title = "Webcam"; + LGraphTextureWebcam.desc = "Webcam texture"; + + LGraphTextureWebcam.is_webcam_open = false; + + LGraphTextureWebcam.prototype.openStream = function() { + if (!navigator.getUserMedia) { + //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); + return; + } + + this._waiting_confirmation = true; - for (var j = 0; j < gamepad.buttons.length; j++) { - this._current_buttons[j] = gamepad.buttons[j].pressed; + // Not showing vendor prefixes. + var constraints = { + audio: false, + video: { facingMode: this.properties.facingMode } + }; + navigator.mediaDevices + .getUserMedia(constraints) + .then(this.streamReady.bind(this)) + .catch(onFailSoHard); - if(j < 12) - { - xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; - if(gamepad.buttons[j].was_pressed) - this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); + var that = this; + function onFailSoHard(e) { + LGraphTextureWebcam.is_webcam_open = false; + console.log("Webcam rejected", e); + that._webcam_stream = false; + that.boxcolor = "red"; + that.trigger("stream_error"); + } + }; + + LGraphTextureWebcam.prototype.closeStream = function() { + if (this._webcam_stream) { + var tracks = this._webcam_stream.getTracks(); + if (tracks.length) { + for (var i = 0; i < tracks.length; ++i) { + tracks[i].stop(); } - else //mapping of XBOX - switch ( j ) //I use a switch to ensure that a player with another gamepad could play - { - case 12: - if (gamepad.buttons[j].pressed) { - xbox.hat += "up"; - xbox.hatmap |= GamepadInput.UP; - } - break; - case 13: - if (gamepad.buttons[j].pressed) { - xbox.hat += "down"; - xbox.hatmap |= GamepadInput.DOWN; - } - break; - case 14: - if (gamepad.buttons[j].pressed) { - xbox.hat += "left"; - xbox.hatmap |= GamepadInput.LEFT; - } - break; - case 15: - if (gamepad.buttons[j].pressed) { - xbox.hat += "right"; - xbox.hatmap |= GamepadInput.RIGHT; - } - break; - case 16: - xbox.buttons["home"] = gamepad.buttons[j].pressed; - break; - default: - } - } - gamepad.xbox = xbox; - return gamepad; - } - }; + } + LGraphTextureWebcam.is_webcam_open = false; + this._webcam_stream = null; + this._video = null; + this.boxcolor = "black"; + this.trigger("stream_closed"); + } + }; - GamepadInput.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } + LGraphTextureWebcam.prototype.streamReady = function(localMediaStream) { + this._webcam_stream = localMediaStream; + //this._waiting_confirmation = false; + this.boxcolor = "green"; + var video = this._video; + if (!video) { + video = document.createElement("video"); + video.autoplay = true; + video.srcObject = localMediaStream; + this._video = video; + //document.body.appendChild( video ); //debug + //when video info is loaded (size and so) + video.onloadedmetadata = function(e) { + // Ready to go. Do some stuff. + LGraphTextureWebcam.is_webcam_open = true; + console.log(e); + }; + } + this.trigger("stream_ready", video); + }; - //render gamepad state? - var la = this._left_axis; - var ra = this._right_axis; - ctx.strokeStyle = "#88A"; - ctx.strokeRect( - (la[0] + 1) * 0.5 * this.size[0] - 4, - (la[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - ctx.strokeStyle = "#8A8"; - ctx.strokeRect( - (ra[0] + 1) * 0.5 * this.size[0] - 4, - (ra[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - var h = this.size[1] / this._current_buttons.length; - ctx.fillStyle = "#AEB"; - for (var i = 0; i < this._current_buttons.length; ++i) { - if (this._current_buttons[i]) { - ctx.fillRect(0, h * i, 6, h); - } - } - }; + LGraphTextureWebcam.prototype.onPropertyChanged = function( + name, + value + ) { + if (name == "facingMode") { + this.properties.facingMode = value; + this.closeStream(); + this.openStream(); + } + }; - GamepadInput.prototype.onGetOutputs = function() { - return [ - ["left_axis", "vec2"], - ["right_axis", "vec2"], - ["left_x_axis", "number"], - ["left_y_axis", "number"], - ["right_x_axis", "number"], - ["right_y_axis", "number"], - ["trigger_left", "number"], - ["trigger_right", "number"], - ["a_button", "number"], - ["b_button", "number"], - ["x_button", "number"], - ["y_button", "number"], - ["lb_button", "number"], - ["rb_button", "number"], - ["ls_button", "number"], - ["rs_button", "number"], - ["start_button", "number"], - ["back_button", "number"], - ["a_button_event", LiteGraph.EVENT ], - ["b_button_event", LiteGraph.EVENT ], - ["x_button_event", LiteGraph.EVENT ], - ["y_button_event", LiteGraph.EVENT ], - ["lb_button_event", LiteGraph.EVENT ], - ["rb_button_event", LiteGraph.EVENT ], - ["ls_button_event", LiteGraph.EVENT ], - ["rs_button_event", LiteGraph.EVENT ], - ["start_button_event", LiteGraph.EVENT ], - ["back_button_event", LiteGraph.EVENT ], - ["hat_left", "number"], - ["hat_right", "number"], - ["hat_up", "number"], - ["hat_down", "number"], - ["hat", "number"], - ["button_pressed", LiteGraph.EVENT] - ]; - }; + LGraphTextureWebcam.prototype.onRemoved = function() { + if (!this._webcam_stream) { + return; + } - LiteGraph.registerNodeType("input/gamepad", GamepadInput); + var tracks = this._webcam_stream.getTracks(); + if (tracks.length) { + for (var i = 0; i < tracks.length; ++i) { + tracks[i].stop(); + } + } -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - //Converter - function Converter() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [80, 30]; - } - - Converter.title = "Converter"; - Converter.desc = "type A to type B"; - - Converter.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - - var result = null; - switch (output.name) { - case "number": - result = v.length ? v[0] : parseFloat(v); - break; - case "vec2": - case "vec3": - case "vec4": - var result = null; - var count = 1; - switch (output.name) { - case "vec2": - count = 2; - break; - case "vec3": - count = 3; - break; - case "vec4": - count = 4; - break; - } - - var result = new Float32Array(count); - if (v.length) { - for ( - var j = 0; - j < v.length && j < result.length; - j++ - ) { - result[j] = v[j]; - } - } else { - result[0] = parseFloat(v); - } - break; - } - this.setOutputData(i, result); - } - } - }; - - Converter.prototype.onGetOutputs = function() { - return [ - ["number", "number"], - ["vec2", "vec2"], - ["vec3", "vec3"], - ["vec4", "vec4"] - ]; - }; - - LiteGraph.registerNodeType("math/converter", Converter); - - //Bypass - function Bypass() { - this.addInput("in"); - this.addOutput("out"); - this.size = [80, 30]; - } - - Bypass.title = "Bypass"; - Bypass.desc = "removes the type"; - - Bypass.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/bypass", Bypass); - - function ToNumber() { - this.addInput("in"); - this.addOutput("out"); - } - - ToNumber.title = "to Number"; - ToNumber.desc = "Cast to number"; - - ToNumber.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, Number(v)); - }; - - LiteGraph.registerNodeType("math/to_number", ToNumber); - - function MathRange() { - this.addInput("in", "number", { locked: true }); - this.addOutput("out", "number", { locked: true }); - this.addOutput("clamped", "number", { locked: true }); - - this.addProperty("in", 0); - this.addProperty("in_min", 0); - this.addProperty("in_max", 1); - this.addProperty("out_min", 0); - this.addProperty("out_max", 1); - - this.size = [120, 50]; - } - - MathRange.title = "Range"; - MathRange.desc = "Convert a number from one range to another"; - - MathRange.prototype.getTitle = function() { - if (this.flags.collapsed) { - return (this._last_v || 0).toFixed(2); - } - return this.title; - }; - - MathRange.prototype.onExecute = function() { - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - this.properties[input.name] = v; - } - } - - var v = this.properties["in"]; - if (v === undefined || v === null || v.constructor !== Number) { - v = 0; - } - - var in_min = this.properties.in_min; - var in_max = this.properties.in_max; - var out_min = this.properties.out_min; - var out_max = this.properties.out_max; - /* - if( in_min > in_max ) - { - in_min = in_max; - in_max = this.properties.in_min; - } - if( out_min > out_max ) - { - out_min = out_max; - out_max = this.properties.out_min; - } - */ - - this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; - this.setOutputData(0, this._last_v); - this.setOutputData(1, clamp( this._last_v, out_min, out_max )); - }; - - MathRange.prototype.onDrawBackground = function(ctx) { - //show the current value - if (this._last_v) { - this.outputs[0].label = this._last_v.toFixed(3); - } else { - this.outputs[0].label = "?"; - } - }; - - MathRange.prototype.onGetInputs = function() { - return [ - ["in_min", "number"], - ["in_max", "number"], - ["out_min", "number"], - ["out_max", "number"] - ]; - }; - - LiteGraph.registerNodeType("math/range", MathRange); - - function MathRand() { - this.addOutput("value", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.size = [80, 30]; - } - - MathRand.title = "Rand"; - MathRand.desc = "Random number"; - - MathRand.prototype.onExecute = function() { - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - this.properties[input.name] = v; - } - } - - var min = this.properties.min; - var max = this.properties.max; - this._last_v = Math.random() * (max - min) + min; - this.setOutputData(0, this._last_v); - }; - - MathRand.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - MathRand.prototype.onGetInputs = function() { - return [["min", "number"], ["max", "number"]]; - }; - - LiteGraph.registerNodeType("math/rand", MathRand); - - //basic continuous noise - function MathNoise() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.addProperty("smooth", true); - this.addProperty("seed", 0); - this.addProperty("octaves", 1); - this.addProperty("persistence", 0.8); - this.addProperty("speed", 1); - this.size = [90, 30]; - } - - MathNoise.title = "Noise"; - MathNoise.desc = "Random number with temporal continuity"; - MathNoise.data = null; - - MathNoise.getValue = function(f, smooth) { - if (!MathNoise.data) { - MathNoise.data = new Float32Array(1024); - for (var i = 0; i < MathNoise.data.length; ++i) { - MathNoise.data[i] = Math.random(); - } - } - f = f % 1024; - if (f < 0) { - f += 1024; - } - var f_min = Math.floor(f); - var f = f - f_min; - var r1 = MathNoise.data[f_min]; - var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; - if (smooth) { - f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); - } - return r1 * (1 - f) + r2 * f; - }; - - MathNoise.prototype.onExecute = function() { - var f = this.getInputData(0) || 0; - var iterations = this.properties.octaves || 1; - var r = 0; - var amp = 1; - var seed = this.properties.seed || 0; - f += seed; - var speed = this.properties.speed || 1; - var total_amp = 0; - for(var i = 0; i < iterations; ++i) - { - r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; - total_amp += amp; - amp *= this.properties.persistence; - if(amp < 0.001) - break; - } - r /= total_amp; - var min = this.properties.min; - var max = this.properties.max; - this._last_v = r * (max - min) + min; - this.setOutputData(0, this._last_v); - }; - - MathNoise.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - LiteGraph.registerNodeType("math/noise", MathNoise); - - //generates spikes every random time - function MathSpikes() { - this.addOutput("out", "number"); - this.addProperty("min_time", 1); - this.addProperty("max_time", 2); - this.addProperty("duration", 0.2); - this.size = [90, 30]; - this._remaining_time = 0; - this._blink_time = 0; - } - - MathSpikes.title = "Spikes"; - MathSpikes.desc = "spike every random time"; - - MathSpikes.prototype.onExecute = function() { - var dt = this.graph.elapsed_time; //in secs - - this._remaining_time -= dt; - this._blink_time -= dt; - - var v = 0; - if (this._blink_time > 0) { - var f = this._blink_time / this.properties.duration; - v = 1 / (Math.pow(f * 8 - 4, 4) + 1); - } - - if (this._remaining_time < 0) { - this._remaining_time = - Math.random() * - (this.properties.max_time - this.properties.min_time) + - this.properties.min_time; - this._blink_time = this.properties.duration; - this.boxcolor = "#FFF"; - } else { - this.boxcolor = "#000"; - } - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/spikes", MathSpikes); - - //Math clamp - function MathClamp() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("min", 0); - this.addProperty("max", 1); - } - - MathClamp.title = "Clamp"; - MathClamp.desc = "Clamp number between min and max"; - //MathClamp.filter = "shader"; - - MathClamp.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - v = Math.max(this.properties.min, v); - v = Math.min(this.properties.max, v); - this.setOutputData(0, v); - }; - - MathClamp.prototype.getCode = function(lang) { - var code = ""; - if (this.isInputConnected(0)) { - code += - "clamp({{0}}," + - this.properties.min + - "," + - this.properties.max + - ")"; - } - return code; - }; - - LiteGraph.registerNodeType("math/clamp", MathClamp); - - //Math ABS - function MathLerp() { - this.properties = { f: 0.5 }; - this.addInput("A", "number"); - this.addInput("B", "number"); - - this.addOutput("out", "number"); - } - - MathLerp.title = "Lerp"; - MathLerp.desc = "Linear Interpolation"; - - MathLerp.prototype.onExecute = function() { - var v1 = this.getInputData(0); - if (v1 == null) { - v1 = 0; - } - var v2 = this.getInputData(1); - if (v2 == null) { - v2 = 0; - } - - var f = this.properties.f; - - var _f = this.getInputData(2); - if (_f !== undefined) { - f = _f; - } - - this.setOutputData(0, v1 * (1 - f) + v2 * f); - }; - - MathLerp.prototype.onGetInputs = function() { - return [["f", "number"]]; - }; - - LiteGraph.registerNodeType("math/lerp", MathLerp); - - //Math ABS - function MathAbs() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathAbs.title = "Abs"; - MathAbs.desc = "Absolute"; - - MathAbs.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, Math.abs(v)); - }; - - LiteGraph.registerNodeType("math/abs", MathAbs); - - //Math Floor - function MathFloor() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathFloor.title = "Floor"; - MathFloor.desc = "Floor number to remove fractional part"; - - MathFloor.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, Math.floor(v)); - }; - - LiteGraph.registerNodeType("math/floor", MathFloor); - - //Math frac - function MathFrac() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathFrac.title = "Frac"; - MathFrac.desc = "Returns fractional part"; - - MathFrac.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, v % 1); - }; - - LiteGraph.registerNodeType("math/frac", MathFrac); - - //Math Floor - function MathSmoothStep() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.properties = { A: 0, B: 1 }; - } - - MathSmoothStep.title = "Smoothstep"; - MathSmoothStep.desc = "Smoothstep"; - - MathSmoothStep.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v === undefined) { - return; - } - - var edge0 = this.properties.A; - var edge1 = this.properties.B; - - // Scale, bias and saturate x to 0..1 range - v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0); - // Evaluate polynomial - v = v * v * (3 - 2 * v); - - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep); - - //Math scale - function MathScale() { - this.addInput("in", "number", { label: "" }); - this.addOutput("out", "number", { label: "" }); - this.size = [80, 30]; - this.addProperty("factor", 1); - } - - MathScale.title = "Scale"; - MathScale.desc = "v * factor"; - - MathScale.prototype.onExecute = function() { - var value = this.getInputData(0); - if (value != null) { - this.setOutputData(0, value * this.properties.factor); - } - }; - - LiteGraph.registerNodeType("math/scale", MathScale); - - //Gate - function Gate() { - this.addInput("v","boolean"); - this.addInput("A"); - this.addInput("B"); - this.addOutput("out"); - } - - Gate.title = "Gate"; - Gate.desc = "if v is true, then outputs A, otherwise B"; - - Gate.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, this.getInputData( v ? 1 : 2 )); - }; - - LiteGraph.registerNodeType("math/gate", Gate); - - - //Math Average - function MathAverageFilter() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("samples", 10); - this._values = new Float32Array(10); - this._current = 0; - } - - MathAverageFilter.title = "Average"; - MathAverageFilter.desc = "Average Filter"; - - MathAverageFilter.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - - var num_samples = this._values.length; - - this._values[this._current % num_samples] = v; - this._current += 1; - if (this._current > num_samples) { - this._current = 0; - } - - var avr = 0; - for (var i = 0; i < num_samples; ++i) { - avr += this._values[i]; - } - - this.setOutputData(0, avr / num_samples); - }; - - MathAverageFilter.prototype.onPropertyChanged = function(name, value) { - if (value < 1) { - value = 1; - } - this.properties.samples = Math.round(value); - var old = this._values; - - this._values = new Float32Array(this.properties.samples); - if (old.length <= this._values.length) { - this._values.set(old); - } else { - this._values.set(old.subarray(0, this._values.length)); - } - }; - - LiteGraph.registerNodeType("math/average", MathAverageFilter); - - //Math - function MathTendTo() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("factor", 0.1); - this.size = [80, 30]; - this._value = null; - } - - MathTendTo.title = "TendTo"; - MathTendTo.desc = "moves the output value always closer to the input"; - - MathTendTo.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - var f = this.properties.factor; - if (this._value == null) { - this._value = v; - } else { - this._value = this._value * (1 - f) + v * f; - } - this.setOutputData(0, this._value); - }; - - LiteGraph.registerNodeType("math/tendTo", MathTendTo); - - //Math operation - function MathOperation() { - this.addInput("A", "number,array,object"); - this.addInput("B", "number"); - this.addOutput("=", "number"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "+", "enum", { values: MathOperation.values }); - this._func = MathOperation.funcs[this.properties.OP]; - this._result = []; //only used for arrays - } - - MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; - MathOperation.funcs = { - "+": function(A,B) { return A + B; }, - "-": function(A,B) { return A - B; }, - "x": function(A,B) { return A * B; }, - "X": function(A,B) { return A * B; }, - "*": function(A,B) { return A * B; }, - "/": function(A,B) { return A / B; }, - "%": function(A,B) { return A % B; }, - "^": function(A,B) { return Math.pow(A, B); }, - "max": function(A,B) { return Math.max(A, B); }, - "min": function(A,B) { return Math.min(A, B); } - }; - - MathOperation.title = "Operation"; - MathOperation.desc = "Easy math operators"; - MathOperation["@OP"] = { - type: "enum", - title: "operation", - values: MathOperation.values - }; - MathOperation.size = [100, 60]; - - MathOperation.prototype.getTitle = function() { - if(this.properties.OP == "max" || this.properties.OP == "min") - return this.properties.OP + "(A,B)"; - return "A " + this.properties.OP + " B"; - }; - - MathOperation.prototype.setValue = function(v) { - if (typeof v == "string") { - v = parseFloat(v); - } - this.properties["value"] = v; - }; - - MathOperation.prototype.onPropertyChanged = function(name, value) - { - if (name != "OP") - return; - this._func = MathOperation.funcs[this.properties.OP]; - if(!this._func) - { - console.warn("Unknown operation: " + this.properties.OP); - this._func = function(A) { return A; }; - } - } - - MathOperation.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - if ( A != null ) { - if( A.constructor === Number ) - this.properties["A"] = A; - } else { - A = this.properties["A"]; - } - - if (B != null) { - this.properties["B"] = B; - } else { - B = this.properties["B"]; - } - - var func = MathOperation.funcs[this.properties.OP]; - - var result; - if(A.constructor === Number) - { - result = 0; - result = func(A,B); - } - else if(A.constructor === Array) - { - result = this._result; - result.length = A.length; - for(var i = 0; i < A.length; ++i) - result[i] = func(A[i],B); - } - else - { - result = {}; - for(var i in A) - result[i] = func(A[i],B); - } - this.setOutputData(0, result); - }; - - MathOperation.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - ctx.font = "40px Arial"; - ctx.fillStyle = "#666"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.OP, - this.size[0] * 0.5, - (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 - ); - ctx.textAlign = "left"; - }; - - LiteGraph.registerNodeType("math/operation", MathOperation); - - LiteGraph.registerSearchboxExtra("math/operation", "MAX", { - properties: {OP:"max"}, - title: "MAX()" - }); - - LiteGraph.registerSearchboxExtra("math/operation", "MIN", { - properties: {OP:"min"}, - title: "MIN()" - }); - - - //Math compare - function MathCompare() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("A==B", "boolean"); - this.addOutput("A!=B", "boolean"); - this.addProperty("A", 0); - this.addProperty("B", 0); - } - - MathCompare.title = "Compare"; - MathCompare.desc = "compares between two values"; - - MathCompare.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - if (A !== undefined) { - this.properties["A"] = A; - } else { - A = this.properties["A"]; - } - - if (B !== undefined) { - this.properties["B"] = B; - } else { - B = this.properties["B"]; - } - - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - var value; - switch (output.name) { - case "A==B": - value = A == B; - break; - case "A!=B": - value = A != B; - break; - case "A>B": - value = A > B; - break; - case "A=B": - value = A >= B; - break; - } - this.setOutputData(i, value); - } - }; - - MathCompare.prototype.onGetOutputs = function() { - return [ - ["A==B", "boolean"], - ["A!=B", "boolean"], - ["A>B", "boolean"], - ["A=B", "boolean"], - ["A<=B", "boolean"] - ]; - }; - - LiteGraph.registerNodeType("math/compare", MathCompare); - - LiteGraph.registerSearchboxExtra("math/compare", "==", { - outputs: [["A==B", "boolean"]], - title: "A==B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "!=", { - outputs: [["A!=B", "boolean"]], - title: "A!=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", ">", { - outputs: [["A>B", "boolean"]], - title: "A>B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<", { - outputs: [["A=", { - outputs: [["A>=B", "boolean"]], - title: "A>=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<=", { - outputs: [["A<=B", "boolean"]], - title: "A<=B" - }); - - function MathCondition() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", ">", "enum", { values: MathCondition.values }); - this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); - - this.size = [80, 60]; - } - - MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; - MathCondition["@OP"] = { - type: "enum", - title: "operation", - values: MathCondition.values - }; - - MathCondition.title = "Condition"; - MathCondition.desc = "evaluates condition between A and B"; - - MathCondition.prototype.getTitle = function() { - return "A " + this.properties.OP + " B"; - }; - - MathCondition.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A === undefined) { - A = this.properties.A; - } else { - this.properties.A = A; - } - - var B = this.getInputData(1); - if (B === undefined) { - B = this.properties.B; - } else { - this.properties.B = B; - } - - var result = true; - switch (this.properties.OP) { - case ">": - result = A > B; - break; - case "<": - result = A < B; - break; - case "==": - result = A == B; - break; - case "!=": - result = A != B; - break; - case "<=": - result = A <= B; - break; - case ">=": - result = A >= B; - break; - case "||": - result = A || B; - break; - case "&&": - result = A && B; - break; - } - - this.setOutputData(0, result); - this.setOutputData(1, !result); - }; - - LiteGraph.registerNodeType("math/condition", MathCondition); - - - function MathBranch() { - this.addInput("in", 0); - this.addInput("cond", "boolean"); - this.addOutput("true", 0); - this.addOutput("false", 0); - this.size = [80, 60]; - } - - MathBranch.title = "Branch"; - MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; - - MathBranch.prototype.onExecute = function() { - var V = this.getInputData(0); - var cond = this.getInputData(1); - - if(cond) - { - this.setOutputData(0, V); - this.setOutputData(1, null); - } - else - { - this.setOutputData(0, null); - this.setOutputData(1, V); - } - } - - LiteGraph.registerNodeType("math/branch", MathBranch); - - - function MathAccumulate() { - this.addInput("inc", "number"); - this.addOutput("total", "number"); - this.addProperty("increment", 1); - this.addProperty("value", 0); - } - - MathAccumulate.title = "Accumulate"; - MathAccumulate.desc = "Increments a value every time"; - - MathAccumulate.prototype.onExecute = function() { - if (this.properties.value === null) { - this.properties.value = 0; - } - - var inc = this.getInputData(0); - if (inc !== null) { - this.properties.value += inc; - } else { - this.properties.value += this.properties.increment; - } - this.setOutputData(0, this.properties.value); - }; - - LiteGraph.registerNodeType("math/accumulate", MathAccumulate); - - //Math Trigonometry - function MathTrigonometry() { - this.addInput("v", "number"); - this.addOutput("sin", "number"); - - this.addProperty("amplitude", 1); - this.addProperty("offset", 0); - this.bgImageUrl = "nodes/imgs/icon-sin.png"; - } - - MathTrigonometry.title = "Trigonometry"; - MathTrigonometry.desc = "Sin Cos Tan"; - //MathTrigonometry.filter = "shader"; - - MathTrigonometry.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - var amplitude = this.properties["amplitude"]; - var slot = this.findInputSlot("amplitude"); - if (slot != -1) { - amplitude = this.getInputData(slot); - } - var offset = this.properties["offset"]; - slot = this.findInputSlot("offset"); - if (slot != -1) { - offset = this.getInputData(slot); - } - - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var value; - switch (output.name) { - case "sin": - value = Math.sin(v); - break; - case "cos": - value = Math.cos(v); - break; - case "tan": - value = Math.tan(v); - break; - case "asin": - value = Math.asin(v); - break; - case "acos": - value = Math.acos(v); - break; - case "atan": - value = Math.atan(v); - break; - } - this.setOutputData(i, amplitude * value + offset); - } - }; - - MathTrigonometry.prototype.onGetInputs = function() { - return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; - }; - - MathTrigonometry.prototype.onGetOutputs = function() { - return [ - ["sin", "number"], - ["cos", "number"], - ["tan", "number"], - ["asin", "number"], - ["acos", "number"], - ["atan", "number"] - ]; - }; - - LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry); - - LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { - outputs: [["sin", "number"]], - title: "SIN()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { - outputs: [["cos", "number"]], - title: "COS()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { - outputs: [["tan", "number"]], - title: "TAN()" - }); - - //math library for safe math operations without eval - function MathFormula() { - this.addInput("x", "number"); - this.addInput("y", "number"); - this.addOutput("", "number"); - this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; - this.code_widget = this.addWidget( - "text", - "F(x,y)", - this.properties.formula, - function(v, canvas, node) { - node.properties.formula = v; - } - ); - this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { - LiteGraph.allow_scripts = v; - }); - this._func = null; - } - - MathFormula.title = "Formula"; - MathFormula.desc = "Compute formula"; - MathFormula.size = [160, 100]; - - MathAverageFilter.prototype.onPropertyChanged = function(name, value) { - if (name == "formula") { - this.code_widget.value = value; - } - }; - - MathFormula.prototype.onExecute = function() { - if (!LiteGraph.allow_scripts) { - return; - } - - var x = this.getInputData(0); - var y = this.getInputData(1); - if (x != null) { - this.properties["x"] = x; - } else { - x = this.properties["x"]; - } - - if (y != null) { - this.properties["y"] = y; - } else { - y = this.properties["y"]; - } - - var f = this.properties["formula"]; - - var value; - try { - if (!this._func || this._func_code != this.properties.formula) { - this._func = new Function( - "x", - "y", - "TIME", - "return " + this.properties.formula - ); - this._func_code = this.properties.formula; - } - value = this._func(x, y, this.graph.globaltime); - this.boxcolor = null; - } catch (err) { - this.boxcolor = "red"; - } - this.setOutputData(0, value); - }; - - MathFormula.prototype.getTitle = function() { - return this._func_code || "Formula"; - }; - - MathFormula.prototype.onDrawBackground = function() { - var f = this.properties["formula"]; - if (this.outputs && this.outputs.length) { - this.outputs[0].label = f; - } - }; - - LiteGraph.registerNodeType("math/formula", MathFormula); - - function Math3DVec2ToXY() { - this.addInput("vec2", "vec2"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - } - - Math3DVec2ToXY.title = "Vec2->XY"; - Math3DVec2ToXY.desc = "vector 2 to components"; - - Math3DVec2ToXY.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - }; - - LiteGraph.registerNodeType("math3d/vec2-to-xy", Math3DVec2ToXY); - - function Math3DXYToVec2() { - this.addInputs([["x", "number"], ["y", "number"]]); - this.addOutput("vec2", "vec2"); - this.properties = { x: 0, y: 0 }; - this._data = new Float32Array(2); - } - - Math3DXYToVec2.title = "XY->Vec2"; - Math3DXYToVec2.desc = "components to vector2"; - - Math3DXYToVec2.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - - var data = this._data; - data[0] = x; - data[1] = y; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); - - function Math3DVec3ToXYZ() { - this.addInput("vec3", "vec3"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - } - - Math3DVec3ToXYZ.title = "Vec3->XYZ"; - Math3DVec3ToXYZ.desc = "vector 3 to components"; - - Math3DVec3ToXYZ.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - }; - - LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); - - function Math3DXYZToVec3() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); - this.addOutput("vec3", "vec3"); - this.properties = { x: 0, y: 0, z: 0 }; - this._data = new Float32Array(3); - } - - Math3DXYZToVec3.title = "XYZ->Vec3"; - Math3DXYZToVec3.desc = "components to vector3"; - - Math3DXYZToVec3.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); - - function Math3DVec4ToXYZW() { - this.addInput("vec4", "vec4"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - this.addOutput("w", "number"); - } - - Math3DVec4ToXYZW.title = "Vec4->XYZW"; - Math3DVec4ToXYZW.desc = "vector 4 to components"; - - Math3DVec4ToXYZW.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - this.setOutputData(3, v[3]); - }; - - LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); - - function Math3DXYZWToVec4() { - this.addInputs([ - ["x", "number"], - ["y", "number"], - ["z", "number"], - ["w", "number"] - ]); - this.addOutput("vec4", "vec4"); - this.properties = { x: 0, y: 0, z: 0, w: 0 }; - this._data = new Float32Array(4); - } - - Math3DXYZWToVec4.title = "XYZW->Vec4"; - Math3DXYZWToVec4.desc = "components to vector4"; - - Math3DXYZWToVec4.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - var w = this.getInputData(3); - if (w == null) { - w = this.properties.w; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - data[3] = w; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); - -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; + this._webcam_stream = null; + this._video = null; + }; + LGraphTextureWebcam.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed || this.size[1] <= 20) { + return; + } - function Math3DMat4() - { - this.addInput("T", "vec3"); - this.addInput("R", "vec3"); - this.addInput("S", "vec3"); - this.addOutput("mat4", "mat4"); - this.properties = { - "T":[0,0,0], - "R":[0,0,0], - "S":[1,1,1], - R_in_degrees: true - }; - this._result = mat4.create(); - this._must_update = true; - } + if (!this._video) { + return; + } - Math3DMat4.title = "mat4"; - Math3DMat4.temp_quat = new Float32Array([0,0,0,1]); - Math3DMat4.temp_mat4 = new Float32Array(16); - Math3DMat4.temp_vec3 = new Float32Array(3); + //render to graph canvas + ctx.save(); + if (!ctx.webgl) { + //reverse image + ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); + } else { + if (this._video_texture) { + ctx.drawImage( + this._video_texture, + 0, + 0, + this.size[0], + this.size[1] + ); + } + } + ctx.restore(); + }; - Math3DMat4.prototype.onPropertyChanged = function(name, value) - { - this._must_update = true; - } + LGraphTextureWebcam.prototype.onExecute = function() { + if (this._webcam_stream == null && !this._waiting_confirmation) { + this.openStream(); + } - Math3DMat4.prototype.onExecute = function() - { - var M = this._result; - var Q = Math3DMat4.temp_quat; - var temp_mat4 = Math3DMat4.temp_mat4; - var temp_vec3 = Math3DMat4.temp_vec3; + if (!this._video || !this._video.videoWidth) { + return; + } - var T = this.getInputData(0); - var R = this.getInputData(1); - var S = this.getInputData(2); + var width = this._video.videoWidth; + var height = this._video.videoHeight; - if( this._must_update || T || R || S ) - { - T = T || this.properties.T; - R = R || this.properties.R; - S = S || this.properties.S; - mat4.identity( M ); - mat4.translate( M, M, T ); - if(this.properties.R_in_degrees) - { - temp_vec3.set( R ); - vec3.scale(temp_vec3,temp_vec3,DEG2RAD); - quat.fromEuler( Q, temp_vec3 ); + var temp = this._video_texture; + if (!temp || temp.width != width || temp.height != height) { + this._video_texture = new GL.Texture(width, height, { + format: gl.RGB, + filter: gl.LINEAR + }); + } + + this._video_texture.uploadImage(this._video); + this._video_texture.version = ++this.version; + + if (this.properties.texture_name) { + var container = LGraphTexture.getTexturesContainer(); + container[this.properties.texture_name] = this._video_texture; + } + + this.setOutputData(0, this._video_texture); + for (var i = 1; i < this.outputs.length; ++i) { + if (!this.outputs[i]) { + continue; + } + switch (this.outputs[i].name) { + case "width": + this.setOutputData(i, this._video.videoWidth); + break; + case "height": + this.setOutputData(i, this._video.videoHeight); + break; } - else - quat.fromEuler( Q, R ); - mat4.fromQuat( temp_mat4, Q ); - mat4.multiply( M, M, temp_mat4 ); - mat4.scale( M, M, S ); } + }; - this.setOutputData(0, M); + LGraphTextureWebcam.prototype.onGetOutputs = function() { + return [ + ["width", "number"], + ["height", "number"], + ["stream_ready", LiteGraph.EVENT], + ["stream_closed", LiteGraph.EVENT], + ["stream_error", LiteGraph.EVENT] + ]; + }; + + LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam); + + //from https://github.com/spite/Wagner + function LGraphLensFX() { + this.addInput("in", "Texture"); + this.addInput("f", "number"); + this.addOutput("out", "Texture"); + this.properties = { + enabled: true, + factor: 1, + precision: LGraphTexture.LOW + }; + + this._uniforms = { u_texture: 0, u_factor: 1 }; } - LiteGraph.registerNodeType("math3d/mat4", Math3DMat4); + LGraphLensFX.title = "Lens FX"; + LGraphLensFX.desc = "distortion and chromatic aberration"; - //Math 3D operation - function Math3DOperation() { - this.addInput("A", "number,vec3"); - this.addInput("B", "number,vec3"); - this.addOutput("=", "number,vec3"); - this.addProperty("OP", "+", "enum", { values: Math3DOperation.values }); - this._result = vec3.create(); - } + LGraphLensFX.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; - Math3DOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min","dot","cross"]; + LGraphLensFX.prototype.onGetInputs = function() { + return [["enabled", "boolean"]]; + }; - LiteGraph.registerSearchboxExtra("math3d/operation", "CROSS()", { - properties: {"OP":"cross"}, - title: "CROSS()" - }); + LGraphLensFX.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex) { + return; + } - LiteGraph.registerSearchboxExtra("math3d/operation", "DOT()", { - properties: {"OP":"dot"}, - title: "DOT()" - }); + if (!this.isOutputConnected(0)) { + return; + } //saves work - Math3DOperation.title = "Operation"; - Math3DOperation.desc = "Easy math 3D operators"; - Math3DOperation["@OP"] = { - type: "enum", - title: "operation", - values: Math3DOperation.values - }; - Math3DOperation.size = [100, 60]; + if ( + this.properties.precision === LGraphTexture.PASS_THROUGH || + this.getInputOrProperty("enabled") === false + ) { + this.setOutputData(0, tex); + return; + } - Math3DOperation.prototype.getTitle = function() { - if(this.properties.OP == "max" || this.properties.OP == "min" ) - return this.properties.OP + "(A,B)"; - return "A " + this.properties.OP + " B"; - }; + var temp = this._temp_texture; + if ( + !temp || + temp.width != tex.width || + temp.height != tex.height || + temp.type != tex.type + ) { + temp = this._temp_texture = new GL.Texture( + tex.width, + tex.height, + { type: tex.type, format: gl.RGBA, filter: gl.LINEAR } + ); + } + + var shader = LGraphLensFX._shader; + if (!shader) { + shader = LGraphLensFX._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphLensFX.pixel_shader + ); + } + + var factor = this.getInputData(1); + if (factor == null) { + factor = this.properties.factor; + } - Math3DOperation.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - if(A == null || B == null) - return; - if(A.constructor === Number) - A = [A,A,A]; - if(B.constructor === Number) - B = [B,B,B]; + var uniforms = this._uniforms; + uniforms.u_factor = factor; - var result = this._result; - switch (this.properties.OP) { - case "+": - result = vec3.add(result,A,B); - break; - case "-": - result = vec3.sub(result,A,B); - break; - case "x": - case "X": - case "*": - result = vec3.mul(result,A,B); - break; - case "/": - result = vec3.div(result,A,B); - break; - case "%": - result[0] = A[0]%B[0]; - result[1] = A[1]%B[1]; - result[2] = A[2]%B[2]; - break; - case "^": - result[0] = Math.pow(A[0],B[0]); - result[1] = Math.pow(A[1],B[1]); - result[2] = Math.pow(A[2],B[2]); - break; - case "max": - result[0] = Math.max(A[0],B[0]); - result[1] = Math.max(A[1],B[1]); - result[2] = Math.max(A[2],B[2]); - break; - case "min": - result[0] = Math.min(A[0],B[0]); - result[1] = Math.min(A[1],B[1]); - result[2] = Math.min(A[2],B[2]); - case "dot": - result = vec3.dot(A,B); - break; - case "cross": - vec3.cross(result,A,B); - break; - default: - console.warn("Unknown operation: " + this.properties.OP); - } - this.setOutputData(0, result); - }; + //apply shader + gl.disable(gl.DEPTH_TEST); + temp.drawTo(function() { + tex.bind(0); + shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad()); + }); - Math3DOperation.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } + this.setOutputData(0, temp); + }; - ctx.font = "40px Arial"; - ctx.fillStyle = "#666"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.OP, - this.size[0] * 0.5, - (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 - ); - ctx.textAlign = "left"; - }; + LGraphLensFX.pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_factor;\n\ + vec2 barrelDistortion(vec2 coord, float amt) {\n\ + vec2 cc = coord - 0.5;\n\ + float dist = dot(cc, cc);\n\ + return coord + cc * dist * amt;\n\ + }\n\ + \n\ + float sat( float t )\n\ + {\n\ + return clamp( t, 0.0, 1.0 );\n\ + }\n\ + \n\ + float linterp( float t ) {\n\ + return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\ + }\n\ + \n\ + float remap( float t, float a, float b ) {\n\ + return sat( (t - a) / (b - a) );\n\ + }\n\ + \n\ + vec4 spectrum_offset( float t ) {\n\ + vec4 ret;\n\ + float lo = step(t,0.5);\n\ + float hi = 1.0-lo;\n\ + float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\ + ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\ + \n\ + return pow( ret, vec4(1.0/2.2) );\n\ + }\n\ + \n\ + const float max_distort = 2.2;\n\ + const int num_iter = 12;\n\ + const float reci_num_iter_f = 1.0 / float(num_iter);\n\ + \n\ + void main()\n\ + { \n\ + vec2 uv=v_coord;\n\ + vec4 sumcol = vec4(0.0);\n\ + vec4 sumw = vec4(0.0); \n\ + for ( int i=0; i range_max) - { - range_min = range_max; - range_max = this.properties.range_min; - } + temp.drawTo(function() { + shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad()); + }); - if(target_min > target_max) - { - target_min = target_max; - target_max = this.properties.target_min; - } - */ + this.setOutputData(0, temp); + }; - for(var i = 0; i < 3; ++i) - { - var r = range_max[i] - range_min[i]; - this._clamped[i] = clamp( this._value[i], range_min[i], range_max[i] ); - if(r == 0) - { - this._value[i] = (target_min[i] + target_max[i]) * 0.5; - continue; - } + LGraphTexturePerlin.pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform vec2 u_offset;\n\ + uniform float u_scale;\n\ + uniform float u_persistence;\n\ + uniform int u_octaves;\n\ + uniform float u_amplitude;\n\ + uniform vec2 u_viewport;\n\ + uniform float u_seed;\n\ + #define M_PI 3.14159265358979323846\n\ + \n\ + float rand(vec2 c){ return fract(sin(dot(c.xy ,vec2( 12.9898 + u_seed,78.233 + u_seed))) * 43758.5453); }\n\ + \n\ + float noise(vec2 p, float freq ){\n\ + float unit = u_viewport.x/freq;\n\ + vec2 ij = floor(p/unit);\n\ + vec2 xy = mod(p,unit)/unit;\n\ + //xy = 3.*xy*xy-2.*xy*xy*xy;\n\ + xy = .5*(1.-cos(M_PI*xy));\n\ + float a = rand((ij+vec2(0.,0.)));\n\ + float b = rand((ij+vec2(1.,0.)));\n\ + float c = rand((ij+vec2(0.,1.)));\n\ + float d = rand((ij+vec2(1.,1.)));\n\ + float x1 = mix(a, b, xy.x);\n\ + float x2 = mix(c, d, xy.x);\n\ + return mix(x1, x2, xy.y);\n\ + }\n\ + \n\ + float pNoise(vec2 p, int res){\n\ + float persistance = u_persistence;\n\ + float n = 0.;\n\ + float normK = 0.;\n\ + float f = 4.;\n\ + float amp = 1.0;\n\ + int iCount = 0;\n\ + for (int i = 0; i<50; i++){\n\ + n+=amp*noise(p, f);\n\ + f*=2.;\n\ + normK+=amp;\n\ + amp*=persistance;\n\ + if (iCount >= res)\n\ + break;\n\ + iCount++;\n\ + }\n\ + float nf = n/normK;\n\ + return nf*nf*nf*nf;\n\ + }\n\ + void main() {\n\ + vec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\n\ + vec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\n\ + gl_FragColor = color;\n\ + }"; - var n = (this._value[i] - range_min[i]) / r; - if(this.properties.clamp) - n = clamp(n,0,1); - var t = target_max[i] - target_min[i]; - this._value[i] = target_min[i] + n * t; - } + LiteGraph.registerNodeType("texture/perlin", LGraphTexturePerlin); - this.setOutputData(0,this._value); - this.setOutputData(1,this._clamped); - }; + function LGraphTextureCanvas2D() { + this.addInput("v"); + this.addOutput("out", "Texture"); + this.properties = { + code: LGraphTextureCanvas2D.default_code, + width: 512, + height: 512, + clear: true, + precision: LGraphTexture.DEFAULT, + use_html_canvas: false + }; + this._func = null; + this._temp_texture = null; + this.compileCode(); + } - LiteGraph.registerNodeType("math3d/remap_range", Math3DRemapRange); + LGraphTextureCanvas2D.title = "Canvas2D"; + LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport."; + LGraphTextureCanvas2D.help = "Set width and height to 0 to match viewport size."; + LGraphTextureCanvas2D.default_code = "//vars: canvas,ctx,time\nctx.fillStyle='red';\nctx.fillRect(0,0,50,50);\n"; + LGraphTextureCanvas2D.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }, + code: { type: "code" }, + width: { type: "number", precision: 0, step: 1 }, + height: { type: "number", precision: 0, step: 1 } + }; - } //glMatrix - else if (LiteGraph.debug) - console.warn("No glmatrix found, some Math3D nodes may not work"); + LGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) { + if (name == "code" ) + this.compileCode( value ); + } + + LGraphTextureCanvas2D.prototype.compileCode = function( code ) { + this._func = null; + if( !LiteGraph.allow_scripts ) + return; -})(this); - -//basic nodes -(function(global) { - var LiteGraph = global.LiteGraph; + try { + this._func = new Function( "canvas", "ctx", "time", "script","v", code ); + this.boxcolor = "#00FF00"; + } catch (err) { + this.boxcolor = "#FF0000"; + console.error("Error parsing script"); + console.error(err); + } + }; - function toString(a) { - if(a && a.constructor === Object) + LGraphTextureCanvas2D.prototype.onExecute = function() { + var func = this._func; + if (!func || !this.isOutputConnected(0)) { + return; + } + this.executeDraw( func ); + } + + LGraphTextureCanvas2D.prototype.executeDraw = function( func_context ) { + + var width = this.properties.width || gl.canvas.width; + var height = this.properties.height || gl.canvas.height; + var temp = this._temp_texture; + var type = LGraphTexture.getTextureType( this.properties.precision ); + if (!temp || temp.width != width || temp.height != height || temp.type != type ) { + temp = this._temp_texture = new GL.Texture(width, height, { + format: gl.RGBA, + filter: gl.LINEAR, + type: type + }); + } + + var v = this.getInputData(0); + + var properties = this.properties; + var that = this; + var time = this.graph.getTime(); + var ctx = gl; + var canvas = gl.canvas; + if( this.properties.use_html_canvas || !global.enableWebGLCanvas ) { - try + if(!this._canvas) { - return JSON.stringify(a); + canvas = this._canvas = createCanvas(width.height); + ctx = this._ctx = canvas.getContext("2d"); } - catch (err) + else { - return String(a); + canvas = this._canvas; + ctx = this._ctx; } + canvas.width = width; + canvas.height = height; } - return String(a); - } - LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "string"); + if(ctx == gl) //using Canvas2DtoWebGL + temp.drawTo(function() { + gl.start2D(); + if(properties.clear) + { + gl.clearColor(0,0,0,0); + gl.clear( gl.COLOR_BUFFER_BIT ); + } - function compare(a, b) { - return a == b; - } + try { + if (func_context.draw) { + func_context.draw.call(that, canvas, ctx, time, func_context, v); + } else { + func_context.call(that, canvas, ctx, time, func_context,v); + } + that.boxcolor = "#00FF00"; + } catch (err) { + that.boxcolor = "#FF0000"; + console.error("Error executing script"); + console.error(err); + } + gl.finish2D(); + }); + else //rendering to offscreen canvas and uploading to texture + { + if(properties.clear) + ctx.clearRect(0,0,canvas.width,canvas.height); - LiteGraph.wrapFunctionAsNode( - "string/compare", - compare, - ["string", "string"], - "boolean" - ); + try { + if (func_context.draw) { + func_context.draw.call(this, canvas, ctx, time, func_context, v); + } else { + func_context.call(this, canvas, ctx, time, func_context,v); + } + this.boxcolor = "#00FF00"; + } catch (err) { + this.boxcolor = "#FF0000"; + console.error("Error executing script"); + console.error(err); + } + temp.uploadImage( canvas ); + } - function concatenate(a, b) { - if (a === undefined) { - return b; - } - if (b === undefined) { - return a; - } - return a + b; - } + this.setOutputData(0, temp); + }; - LiteGraph.wrapFunctionAsNode( - "string/concatenate", - concatenate, - ["string", "string"], - "string" - ); + LiteGraph.registerNodeType("texture/canvas2D", LGraphTextureCanvas2D); - function contains(a, b) { - if (a === undefined || b === undefined) { - return false; - } - return a.indexOf(b) != -1; - } + // To do chroma keying ***************** - LiteGraph.wrapFunctionAsNode( - "string/contains", - contains, - ["string", "string"], - "boolean" - ); + function LGraphTextureMatte() { + this.addInput("in", "Texture"); - function toUpperCase(a) { - if (a != null && a.constructor === String) { - return a.toUpperCase(); - } - return a; - } + this.addOutput("out", "Texture"); + this.properties = { + key_color: vec3.fromValues(0, 1, 0), + threshold: 0.8, + slope: 0.2, + precision: LGraphTexture.DEFAULT + }; + } - LiteGraph.wrapFunctionAsNode( - "string/toUpperCase", - toUpperCase, - ["string"], - "string" - ); + LGraphTextureMatte.title = "Matte"; + LGraphTextureMatte.desc = "Extracts background"; - function split(str, separator) { - if(separator == null) - separator = this.properties.separator; - if (str == null ) - return []; - if( str.constructor === String ) - return str.split(separator || " "); - else if( str.constructor === Array ) - { - var r = []; - for(var i = 0; i < str.length; ++i){ - if (typeof str[i] == "string") - r[i] = str[i].split(separator || " "); - } - return r; + LGraphTextureMatte.widgets_info = { + key_color: { widget: "color" }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureMatte.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) { + return; + } //saves work + + var tex = this.getInputData(0); + + if (this.properties.precision === LGraphTexture.PASS_THROUGH) { + this.setOutputData(0, tex); + return; } - return null; - } - LiteGraph.wrapFunctionAsNode( - "string/split", - split, - ["string,array", "string"], - "array", - { separator: "," } - ); + if (!tex) { + return; + } - function toFixed(a) { - if (a != null && a.constructor === Number) { - return a.toFixed(this.properties.precision); - } - return a; - } + this._tex = LGraphTexture.getTargetTexture( + tex, + this._tex, + this.properties.precision + ); + + gl.disable(gl.BLEND); + gl.disable(gl.DEPTH_TEST); + + if (!this._uniforms) { + this._uniforms = { + u_texture: 0, + u_key_color: this.properties.key_color, + u_threshold: 1, + u_slope: 1 + }; + } + var uniforms = this._uniforms; - LiteGraph.wrapFunctionAsNode( - "string/toFixed", - toFixed, - ["number"], - "string", - { precision: 0 } - ); + var mesh = Mesh.getScreenQuad(); + var shader = LGraphTextureMatte._shader; + if (!shader) { + shader = LGraphTextureMatte._shader = new GL.Shader( + GL.Shader.SCREEN_VERTEX_SHADER, + LGraphTextureMatte.pixel_shader + ); + } + uniforms.u_key_color = this.properties.key_color; + uniforms.u_threshold = this.properties.threshold; + uniforms.u_slope = this.properties.slope; - function StringToTable() { - this.addInput("", "string"); - this.addOutput("table", "table"); - this.addOutput("rows", "number"); - this.addProperty("value", ""); - this.addProperty("separator", ","); - this._table = null; - } + this._tex.drawTo(function() { + tex.bind(0); + shader.uniforms(uniforms).draw(mesh); + }); - StringToTable.title = "toTable"; - StringToTable.desc = "Splits a string to table"; + this.setOutputData(0, this._tex); + }; - StringToTable.prototype.onExecute = function() { - var input = this.getInputData(0); - if(!input) + LGraphTextureMatte.pixel_shader = + "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec3 u_key_color;\n\ + uniform float u_threshold;\n\ + uniform float u_slope;\n\ + \n\ + void main() {\n\ + vec3 color = texture2D( u_texture, v_coord ).xyz;\n\ + float diff = length( normalize(color) - normalize(u_key_color) );\n\ + float edge = u_threshold * (1.0 - u_slope);\n\ + float alpha = smoothstep( edge, u_threshold, diff);\n\ + gl_FragColor = vec4( color, alpha );\n\ + }"; + + LiteGraph.registerNodeType("texture/matte", LGraphTextureMatte); + + //*********************************** + function LGraphCubemapToTexture2D() { + this.addInput("in", "texture"); + this.addInput("yaw", "number"); + this.addOutput("out", "texture"); + this.properties = { yaw: 0 }; + } + + LGraphCubemapToTexture2D.title = "CubemapToTexture2D"; + LGraphCubemapToTexture2D.desc = "Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation"; + + LGraphCubemapToTexture2D.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) return; - var separator = this.properties.separator || ","; - if(input != this._str || separator != this._last_separator ) - { - this._last_separator = separator; - this._str = input; - this._table = input.split("\n").map(function(a){ return a.trim().split(separator)}); - } - this.setOutputData(0, this._table ); - this.setOutputData(1, this._table ? this._table.length : 0 ); - }; - LiteGraph.registerNodeType("string/toTable", StringToTable); + var tex = this.getInputData(0); + if ( !tex || tex.texture_type != GL.TEXTURE_CUBE_MAP ) + return; + if( this._last_tex && ( this._last_tex.height != tex.height || this._last_tex.type != tex.type )) + this._last_tex = null; + var yaw = this.getInputOrProperty("yaw"); + this._last_tex = GL.Texture.cubemapToTexture2D( tex, tex.height, this._last_tex, true, yaw ); + this.setOutputData( 0, this._last_tex ); + }; + LiteGraph.registerNodeType( "texture/cubemapToTexture2D", LGraphCubemapToTexture2D ); })(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - function Selector() { - this.addInput("sel", "number"); - this.addInput("A"); - this.addInput("B"); - this.addInput("C"); - this.addInput("D"); - this.addOutput("out"); - - this.selected = 0; - } - - Selector.title = "Selector"; - Selector.desc = "selects an output"; - - Selector.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - ctx.fillStyle = "#AFB"; - var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6; - ctx.beginPath(); - ctx.moveTo(50, y); - ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); - }; - - Selector.prototype.onExecute = function() { - var sel = this.getInputData(0); - if (sel == null || sel.constructor !== Number) - sel = 0; - this.selected = sel = Math.round(sel) % (this.inputs.length - 1); - var v = this.getInputData(sel + 1); - if (v !== undefined) { - this.setOutputData(0, v); - } - }; - - Selector.prototype.onGetInputs = function() { - return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; - }; - - LiteGraph.registerNodeType("logic/selector", Selector); - - function Sequence() { - this.properties = { - sequence: "A,B,C" - }; - this.addInput("index", "number"); - this.addInput("seq"); - this.addOutput("out"); - - this.index = 0; - this.values = this.properties.sequence.split(","); - } - - Sequence.title = "Sequence"; - Sequence.desc = "select one element from a sequence from a string"; - - Sequence.prototype.onPropertyChanged = function(name, value) { - if (name == "sequence") { - this.values = value.split(","); - } - }; - - Sequence.prototype.onExecute = function() { - var seq = this.getInputData(1); - if (seq && seq != this.current_sequence) { - this.values = seq.split(","); - this.current_sequence = seq; - } - var index = this.getInputData(0); - if (index == null) { - index = 0; - } - this.index = index = Math.round(index) % this.values.length; - - this.setOutputData(0, this.values[index]); - }; - - LiteGraph.registerNodeType("logic/sequence", Sequence); - - - function logicAnd(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicAnd.title = "AND"; - logicAnd.desc = "Return true if all inputs are true"; - logicAnd.prototype.onExecute = function() { - var ret = true; - for (var inX in this.inputs){ - if (!this.getInputData(inX)){ - var ret = false; - break; - } - } - this.setOutputData(0, ret); - }; - logicAnd.prototype.onGetInputs = function() { - return [ - ["and", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/AND", logicAnd); - - - function logicOr(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicOr.title = "OR"; - logicOr.desc = "Return true if at least one input is true"; - logicOr.prototype.onExecute = function() { - var ret = false; - for (var inX in this.inputs){ - if (this.getInputData(inX)){ - ret = true; - break; - } - } - this.setOutputData(0, ret); - }; - logicOr.prototype.onGetInputs = function() { - return [ - ["or", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/OR", logicOr); - - - function logicNot(){ - this.properties = { }; - this.addInput("in", "boolean"); - this.addOutput("out", "boolean"); - } - logicNot.title = "NOT"; - logicNot.desc = "Return the logical negation"; - logicNot.prototype.onExecute = function() { - var ret = !this.getInputData(0); - this.setOutputData(0, ret); - }; - LiteGraph.registerNodeType("logic/NOT", logicNot); - - - function logicCompare(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicCompare.title = "bool == bool"; - logicCompare.desc = "Compare for logical equality"; - logicCompare.prototype.onExecute = function() { - var last = null; - var ret = true; - for (var inX in this.inputs){ - if (last === null) last = this.getInputData(inX); - else - if (last != this.getInputData(inX)){ - ret = false; - break; - } - } - this.setOutputData(0, ret); - }; - logicCompare.prototype.onGetInputs = function() { - return [ - ["bool", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/CompareBool", logicCompare); - - - function logicBranch(){ - this.properties = { }; - this.addInput("onTrigger", LiteGraph.ACTION); - this.addInput("condition", "boolean"); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.mode = LiteGraph.ON_TRIGGER; - } - logicBranch.title = "Branch"; - logicBranch.desc = "Branch execution on condition"; - logicBranch.prototype.onExecute = function(param, options) { - var condtition = this.getInputData(1); - if (condtition){ - this.triggerSlot(0); - }else{ - this.triggerSlot(1); - } - }; - LiteGraph.registerNodeType("logic/IF", logicBranch); -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - function GraphicsPlot() { - this.addInput("A", "Number"); - this.addInput("B", "Number"); - this.addInput("C", "Number"); - this.addInput("D", "Number"); - - this.values = [[], [], [], []]; - this.properties = { scale: 2 }; - } - - GraphicsPlot.title = "Plot"; - GraphicsPlot.desc = "Plots data over time"; - GraphicsPlot.colors = ["#FFF", "#F99", "#9F9", "#99F"]; - - GraphicsPlot.prototype.onExecute = function(ctx) { - if (this.flags.collapsed) { - return; - } - - var size = this.size; - - for (var i = 0; i < 4; ++i) { - var v = this.getInputData(i); - if (v == null) { - continue; - } - var values = this.values[i]; - values.push(v); - if (values.length > size[0]) { - values.shift(); - } - } - }; - - GraphicsPlot.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - var size = this.size; - - var scale = (0.5 * size[1]) / this.properties.scale; - var colors = GraphicsPlot.colors; - var offset = size[1] * 0.5; - - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, size[0], size[1]); - ctx.strokeStyle = "#555"; - ctx.beginPath(); - ctx.moveTo(0, offset); - ctx.lineTo(size[0], offset); - ctx.stroke(); - - if (this.inputs) { - for (var i = 0; i < 4; ++i) { - var values = this.values[i]; - if (!this.inputs[i] || !this.inputs[i].link) { - continue; - } - ctx.strokeStyle = colors[i]; - ctx.beginPath(); - var v = values[0] * scale * -1 + offset; - ctx.moveTo(0, clamp(v, 0, size[1])); - for (var j = 1; j < values.length && j < size[0]; ++j) { - var v = values[j] * scale * -1 + offset; - ctx.lineTo(j, clamp(v, 0, size[1])); - } - ctx.stroke(); - } - } - }; - - LiteGraph.registerNodeType("graphics/plot", GraphicsPlot); - - function GraphicsImage() { - this.addOutput("frame", "image"); - this.properties = { url: "" }; - } - - GraphicsImage.title = "Image"; - GraphicsImage.desc = "Image loader"; - GraphicsImage.widgets = [{ name: "load", text: "Load", type: "button" }]; - - GraphicsImage.supported_extensions = ["jpg", "jpeg", "png", "gif"]; - - GraphicsImage.prototype.onAdded = function() { - if (this.properties["url"] != "" && this.img == null) { - this.loadImage(this.properties["url"]); - } - }; - - GraphicsImage.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - if (this.img && this.size[0] > 5 && this.size[1] > 5 && this.img.width) { - ctx.drawImage(this.img, 0, 0, this.size[0], this.size[1]); - } - }; - - GraphicsImage.prototype.onExecute = function() { - if (!this.img) { - this.boxcolor = "#000"; - } - if (this.img && this.img.width) { - this.setOutputData(0, this.img); - } else { - this.setOutputData(0, null); - } - if (this.img && this.img.dirty) { - this.img.dirty = false; - } - }; - - GraphicsImage.prototype.onPropertyChanged = function(name, value) { - this.properties[name] = value; - if (name == "url" && value != "") { - this.loadImage(value); - } - - return true; - }; - - GraphicsImage.prototype.loadImage = function(url, callback) { - if (url == "") { - this.img = null; - return; - } - - this.img = document.createElement("img"); - - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - - this.img.src = url; - this.boxcolor = "#F95"; - var that = this; - this.img.onload = function() { - if (callback) { - callback(this); - } - console.log( "Image loaded, size: " + that.img.width + "x" + that.img.height ); - this.dirty = true; - that.boxcolor = "#9F9"; - that.setDirtyCanvas(true); - }; - this.img.onerror = function() { - console.log("error loading the image:" + url); - } - }; - - GraphicsImage.prototype.onWidget = function(e, widget) { - if (widget.name == "load") { - this.loadImage(this.properties["url"]); - } - }; - - GraphicsImage.prototype.onDropFile = function(file) { - var that = this; - if (this._url) { - URL.revokeObjectURL(this._url); - } - this._url = URL.createObjectURL(file); - this.properties.url = this._url; - this.loadImage(this._url, function(img) { - that.size[1] = (img.height / img.width) * that.size[0]; - }); - }; - - LiteGraph.registerNodeType("graphics/image", GraphicsImage); - - function ColorPalette() { - this.addInput("f", "number"); - this.addOutput("Color", "color"); - this.properties = { - colorA: "#444444", - colorB: "#44AAFF", - colorC: "#44FFAA", - colorD: "#FFFFFF" - }; - } - - ColorPalette.title = "Palette"; - ColorPalette.desc = "Generates a color"; - - ColorPalette.prototype.onExecute = function() { - var c = []; - - if (this.properties.colorA != null) { - c.push(hex2num(this.properties.colorA)); - } - if (this.properties.colorB != null) { - c.push(hex2num(this.properties.colorB)); - } - if (this.properties.colorC != null) { - c.push(hex2num(this.properties.colorC)); - } - if (this.properties.colorD != null) { - c.push(hex2num(this.properties.colorD)); - } - - var f = this.getInputData(0); - if (f == null) { - f = 0.5; - } - if (f > 1.0) { - f = 1.0; - } else if (f < 0.0) { - f = 0.0; - } - - if (c.length == 0) { - return; - } - - var result = [0, 0, 0]; - if (f == 0) { - result = c[0]; - } else if (f == 1) { - result = c[c.length - 1]; - } else { - var pos = (c.length - 1) * f; - var c1 = c[Math.floor(pos)]; - var c2 = c[Math.floor(pos) + 1]; - var t = pos - Math.floor(pos); - result[0] = c1[0] * (1 - t) + c2[0] * t; - result[1] = c1[1] * (1 - t) + c2[1] * t; - result[2] = c1[2] * (1 - t) + c2[2] * t; - } - - /* - c[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) ); - c[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) ); - c[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) ); - */ - - for (var i=0; i < result.length; i++) { - result[i] /= 255; - } - - this.boxcolor = colorToString(result); - this.setOutputData(0, result); - }; - - LiteGraph.registerNodeType("color/palette", ColorPalette); - - function ImageFrame() { - this.addInput("", "image,canvas"); - this.size = [200, 200]; - } - - ImageFrame.title = "Frame"; - ImageFrame.desc = "Frame viewerew"; - ImageFrame.widgets = [ - { name: "resize", text: "Resize box", type: "button" }, - { name: "view", text: "View Image", type: "button" } - ]; - - ImageFrame.prototype.onDrawBackground = function(ctx) { - if (this.frame && !this.flags.collapsed) { - ctx.drawImage(this.frame, 0, 0, this.size[0], this.size[1]); - } - }; - - ImageFrame.prototype.onExecute = function() { - this.frame = this.getInputData(0); - this.setDirtyCanvas(true); - }; - - ImageFrame.prototype.onWidget = function(e, widget) { - if (widget.name == "resize" && this.frame) { - var width = this.frame.width; - var height = this.frame.height; - - if (!width && this.frame.videoWidth != null) { - width = this.frame.videoWidth; - height = this.frame.videoHeight; - } - - if (width && height) { - this.size = [width, height]; - } - this.setDirtyCanvas(true, true); - } else if (widget.name == "view") { - this.show(); - } - }; - - ImageFrame.prototype.show = function() { - //var str = this.canvas.toDataURL("image/png"); - if (showElement && this.frame) { - showElement(this.frame); - } - }; - - LiteGraph.registerNodeType("graphics/frame", ImageFrame); - - function ImageFade() { - this.addInputs([ - ["img1", "image"], - ["img2", "image"], - ["fade", "number"] - ]); - this.addOutput("", "image"); - this.properties = { fade: 0.5, width: 512, height: 512 }; - } - - ImageFade.title = "Image fade"; - ImageFade.desc = "Fades between images"; - ImageFade.widgets = [ - { name: "resizeA", text: "Resize to A", type: "button" }, - { name: "resizeB", text: "Resize to B", type: "button" } - ]; - - ImageFade.prototype.onAdded = function() { - this.createCanvas(); - var ctx = this.canvas.getContext("2d"); - ctx.fillStyle = "#000"; - ctx.fillRect(0, 0, this.properties["width"], this.properties["height"]); - }; - - ImageFade.prototype.createCanvas = function() { - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties["width"]; - this.canvas.height = this.properties["height"]; - }; - - ImageFade.prototype.onExecute = function() { - var ctx = this.canvas.getContext("2d"); - this.canvas.width = this.canvas.width; - - var A = this.getInputData(0); - if (A != null) { - ctx.drawImage(A, 0, 0, this.canvas.width, this.canvas.height); - } - - var fade = this.getInputData(2); - if (fade == null) { - fade = this.properties["fade"]; - } else { - this.properties["fade"] = fade; - } - - ctx.globalAlpha = fade; - var B = this.getInputData(1); - if (B != null) { - ctx.drawImage(B, 0, 0, this.canvas.width, this.canvas.height); - } - ctx.globalAlpha = 1.0; - - this.setOutputData(0, this.canvas); - this.setDirtyCanvas(true); - }; - - LiteGraph.registerNodeType("graphics/imagefade", ImageFade); - - function ImageCrop() { - this.addInput("", "image"); - this.addOutput("", "image"); - this.properties = { width: 256, height: 256, x: 0, y: 0, scale: 1.0 }; - this.size = [50, 20]; - } - - ImageCrop.title = "Crop"; - ImageCrop.desc = "Crop Image"; - - ImageCrop.prototype.onAdded = function() { - this.createCanvas(); - }; - - ImageCrop.prototype.createCanvas = function() { - this.canvas = document.createElement("canvas"); - this.canvas.width = this.properties["width"]; - this.canvas.height = this.properties["height"]; - }; - - ImageCrop.prototype.onExecute = function() { - var input = this.getInputData(0); - if (!input) { - return; - } - - if (input.width) { - var ctx = this.canvas.getContext("2d"); - - ctx.drawImage( - input, - -this.properties["x"], - -this.properties["y"], - input.width * this.properties["scale"], - input.height * this.properties["scale"] - ); - this.setOutputData(0, this.canvas); - } else { - this.setOutputData(0, null); - } - }; - - ImageCrop.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - if (this.canvas) { - ctx.drawImage( - this.canvas, - 0, - 0, - this.canvas.width, - this.canvas.height, - 0, - 0, - this.size[0], - this.size[1] - ); - } - }; - - ImageCrop.prototype.onPropertyChanged = function(name, value) { - this.properties[name] = value; - - if (name == "scale") { - this.properties[name] = parseFloat(value); - if (this.properties[name] == 0) { - console.error("Error in scale"); - this.properties[name] = 1.0; - } - } else { - this.properties[name] = parseInt(value); - } - - this.createCanvas(); - - return true; - }; - - LiteGraph.registerNodeType("graphics/cropImage", ImageCrop); - - //CANVAS stuff - - function CanvasNode() { - this.addInput("clear", LiteGraph.ACTION); - this.addOutput("", "canvas"); - this.properties = { width: 512, height: 512, autoclear: true }; - - this.canvas = document.createElement("canvas"); - this.ctx = this.canvas.getContext("2d"); - } - - CanvasNode.title = "Canvas"; - CanvasNode.desc = "Canvas to render stuff"; - - CanvasNode.prototype.onExecute = function() { - var canvas = this.canvas; - var w = this.properties.width | 0; - var h = this.properties.height | 0; - if (canvas.width != w) { - canvas.width = w; - } - if (canvas.height != h) { - canvas.height = h; - } - - if (this.properties.autoclear) { - this.ctx.clearRect(0, 0, canvas.width, canvas.height); - } - this.setOutputData(0, canvas); - }; - - CanvasNode.prototype.onAction = function(action, param) { - if (action == "clear") { - this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height); - } - }; - - LiteGraph.registerNodeType("graphics/canvas", CanvasNode); - - function DrawImageNode() { - this.addInput("canvas", "canvas"); - this.addInput("img", "image,canvas"); - this.addInput("x", "number"); - this.addInput("y", "number"); - this.properties = { x: 0, y: 0, opacity: 1 }; - } - - DrawImageNode.title = "DrawImage"; - DrawImageNode.desc = "Draws image into a canvas"; - - DrawImageNode.prototype.onExecute = function() { - var canvas = this.getInputData(0); - if (!canvas) { - return; - } - - var img = this.getInputOrProperty("img"); - if (!img) { - return; - } - - var x = this.getInputOrProperty("x"); - var y = this.getInputOrProperty("y"); - var ctx = canvas.getContext("2d"); - ctx.drawImage(img, x, y); - }; - - LiteGraph.registerNodeType("graphics/drawImage", DrawImageNode); - - function DrawRectangleNode() { - this.addInput("canvas", "canvas"); - this.addInput("x", "number"); - this.addInput("y", "number"); - this.addInput("w", "number"); - this.addInput("h", "number"); - this.properties = { - x: 0, - y: 0, - w: 10, - h: 10, - color: "white", - opacity: 1 - }; - } - - DrawRectangleNode.title = "DrawRectangle"; - DrawRectangleNode.desc = "Draws rectangle in canvas"; - - DrawRectangleNode.prototype.onExecute = function() { - var canvas = this.getInputData(0); - if (!canvas) { - return; - } - - var x = this.getInputOrProperty("x"); - var y = this.getInputOrProperty("y"); - var w = this.getInputOrProperty("w"); - var h = this.getInputOrProperty("h"); - var ctx = canvas.getContext("2d"); - ctx.fillRect(x, y, w, h); - }; - - LiteGraph.registerNodeType("graphics/drawRectangle", DrawRectangleNode); - - function ImageVideo() { - this.addInput("t", "number"); - this.addOutputs([["frame", "image"], ["t", "number"], ["d", "number"]]); - this.properties = { url: "", use_proxy: true }; - } - - ImageVideo.title = "Video"; - ImageVideo.desc = "Video playback"; - ImageVideo.widgets = [ - { name: "play", text: "PLAY", type: "minibutton" }, - { name: "stop", text: "STOP", type: "minibutton" }, - { name: "demo", text: "Demo video", type: "button" }, - { name: "mute", text: "Mute video", type: "button" } - ]; - - ImageVideo.prototype.onExecute = function() { - if (!this.properties.url) { - return; - } - - if (this.properties.url != this._video_url) { - this.loadVideo(this.properties.url); - } - - if (!this._video || this._video.width == 0) { - return; - } - - var t = this.getInputData(0); - if (t && t >= 0 && t <= 1.0) { - this._video.currentTime = t * this._video.duration; - this._video.pause(); - } - - this._video.dirty = true; - this.setOutputData(0, this._video); - this.setOutputData(1, this._video.currentTime); - this.setOutputData(2, this._video.duration); - this.setDirtyCanvas(true); - }; - - ImageVideo.prototype.onStart = function() { - this.play(); - }; - - ImageVideo.prototype.onStop = function() { - this.stop(); - }; - - ImageVideo.prototype.loadVideo = function(url) { - this._video_url = url; - - var pos = url.substr(0,10).indexOf(":"); - var protocol = ""; - if(pos != -1) - protocol = url.substr(0,pos); - - var host = ""; - if(protocol) - { - host = url.substr(0,url.indexOf("/",protocol.length + 3)); - host = host.substr(protocol.length+3); - } - - if ( - this.properties.use_proxy && - protocol && - LiteGraph.proxy && - host != location.host - ) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - - this._video = document.createElement("video"); - this._video.src = url; - this._video.type = "type=video/mp4"; - - this._video.muted = true; - this._video.autoplay = true; - - var that = this; - this._video.addEventListener("loadedmetadata", function(e) { - //onload - console.log("Duration: " + this.duration + " seconds"); - console.log("Size: " + this.videoWidth + "," + this.videoHeight); - that.setDirtyCanvas(true); - this.width = this.videoWidth; - this.height = this.videoHeight; - }); - this._video.addEventListener("progress", function(e) { - //onload - console.log("video loading..."); - }); - this._video.addEventListener("error", function(e) { - console.error("Error loading video: " + this.src); - if (this.error) { - switch (this.error.code) { - case this.error.MEDIA_ERR_ABORTED: - console.error("You stopped the video."); - break; - case this.error.MEDIA_ERR_NETWORK: - console.error("Network error - please try again later."); - break; - case this.error.MEDIA_ERR_DECODE: - console.error("Video is broken.."); - break; - case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: - console.error("Sorry, your browser can't play this video."); - break; - } - } - }); - - this._video.addEventListener("ended", function(e) { - console.log("Video Ended."); - this.play(); //loop - }); - - //document.body.appendChild(this.video); - }; - - ImageVideo.prototype.onPropertyChanged = function(name, value) { - this.properties[name] = value; - if (name == "url" && value != "") { - this.loadVideo(value); - } - - return true; - }; - - ImageVideo.prototype.play = function() { - if (this._video && this._video.videoWidth ) { //is loaded - this._video.play(); - } - }; - - ImageVideo.prototype.playPause = function() { - if (!this._video) { - return; - } - if (this._video.paused) { - this.play(); - } else { - this.pause(); - } - }; - - ImageVideo.prototype.stop = function() { - if (!this._video) { - return; - } - this._video.pause(); - this._video.currentTime = 0; - }; - - ImageVideo.prototype.pause = function() { - if (!this._video) { - return; - } - console.log("Video paused"); - this._video.pause(); - }; - - ImageVideo.prototype.onWidget = function(e, widget) { - /* - if(widget.name == "demo") - { - this.loadVideo(); - } - else if(widget.name == "play") - { - if(this._video) - this.playPause(); - } - if(widget.name == "stop") - { - this.stop(); - } - else if(widget.name == "mute") - { - if(this._video) - this._video.muted = !this._video.muted; - } - */ - }; - - LiteGraph.registerNodeType("graphics/video", ImageVideo); - - // Texture Webcam ***************************************** - function ImageWebcam() { - this.addOutput("Webcam", "image"); - this.properties = { filterFacingMode: false, facingMode: "user" }; - this.boxcolor = "black"; - this.frame = 0; - } - - ImageWebcam.title = "Webcam"; - ImageWebcam.desc = "Webcam image"; - ImageWebcam.is_webcam_open = false; - - ImageWebcam.prototype.openStream = function() { - if (!navigator.mediaDevices.getUserMedia) { - console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); - return; - } - - this._waiting_confirmation = true; - - // Not showing vendor prefixes. - var constraints = { - audio: false, - video: !this.properties.filterFacingMode ? true : { facingMode: this.properties.facingMode } - }; - navigator.mediaDevices - .getUserMedia(constraints) - .then(this.streamReady.bind(this)) - .catch(onFailSoHard); - - var that = this; - function onFailSoHard(e) { - console.log("Webcam rejected", e); - that._webcam_stream = false; - ImageWebcam.is_webcam_open = false; - that.boxcolor = "red"; - that.trigger("stream_error"); - } - }; - - ImageWebcam.prototype.closeStream = function() { - if (this._webcam_stream) { - var tracks = this._webcam_stream.getTracks(); - if (tracks.length) { - for (var i = 0; i < tracks.length; ++i) { - tracks[i].stop(); - } - } - ImageWebcam.is_webcam_open = false; - this._webcam_stream = null; - this._video = null; - this.boxcolor = "black"; - this.trigger("stream_closed"); - } - }; - - ImageWebcam.prototype.onPropertyChanged = function(name, value) { - if (name == "facingMode") { - this.properties.facingMode = value; - this.closeStream(); - this.openStream(); - } - }; - - ImageWebcam.prototype.onRemoved = function() { - this.closeStream(); - }; - - ImageWebcam.prototype.streamReady = function(localMediaStream) { - this._webcam_stream = localMediaStream; - //this._waiting_confirmation = false; - this.boxcolor = "green"; - - var video = this._video; - if (!video) { - video = document.createElement("video"); - video.autoplay = true; - video.srcObject = localMediaStream; - this._video = video; - //document.body.appendChild( video ); //debug - //when video info is loaded (size and so) - video.onloadedmetadata = function(e) { - // Ready to go. Do some stuff. - console.log(e); - ImageWebcam.is_webcam_open = true; - }; - } - - this.trigger("stream_ready", video); - }; - - ImageWebcam.prototype.onExecute = function() { - if (this._webcam_stream == null && !this._waiting_confirmation) { - this.openStream(); - } - - if (!this._video || !this._video.videoWidth) { - return; - } - - this._video.frame = ++this.frame; - this._video.width = this._video.videoWidth; - this._video.height = this._video.videoHeight; - this.setOutputData(0, this._video); - for (var i = 1; i < this.outputs.length; ++i) { - if (!this.outputs[i]) { - continue; - } - switch (this.outputs[i].name) { - case "width": - this.setOutputData(i, this._video.videoWidth); - break; - case "height": - this.setOutputData(i, this._video.videoHeight); - break; - } - } - }; - - ImageWebcam.prototype.getExtraMenuOptions = function(graphcanvas) { - var that = this; - var txt = !that.properties.show ? "Show Frame" : "Hide Frame"; - return [ - { - content: txt, - callback: function() { - that.properties.show = !that.properties.show; - } - } - ]; - }; - - ImageWebcam.prototype.onDrawBackground = function(ctx) { - if ( - this.flags.collapsed || - this.size[1] <= 20 || - !this.properties.show - ) { - return; - } - - if (!this._video) { - return; - } - - //render to graph canvas - ctx.save(); - ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); - ctx.restore(); - }; - - ImageWebcam.prototype.onGetOutputs = function() { - return [ - ["width", "number"], - ["height", "number"], - ["stream_ready", LiteGraph.EVENT], - ["stream_closed", LiteGraph.EVENT], - ["stream_error", LiteGraph.EVENT] - ]; - }; - - LiteGraph.registerNodeType("graphics/webcam", ImageWebcam); -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - var LGraphCanvas = global.LGraphCanvas; - - //Works with Litegl.js to create WebGL nodes - global.LGraphTexture = null; - - if (typeof GL == "undefined") - return; - - LGraphCanvas.link_type_colors["Texture"] = "#987"; - - function LGraphTexture() { - this.addOutput("tex", "Texture"); - this.addOutput("name", "string"); - this.properties = { name: "", filter: true }; - this.size = [ - LGraphTexture.image_preview_size, - LGraphTexture.image_preview_size - ]; - } - - global.LGraphTexture = LGraphTexture; - - LGraphTexture.title = "Texture"; - LGraphTexture.desc = "Texture"; - LGraphTexture.widgets_info = { - name: { widget: "texture" }, - filter: { widget: "checkbox" } - }; - - //REPLACE THIS TO INTEGRATE WITH YOUR FRAMEWORK - LGraphTexture.loadTextureCallback = null; //function in charge of loading textures when not present in the container - LGraphTexture.image_preview_size = 256; - - //flags to choose output texture type - LGraphTexture.UNDEFINED = 0; //not specified - LGraphTexture.PASS_THROUGH = 1; //do not apply FX (like disable but passing the in to the out) - LGraphTexture.COPY = 2; //create new texture with the same properties as the origin texture - LGraphTexture.LOW = 3; //create new texture with low precision (byte) - LGraphTexture.HIGH = 4; //create new texture with high precision (half-float) - LGraphTexture.REUSE = 5; //reuse input texture - LGraphTexture.DEFAULT = 2; //use the default - - LGraphTexture.MODE_VALUES = { - "undefined": LGraphTexture.UNDEFINED, - "pass through": LGraphTexture.PASS_THROUGH, - copy: LGraphTexture.COPY, - low: LGraphTexture.LOW, - high: LGraphTexture.HIGH, - reuse: LGraphTexture.REUSE, - default: LGraphTexture.DEFAULT - }; - - //returns the container where all the loaded textures are stored (overwrite if you have a Resources Manager) - LGraphTexture.getTexturesContainer = function() { - return gl.textures; - }; - - //process the loading of a texture (overwrite it if you have a Resources Manager) - LGraphTexture.loadTexture = function(name, options) { - options = options || {}; - var url = name; - if (url.substr(0, 7) == "http://") { - if (LiteGraph.proxy) { - //proxy external files - url = LiteGraph.proxy + url.substr(7); - } - } - - var container = LGraphTexture.getTexturesContainer(); - var tex = (container[name] = GL.Texture.fromURL(url, options)); - return tex; - }; - - LGraphTexture.getTexture = function(name) { - var container = this.getTexturesContainer(); - - if (!container) { - throw "Cannot load texture, container of textures not found"; - } - - var tex = container[name]; - if (!tex && name && name[0] != ":") { - return this.loadTexture(name); - } - - return tex; - }; - - //used to compute the appropiate output texture - LGraphTexture.getTargetTexture = function(origin, target, mode) { - if (!origin) { - throw "LGraphTexture.getTargetTexture expects a reference texture"; - } - - var tex_type = null; - - switch (mode) { - case LGraphTexture.LOW: - tex_type = gl.UNSIGNED_BYTE; - break; - case LGraphTexture.HIGH: - tex_type = gl.HIGH_PRECISION_FORMAT; - break; - case LGraphTexture.REUSE: - return origin; - break; - case LGraphTexture.COPY: - default: - tex_type = origin ? origin.type : gl.UNSIGNED_BYTE; - break; - } - - if ( - !target || - target.width != origin.width || - target.height != origin.height || - target.type != tex_type || - target.format != origin.format - ) { - target = new GL.Texture(origin.width, origin.height, { - type: tex_type, - format: origin.format, - filter: gl.LINEAR - }); - } - - return target; - }; - - LGraphTexture.getTextureType = function(precision, ref_texture) { - var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE; - switch (precision) { - case LGraphTexture.HIGH: - type = gl.HIGH_PRECISION_FORMAT; - break; - case LGraphTexture.LOW: - type = gl.UNSIGNED_BYTE; - break; - //no default - } - return type; - }; - - LGraphTexture.getWhiteTexture = function() { - if (this._white_texture) { - return this._white_texture; - } - var texture = (this._white_texture = GL.Texture.fromMemory( - 1, - 1, - [255, 255, 255, 255], - { format: gl.RGBA, wrap: gl.REPEAT, filter: gl.NEAREST } - )); - return texture; - }; - - LGraphTexture.getNoiseTexture = function() { - if (this._noise_texture) { - return this._noise_texture; - } - - var noise = new Uint8Array(512 * 512 * 4); - for (var i = 0; i < 512 * 512 * 4; ++i) { - noise[i] = Math.random() * 255; - } - - var texture = GL.Texture.fromMemory(512, 512, noise, { - format: gl.RGBA, - wrap: gl.REPEAT, - filter: gl.NEAREST - }); - this._noise_texture = texture; - return texture; - }; - - LGraphTexture.prototype.onDropFile = function(data, filename, file) { - if (!data) { - this._drop_texture = null; - this.properties.name = ""; - } else { - var texture = null; - if (typeof data == "string") { - texture = GL.Texture.fromURL(data); - } else if (filename.toLowerCase().indexOf(".dds") != -1) { - texture = GL.Texture.fromDDSInMemory(data); - } else { - var blob = new Blob([file]); - var url = URL.createObjectURL(blob); - texture = GL.Texture.fromURL(url); - } - - this._drop_texture = texture; - this.properties.name = filename; - } - }; - - LGraphTexture.prototype.getExtraMenuOptions = function(graphcanvas) { - var that = this; - if (!this._drop_texture) { - return; - } - return [ - { - content: "Clear", - callback: function() { - that._drop_texture = null; - that.properties.name = ""; - } - } - ]; - }; - - LGraphTexture.prototype.onExecute = function() { - var tex = null; - if (this.isOutputConnected(1)) { - tex = this.getInputData(0); - } - - if (!tex && this._drop_texture) { - tex = this._drop_texture; - } - - if (!tex && this.properties.name) { - tex = LGraphTexture.getTexture(this.properties.name); - } - - if (!tex) { - this.setOutputData( 0, null ); - this.setOutputData( 1, "" ); - return; - } - - this._last_tex = tex; - - if (this.properties.filter === false) { - tex.setParameter(gl.TEXTURE_MAG_FILTER, gl.NEAREST); - } else { - tex.setParameter(gl.TEXTURE_MAG_FILTER, gl.LINEAR); - } - - this.setOutputData( 0, tex ); - this.setOutputData( 1, tex.fullpath || tex.filename ); - - for (var i = 2; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output) { - continue; - } - var v = null; - if (output.name == "width") { - v = tex.width; - } else if (output.name == "height") { - v = tex.height; - } else if (output.name == "aspect") { - v = tex.width / tex.height; - } - this.setOutputData(i, v); - } - }; - - LGraphTexture.prototype.onResourceRenamed = function( - old_name, - new_name - ) { - if (this.properties.name == old_name) { - this.properties.name = new_name; - } - }; - - LGraphTexture.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed || this.size[1] <= 20) { - return; - } - - if (this._drop_texture && ctx.webgl) { - ctx.drawImage( - this._drop_texture, - 0, - 0, - this.size[0], - this.size[1] - ); - //this._drop_texture.renderQuad(this.pos[0],this.pos[1],this.size[0],this.size[1]); - return; - } - - //Different texture? then get it from the GPU - if (this._last_preview_tex != this._last_tex) { - if (ctx.webgl) { - this._canvas = this._last_tex; - } else { - var tex_canvas = LGraphTexture.generateLowResTexturePreview( - this._last_tex - ); - if (!tex_canvas) { - return; - } - - this._last_preview_tex = this._last_tex; - this._canvas = cloneCanvas(tex_canvas); - } - } - - if (!this._canvas) { - return; - } - - //render to graph canvas - ctx.save(); - if (!ctx.webgl) { - //reverse image - ctx.translate(0, this.size[1]); - ctx.scale(1, -1); - } - ctx.drawImage(this._canvas, 0, 0, this.size[0], this.size[1]); - ctx.restore(); - }; - - //very slow, used at your own risk - LGraphTexture.generateLowResTexturePreview = function(tex) { - if (!tex) { - return null; - } - - var size = LGraphTexture.image_preview_size; - var temp_tex = tex; - - if (tex.format == gl.DEPTH_COMPONENT) { - return null; - } //cannot generate from depth - - //Generate low-level version in the GPU to speed up - if (tex.width > size || tex.height > size) { - temp_tex = this._preview_temp_tex; - if (!this._preview_temp_tex) { - temp_tex = new GL.Texture(size, size, { - minFilter: gl.NEAREST - }); - this._preview_temp_tex = temp_tex; - } - - //copy - tex.copyTo(temp_tex); - tex = temp_tex; - } - - //create intermediate canvas with lowquality version - var tex_canvas = this._preview_canvas; - if (!tex_canvas) { - tex_canvas = createCanvas(size, size); - this._preview_canvas = tex_canvas; - } - - if (temp_tex) { - temp_tex.toCanvas(tex_canvas); - } - return tex_canvas; - }; - - LGraphTexture.prototype.getResources = function(res) { - if(this.properties.name) - res[this.properties.name] = GL.Texture; - return res; - }; - - LGraphTexture.prototype.onGetInputs = function() { - return [["in", "Texture"]]; - }; - - LGraphTexture.prototype.onGetOutputs = function() { - return [ - ["width", "number"], - ["height", "number"], - ["aspect", "number"] - ]; - }; - - //used to replace shader code - LGraphTexture.replaceCode = function( code, context ) - { - return code.replace(/\{\{[a-zA-Z0-9_]*\}\}/g, function(v){ - v = v.replace( /[\{\}]/g, "" ); - return context[v] || ""; - }); - } - - LiteGraph.registerNodeType("texture/texture", LGraphTexture); - - //************************** - function LGraphTexturePreview() { - this.addInput("Texture", "Texture"); - this.properties = { flipY: false }; - this.size = [ - LGraphTexture.image_preview_size, - LGraphTexture.image_preview_size - ]; - } - - LGraphTexturePreview.title = "Preview"; - LGraphTexturePreview.desc = "Show a texture in the graph canvas"; - LGraphTexturePreview.allow_preview = false; - - LGraphTexturePreview.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - if (!ctx.webgl && !LGraphTexturePreview.allow_preview) { - return; - } //not working well - - var tex = this.getInputData(0); - if (!tex) { - return; - } - - var tex_canvas = null; - - if (!tex.handle && ctx.webgl) { - tex_canvas = tex; - } else { - tex_canvas = LGraphTexture.generateLowResTexturePreview(tex); - } - - //render to graph canvas - ctx.save(); - if (this.properties.flipY) { - ctx.translate(0, this.size[1]); - ctx.scale(1, -1); - } - ctx.drawImage(tex_canvas, 0, 0, this.size[0], this.size[1]); - ctx.restore(); - }; - - LiteGraph.registerNodeType("texture/preview", LGraphTexturePreview); - - //************************************** - - function LGraphTextureSave() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("name", "string"); - this.properties = { name: "", generate_mipmaps: false }; - } - - LGraphTextureSave.title = "Save"; - LGraphTextureSave.desc = "Save a texture in the repository"; - - LGraphTextureSave.prototype.getPreviewTexture = function() - { - return this._texture; - } - - LGraphTextureSave.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (this.properties.generate_mipmaps) { - tex.bind(0); - tex.setParameter( gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR ); - gl.generateMipmap(tex.texture_type); - tex.unbind(0); - } - - if (this.properties.name) { - //for cases where we want to perform something when storing it - if (LGraphTexture.storeTexture) { - LGraphTexture.storeTexture(this.properties.name, tex); - } else { - var container = LGraphTexture.getTexturesContainer(); - container[this.properties.name] = tex; - } - } - - this._texture = tex; - this.setOutputData(0, tex); - this.setOutputData(1, this.properties.name); - }; - - LiteGraph.registerNodeType("texture/save", LGraphTextureSave); - - //**************************************************** - - function LGraphTextureOperation() { - this.addInput("Texture", "Texture"); - this.addInput("TextureB", "Texture"); - this.addInput("value", "number"); - this.addOutput("Texture", "Texture"); - this.help = "

pixelcode must be vec3, uvcode must be vec2, is optional

\ -

uv: tex. coords

color: texture colorB: textureB

time: scene time value: input value

For multiline you must type: result = ...

"; - - this.properties = { - value: 1, - pixelcode: "color + colorB * value", - uvcode: "", - precision: LGraphTexture.DEFAULT - }; - - this.has_error = false; - } - - LGraphTextureOperation.widgets_info = { - uvcode: { widget: "code" }, - pixelcode: { widget: "code" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureOperation.title = "Operation"; - LGraphTextureOperation.desc = "Texture shader operation"; - - LGraphTextureOperation.presets = {}; - - LGraphTextureOperation.prototype.getExtraMenuOptions = function( - graphcanvas - ) { - var that = this; - var txt = !that.properties.show ? "Show Texture" : "Hide Texture"; - return [ - { - content: txt, - callback: function() { - that.properties.show = !that.properties.show; - } - } - ]; - }; - - LGraphTextureOperation.prototype.onPropertyChanged = function() - { - this.has_error = false; - } - - LGraphTextureOperation.prototype.onDrawBackground = function(ctx) { - if ( - this.flags.collapsed || - this.size[1] <= 20 || - !this.properties.show - ) { - return; - } - - if (!this._tex) { - return; - } - - //only works if using a webgl renderer - if (this._tex.gl != ctx) { - return; - } - - //render to graph canvas - ctx.save(); - ctx.drawImage(this._tex, 0, 0, this.size[0], this.size[1]); - ctx.restore(); - }; - - LGraphTextureOperation.prototype.onExecute = function() { - var tex = this.getInputData(0); - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, tex); - return; - } - - var texB = this.getInputData(1); - - if (!this.properties.uvcode && !this.properties.pixelcode) { - return; - } - - var width = 512; - var height = 512; - if (tex) { - width = tex.width; - height = tex.height; - } else if (texB) { - width = texB.width; - height = texB.height; - } - - if(!texB) - texB = GL.Texture.getWhiteTexture(); - - var type = LGraphTexture.getTextureType( this.properties.precision, tex ); - - if (!tex && !this._tex) { - this._tex = new GL.Texture(width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR }); - } else { - this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision ); - } - - var uvcode = ""; - if (this.properties.uvcode) { - uvcode = "uv = " + this.properties.uvcode; - if (this.properties.uvcode.indexOf(";") != -1) { - //there are line breaks, means multiline code - uvcode = this.properties.uvcode; - } - } - - var pixelcode = ""; - if (this.properties.pixelcode) { - pixelcode = "result = " + this.properties.pixelcode; - if (this.properties.pixelcode.indexOf(";") != -1) { - //there are line breaks, means multiline code - pixelcode = this.properties.pixelcode; - } - } - - var shader = this._shader; - - if ( !this.has_error && (!shader || this._shader_code != uvcode + "|" + pixelcode) ) { - - var final_pixel_code = LGraphTexture.replaceCode( LGraphTextureOperation.pixel_shader, { UV_CODE:uvcode, PIXEL_CODE:pixelcode }); - - try { - shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, final_pixel_code ); - this.boxcolor = "#00FF00"; - } catch (err) { - //console.log("Error compiling shader: ", err, final_pixel_code ); - GL.Shader.dumpErrorToConsole(err,Shader.SCREEN_VERTEX_SHADER, final_pixel_code); - this.boxcolor = "#FF0000"; - this.has_error = true; - return; - } - this._shader = shader; - this._shader_code = uvcode + "|" + pixelcode; - } - - if(!this._shader) - return; - - var value = this.getInputData(2); - if (value != null) { - this.properties.value = value; - } else { - value = parseFloat(this.properties.value); - } - - var time = this.graph.getTime(); - - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - if (tex) { - tex.bind(0); - } - if (texB) { - texB.bind(1); - } - var mesh = Mesh.getScreenQuad(); - shader - .uniforms({ - u_texture: 0, - u_textureB: 1, - value: value, - texSize: [width, height,1/width,1/height], - time: time - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureOperation.pixel_shader = - "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform vec4 texSize;\n\ - uniform float time;\n\ - uniform float value;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - {{UV_CODE}};\n\ - vec4 color4 = texture2D(u_texture, uv);\n\ - vec3 color = color4.rgb;\n\ - vec4 color4B = texture2D(u_textureB, uv);\n\ - vec3 colorB = color4B.rgb;\n\ - vec3 result = color;\n\ - float alpha = 1.0;\n\ - {{PIXEL_CODE}};\n\ - gl_FragColor = vec4(result, alpha);\n\ - }\n\ - "; - - LGraphTextureOperation.registerPreset = function ( name, code ) - { - LGraphTextureOperation.presets[name] = code; - } - - LGraphTextureOperation.registerPreset("",""); - LGraphTextureOperation.registerPreset("bypass","color"); - LGraphTextureOperation.registerPreset("add","color + colorB * value"); - LGraphTextureOperation.registerPreset("substract","(color - colorB) * value"); - LGraphTextureOperation.registerPreset("mate","mix( color, colorB, color4B.a * value)"); - LGraphTextureOperation.registerPreset("invert","vec3(1.0) - color"); - LGraphTextureOperation.registerPreset("multiply","color * colorB * value"); - LGraphTextureOperation.registerPreset("divide","(color / colorB) / value"); - LGraphTextureOperation.registerPreset("difference","abs(color - colorB) * value"); - LGraphTextureOperation.registerPreset("max","max(color, colorB) * value"); - LGraphTextureOperation.registerPreset("min","min(color, colorB) * value"); - LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); - LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); - LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); - LGraphTextureOperation.registerPreset("normalmap","\n\ - float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\ - float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\ - float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\ - float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\ - float z4 = color.x;\n\ - float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\ - float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\ - float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\ - float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\ - vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\ - normal.xy *= value;\n\ - result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\ - "); - LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); - - //webglstudio stuff... - LGraphTextureOperation.prototype.onInspect = function(widgets) - { - var that = this; - widgets.addCombo("Presets","",{ values: Object.keys(LGraphTextureOperation.presets), callback: function(v){ - var code = LGraphTextureOperation.presets[v]; - if(!code) - return; - that.setProperty("pixelcode",code); - that.title = v; - widgets.refresh(); - }}); - } - - LiteGraph.registerNodeType("texture/operation", LGraphTextureOperation); - - //**************************************************** - - function LGraphTextureShader() { - this.addOutput("out", "Texture"); - this.properties = { - code: "", - u_value: 1, - u_color: [1,1,1,1], - width: 512, - height: 512, - precision: LGraphTexture.DEFAULT - }; - - this.properties.code = LGraphTextureShader.pixel_shader; - this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 }; - } - - LGraphTextureShader.title = "Shader"; - LGraphTextureShader.desc = "Texture shader"; - LGraphTextureShader.widgets_info = { - code: { type: "code", lang: "glsl" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureShader.prototype.onPropertyChanged = function( - name, - value - ) { - if (name != "code") { - return; - } - - var shader = this.getShader(); - if (!shader) { - return; - } - - //update connections - var uniforms = shader.uniformInfo; - - //remove deprecated slots - if (this.inputs) { - var already = {}; - for (var i = 0; i < this.inputs.length; ++i) { - var info = this.getInputInfo(i); - if (!info) { - continue; - } - - if (uniforms[info.name] && !already[info.name]) { - already[info.name] = true; - continue; - } - this.removeInput(i); - i--; - } - } - - //update existing ones - for (var i in uniforms) { - var info = shader.uniformInfo[i]; - if (info.loc === null) { - continue; - } //is an attribute, not a uniform - if (i == "time") { - //default one - continue; - } - - var type = "number"; - if (this._shader.samplers[i]) { - type = "texture"; - } else { - switch (info.size) { - case 1: - type = "number"; - break; - case 2: - type = "vec2"; - break; - case 3: - type = "vec3"; - break; - case 4: - type = "vec4"; - break; - case 9: - type = "mat3"; - break; - case 16: - type = "mat4"; - break; - default: - continue; - } - } - - var slot = this.findInputSlot(i); - if (slot == -1) { - this.addInput(i, type); - continue; - } - - var input_info = this.getInputInfo(slot); - if (!input_info) { - this.addInput(i, type); - } else { - if (input_info.type == type) { - continue; - } - this.removeInput(slot, type); - this.addInput(i, type); - } - } - }; - - LGraphTextureShader.prototype.getShader = function() { - //replug - if (this._shader && this._shader_code == this.properties.code) { - return this._shader; - } - - this._shader_code = this.properties.code; - this._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, this.properties.code ); - if (!this._shader) { - this.boxcolor = "red"; - return null; - } else { - this.boxcolor = "green"; - } - return this._shader; - }; - - LGraphTextureShader.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var shader = this.getShader(); - if (!shader) { - return; - } - - var tex_slot = 0; - var in_tex = null; - - //set uniforms - if(this.inputs) - for (var i = 0; i < this.inputs.length; ++i) { - var info = this.getInputInfo(i); - var data = this.getInputData(i); - if (data == null) { - continue; - } - - if (data.constructor === GL.Texture) { - data.bind(tex_slot); - if (!in_tex) { - in_tex = data; - } - data = tex_slot; - tex_slot++; - } - shader.setUniform(info.name, data); //data is tex_slot - } - - var uniforms = this._uniforms; - var type = LGraphTexture.getTextureType( this.properties.precision, in_tex ); - - //render to texture - var w = this.properties.width | 0; - var h = this.properties.height | 0; - if (w == 0) { - w = in_tex ? in_tex.width : gl.canvas.width; - } - if (h == 0) { - h = in_tex ? in_tex.height : gl.canvas.height; - } - uniforms.texSize[0] = w; - uniforms.texSize[1] = h; - uniforms.texSize[2] = 1/w; - uniforms.texSize[3] = 1/h; - uniforms.time = this.graph.getTime(); - uniforms.u_value = this.properties.u_value; - uniforms.u_color.set( this.properties.u_color ); - - if ( !this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h ) { - this._tex = new GL.Texture(w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR }); - } - var tex = this._tex; - tex.drawTo(function() { - shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad()); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureShader.pixel_shader = -"precision highp float;\n\ -\n\ -varying vec2 v_coord;\n\ -uniform float time; //time in seconds\n\ -uniform vec4 texSize; //tex resolution\n\ -uniform float u_value;\n\ -uniform vec4 u_color;\n\n\ -void main() {\n\ - vec2 uv = v_coord;\n\ - vec3 color = vec3(0.0);\n\ - //your code here\n\ - color.xy=uv;\n\n\ - gl_FragColor = vec4(color, 1.0);\n\ -}\n\ -"; - - LiteGraph.registerNodeType("texture/shader", LGraphTextureShader); - - // Texture Scale Offset - - function LGraphTextureScaleOffset() { - this.addInput("in", "Texture"); - this.addInput("scale", "vec2"); - this.addInput("offset", "vec2"); - this.addOutput("out", "Texture"); - this.properties = { - offset: vec2.fromValues(0, 0), - scale: vec2.fromValues(1, 1), - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureScaleOffset.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureScaleOffset.title = "Scale/Offset"; - LGraphTextureScaleOffset.desc = "Applies an scaling and offseting"; - - LGraphTextureScaleOffset.prototype.onExecute = function() { - var tex = this.getInputData(0); - - if (!this.isOutputConnected(0) || !tex) { - return; - } //saves work - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, tex); - return; - } - - var width = tex.width; - var height = tex.height; - var type = this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT; - if (this.precision === LGraphTexture.DEFAULT) { - type = tex.type; - } - - if ( - !this._tex || - this._tex.width != width || - this._tex.height != height || - this._tex.type != type - ) { - this._tex = new GL.Texture(width, height, { - type: type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - var shader = this._shader; - - if (!shader) { - shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureScaleOffset.pixel_shader - ); - } - - var scale = this.getInputData(1); - if (scale) { - this.properties.scale[0] = scale[0]; - this.properties.scale[1] = scale[1]; - } else { - scale = this.properties.scale; - } - - var offset = this.getInputData(2); - if (offset) { - this.properties.offset[0] = offset[0]; - this.properties.offset[1] = offset[1]; - } else { - offset = this.properties.offset; - } - - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - tex.bind(0); - var mesh = Mesh.getScreenQuad(); - shader - .uniforms({ - u_texture: 0, - u_scale: scale, - u_offset: offset - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureScaleOffset.pixel_shader = - "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform vec2 u_scale;\n\ - uniform vec2 u_offset;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - uv = uv / u_scale - u_offset;\n\ - gl_FragColor = texture2D(u_texture, uv);\n\ - }\n\ - "; - - LiteGraph.registerNodeType( - "texture/scaleOffset", - LGraphTextureScaleOffset - ); - - // Warp (distort a texture) ************************* - - function LGraphTextureWarp() { - this.addInput("in", "Texture"); - this.addInput("warp", "Texture"); - this.addInput("factor", "number"); - this.addOutput("out", "Texture"); - this.properties = { - factor: 0.01, - scale: [1,1], - offset: [0,0], - precision: LGraphTexture.DEFAULT - }; - - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_factor: 1, - u_scale: vec2.create(), - u_offset: vec2.create() - }; - } - - LGraphTextureWarp.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureWarp.title = "Warp"; - LGraphTextureWarp.desc = "Texture warp operation"; - - LGraphTextureWarp.prototype.onExecute = function() { - var tex = this.getInputData(0); - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, tex); - return; - } - - var texB = this.getInputData(1); - - var width = 512; - var height = 512; - var type = gl.UNSIGNED_BYTE; - if (tex) { - width = tex.width; - height = tex.height; - type = tex.type; - } else if (texB) { - width = texB.width; - height = texB.height; - type = texB.type; - } - - if (!tex && !this._tex) { - this._tex = new GL.Texture(width, height, { - type: - this.precision === LGraphTexture.LOW - ? gl.UNSIGNED_BYTE - : gl.HIGH_PRECISION_FORMAT, - format: gl.RGBA, - filter: gl.LINEAR - }); - } else { - this._tex = LGraphTexture.getTargetTexture( - tex || this._tex, - this._tex, - this.properties.precision - ); - } - - var shader = this._shader; - - if (!shader) { - shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureWarp.pixel_shader - ); - } - - var factor = this.getInputData(2); - if (factor != null) { - this.properties.factor = factor; - } else { - factor = parseFloat(this.properties.factor); - } - var uniforms = this._uniforms; - uniforms.u_factor = factor; - uniforms.u_scale.set( this.properties.scale ); - uniforms.u_offset.set( this.properties.offset ); - - this._tex.drawTo(function() { - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.CULL_FACE); - gl.disable(gl.BLEND); - if (tex) { - tex.bind(0); - } - if (texB) { - texB.bind(1); - } - var mesh = Mesh.getScreenQuad(); - shader - .uniforms( uniforms ) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureWarp.pixel_shader = - "precision highp float;\n\ - \n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - varying vec2 v_coord;\n\ - uniform float u_factor;\n\ - uniform vec2 u_scale;\n\ - uniform vec2 u_offset;\n\ - \n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - uv += ( texture2D(u_textureB, uv).rg - vec2(0.5)) * u_factor * u_scale + u_offset;\n\ - gl_FragColor = texture2D(u_texture, uv);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/warp", LGraphTextureWarp); - - //**************************************************** - - // Texture to Viewport ***************************************** - function LGraphTextureToViewport() { - this.addInput("Texture", "Texture"); - this.properties = { - additive: false, - antialiasing: false, - filter: true, - disable_alpha: false, - gamma: 1.0, - viewport: [0,0,1,1] - }; - this.size[0] = 130; - } - - LGraphTextureToViewport.title = "to Viewport"; - LGraphTextureToViewport.desc = "Texture to viewport"; - - LGraphTextureToViewport._prev_viewport = new Float32Array(4); - - LGraphTextureToViewport.prototype.onDrawBackground = function( ctx ) - { - if ( this.flags.collapsed || this.size[1] <= 40 ) - return; - - var tex = this.getInputData(0); - if (!tex) { - return; - } - - ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40); - } - - LGraphTextureToViewport.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (this.properties.disable_alpha) { - gl.disable(gl.BLEND); - } else { - gl.enable(gl.BLEND); - if (this.properties.additive) { - gl.blendFunc(gl.SRC_ALPHA, gl.ONE); - } else { - gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA); - } - } - - gl.disable(gl.DEPTH_TEST); - var gamma = this.properties.gamma || 1.0; - if (this.isInputConnected(1)) { - gamma = this.getInputData(1); - } - - tex.setParameter( - gl.TEXTURE_MAG_FILTER, - this.properties.filter ? gl.LINEAR : gl.NEAREST - ); - - var old_viewport = LGraphTextureToViewport._prev_viewport; - old_viewport.set( gl.viewport_data ); - var new_view = this.properties.viewport; - gl.viewport( old_viewport[0] + old_viewport[2] * new_view[0], old_viewport[1] + old_viewport[3] * new_view[1], old_viewport[2] * new_view[2], old_viewport[3] * new_view[3] ); - var viewport = gl.getViewport(); //gl.getParameter(gl.VIEWPORT); - - if (this.properties.antialiasing) { - if (!LGraphTextureToViewport._shader) { - LGraphTextureToViewport._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureToViewport.aa_pixel_shader - ); - } - - var mesh = Mesh.getScreenQuad(); - tex.bind(0); - LGraphTextureToViewport._shader - .uniforms({ - u_texture: 0, - uViewportSize: [tex.width, tex.height], - u_igamma: 1 / gamma, - inverseVP: [1 / tex.width, 1 / tex.height] - }) - .draw(mesh); - } else { - if (gamma != 1.0) { - if (!LGraphTextureToViewport._gamma_shader) { - LGraphTextureToViewport._gamma_shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureToViewport.gamma_pixel_shader - ); - } - tex.toViewport(LGraphTextureToViewport._gamma_shader, { - u_texture: 0, - u_igamma: 1 / gamma - }); - } else { - tex.toViewport(); - } - } - - gl.viewport( old_viewport[0], old_viewport[1], old_viewport[2], old_viewport[3] ); - }; - - LGraphTextureToViewport.prototype.onGetInputs = function() { - return [["gamma", "number"]]; - }; - - LGraphTextureToViewport.aa_pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 uViewportSize;\n\ - uniform vec2 inverseVP;\n\ - uniform float u_igamma;\n\ - #define FXAA_REDUCE_MIN (1.0/ 128.0)\n\ - #define FXAA_REDUCE_MUL (1.0 / 8.0)\n\ - #define FXAA_SPAN_MAX 8.0\n\ - \n\ - /* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\ - vec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\ - {\n\ - vec4 color = vec4(0.0);\n\ - /*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\ - vec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\ - vec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\ - vec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\ - vec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\ - vec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\ - vec3 luma = vec3(0.299, 0.587, 0.114);\n\ - float lumaNW = dot(rgbNW, luma);\n\ - float lumaNE = dot(rgbNE, luma);\n\ - float lumaSW = dot(rgbSW, luma);\n\ - float lumaSE = dot(rgbSE, luma);\n\ - float lumaM = dot(rgbM, luma);\n\ - float lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\ - float lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\ - \n\ - vec2 dir;\n\ - dir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\ - dir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\ - \n\ - float dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\ - \n\ - float rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\ - dir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\ - \n\ - vec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\ - texture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\ - vec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\ - texture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\ - \n\ - //return vec4(rgbA,1.0);\n\ - float lumaB = dot(rgbB, luma);\n\ - if ((lumaB < lumaMin) || (lumaB > lumaMax))\n\ - color = vec4(rgbA, 1.0);\n\ - else\n\ - color = vec4(rgbB, 1.0);\n\ - if(u_igamma != 1.0)\n\ - color.xyz = pow( color.xyz, vec3(u_igamma) );\n\ - return color;\n\ - }\n\ - \n\ - void main() {\n\ - gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\ - }\n\ - "; - - LGraphTextureToViewport.gamma_pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_igamma;\n\ - void main() {\n\ - vec4 color = texture2D( u_texture, v_coord);\n\ - color.xyz = pow(color.xyz, vec3(u_igamma) );\n\ - gl_FragColor = color;\n\ - }\n\ - "; - - LiteGraph.registerNodeType( - "texture/toviewport", - LGraphTextureToViewport - ); - - // Texture Copy ***************************************** - function LGraphTextureCopy() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = { - size: 0, - generate_mipmaps: false, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureCopy.title = "Copy"; - LGraphTextureCopy.desc = "Copy Texture"; - LGraphTextureCopy.widgets_info = { - size: { - widget: "combo", - values: [0, 32, 64, 128, 256, 512, 1024, 2048] - }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureCopy.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex && !this._temp_texture) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - //copy the texture - if (tex) { - var width = tex.width; - var height = tex.height; - - if (this.properties.size != 0) { - width = this.properties.size; - height = this.properties.size; - } - - var temp = this._temp_texture; - - var type = tex.type; - if (this.properties.precision === LGraphTexture.LOW) { - type = gl.UNSIGNED_BYTE; - } else if (this.properties.precision === LGraphTexture.HIGH) { - type = gl.HIGH_PRECISION_FORMAT; - } - - if ( - !temp || - temp.width != width || - temp.height != height || - temp.type != type - ) { - var minFilter = gl.LINEAR; - if ( - this.properties.generate_mipmaps && - isPowerOfTwo(width) && - isPowerOfTwo(height) - ) { - minFilter = gl.LINEAR_MIPMAP_LINEAR; - } - this._temp_texture = new GL.Texture(width, height, { - type: type, - format: gl.RGBA, - minFilter: minFilter, - magFilter: gl.LINEAR - }); - } - tex.copyTo(this._temp_texture); - - if (this.properties.generate_mipmaps) { - this._temp_texture.bind(0); - gl.generateMipmap(this._temp_texture.texture_type); - this._temp_texture.unbind(0); - } - } - - this.setOutputData(0, this._temp_texture); - }; - - LiteGraph.registerNodeType("texture/copy", LGraphTextureCopy); - - // Texture Downsample ***************************************** - function LGraphTextureDownsample() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = { - iterations: 1, - generate_mipmaps: false, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureDownsample.title = "Downsample"; - LGraphTextureDownsample.desc = "Downsample Texture"; - LGraphTextureDownsample.widgets_info = { - iterations: { type: "number", step: 1, precision: 0, min: 0 }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureDownsample.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex && !this._temp_texture) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - //we do not allow any texture different than texture 2D - if (!tex || tex.texture_type !== GL.TEXTURE_2D) { - return; - } - - if (this.properties.iterations < 1) { - this.setOutputData(0, tex); - return; - } - - var shader = LGraphTextureDownsample._shader; - if (!shader) { - LGraphTextureDownsample._shader = shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureDownsample.pixel_shader - ); - } - - var width = tex.width | 0; - var height = tex.height | 0; - var type = tex.type; - if (this.properties.precision === LGraphTexture.LOW) { - type = gl.UNSIGNED_BYTE; - } else if (this.properties.precision === LGraphTexture.HIGH) { - type = gl.HIGH_PRECISION_FORMAT; - } - var iterations = this.properties.iterations || 1; - - var origin = tex; - var target = null; - - var temp = []; - var options = { - type: type, - format: tex.format - }; - - var offset = vec2.create(); - var uniforms = { - u_offset: offset - }; - - if (this._texture) { - GL.Texture.releaseTemporary(this._texture); - } - - for (var i = 0; i < iterations; ++i) { - offset[0] = 1 / width; - offset[1] = 1 / height; - width = width >> 1 || 0; - height = height >> 1 || 0; - target = GL.Texture.getTemporary(width, height, options); - temp.push(target); - origin.setParameter(GL.TEXTURE_MAG_FILTER, GL.NEAREST); - origin.copyTo(target, shader, uniforms); - if (width == 1 && height == 1) { - break; - } //nothing else to do - origin = target; - } - - //keep the last texture used - this._texture = temp.pop(); - - //free the rest - for (var i = 0; i < temp.length; ++i) { - GL.Texture.releaseTemporary(temp[i]); - } - - if (this.properties.generate_mipmaps) { - this._texture.bind(0); - gl.generateMipmap(this._texture.texture_type); - this._texture.unbind(0); - } - - this.setOutputData(0, this._texture); - }; - - LGraphTextureDownsample.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_offset;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = texture2D(u_texture, v_coord );\n\ - color += texture2D(u_texture, v_coord + vec2( u_offset.x, 0.0 ) );\n\ - color += texture2D(u_texture, v_coord + vec2( 0.0, u_offset.y ) );\n\ - color += texture2D(u_texture, v_coord + vec2( u_offset.x, u_offset.y ) );\n\ - gl_FragColor = color * 0.25;\n\ - }\n\ - "; - - LiteGraph.registerNodeType( - "texture/downsample", - LGraphTextureDownsample - ); - - - - function LGraphTextureResize() { - this.addInput("Texture", "Texture"); - this.addOutput("", "Texture"); - this.properties = { - size: [512,512], - generate_mipmaps: false, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureResize.title = "Resize"; - LGraphTextureResize.desc = "Resize Texture"; - LGraphTextureResize.widgets_info = { - iterations: { type: "number", step: 1, precision: 0, min: 0 }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureResize.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex && !this._temp_texture) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - //we do not allow any texture different than texture 2D - if (!tex || tex.texture_type !== GL.TEXTURE_2D) { - return; - } - - var width = this.properties.size[0] | 0; - var height = this.properties.size[1] | 0; - if(width == 0) - width = tex.width; - if(height == 0) - height = tex.height; - var type = tex.type; - if (this.properties.precision === LGraphTexture.LOW) { - type = gl.UNSIGNED_BYTE; - } else if (this.properties.precision === LGraphTexture.HIGH) { - type = gl.HIGH_PRECISION_FORMAT; - } - - if( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type ) - this._texture = new GL.Texture( width, height, { type: type } ); - - tex.copyTo( this._texture ); - - if (this.properties.generate_mipmaps) { - this._texture.bind(0); - gl.generateMipmap(this._texture.texture_type); - this._texture.unbind(0); - } - - this.setOutputData(0, this._texture); - }; - - LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize ); - - // Texture Average ***************************************** - function LGraphTextureAverage() { - this.addInput("Texture", "Texture"); - this.addOutput("tex", "Texture"); - this.addOutput("avg", "vec4"); - this.addOutput("lum", "number"); - this.properties = { - use_previous_frame: true, //to avoid stalls - high_quality: false //to use as much pixels as possible - }; - - this._uniforms = { - u_texture: 0, - u_mipmap_offset: 0 - }; - this._luminance = new Float32Array(4); - } - - LGraphTextureAverage.title = "Average"; - LGraphTextureAverage.desc = - "Compute a partial average (32 random samples) of a texture and stores it as a 1x1 pixel texture.\n If high_quality is true, then it generates the mipmaps first and reads from the lower one."; - - LGraphTextureAverage.prototype.onExecute = function() { - if (!this.properties.use_previous_frame) { - this.updateAverage(); - } - - var v = this._luminance; - this.setOutputData(0, this._temp_texture); - this.setOutputData(1, v); - this.setOutputData(2, (v[0] + v[1] + v[2]) / 3); - }; - - //executed before rendering the frame - LGraphTextureAverage.prototype.onPreRenderExecute = function() { - this.updateAverage(); - }; - - LGraphTextureAverage.prototype.updateAverage = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if ( - !this.isOutputConnected(0) && - !this.isOutputConnected(1) && - !this.isOutputConnected(2) - ) { - return; - } //saves work - - if (!LGraphTextureAverage._shader) { - LGraphTextureAverage._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureAverage.pixel_shader - ); - //creates 256 random numbers and stores them in two mat4 - var samples = new Float32Array(16); - for (var i = 0; i < samples.length; ++i) { - samples[i] = Math.random(); //poorly distributed samples - } - //upload only once - LGraphTextureAverage._shader.uniforms({ - u_samples_a: samples.subarray(0, 16), - u_samples_b: samples.subarray(16, 32) - }); - } - - var temp = this._temp_texture; - var type = gl.UNSIGNED_BYTE; - if (tex.type != type) { - //force floats, half floats cannot be read with gl.readPixels - type = gl.FLOAT; - } - - if (!temp || temp.type != type) { - this._temp_texture = new GL.Texture(1, 1, { - type: type, - format: gl.RGBA, - filter: gl.NEAREST - }); - } - - this._uniforms.u_mipmap_offset = 0; - - if(this.properties.high_quality) - { - if( !this._temp_pot2_texture || this._temp_pot2_texture.type != type ) - this._temp_pot2_texture = new GL.Texture(512, 512, { - type: type, - format: gl.RGBA, - minFilter: gl.LINEAR_MIPMAP_LINEAR, - magFilter: gl.LINEAR - }); - - tex.copyTo( this._temp_pot2_texture ); - tex = this._temp_pot2_texture; - tex.bind(0); - gl.generateMipmap(GL.TEXTURE_2D); - this._uniforms.u_mipmap_offset = 9; - } - - var shader = LGraphTextureAverage._shader; - var uniforms = this._uniforms; - uniforms.u_mipmap_offset = this.properties.mipmap_offset; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - tex.toViewport(shader, uniforms); - }); - - if (this.isOutputConnected(1) || this.isOutputConnected(2)) { - var pixel = this._temp_texture.getPixels(); - if (pixel) { - var v = this._luminance; - var type = this._temp_texture.type; - v.set(pixel); - if (type == gl.UNSIGNED_BYTE) { - vec4.scale(v, v, 1 / 255); - } else if ( - type == GL.HALF_FLOAT || - type == GL.HALF_FLOAT_OES - ) { - //no half floats possible, hard to read back unless copyed to a FLOAT texture, so temp_texture is always forced to FLOAT - } - } - } - }; - - LGraphTextureAverage.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - uniform mat4 u_samples_a;\n\ - uniform mat4 u_samples_b;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_mipmap_offset;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = vec4(0.0);\n\ - //random average\n\ - for(int i = 0; i < 4; ++i)\n\ - for(int j = 0; j < 4; ++j)\n\ - {\n\ - color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ - color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ - }\n\ - gl_FragColor = color * 0.03125;\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/average", LGraphTextureAverage); - - - - // Computes operation between pixels (max, min) ***************************************** - function LGraphTextureMinMax() { - this.addInput("Texture", "Texture"); - this.addOutput("min_t", "Texture"); - this.addOutput("max_t", "Texture"); - this.addOutput("min", "vec4"); - this.addOutput("max", "vec4"); - this.properties = { - mode: "max", - use_previous_frame: true //to avoid stalls - }; - - this._uniforms = { - u_texture: 0 - }; - - this._max = new Float32Array(4); - this._min = new Float32Array(4); - - this._textures_chain = []; - } - - LGraphTextureMinMax.widgets_info = { - mode: { widget: "combo", values: ["min","max","avg"] } - }; - - LGraphTextureMinMax.title = "MinMax"; - LGraphTextureMinMax.desc = "Compute the scene min max"; - - LGraphTextureMinMax.prototype.onExecute = function() { - if (!this.properties.use_previous_frame) { - this.update(); - } - - this.setOutputData(0, this._temp_texture); - this.setOutputData(1, this._luminance); - }; - - //executed before rendering the frame - LGraphTextureMinMax.prototype.onPreRenderExecute = function() { - this.update(); - }; - - LGraphTextureMinMax.prototype.update = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if ( !this.isOutputConnected(0) && !this.isOutputConnected(1) ) { - return; - } //saves work - - if (!LGraphTextureMinMax._shader) { - LGraphTextureMinMax._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMinMax.pixel_shader ); - } - - var temp = this._temp_texture; - var type = gl.UNSIGNED_BYTE; - if (tex.type != type) { - //force floats, half floats cannot be read with gl.readPixels - type = gl.FLOAT; - } - - var size = 512; - - if( !this._textures_chain.length || this._textures_chain[0].type != type ) - { - var index = 0; - while(i) - { - this._textures_chain[i] = new GL.Texture( size, size, { - type: type, - format: gl.RGBA, - filter: gl.NEAREST - }); - size = size >> 2; - i++; - if(size == 1) - break; - } - } - - tex.copyTo( this._textures_chain[0] ); - var prev = this._textures_chain[0]; - for(var i = 1; i <= this._textures_chain.length; ++i) - { - var tex = this._textures_chain[i]; - - prev = tex; - } - - var shader = LGraphTextureMinMax._shader; - var uniforms = this._uniforms; - uniforms.u_mipmap_offset = this.properties.mipmap_offset; - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - this._temp_texture.drawTo(function() { - tex.toViewport(shader, uniforms); - }); - }; - - LGraphTextureMinMax.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - uniform mat4 u_samples_a;\n\ - uniform mat4 u_samples_b;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_mipmap_offset;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = vec4(0.0);\n\ - //random average\n\ - for(int i = 0; i < 4; ++i)\n\ - for(int j = 0; j < 4; ++j)\n\ - {\n\ - color += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\ - color += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\ - }\n\ - gl_FragColor = color * 0.03125;\n\ - }\n\ - "; - - //LiteGraph.registerNodeType("texture/clustered_operation", LGraphTextureClusteredOperation); - - - function LGraphTextureTemporalSmooth() { - this.addInput("in", "Texture"); - this.addInput("factor", "Number"); - this.addOutput("out", "Texture"); - this.properties = { factor: 0.5 }; - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_factor: this.properties.factor - }; - } - - LGraphTextureTemporalSmooth.title = "Smooth"; - LGraphTextureTemporalSmooth.desc = "Smooth texture over time"; - - LGraphTextureTemporalSmooth.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex || !this.isOutputConnected(0)) { - return; - } - - if (!LGraphTextureTemporalSmooth._shader) { - LGraphTextureTemporalSmooth._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureTemporalSmooth.pixel_shader - ); - } - - var temp = this._temp_texture; - if ( - !temp || - temp.type != tex.type || - temp.width != tex.width || - temp.height != tex.height - ) { - var options = { - type: tex.type, - format: gl.RGBA, - filter: gl.NEAREST - }; - this._temp_texture = new GL.Texture(tex.width, tex.height, options ); - this._temp_texture2 = new GL.Texture(tex.width, tex.height, options ); - tex.copyTo(this._temp_texture2); - } - - var tempA = this._temp_texture; - var tempB = this._temp_texture2; - - var shader = LGraphTextureTemporalSmooth._shader; - var uniforms = this._uniforms; - uniforms.u_factor = 1.0 - this.getInputOrProperty("factor"); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - tempA.drawTo(function() { - tempB.bind(1); - tex.toViewport(shader, uniforms); - }); - - this.setOutputData(0, tempA); - - //swap - this._temp_texture = tempB; - this._temp_texture2 = tempA; - }; - - LGraphTextureTemporalSmooth.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_factor;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - gl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\ - }\n\ - "; - - LiteGraph.registerNodeType( "texture/temporal_smooth", LGraphTextureTemporalSmooth ); - - - function LGraphTextureLinearAvgSmooth() { - this.addInput("in", "Texture"); - this.addOutput("avg", "Texture"); - this.addOutput("array", "Texture"); - this.properties = { samples: 64, frames_interval: 1 }; - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_samples: this.properties.samples, - u_isamples: 1/this.properties.samples - }; - this.frame = 0; - } - - LGraphTextureLinearAvgSmooth.title = "Lineal Avg Smooth"; - LGraphTextureLinearAvgSmooth.desc = "Smooth texture linearly over time"; - - LGraphTextureLinearAvgSmooth["@samples"] = { type: "number", min: 1, max: 64, step: 1, precision: 1 }; - - LGraphTextureLinearAvgSmooth.prototype.getPreviewTexture = function() - { - return this._temp_texture2; - } - - LGraphTextureLinearAvgSmooth.prototype.onExecute = function() { - - var tex = this.getInputData(0); - if (!tex || !this.isOutputConnected(0)) { - return; - } - - if (!LGraphTextureLinearAvgSmooth._shader) { - LGraphTextureLinearAvgSmooth._shader_copy = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_copy ); - LGraphTextureLinearAvgSmooth._shader_avg = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearAvgSmooth.pixel_shader_avg ); - } - - var samples = clamp(this.properties.samples,0,64); - var frame = this.frame; - var interval = this.properties.frames_interval; - - if( interval == 0 || frame % interval == 0 ) - { - var temp = this._temp_texture; - if ( !temp || temp.type != tex.type || temp.width != samples ) { - var options = { - type: tex.type, - format: gl.RGBA, - filter: gl.NEAREST - }; - this._temp_texture = new GL.Texture( samples, 1, options ); - this._temp_texture2 = new GL.Texture( samples, 1, options ); - this._temp_texture_out = new GL.Texture( 1, 1, options ); - } - - var tempA = this._temp_texture; - var tempB = this._temp_texture2; - - var shader_copy = LGraphTextureLinearAvgSmooth._shader_copy; - var shader_avg = LGraphTextureLinearAvgSmooth._shader_avg; - var uniforms = this._uniforms; - uniforms.u_samples = samples; - uniforms.u_isamples = 1.0 / samples; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - tempA.drawTo(function() { - tempB.bind(1); - tex.toViewport( shader_copy, uniforms ); - }); - - this._temp_texture_out.drawTo(function() { - tempA.toViewport( shader_avg, uniforms ); - }); - - this.setOutputData( 0, this._temp_texture_out ); - - //swap - this._temp_texture = tempB; - this._temp_texture2 = tempA; - } - else - this.setOutputData(0, this._temp_texture_out); - this.setOutputData(1, this._temp_texture2); - this.frame++; - }; - - LGraphTextureLinearAvgSmooth.pixel_shader_copy = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_isamples;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - if( v_coord.x <= u_isamples )\n\ - gl_FragColor = texture2D( u_texture, vec2(0.5) );\n\ - else\n\ - gl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\n\ - }\n\ - "; - - LGraphTextureLinearAvgSmooth.pixel_shader_avg = - "precision highp float;\n\ - precision highp float;\n\ - uniform sampler2D u_texture;\n\ - uniform int u_samples;\n\ - uniform float u_isamples;\n\ - varying vec2 v_coord;\n\ - \n\ - void main() {\n\ - vec4 color = vec4(0.0);\n\ - for(int i = 0; i < 64; ++i)\n\ - {\n\ - color += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\n\ - if(i == (u_samples - 1))\n\ - break;\n\ - }\n\ - gl_FragColor = color * u_isamples;\n\ - }\n\ - "; - - - LiteGraph.registerNodeType( "texture/linear_avg_smooth", LGraphTextureLinearAvgSmooth ); - - // Image To Texture ***************************************** - function LGraphImageToTexture() { - this.addInput("Image", "image"); - this.addOutput("", "Texture"); - this.properties = {}; - } - - LGraphImageToTexture.title = "Image to Texture"; - LGraphImageToTexture.desc = "Uploads an image to the GPU"; - //LGraphImageToTexture.widgets_info = { size: { widget:"combo", values:[0,32,64,128,256,512,1024,2048]} }; - - LGraphImageToTexture.prototype.onExecute = function() { - var img = this.getInputData(0); - if (!img) { - return; - } - - var width = img.videoWidth || img.width; - var height = img.videoHeight || img.height; - - //this is in case we are using a webgl canvas already, no need to reupload it - if (img.gltexture) { - this.setOutputData(0, img.gltexture); - return; - } - - var temp = this._temp_texture; - if (!temp || temp.width != width || temp.height != height) { - this._temp_texture = new GL.Texture(width, height, { - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - try { - this._temp_texture.uploadImage(img); - } catch (err) { - console.error( - "image comes from an unsafe location, cannot be uploaded to webgl: " + - err - ); - return; - } - - this.setOutputData(0, this._temp_texture); - }; - - LiteGraph.registerNodeType( - "texture/imageToTexture", - LGraphImageToTexture - ); - - // Texture LUT ***************************************** - function LGraphTextureLUT() { - this.addInput("Texture", "Texture"); - this.addInput("LUT", "Texture"); - this.addInput("Intensity", "number"); - this.addOutput("", "Texture"); - this.properties = { enabled: true, intensity: 1, precision: LGraphTexture.DEFAULT, texture: null }; - - if (!LGraphTextureLUT._shader) { - LGraphTextureLUT._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureLUT.pixel_shader ); - } - } - - LGraphTextureLUT.widgets_info = { - texture: { widget: "texture" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureLUT.title = "LUT"; - LGraphTextureLUT.desc = "Apply LUT to Texture"; - - LGraphTextureLUT.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - - if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) { - this.setOutputData(0, tex); - return; - } - - if (!tex) { - return; - } - - var lut_tex = this.getInputData(1); - - if (!lut_tex) { - lut_tex = LGraphTexture.getTexture(this.properties.texture); - } - - if (!lut_tex) { - this.setOutputData(0, tex); - return; - } - - lut_tex.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); - gl.texParameteri( - gl.TEXTURE_2D, - gl.TEXTURE_WRAP_S, - gl.CLAMP_TO_EDGE - ); - gl.texParameteri( - gl.TEXTURE_2D, - gl.TEXTURE_WRAP_T, - gl.CLAMP_TO_EDGE - ); - gl.bindTexture(gl.TEXTURE_2D, null); - - var intensity = this.properties.intensity; - if (this.isInputConnected(2)) { - this.properties.intensity = intensity = this.getInputData(2); - } - - this._tex = LGraphTexture.getTargetTexture( - tex, - this._tex, - this.properties.precision - ); - - //var mesh = Mesh.getScreenQuad(); - - this._tex.drawTo(function() { - lut_tex.bind(1); - tex.toViewport(LGraphTextureLUT._shader, { - u_texture: 0, - u_textureB: 1, - u_amount: intensity - }); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureLUT.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_amount;\n\ - \n\ - void main() {\n\ - lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\ - mediump float blueColor = textureColor.b * 63.0;\n\ - mediump vec2 quad1;\n\ - quad1.y = floor(floor(blueColor) / 8.0);\n\ - quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\ - mediump vec2 quad2;\n\ - quad2.y = floor(ceil(blueColor) / 8.0);\n\ - quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\ - highp vec2 texPos1;\n\ - texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ - texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ - highp vec2 texPos2;\n\ - texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\ - texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\ - lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\ - lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\ - lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\ - gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/LUT", LGraphTextureLUT); - - - // Texture LUT ***************************************** - function LGraphTextureEncode() { - this.addInput("Texture", "Texture"); - this.addInput("Atlas", "Texture"); - this.addOutput("", "Texture"); - this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null }; - - if (!LGraphTextureEncode._shader) { - LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader ); - } - - this._uniforms = { - u_texture: 0, - u_textureB: 1, - u_row_simbols: 4, - u_simbol_size: 16, - u_res: vec2.create() - }; - } - - LGraphTextureEncode.widgets_info = { - texture: { widget: "texture" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureEncode.title = "Encode"; - LGraphTextureEncode.desc = "Apply a texture atlas to encode a texture"; - - LGraphTextureEncode.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - - if (this.properties.precision === LGraphTexture.PASS_THROUGH || this.properties.enabled === false) { - this.setOutputData(0, tex); - return; - } - - if (!tex) { - return; - } - - var symbols_tex = this.getInputData(1); - - if (!symbols_tex) { - symbols_tex = LGraphTexture.getTexture(this.properties.texture); - } - - if (!symbols_tex) { - this.setOutputData(0, tex); - return; - } - - symbols_tex.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, this.properties.filter ? gl.LINEAR : gl.NEAREST ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE ); - gl.bindTexture(gl.TEXTURE_2D, null); - - var uniforms = this._uniforms; - uniforms.u_row_simbols = Math.floor(this.properties.num_row_symbols); - uniforms.u_symbol_size = this.properties.symbol_size; - uniforms.u_brightness = this.properties.brightness; - uniforms.u_invert = this.properties.invert ? 1 : 0; - uniforms.u_colorize = this.properties.colorize ? 1 : 0; - - this._tex = LGraphTexture.getTargetTexture( tex, this._tex, this.properties.precision ); - uniforms.u_res[0] = this._tex.width; - uniforms.u_res[1] = this._tex.height; - this._tex.bind(0); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST ); - gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST ); - - this._tex.drawTo(function() { - symbols_tex.bind(1); - tex.toViewport(LGraphTextureEncode._shader, uniforms); - }); - - if (this.properties.generate_mipmaps) { - this._tex.bind(0); - gl.generateMipmap(this._tex.texture_type); - this._tex.unbind(0); - } - - this.setOutputData(0, this._tex); - }; - - LGraphTextureEncode.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_textureB;\n\ - uniform float u_row_simbols;\n\ - uniform float u_symbol_size;\n\ - uniform float u_brightness;\n\ - uniform float u_invert;\n\ - uniform float u_colorize;\n\ - uniform vec2 u_res;\n\ - \n\ - void main() {\n\ - vec2 total_symbols = u_res / u_symbol_size;\n\ - vec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \n\ - vec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\n\ - lowp vec4 textureColor = texture2D(u_texture, uv );\n\ - float lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\n\ - if( u_invert == 1.0 ) lum = 1.0 - lum;\n\ - float index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\n\ - float col = mod( index, u_row_simbols );\n\ - float row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\n\ - vec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\n\ - vec4 color = texture2D( u_textureB, simbol_uv );\n\ - if(u_colorize == 1.0)\n\ - color *= textureColor;\n\ - gl_FragColor = color;\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/encode", LGraphTextureEncode); - - // Texture Channels ***************************************** - function LGraphTextureChannels() { - this.addInput("Texture", "Texture"); - - this.addOutput("R", "Texture"); - this.addOutput("G", "Texture"); - this.addOutput("B", "Texture"); - this.addOutput("A", "Texture"); - - //this.properties = { use_single_channel: true }; - if (!LGraphTextureChannels._shader) { - LGraphTextureChannels._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureChannels.pixel_shader - ); - } - } - - LGraphTextureChannels.title = "Texture to Channels"; - LGraphTextureChannels.desc = "Split texture channels"; - - LGraphTextureChannels.prototype.onExecute = function() { - var texA = this.getInputData(0); - if (!texA) { - return; - } - - if (!this._channels) { - this._channels = Array(4); - } - - //var format = this.properties.use_single_channel ? gl.LUMINANCE : gl.RGBA; //not supported by WebGL1 - var format = gl.RGB; - var connections = 0; - for (var i = 0; i < 4; i++) { - if (this.isOutputConnected(i)) { - if ( - !this._channels[i] || - this._channels[i].width != texA.width || - this._channels[i].height != texA.height || - this._channels[i].type != texA.type || - this._channels[i].format != format - ) { - this._channels[i] = new GL.Texture( - texA.width, - texA.height, - { - type: texA.type, - format: format, - filter: gl.LINEAR - } - ); - } - connections++; - } else { - this._channels[i] = null; - } - } - - if (!connections) { - return; - } - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureChannels._shader; - var masks = [ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 1, 0], - [0, 0, 0, 1] - ]; - - for (var i = 0; i < 4; i++) { - if (!this._channels[i]) { - continue; - } - - this._channels[i].drawTo(function() { - texA.bind(0); - shader - .uniforms({ u_texture: 0, u_mask: masks[i] }) - .draw(mesh); - }); - this.setOutputData(i, this._channels[i]); - } - }; - - LGraphTextureChannels.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec4 u_mask;\n\ - \n\ - void main() {\n\ - gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\ - }\n\ - "; - - LiteGraph.registerNodeType( - "texture/textureChannels", - LGraphTextureChannels - ); - - // Texture Channels to Texture ***************************************** - function LGraphChannelsTexture() { - this.addInput("R", "Texture"); - this.addInput("G", "Texture"); - this.addInput("B", "Texture"); - this.addInput("A", "Texture"); - - this.addOutput("Texture", "Texture"); - - this.properties = { - precision: LGraphTexture.DEFAULT, - R: 1, - G: 1, - B: 1, - A: 1 - }; - this._color = vec4.create(); - this._uniforms = { - u_textureR: 0, - u_textureG: 1, - u_textureB: 2, - u_textureA: 3, - u_color: this._color - }; - } - - LGraphChannelsTexture.title = "Channels to Texture"; - LGraphChannelsTexture.desc = "Split texture channels"; - LGraphChannelsTexture.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphChannelsTexture.prototype.onExecute = function() { - var white = LGraphTexture.getWhiteTexture(); - var texR = this.getInputData(0) || white; - var texG = this.getInputData(1) || white; - var texB = this.getInputData(2) || white; - var texA = this.getInputData(3) || white; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - if (!LGraphChannelsTexture._shader) { - LGraphChannelsTexture._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphChannelsTexture.pixel_shader - ); - } - var shader = LGraphChannelsTexture._shader; - - var w = Math.max(texR.width, texG.width, texB.width, texA.width); - var h = Math.max( - texR.height, - texG.height, - texB.height, - texA.height - ); - var type = - this.properties.precision == LGraphTexture.HIGH - ? LGraphTexture.HIGH_PRECISION_FORMAT - : gl.UNSIGNED_BYTE; - - if ( - !this._texture || - this._texture.width != w || - this._texture.height != h || - this._texture.type != type - ) { - this._texture = new GL.Texture(w, h, { - type: type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - var color = this._color; - color[0] = this.properties.R; - color[1] = this.properties.G; - color[2] = this.properties.B; - color[3] = this.properties.A; - var uniforms = this._uniforms; - - this._texture.drawTo(function() { - texR.bind(0); - texG.bind(1); - texB.bind(2); - texA.bind(3); - shader.uniforms(uniforms).draw(mesh); - }); - this.setOutputData(0, this._texture); - }; - - LGraphChannelsTexture.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_textureR;\n\ - uniform sampler2D u_textureG;\n\ - uniform sampler2D u_textureB;\n\ - uniform sampler2D u_textureA;\n\ - uniform vec4 u_color;\n\ - \n\ - void main() {\n\ - gl_FragColor = u_color * vec4( \ - texture2D(u_textureR, v_coord).r,\ - texture2D(u_textureG, v_coord).r,\ - texture2D(u_textureB, v_coord).r,\ - texture2D(u_textureA, v_coord).r);\n\ - }\n\ - "; - - LiteGraph.registerNodeType( - "texture/channelsTexture", - LGraphChannelsTexture - ); - - // Texture Color ***************************************** - function LGraphTextureColor() { - this.addOutput("Texture", "Texture"); - - this._tex_color = vec4.create(); - this.properties = { - color: vec4.create(), - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureColor.title = "Color"; - LGraphTextureColor.desc = - "Generates a 1x1 texture with a constant color"; - - LGraphTextureColor.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureColor.prototype.onDrawBackground = function(ctx) { - var c = this.properties.color; - ctx.fillStyle = - "rgb(" + - Math.floor(clamp(c[0], 0, 1) * 255) + - "," + - Math.floor(clamp(c[1], 0, 1) * 255) + - "," + - Math.floor(clamp(c[2], 0, 1) * 255) + - ")"; - if (this.flags.collapsed) { - this.boxcolor = ctx.fillStyle; - } else { - ctx.fillRect(0, 0, this.size[0], this.size[1]); - } - }; - - LGraphTextureColor.prototype.onExecute = function() { - var type = - this.properties.precision == LGraphTexture.HIGH - ? LGraphTexture.HIGH_PRECISION_FORMAT - : gl.UNSIGNED_BYTE; - - if (!this._tex || this._tex.type != type) { - this._tex = new GL.Texture(1, 1, { - format: gl.RGBA, - type: type, - minFilter: gl.NEAREST - }); - } - var color = this.properties.color; - - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - switch (input.name) { - case "RGB": - case "RGBA": - color.set(v); - break; - case "R": - color[0] = v; - break; - case "G": - color[1] = v; - break; - case "B": - color[2] = v; - break; - case "A": - color[3] = v; - break; - } - } - } - - if (vec4.sqrDist(this._tex_color, color) > 0.001) { - this._tex_color.set(color); - this._tex.fill(color); - } - this.setOutputData(0, this._tex); - }; - - LGraphTextureColor.prototype.onGetInputs = function() { - return [ - ["RGB", "vec3"], - ["RGBA", "vec4"], - ["R", "number"], - ["G", "number"], - ["B", "number"], - ["A", "number"] - ]; - }; - - LiteGraph.registerNodeType("texture/color", LGraphTextureColor); - - // Texture Channels to Texture ***************************************** - function LGraphTextureGradient() { - this.addInput("A", "color"); - this.addInput("B", "color"); - this.addOutput("Texture", "Texture"); - - this.properties = { - angle: 0, - scale: 1, - A: [0, 0, 0], - B: [1, 1, 1], - texture_size: 32 - }; - if (!LGraphTextureGradient._shader) { - LGraphTextureGradient._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureGradient.pixel_shader - ); - } - - this._uniforms = { - u_angle: 0, - u_colorA: vec3.create(), - u_colorB: vec3.create() - }; - } - - LGraphTextureGradient.title = "Gradient"; - LGraphTextureGradient.desc = "Generates a gradient"; - LGraphTextureGradient["@A"] = { type: "color" }; - LGraphTextureGradient["@B"] = { type: "color" }; - LGraphTextureGradient["@texture_size"] = { - type: "enum", - values: [32, 64, 128, 256, 512] - }; - - LGraphTextureGradient.prototype.onExecute = function() { - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = GL.Mesh.getScreenQuad(); - var shader = LGraphTextureGradient._shader; - - var A = this.getInputData(0); - if (!A) { - A = this.properties.A; - } - var B = this.getInputData(1); - if (!B) { - B = this.properties.B; - } - - //angle and scale - for (var i = 2; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - this.properties[input.name] = v; - } - - var uniforms = this._uniforms; - this._uniforms.u_angle = this.properties.angle * DEG2RAD; - this._uniforms.u_scale = this.properties.scale; - vec3.copy(uniforms.u_colorA, A); - vec3.copy(uniforms.u_colorB, B); - - var size = parseInt(this.properties.texture_size); - if (!this._tex || this._tex.width != size) { - this._tex = new GL.Texture(size, size, { - format: gl.RGB, - filter: gl.LINEAR - }); - } - - this._tex.drawTo(function() { - shader.uniforms(uniforms).draw(mesh); - }); - this.setOutputData(0, this._tex); - }; - - LGraphTextureGradient.prototype.onGetInputs = function() { - return [["angle", "number"], ["scale", "number"]]; - }; - - LGraphTextureGradient.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform float u_angle;\n\ - uniform float u_scale;\n\ - uniform vec3 u_colorA;\n\ - uniform vec3 u_colorB;\n\ - \n\ - vec2 rotate(vec2 v, float angle)\n\ - {\n\ - vec2 result;\n\ - float _cos = cos(angle);\n\ - float _sin = sin(angle);\n\ - result.x = v.x * _cos - v.y * _sin;\n\ - result.y = v.x * _sin + v.y * _cos;\n\ - return result;\n\ - }\n\ - void main() {\n\ - float f = (rotate(u_scale * (v_coord - vec2(0.5)), u_angle) + vec2(0.5)).x;\n\ - vec3 color = mix(u_colorA,u_colorB,clamp(f,0.0,1.0));\n\ - gl_FragColor = vec4(color,1.0);\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/gradient", LGraphTextureGradient); - - // Texture Mix ***************************************** - function LGraphTextureMix() { - this.addInput("A", "Texture"); - this.addInput("B", "Texture"); - this.addInput("Mixer", "Texture"); - - this.addOutput("Texture", "Texture"); - this.properties = { factor: 0.5, size_from_biggest: true, invert: false, precision: LGraphTexture.DEFAULT }; - this._uniforms = { - u_textureA: 0, - u_textureB: 1, - u_textureMix: 2, - u_mix: vec4.create() - }; - } - - LGraphTextureMix.title = "Mix"; - LGraphTextureMix.desc = "Generates a texture mixing two textures"; - - LGraphTextureMix.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureMix.prototype.onExecute = function() { - var texA = this.getInputData(0); - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, texA); - return; - } - - var texB = this.getInputData(1); - if (!texA || !texB) { - return; - } - - var texMix = this.getInputData(2); - - var factor = this.getInputData(3); - - this._tex = LGraphTexture.getTargetTexture( - this.properties.size_from_biggest && texB.width > texA.width ? texB : texA, - this._tex, - this.properties.precision - ); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - var shader = null; - var uniforms = this._uniforms; - if (texMix) { - shader = LGraphTextureMix._shader_tex; - if (!shader) { - shader = LGraphTextureMix._shader_tex = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMix.pixel_shader, - { MIX_TEX: "" } - ); - } - } else { - shader = LGraphTextureMix._shader_factor; - if (!shader) { - shader = LGraphTextureMix._shader_factor = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMix.pixel_shader - ); - } - var f = factor == null ? this.properties.factor : factor; - uniforms.u_mix.set([f, f, f, f]); - } - - var invert = this.properties.invert; - - this._tex.drawTo(function() { - texA.bind( invert ? 1 : 0 ); - texB.bind( invert ? 0 : 1 ); - if (texMix) { - texMix.bind(2); - } - shader.uniforms(uniforms).draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureMix.prototype.onGetInputs = function() { - return [["factor", "number"]]; - }; - - LGraphTextureMix.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_textureA;\n\ - uniform sampler2D u_textureB;\n\ - #ifdef MIX_TEX\n\ - uniform sampler2D u_textureMix;\n\ - #else\n\ - uniform vec4 u_mix;\n\ - #endif\n\ - \n\ - void main() {\n\ - #ifdef MIX_TEX\n\ - vec4 f = texture2D(u_textureMix, v_coord);\n\ - #else\n\ - vec4 f = u_mix;\n\ - #endif\n\ - gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/mix", LGraphTextureMix); - - // Texture Edges detection ***************************************** - function LGraphTextureEdges() { - this.addInput("Tex.", "Texture"); - - this.addOutput("Edges", "Texture"); - this.properties = { - invert: true, - threshold: false, - factor: 1, - precision: LGraphTexture.DEFAULT - }; - - if (!LGraphTextureEdges._shader) { - LGraphTextureEdges._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureEdges.pixel_shader - ); - } - } - - LGraphTextureEdges.title = "Edges"; - LGraphTextureEdges.desc = "Detects edges"; - - LGraphTextureEdges.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureEdges.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, tex); - return; - } - - if (!tex) { - return; - } - - this._tex = LGraphTexture.getTargetTexture( - tex, - this._tex, - this.properties.precision - ); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureEdges._shader; - var invert = this.properties.invert; - var factor = this.properties.factor; - var threshold = this.properties.threshold ? 1 : 0; - - this._tex.drawTo(function() { - tex.bind(0); - shader - .uniforms({ - u_texture: 0, - u_isize: [1 / tex.width, 1 / tex.height], - u_factor: factor, - u_threshold: threshold, - u_invert: invert ? 1 : 0 - }) - .draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureEdges.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_isize;\n\ - uniform int u_invert;\n\ - uniform float u_factor;\n\ - uniform float u_threshold;\n\ - \n\ - void main() {\n\ - vec4 center = texture2D(u_texture, v_coord);\n\ - vec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\ - vec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\ - vec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\ - vec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\ - vec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\ - diff *= u_factor;\n\ - if(u_invert == 1)\n\ - diff.xyz = vec3(1.0) - diff.xyz;\n\ - if( u_threshold == 0.0 )\n\ - gl_FragColor = vec4( diff.xyz, center.a );\n\ - else\n\ - gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\ - }\n\ - "; - - LiteGraph.registerNodeType("texture/edges", LGraphTextureEdges); - - // Texture Depth ***************************************** - function LGraphTextureDepthRange() { - this.addInput("Texture", "Texture"); - this.addInput("Distance", "number"); - this.addInput("Range", "number"); - this.addOutput("Texture", "Texture"); - this.properties = { - distance: 100, - range: 50, - only_depth: false, - high_precision: false - }; - this._uniforms = { - u_texture: 0, - u_distance: 100, - u_range: 50, - u_camera_planes: null - }; - } - - LGraphTextureDepthRange.title = "Depth Range"; - LGraphTextureDepthRange.desc = "Generates a texture with a depth range"; - - LGraphTextureDepthRange.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - if (!tex) { - return; - } - - var precision = gl.UNSIGNED_BYTE; - if (this.properties.high_precision) { - precision = gl.half_float_ext ? gl.HALF_FLOAT_OES : gl.FLOAT; - } - - if ( - !this._temp_texture || - this._temp_texture.type != precision || - this._temp_texture.width != tex.width || - this._temp_texture.height != tex.height - ) { - this._temp_texture = new GL.Texture(tex.width, tex.height, { - type: precision, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - var uniforms = this._uniforms; - - //iterations - var distance = this.properties.distance; - if (this.isInputConnected(1)) { - distance = this.getInputData(1); - this.properties.distance = distance; - } - - var range = this.properties.range; - if (this.isInputConnected(2)) { - range = this.getInputData(2); - this.properties.range = range; - } - - uniforms.u_distance = distance; - uniforms.u_range = range; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var mesh = Mesh.getScreenQuad(); - if (!LGraphTextureDepthRange._shader) { - LGraphTextureDepthRange._shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureDepthRange.pixel_shader - ); - LGraphTextureDepthRange._shader_onlydepth = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureDepthRange.pixel_shader, - { ONLY_DEPTH: "" } - ); - } - var shader = this.properties.only_depth - ? LGraphTextureDepthRange._shader_onlydepth - : LGraphTextureDepthRange._shader; - - //NEAR AND FAR PLANES - var planes = null; - if (tex.near_far_planes) { - planes = tex.near_far_planes; - } else if (window.LS && LS.Renderer._main_camera) { - planes = LS.Renderer._main_camera._uniforms.u_camera_planes; - } else { - planes = [0.1, 1000]; - } //hardcoded - uniforms.u_camera_planes = planes; - - this._temp_texture.drawTo(function() { - tex.bind(0); - shader.uniforms(uniforms).draw(mesh); - }); - - this._temp_texture.near_far_planes = planes; - this.setOutputData(0, this._temp_texture); - }; - - LGraphTextureDepthRange.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform float u_distance;\n\ - uniform float u_range;\n\ - \n\ - float LinearDepth()\n\ - {\n\ - float zNear = u_camera_planes.x;\n\ - float zFar = u_camera_planes.y;\n\ - float depth = texture2D(u_texture, v_coord).x;\n\ - depth = depth * 2.0 - 1.0;\n\ - return zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ - }\n\ - \n\ - void main() {\n\ - float depth = LinearDepth();\n\ - #ifdef ONLY_DEPTH\n\ - gl_FragColor = vec4(depth);\n\ - #else\n\ - float diff = abs(depth * u_camera_planes.y - u_distance);\n\ - float dof = 1.0;\n\ - if(diff <= u_range)\n\ - dof = diff / u_range;\n\ - gl_FragColor = vec4(dof);\n\ - #endif\n\ - }\n\ - "; - - LiteGraph.registerNodeType( "texture/depth_range", LGraphTextureDepthRange ); - - - // Texture Depth ***************************************** - function LGraphTextureLinearDepth() { - this.addInput("Texture", "Texture"); - this.addOutput("Texture", "Texture"); - this.properties = { - precision: LGraphTexture.DEFAULT, - invert: false - }; - this._uniforms = { - u_texture: 0, - u_camera_planes: null, //filled later - u_ires: vec2.create() - }; - } - - LGraphTextureLinearDepth.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureLinearDepth.title = "Linear Depth"; - LGraphTextureLinearDepth.desc = "Creates a color texture with linear depth"; - - LGraphTextureLinearDepth.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - if (!tex || (tex.format != gl.DEPTH_COMPONENT && tex.format != gl.DEPTH_STENCIL) ) { - return; - } - - var precision = this.properties.precision == LGraphTexture.HIGH ? gl.HIGH_PRECISION_FORMAT : gl.UNSIGNED_BYTE; - - if ( !this._temp_texture || this._temp_texture.type != precision || this._temp_texture.width != tex.width || this._temp_texture.height != tex.height ) { - this._temp_texture = new GL.Texture(tex.width, tex.height, { - type: precision, - format: gl.RGB, - filter: gl.LINEAR - }); - } - - var uniforms = this._uniforms; - uniforms.u_invert = this.properties.invert ? 1 : 0; - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - var mesh = Mesh.getScreenQuad(); - if(!LGraphTextureLinearDepth._shader) - LGraphTextureLinearDepth._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureLinearDepth.pixel_shader); - var shader = LGraphTextureLinearDepth._shader; - - //NEAR AND FAR PLANES - var planes = null; - if (tex.near_far_planes) { - planes = tex.near_far_planes; - } else if (window.LS && LS.Renderer._main_camera) { - planes = LS.Renderer._main_camera._uniforms.u_camera_planes; - } else { - planes = [0.1, 1000]; - } //hardcoded - uniforms.u_camera_planes = planes; - //uniforms.u_ires.set([1/tex.width, 1/tex.height]); - uniforms.u_ires.set([0,0]); - - this._temp_texture.drawTo(function() { - tex.bind(0); - shader.uniforms(uniforms).draw(mesh); - }); - - this._temp_texture.near_far_planes = planes; - this.setOutputData(0, this._temp_texture); - }; - - LGraphTextureLinearDepth.pixel_shader = - "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_camera_planes;\n\ - uniform int u_invert;\n\ - uniform vec2 u_ires;\n\ - \n\ - void main() {\n\ - float zNear = u_camera_planes.x;\n\ - float zFar = u_camera_planes.y;\n\ - float depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\ - float f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\ - if( u_invert == 1 )\n\ - f = 1.0 - f;\n\ - gl_FragColor = vec4(vec3(f),1.0);\n\ - }\n\ - "; - - LiteGraph.registerNodeType( "texture/linear_depth", LGraphTextureLinearDepth ); - - // Texture Blur ***************************************** - function LGraphTextureBlur() { - this.addInput("Texture", "Texture"); - this.addInput("Iterations", "number"); - this.addInput("Intensity", "number"); - this.addOutput("Blurred", "Texture"); - this.properties = { - intensity: 1, - iterations: 1, - preserve_aspect: false, - scale: [1, 1], - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureBlur.title = "Blur"; - LGraphTextureBlur.desc = "Blur a texture"; - - LGraphTextureBlur.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureBlur.max_iterations = 20; - - LGraphTextureBlur.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var temp = this._final_texture; - - if ( - !temp || - temp.width != tex.width || - temp.height != tex.height || - temp.type != tex.type - ) { - //we need two textures to do the blurring - //this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); - temp = this._final_texture = new GL.Texture( - tex.width, - tex.height, - { type: tex.type, format: gl.RGBA, filter: gl.LINEAR } - ); - } - - //iterations - var iterations = this.properties.iterations; - if (this.isInputConnected(1)) { - iterations = this.getInputData(1); - this.properties.iterations = iterations; - } - iterations = Math.min( - Math.floor(iterations), - LGraphTextureBlur.max_iterations - ); - if (iterations == 0) { - //skip blurring - this.setOutputData(0, tex); - return; - } - - var intensity = this.properties.intensity; - if (this.isInputConnected(2)) { - intensity = this.getInputData(2); - this.properties.intensity = intensity; - } - - //blur sometimes needs an aspect correction - var aspect = LiteGraph.camera_aspect; - if (!aspect && window.gl !== undefined) { - aspect = gl.canvas.height / gl.canvas.width; - } - if (!aspect) { - aspect = 1; - } - aspect = this.properties.preserve_aspect ? aspect : 1; - - var scale = this.properties.scale || [1, 1]; - tex.applyBlur(aspect * scale[0], scale[1], intensity, temp); - for (var i = 1; i < iterations; ++i) { - temp.applyBlur( - aspect * scale[0] * (i + 1), - scale[1] * (i + 1), - intensity - ); - } - - this.setOutputData(0, temp); - }; - - /* -LGraphTextureBlur.pixel_shader = "precision highp float;\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_offset;\n\ - uniform float u_intensity;\n\ - void main() {\n\ - vec4 sum = vec4(0.0);\n\ - vec4 center = texture2D(u_texture, v_coord);\n\ - sum += texture2D(u_texture, v_coord + u_offset * -4.0) * 0.05/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * -3.0) * 0.09/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * -2.0) * 0.12/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * -1.0) * 0.15/0.98;\n\ - sum += center * 0.16/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 4.0) * 0.05/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 3.0) * 0.09/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\ - sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\ - gl_FragColor = u_intensity * sum;\n\ - }\n\ - "; -*/ - - LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur); - - //Independent glow FX - //based on https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/ - function FXGlow() - { - this.intensity = 0.5; - this.persistence = 0.6; - this.iterations = 8; - this.threshold = 0.8; - this.scale = 1; - - this.dirt_texture = null; - this.dirt_factor = 0.5; - - this._textures = []; - this._uniforms = { - u_intensity: 1, - u_texture: 0, - u_glow_texture: 1, - u_threshold: 0, - u_texel_size: vec2.create() - }; - } - - FXGlow.prototype.applyFX = function( tex, output_texture, glow_texture, average_texture ) { - - var width = tex.width; - var height = tex.height; - - var texture_info = { - format: tex.format, - type: tex.type, - minFilter: GL.LINEAR, - magFilter: GL.LINEAR, - wrap: gl.CLAMP_TO_EDGE - }; - - var uniforms = this._uniforms; - var textures = this._textures; - - //cut - var shader = FXGlow._cut_shader; - if (!shader) { - shader = FXGlow._cut_shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - FXGlow.cut_pixel_shader - ); - } - - gl.disable(gl.DEPTH_TEST); - gl.disable(gl.BLEND); - - uniforms.u_threshold = this.threshold; - var currentDestination = (textures[0] = GL.Texture.getTemporary( - width, - height, - texture_info - )); - tex.blit( currentDestination, shader.uniforms(uniforms) ); - var currentSource = currentDestination; - - var iterations = this.iterations; - iterations = clamp(iterations, 1, 16) | 0; - var texel_size = uniforms.u_texel_size; - var intensity = this.intensity; - - uniforms.u_intensity = 1; - uniforms.u_delta = this.scale; //1 - - //downscale/upscale shader - var shader = FXGlow._shader; - if (!shader) { - shader = FXGlow._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - FXGlow.scale_pixel_shader - ); - } - - var i = 1; - //downscale - for (; i < iterations; i++) { - width = width >> 1; - if ((height | 0) > 1) { - height = height >> 1; - } - if (width < 2) { - break; - } - currentDestination = textures[i] = GL.Texture.getTemporary( - width, - height, - texture_info - ); - texel_size[0] = 1 / currentSource.width; - texel_size[1] = 1 / currentSource.height; - currentSource.blit( - currentDestination, - shader.uniforms(uniforms) - ); - currentSource = currentDestination; - } - - //average - if (average_texture) { - texel_size[0] = 1 / currentSource.width; - texel_size[1] = 1 / currentSource.height; - uniforms.u_intensity = intensity; - uniforms.u_delta = 1; - currentSource.blit(average_texture, shader.uniforms(uniforms)); - } - - //upscale and blend - gl.enable(gl.BLEND); - gl.blendFunc(gl.ONE, gl.ONE); - uniforms.u_intensity = this.persistence; - uniforms.u_delta = 0.5; - - // i-=2 => -1 to point to last element in array, -1 to go to texture above - for ( i -= 2; i >= 0; i-- ) - { - currentDestination = textures[i]; - textures[i] = null; - texel_size[0] = 1 / currentSource.width; - texel_size[1] = 1 / currentSource.height; - currentSource.blit( - currentDestination, - shader.uniforms(uniforms) - ); - GL.Texture.releaseTemporary(currentSource); - currentSource = currentDestination; - } - gl.disable(gl.BLEND); - - //glow - if (glow_texture) { - currentSource.blit(glow_texture); - } - - //final composition - if ( output_texture ) { - var final_texture = output_texture; - var dirt_texture = this.dirt_texture; - var dirt_factor = this.dirt_factor; - uniforms.u_intensity = intensity; - - shader = dirt_texture - ? FXGlow._dirt_final_shader - : FXGlow._final_shader; - if (!shader) { - if (dirt_texture) { - shader = FXGlow._dirt_final_shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - FXGlow.final_pixel_shader, - { USE_DIRT: "" } - ); - } else { - shader = FXGlow._final_shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - FXGlow.final_pixel_shader - ); - } - } - - final_texture.drawTo(function() { - tex.bind(0); - currentSource.bind(1); - if (dirt_texture) { - shader.setUniform("u_dirt_factor", dirt_factor); - shader.setUniform( - "u_dirt_texture", - dirt_texture.bind(2) - ); - } - shader.toViewport(uniforms); - }); - } - - GL.Texture.releaseTemporary(currentSource); - }; - - FXGlow.cut_pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_threshold;\n\ - void main() {\n\ - gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\ - }"; - - FXGlow.scale_pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec2 u_texel_size;\n\ - uniform float u_delta;\n\ - uniform float u_intensity;\n\ - \n\ - vec4 sampleBox(vec2 uv) {\n\ - vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ - vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\ - return s * 0.25;\n\ - }\n\ - void main() {\n\ - gl_FragColor = u_intensity * sampleBox( v_coord );\n\ - }"; - - FXGlow.final_pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform sampler2D u_glow_texture;\n\ - #ifdef USE_DIRT\n\ - uniform sampler2D u_dirt_texture;\n\ - #endif\n\ - uniform vec2 u_texel_size;\n\ - uniform float u_delta;\n\ - uniform float u_intensity;\n\ - uniform float u_dirt_factor;\n\ - \n\ - vec4 sampleBox(vec2 uv) {\n\ - vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ - vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\ - return s * 0.25;\n\ - }\n\ - void main() {\n\ - vec4 glow = sampleBox( v_coord );\n\ - #ifdef USE_DIRT\n\ - glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\ - #endif\n\ - gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\ - }"; - - - // Texture Glow ***************************************** - function LGraphTextureGlow() { - this.addInput("in", "Texture"); - this.addInput("dirt", "Texture"); - this.addOutput("out", "Texture"); - this.addOutput("glow", "Texture"); - this.properties = { - enabled: true, - intensity: 1, - persistence: 0.99, - iterations: 16, - threshold: 0, - scale: 1, - dirt_factor: 0.5, - precision: LGraphTexture.DEFAULT - }; - - this.fx = new FXGlow(); - } - - LGraphTextureGlow.title = "Glow"; - LGraphTextureGlow.desc = "Filters a texture giving it a glow effect"; - - LGraphTextureGlow.widgets_info = { - iterations: { - type: "number", - min: 0, - max: 16, - step: 1, - precision: 0 - }, - threshold: { - type: "number", - min: 0, - max: 10, - step: 0.01, - precision: 2 - }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureGlow.prototype.onGetInputs = function() { - return [ - ["enabled", "boolean"], - ["threshold", "number"], - ["intensity", "number"], - ["persistence", "number"], - ["iterations", "number"], - ["dirt_factor", "number"] - ]; - }; - - LGraphTextureGlow.prototype.onGetOutputs = function() { - return [["average", "Texture"]]; - }; - - LGraphTextureGlow.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (!this.isAnyOutputConnected()) { - return; - } //saves work - - if ( - this.properties.precision === LGraphTexture.PASS_THROUGH || - this.getInputOrProperty("enabled") === false - ) { - this.setOutputData(0, tex); - return; - } - - var width = tex.width; - var height = tex.height; - - var fx = this.fx; - fx.threshold = this.getInputOrProperty("threshold"); - fx.iterations = this.getInputOrProperty("iterations"); - fx.intensity = this.getInputOrProperty("intensity"); - fx.persistence = this.getInputOrProperty("persistence"); - fx.dirt_texture = this.getInputData(1); - fx.dirt_factor = this.getInputOrProperty("dirt_factor"); - fx.scale = this.properties.scale; - - var type = LGraphTexture.getTextureType( this.properties.precision, tex ); - - var average_texture = null; - if (this.isOutputConnected(2)) { - average_texture = this._average_texture; - if ( - !average_texture || - average_texture.type != tex.type || - average_texture.format != tex.format - ) { - average_texture = this._average_texture = new GL.Texture( - 1, - 1, - { - type: tex.type, - format: tex.format, - filter: gl.LINEAR - } - ); - } - } - - var glow_texture = null; - if (this.isOutputConnected(1)) { - glow_texture = this._glow_texture; - if ( - !glow_texture || - glow_texture.width != tex.width || - glow_texture.height != tex.height || - glow_texture.type != type || - glow_texture.format != tex.format - ) { - glow_texture = this._glow_texture = new GL.Texture( - tex.width, - tex.height, - { type: type, format: tex.format, filter: gl.LINEAR } - ); - } - } - - var final_texture = null; - if (this.isOutputConnected(0)) { - final_texture = this._final_texture; - if ( - !final_texture || - final_texture.width != tex.width || - final_texture.height != tex.height || - final_texture.type != type || - final_texture.format != tex.format - ) { - final_texture = this._final_texture = new GL.Texture( - tex.width, - tex.height, - { type: type, format: tex.format, filter: gl.LINEAR } - ); - } - - } - - //apply FX - fx.applyFX(tex, final_texture, glow_texture, average_texture ); - - if (this.isOutputConnected(0)) - this.setOutputData(0, final_texture); - - if (this.isOutputConnected(1)) - this.setOutputData(1, average_texture); - - if (this.isOutputConnected(2)) - this.setOutputData(2, glow_texture); - }; - - LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow); - - // Texture Filter ***************************************** - function LGraphTextureKuwaharaFilter() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = { intensity: 1, radius: 5 }; - } - - LGraphTextureKuwaharaFilter.title = "Kuwahara Filter"; - LGraphTextureKuwaharaFilter.desc = - "Filters a texture giving an artistic oil canvas painting"; - - LGraphTextureKuwaharaFilter.max_radius = 10; - LGraphTextureKuwaharaFilter._shaders = []; - - LGraphTextureKuwaharaFilter.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var temp = this._temp_texture; - - if ( - !temp || - temp.width != tex.width || - temp.height != tex.height || - temp.type != tex.type - ) { - this._temp_texture = new GL.Texture(tex.width, tex.height, { - type: tex.type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - //iterations - var radius = this.properties.radius; - radius = Math.min( - Math.floor(radius), - LGraphTextureKuwaharaFilter.max_radius - ); - if (radius == 0) { - //skip blurring - this.setOutputData(0, tex); - return; - } - - var intensity = this.properties.intensity; - - //blur sometimes needs an aspect correction - var aspect = LiteGraph.camera_aspect; - if (!aspect && window.gl !== undefined) { - aspect = gl.canvas.height / gl.canvas.width; - } - if (!aspect) { - aspect = 1; - } - aspect = this.properties.preserve_aspect ? aspect : 1; - - if (!LGraphTextureKuwaharaFilter._shaders[radius]) { - LGraphTextureKuwaharaFilter._shaders[radius] = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureKuwaharaFilter.pixel_shader, - { RADIUS: radius.toFixed(0) } - ); - } - - var shader = LGraphTextureKuwaharaFilter._shaders[radius]; - var mesh = GL.Mesh.getScreenQuad(); - tex.bind(0); - - this._temp_texture.drawTo(function() { - shader - .uniforms({ - u_texture: 0, - u_intensity: intensity, - u_resolution: [tex.width, tex.height], - u_iResolution: [1 / tex.width, 1 / tex.height] - }) - .draw(mesh); - }); - - this.setOutputData(0, this._temp_texture); - }; - - //from https://www.shadertoy.com/view/MsXSz4 - LGraphTextureKuwaharaFilter.pixel_shader = - "\n\ -precision highp float;\n\ -varying vec2 v_coord;\n\ -uniform sampler2D u_texture;\n\ -uniform float u_intensity;\n\ -uniform vec2 u_resolution;\n\ -uniform vec2 u_iResolution;\n\ -#ifndef RADIUS\n\ - #define RADIUS 7\n\ -#endif\n\ -void main() {\n\ -\n\ - const int radius = RADIUS;\n\ - vec2 fragCoord = v_coord;\n\ - vec2 src_size = u_iResolution;\n\ - vec2 uv = v_coord;\n\ - float n = float((radius + 1) * (radius + 1));\n\ - int i;\n\ - int j;\n\ - vec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\ - vec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\ - vec3 c;\n\ - \n\ - for (int j = -radius; j <= 0; ++j) {\n\ - for (int i = -radius; i <= 0; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m0 += c;\n\ - s0 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = -radius; j <= 0; ++j) {\n\ - for (int i = 0; i <= radius; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m1 += c;\n\ - s1 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = 0; j <= radius; ++j) {\n\ - for (int i = 0; i <= radius; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m2 += c;\n\ - s2 += c * c;\n\ - }\n\ - }\n\ - \n\ - for (int j = 0; j <= radius; ++j) {\n\ - for (int i = -radius; i <= 0; ++i) {\n\ - c = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\ - m3 += c;\n\ - s3 += c * c;\n\ - }\n\ - }\n\ - \n\ - float min_sigma2 = 1e+2;\n\ - m0 /= n;\n\ - s0 = abs(s0 / n - m0 * m0);\n\ - \n\ - float sigma2 = s0.r + s0.g + s0.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m0, 1.0);\n\ - }\n\ - \n\ - m1 /= n;\n\ - s1 = abs(s1 / n - m1 * m1);\n\ - \n\ - sigma2 = s1.r + s1.g + s1.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m1, 1.0);\n\ - }\n\ - \n\ - m2 /= n;\n\ - s2 = abs(s2 / n - m2 * m2);\n\ - \n\ - sigma2 = s2.r + s2.g + s2.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m2, 1.0);\n\ - }\n\ - \n\ - m3 /= n;\n\ - s3 = abs(s3 / n - m3 * m3);\n\ - \n\ - sigma2 = s3.r + s3.g + s3.b;\n\ - if (sigma2 < min_sigma2) {\n\ - min_sigma2 = sigma2;\n\ - gl_FragColor = vec4(m3, 1.0);\n\ - }\n\ -}\n\ -"; - - LiteGraph.registerNodeType( - "texture/kuwahara", - LGraphTextureKuwaharaFilter - ); - - // Texture ***************************************** - function LGraphTextureXDoGFilter() { - this.addInput("Texture", "Texture"); - this.addOutput("Filtered", "Texture"); - this.properties = { - sigma: 1.4, - k: 1.6, - p: 21.7, - epsilon: 79, - phi: 0.017 - }; - } - - LGraphTextureXDoGFilter.title = "XDoG Filter"; - LGraphTextureXDoGFilter.desc = - "Filters a texture giving an artistic ink style"; - - LGraphTextureXDoGFilter.max_radius = 10; - LGraphTextureXDoGFilter._shaders = []; - - LGraphTextureXDoGFilter.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var temp = this._temp_texture; - if ( - !temp || - temp.width != tex.width || - temp.height != tex.height || - temp.type != tex.type - ) { - this._temp_texture = new GL.Texture(tex.width, tex.height, { - type: tex.type, - format: gl.RGBA, - filter: gl.LINEAR - }); - } - - if (!LGraphTextureXDoGFilter._xdog_shader) { - LGraphTextureXDoGFilter._xdog_shader = new GL.Shader( - Shader.SCREEN_VERTEX_SHADER, - LGraphTextureXDoGFilter.xdog_pixel_shader - ); - } - var shader = LGraphTextureXDoGFilter._xdog_shader; - var mesh = GL.Mesh.getScreenQuad(); - - var sigma = this.properties.sigma; - var k = this.properties.k; - var p = this.properties.p; - var epsilon = this.properties.epsilon; - var phi = this.properties.phi; - tex.bind(0); - this._temp_texture.drawTo(function() { - shader - .uniforms({ - src: 0, - sigma: sigma, - k: k, - p: p, - epsilon: epsilon, - phi: phi, - cvsWidth: tex.width, - cvsHeight: tex.height - }) - .draw(mesh); - }); - - this.setOutputData(0, this._temp_texture); - }; - - //from https://github.com/RaymondMcGuire/GPU-Based-Image-Processing-Tools/blob/master/lib_webgl/scripts/main.js - LGraphTextureXDoGFilter.xdog_pixel_shader = - "\n\ -precision highp float;\n\ -uniform sampler2D src;\n\n\ -uniform float cvsHeight;\n\ -uniform float cvsWidth;\n\n\ -uniform float sigma;\n\ -uniform float k;\n\ -uniform float p;\n\ -uniform float epsilon;\n\ -uniform float phi;\n\ -varying vec2 v_coord;\n\n\ -float cosh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float cosH = (tmp + 1.0 / tmp) / 2.0;\n\ - return cosH;\n\ -}\n\n\ -float tanh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\ - return tanH;\n\ -}\n\n\ -float sinh(float val)\n\ -{\n\ - float tmp = exp(val);\n\ - float sinH = (tmp - 1.0 / tmp) / 2.0;\n\ - return sinH;\n\ -}\n\n\ -void main(void){\n\ - vec3 destColor = vec3(0.0);\n\ - float tFrag = 1.0 / cvsHeight;\n\ - float sFrag = 1.0 / cvsWidth;\n\ - vec2 Frag = vec2(sFrag,tFrag);\n\ - vec2 uv = gl_FragCoord.st;\n\ - float twoSigmaESquared = 2.0 * sigma * sigma;\n\ - float twoSigmaRSquared = twoSigmaESquared * k * k;\n\ - int halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\ - const int MAX_NUM_ITERATION = 99999;\n\ - vec2 sum = vec2(0.0);\n\ - vec2 norm = vec2(0.0);\n\n\ - for(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\ - int i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\ - int j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\ - float d = length(vec2(i,j));\n\ - vec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\ - exp( -d * d / twoSigmaRSquared ));\n\n\ - vec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\ - norm += kernel;\n\ - sum += kernel * L;\n\ - }\n\n\ - sum /= norm;\n\n\ - float H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\ - float edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\ - destColor = vec3(edge);\n\ - gl_FragColor = vec4(destColor, 1.0);\n\ -}"; - - LiteGraph.registerNodeType("texture/xDoG", LGraphTextureXDoGFilter); - - // Texture Webcam ***************************************** - function LGraphTextureWebcam() { - this.addOutput("Webcam", "Texture"); - this.properties = { texture_name: "", facingMode: "user" }; - this.boxcolor = "black"; - this.version = 0; - } - - LGraphTextureWebcam.title = "Webcam"; - LGraphTextureWebcam.desc = "Webcam texture"; - - LGraphTextureWebcam.is_webcam_open = false; - - LGraphTextureWebcam.prototype.openStream = function() { - if (!navigator.getUserMedia) { - //console.log('getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags'); - return; - } - - this._waiting_confirmation = true; - - // Not showing vendor prefixes. - var constraints = { - audio: false, - video: { facingMode: this.properties.facingMode } - }; - navigator.mediaDevices - .getUserMedia(constraints) - .then(this.streamReady.bind(this)) - .catch(onFailSoHard); - - var that = this; - function onFailSoHard(e) { - LGraphTextureWebcam.is_webcam_open = false; - console.log("Webcam rejected", e); - that._webcam_stream = false; - that.boxcolor = "red"; - that.trigger("stream_error"); - } - }; - - LGraphTextureWebcam.prototype.closeStream = function() { - if (this._webcam_stream) { - var tracks = this._webcam_stream.getTracks(); - if (tracks.length) { - for (var i = 0; i < tracks.length; ++i) { - tracks[i].stop(); - } - } - LGraphTextureWebcam.is_webcam_open = false; - this._webcam_stream = null; - this._video = null; - this.boxcolor = "black"; - this.trigger("stream_closed"); - } - }; - - LGraphTextureWebcam.prototype.streamReady = function(localMediaStream) { - this._webcam_stream = localMediaStream; - //this._waiting_confirmation = false; - this.boxcolor = "green"; - var video = this._video; - if (!video) { - video = document.createElement("video"); - video.autoplay = true; - video.srcObject = localMediaStream; - this._video = video; - //document.body.appendChild( video ); //debug - //when video info is loaded (size and so) - video.onloadedmetadata = function(e) { - // Ready to go. Do some stuff. - LGraphTextureWebcam.is_webcam_open = true; - console.log(e); - }; - } - this.trigger("stream_ready", video); - }; - - LGraphTextureWebcam.prototype.onPropertyChanged = function( - name, - value - ) { - if (name == "facingMode") { - this.properties.facingMode = value; - this.closeStream(); - this.openStream(); - } - }; - - LGraphTextureWebcam.prototype.onRemoved = function() { - if (!this._webcam_stream) { - return; - } - - var tracks = this._webcam_stream.getTracks(); - if (tracks.length) { - for (var i = 0; i < tracks.length; ++i) { - tracks[i].stop(); - } - } - - this._webcam_stream = null; - this._video = null; - }; - - LGraphTextureWebcam.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed || this.size[1] <= 20) { - return; - } - - if (!this._video) { - return; - } - - //render to graph canvas - ctx.save(); - if (!ctx.webgl) { - //reverse image - ctx.drawImage(this._video, 0, 0, this.size[0], this.size[1]); - } else { - if (this._video_texture) { - ctx.drawImage( - this._video_texture, - 0, - 0, - this.size[0], - this.size[1] - ); - } - } - ctx.restore(); - }; - - LGraphTextureWebcam.prototype.onExecute = function() { - if (this._webcam_stream == null && !this._waiting_confirmation) { - this.openStream(); - } - - if (!this._video || !this._video.videoWidth) { - return; - } - - var width = this._video.videoWidth; - var height = this._video.videoHeight; - - var temp = this._video_texture; - if (!temp || temp.width != width || temp.height != height) { - this._video_texture = new GL.Texture(width, height, { - format: gl.RGB, - filter: gl.LINEAR - }); - } - - this._video_texture.uploadImage(this._video); - this._video_texture.version = ++this.version; - - if (this.properties.texture_name) { - var container = LGraphTexture.getTexturesContainer(); - container[this.properties.texture_name] = this._video_texture; - } - - this.setOutputData(0, this._video_texture); - for (var i = 1; i < this.outputs.length; ++i) { - if (!this.outputs[i]) { - continue; - } - switch (this.outputs[i].name) { - case "width": - this.setOutputData(i, this._video.videoWidth); - break; - case "height": - this.setOutputData(i, this._video.videoHeight); - break; - } - } - }; - - LGraphTextureWebcam.prototype.onGetOutputs = function() { - return [ - ["width", "number"], - ["height", "number"], - ["stream_ready", LiteGraph.EVENT], - ["stream_closed", LiteGraph.EVENT], - ["stream_error", LiteGraph.EVENT] - ]; - }; - - LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam); - - //from https://github.com/spite/Wagner - function LGraphLensFX() { - this.addInput("in", "Texture"); - this.addInput("f", "number"); - this.addOutput("out", "Texture"); - this.properties = { - enabled: true, - factor: 1, - precision: LGraphTexture.LOW - }; - - this._uniforms = { u_texture: 0, u_factor: 1 }; - } - - LGraphLensFX.title = "Lens FX"; - LGraphLensFX.desc = "distortion and chromatic aberration"; - - LGraphLensFX.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphLensFX.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; - - LGraphLensFX.prototype.onExecute = function() { - var tex = this.getInputData(0); - if (!tex) { - return; - } - - if (!this.isOutputConnected(0)) { - return; - } //saves work - - if ( - this.properties.precision === LGraphTexture.PASS_THROUGH || - this.getInputOrProperty("enabled") === false - ) { - this.setOutputData(0, tex); - return; - } - - var temp = this._temp_texture; - if ( - !temp || - temp.width != tex.width || - temp.height != tex.height || - temp.type != tex.type - ) { - temp = this._temp_texture = new GL.Texture( - tex.width, - tex.height, - { type: tex.type, format: gl.RGBA, filter: gl.LINEAR } - ); - } - - var shader = LGraphLensFX._shader; - if (!shader) { - shader = LGraphLensFX._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphLensFX.pixel_shader - ); - } - - var factor = this.getInputData(1); - if (factor == null) { - factor = this.properties.factor; - } - - var uniforms = this._uniforms; - uniforms.u_factor = factor; - - //apply shader - gl.disable(gl.DEPTH_TEST); - temp.drawTo(function() { - tex.bind(0); - shader.uniforms(uniforms).draw(GL.Mesh.getScreenQuad()); - }); - - this.setOutputData(0, temp); - }; - - LGraphLensFX.pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform float u_factor;\n\ - vec2 barrelDistortion(vec2 coord, float amt) {\n\ - vec2 cc = coord - 0.5;\n\ - float dist = dot(cc, cc);\n\ - return coord + cc * dist * amt;\n\ - }\n\ - \n\ - float sat( float t )\n\ - {\n\ - return clamp( t, 0.0, 1.0 );\n\ - }\n\ - \n\ - float linterp( float t ) {\n\ - return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\ - }\n\ - \n\ - float remap( float t, float a, float b ) {\n\ - return sat( (t - a) / (b - a) );\n\ - }\n\ - \n\ - vec4 spectrum_offset( float t ) {\n\ - vec4 ret;\n\ - float lo = step(t,0.5);\n\ - float hi = 1.0-lo;\n\ - float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\ - ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\ - \n\ - return pow( ret, vec4(1.0/2.2) );\n\ - }\n\ - \n\ - const float max_distort = 2.2;\n\ - const int num_iter = 12;\n\ - const float reci_num_iter_f = 1.0 / float(num_iter);\n\ - \n\ - void main()\n\ - { \n\ - vec2 uv=v_coord;\n\ - vec4 sumcol = vec4(0.0);\n\ - vec4 sumw = vec4(0.0); \n\ - for ( int i=0; i= res)\n\ - break;\n\ - iCount++;\n\ - }\n\ - float nf = n/normK;\n\ - return nf*nf*nf*nf;\n\ - }\n\ - void main() {\n\ - vec2 uv = v_coord * u_scale * u_viewport + u_offset * u_scale;\n\ - vec4 color = vec4( pNoise( uv, u_octaves ) * u_amplitude );\n\ - gl_FragColor = color;\n\ - }"; - - LiteGraph.registerNodeType("texture/perlin", LGraphTexturePerlin); - - function LGraphTextureCanvas2D() { - this.addInput("v"); - this.addOutput("out", "Texture"); - this.properties = { - code: LGraphTextureCanvas2D.default_code, - width: 512, - height: 512, - clear: true, - precision: LGraphTexture.DEFAULT, - use_html_canvas: false - }; - this._func = null; - this._temp_texture = null; - this.compileCode(); - } - - LGraphTextureCanvas2D.title = "Canvas2D"; - LGraphTextureCanvas2D.desc = "Executes Canvas2D code inside a texture or the viewport."; - LGraphTextureCanvas2D.help = "Set width and height to 0 to match viewport size."; - - LGraphTextureCanvas2D.default_code = "//vars: canvas,ctx,time\nctx.fillStyle='red';\nctx.fillRect(0,0,50,50);\n"; - - LGraphTextureCanvas2D.widgets_info = { - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES }, - code: { type: "code" }, - width: { type: "number", precision: 0, step: 1 }, - height: { type: "number", precision: 0, step: 1 } - }; - - LGraphTextureCanvas2D.prototype.onPropertyChanged = function( name, value ) { - if (name == "code" ) - this.compileCode( value ); - } - - LGraphTextureCanvas2D.prototype.compileCode = function( code ) { - this._func = null; - if( !LiteGraph.allow_scripts ) - return; - - try { - this._func = new Function( "canvas", "ctx", "time", "script","v", code ); - this.boxcolor = "#00FF00"; - } catch (err) { - this.boxcolor = "#FF0000"; - console.error("Error parsing script"); - console.error(err); - } - }; - - LGraphTextureCanvas2D.prototype.onExecute = function() { - var func = this._func; - if (!func || !this.isOutputConnected(0)) { - return; - } - this.executeDraw( func ); - } - - LGraphTextureCanvas2D.prototype.executeDraw = function( func_context ) { - - var width = this.properties.width || gl.canvas.width; - var height = this.properties.height || gl.canvas.height; - var temp = this._temp_texture; - var type = LGraphTexture.getTextureType( this.properties.precision ); - if (!temp || temp.width != width || temp.height != height || temp.type != type ) { - temp = this._temp_texture = new GL.Texture(width, height, { - format: gl.RGBA, - filter: gl.LINEAR, - type: type - }); - } - - var v = this.getInputData(0); - - var properties = this.properties; - var that = this; - var time = this.graph.getTime(); - var ctx = gl; - var canvas = gl.canvas; - if( this.properties.use_html_canvas || !global.enableWebGLCanvas ) - { - if(!this._canvas) - { - canvas = this._canvas = createCanvas(width.height); - ctx = this._ctx = canvas.getContext("2d"); - } - else - { - canvas = this._canvas; - ctx = this._ctx; - } - canvas.width = width; - canvas.height = height; - } - - if(ctx == gl) //using Canvas2DtoWebGL - temp.drawTo(function() { - gl.start2D(); - if(properties.clear) - { - gl.clearColor(0,0,0,0); - gl.clear( gl.COLOR_BUFFER_BIT ); - } - - try { - if (func_context.draw) { - func_context.draw.call(that, canvas, ctx, time, func_context, v); - } else { - func_context.call(that, canvas, ctx, time, func_context,v); - } - that.boxcolor = "#00FF00"; - } catch (err) { - that.boxcolor = "#FF0000"; - console.error("Error executing script"); - console.error(err); - } - gl.finish2D(); - }); - else //rendering to offscreen canvas and uploading to texture - { - if(properties.clear) - ctx.clearRect(0,0,canvas.width,canvas.height); - - try { - if (func_context.draw) { - func_context.draw.call(this, canvas, ctx, time, func_context, v); - } else { - func_context.call(this, canvas, ctx, time, func_context,v); - } - this.boxcolor = "#00FF00"; - } catch (err) { - this.boxcolor = "#FF0000"; - console.error("Error executing script"); - console.error(err); - } - temp.uploadImage( canvas ); - } - - this.setOutputData(0, temp); - }; - - LiteGraph.registerNodeType("texture/canvas2D", LGraphTextureCanvas2D); - - // To do chroma keying ***************** - - function LGraphTextureMatte() { - this.addInput("in", "Texture"); - - this.addOutput("out", "Texture"); - this.properties = { - key_color: vec3.fromValues(0, 1, 0), - threshold: 0.8, - slope: 0.2, - precision: LGraphTexture.DEFAULT - }; - } - - LGraphTextureMatte.title = "Matte"; - LGraphTextureMatte.desc = "Extracts background"; - - LGraphTextureMatte.widgets_info = { - key_color: { widget: "color" }, - precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } - }; - - LGraphTextureMatte.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) { - return; - } //saves work - - var tex = this.getInputData(0); - - if (this.properties.precision === LGraphTexture.PASS_THROUGH) { - this.setOutputData(0, tex); - return; - } - - if (!tex) { - return; - } - - this._tex = LGraphTexture.getTargetTexture( - tex, - this._tex, - this.properties.precision - ); - - gl.disable(gl.BLEND); - gl.disable(gl.DEPTH_TEST); - - if (!this._uniforms) { - this._uniforms = { - u_texture: 0, - u_key_color: this.properties.key_color, - u_threshold: 1, - u_slope: 1 - }; - } - var uniforms = this._uniforms; - - var mesh = Mesh.getScreenQuad(); - var shader = LGraphTextureMatte._shader; - if (!shader) { - shader = LGraphTextureMatte._shader = new GL.Shader( - GL.Shader.SCREEN_VERTEX_SHADER, - LGraphTextureMatte.pixel_shader - ); - } - - uniforms.u_key_color = this.properties.key_color; - uniforms.u_threshold = this.properties.threshold; - uniforms.u_slope = this.properties.slope; - - this._tex.drawTo(function() { - tex.bind(0); - shader.uniforms(uniforms).draw(mesh); - }); - - this.setOutputData(0, this._tex); - }; - - LGraphTextureMatte.pixel_shader = - "precision highp float;\n\ - varying vec2 v_coord;\n\ - uniform sampler2D u_texture;\n\ - uniform vec3 u_key_color;\n\ - uniform float u_threshold;\n\ - uniform float u_slope;\n\ - \n\ - void main() {\n\ - vec3 color = texture2D( u_texture, v_coord ).xyz;\n\ - float diff = length( normalize(color) - normalize(u_key_color) );\n\ - float edge = u_threshold * (1.0 - u_slope);\n\ - float alpha = smoothstep( edge, u_threshold, diff);\n\ - gl_FragColor = vec4( color, alpha );\n\ - }"; - - LiteGraph.registerNodeType("texture/matte", LGraphTextureMatte); - - //*********************************** - function LGraphCubemapToTexture2D() { - this.addInput("in", "texture"); - this.addInput("yaw", "number"); - this.addOutput("out", "texture"); - this.properties = { yaw: 0 }; - } - - LGraphCubemapToTexture2D.title = "CubemapToTexture2D"; - LGraphCubemapToTexture2D.desc = "Transforms a CUBEMAP texture into a TEXTURE2D in Polar Representation"; - - LGraphCubemapToTexture2D.prototype.onExecute = function() { - if (!this.isOutputConnected(0)) - return; - - var tex = this.getInputData(0); - if ( !tex || tex.texture_type != GL.TEXTURE_CUBE_MAP ) - return; - if( this._last_tex && ( this._last_tex.height != tex.height || this._last_tex.type != tex.type )) - this._last_tex = null; - var yaw = this.getInputOrProperty("yaw"); - this._last_tex = GL.Texture.cubemapToTexture2D( tex, tex.height, this._last_tex, true, yaw ); - this.setOutputData( 0, this._last_tex ); - }; - - LiteGraph.registerNodeType( "texture/cubemapToTexture2D", LGraphCubemapToTexture2D ); -})(this); - + (function(global) { if (typeof GL == "undefined") @@ -28398,7 +28484,7 @@ float iqnoise( in vec2 x, float u, float v ){\n\ })(this); - + (function(global) { var LiteGraph = global.LiteGraph; @@ -30308,7 +30394,7 @@ function LGraphGeometryDisplace() { -})(this); +})(this); (function(global) { var LiteGraph = global.LiteGraph; var LGraphTexture = global.LGraphTexture; @@ -31097,7 +31183,7 @@ function LGraphGeometryDisplace() { global.LGraphFXVigneting = LGraphFXVigneting; } })(this); - + (function(global) { var LiteGraph = global.LiteGraph; var MIDI_COLOR = "#243"; @@ -32684,7 +32770,7 @@ function LGraphGeometryDisplace() { return window.performance.now(); } })(this); - + (function(global) { var LiteGraph = global.LiteGraph; @@ -34141,7 +34227,7 @@ LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); LGAudioDestination.desc = "Audio output"; LiteGraph.registerNodeType("audio/destination", LGAudioDestination); })(this); - + //event related nodes (function(global) { var LiteGraph = global.LiteGraph; @@ -34570,4 +34656,3 @@ LiteGraph.registerNodeType("network/httprequest", HTTPRequestNode); })(this); - diff --git a/build/litegraph.min.js b/build/litegraph.min.js index 3cdfbb345..6a5f09576 100644 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,915 +1 @@ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(y,c,k){y!=Array.prototype&&y!=Object.prototype&&(y[c]=k.value)};$jscomp.getGlobal=function(y){return"undefined"!=typeof window&&window===y?y:"undefined"!=typeof global&&null!=global?global:y};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_"; -$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var y=0;return function(c){return $jscomp.SYMBOL_PREFIX+(c||"")+y++}}(); -$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var y=$jscomp.global.Symbol.iterator;y||(y=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[y]&&$jscomp.defineProperty(Array.prototype,y,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(y){var c=0;return $jscomp.iteratorPrototype(function(){return ck&&(k=Math.max(0,r+k));if(null==n||n>r)n=r;n=Number(n);0>n&&(n=Math.max(0,r+n));for(k=Number(k||0);k=u}},"es6","es3");$jscomp.findInternal=function(y,c,k){y instanceof String&&(y=String(y));for(var n=y.length,r=0;rc?-k:k}},"es6","es3"); -(function(y){function c(a){e.debug&&console.log("Graph created");this.list_of_graphcanvas=null;this.clear();a&&this.configure(a)}function k(a,b,d,f,g,e){this.id=a;this.type=b;this.origin_id=d;this.origin_slot=f;this.target_id=g;this.target_slot=e;this._data=null;this._pos=new Float32Array(2)}function n(a){this._ctor(a)}function r(a){this._ctor(a)}function u(a,b){this.offset=new Float32Array([0,0]);this.scale=1;this.max_scale=10;this.min_scale=.1;this.onredraw=null;this.enabled=!0;this.last_mouse= -[0,0];this.element=null;this.visible_area=new Float32Array(4);a&&(this.element=a,b||this.bindEvents(a))}function h(a,b,d){this.options=d=d||{};this.background_image=h.DEFAULT_BACKGROUND_IMAGE;a&&a.constructor===String&&(a=document.querySelector(a));this.ds=new u;this.zoom_modify_alpha=!0;this.title_text_font=""+e.NODE_TEXT_SIZE+"px Arial";this.inner_text_font="normal "+e.NODE_SUBTEXT_SIZE+"px Arial";this.node_title_color=e.NODE_TITLE_COLOR;this.default_link_color=e.LINK_COLOR;this.default_connection_color= -{input_off:"#778",input_on:"#7F7",output_off:"#778",output_on:"#7F7"};this.default_connection_color_byType={};this.default_connection_color_byTypeOff={};this.highquality_render=!0;this.use_gradients=!1;this.editor_alpha=1;this.pause_rendering=!1;this.clear_background=!0;this.clear_background_color="#222";this.read_only=!1;this.render_only_selected=!0;this.live_mode=!1;this.allow_interaction=this.allow_dragnodes=this.allow_dragcanvas=this.show_info=!0;this.multi_select=!1;this.allow_reconnect_links= -this.allow_searchbox=!0;this.drag_mode=this.align_to_grid=!1;this.filter=this.dragging_rectangle=null;this.set_canvas_dirty_on_mouse_event=!0;this.always_render_background=!1;this.render_canvas_border=this.render_shadows=!0;this.render_connections_shadows=!1;this.render_connections_border=!0;this.render_connection_arrows=this.render_curved_connections=!1;this.render_collapsed_slots=!0;this.render_execution_order=!1;this.render_link_tooltip=this.render_title_colored=!0;this.links_render_mode=e.SPLINE_LINK; -this.mouse=[0,0];this.canvas_mouse=this.graph_mouse=[0,0];this.onAfterChange=this.onBeforeChange=this.onConnectingChange=this.onSelectionChange=this.onNodeMoved=this.onDrawLinkTooltip=this.onDrawOverlay=this.onDrawForeground=this.onDrawBackground=this.onMouse=this.onSearchBoxSelection=this.onSearchBox=null;this.connections_width=3;this.round_radius=8;this.over_link_center=this.node_widget=this.current_node=null;this.last_mouse_position=[0,0];this.visible_area=this.ds.visible_area;this.visible_links= -[];this.viewport=d.viewport||null;b&&b.attachCanvas(this);this.setCanvas(a,d.skip_events);this.clear();d.skip_render||this.startRendering();this.autoresize=d.autoresize}function E(a,b){return Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1]))}function D(a,b,d,f,g,e){return da&&fb?!0:!1}function J(a,b){var d=a[0]+a[2],f=a[1]+a[3],g=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>g||dz.width-c.width-10&&(g=z.width-c.width-10),z.height&&a>z.height-c.height-10&&(a=z.height-c.height-10));q.style.left=g+"px";q.style.top=a+"px";b.scale&&(q.style.transform="scale("+b.scale+")")}function H(a){this.points=a;this.nearest=this.selected=-1;this.size=null;this.must_update=!0;this.margin=5}function F(a,b,d){return b>a?b:d>a/4).toString(16)})},isValidConnection:function(a, -b){if(""==a||"*"===a)a=0;if(""==b||"*"===b)b=0;if(!a||!b||a==b||a==e.EVENT&&b==e.ACTION)return!0;a=String(a);b=String(b);a=a.toLowerCase();b=b.toLowerCase();if(-1==a.indexOf(",")&&-1==b.indexOf(","))return a==b;a=a.split(",");b=b.split(",");for(var d=0;dz&&(z=t.size[v]);c+=t.size[b==e.VERTICAL_LAYOUT?0:1]+a+e.NODE_TITLE_HEIGHT}d+=z+a}this.setDirtyCanvas(!0,!0)};c.prototype.getTime=function(){return this.globaltime};c.prototype.getFixedTime=function(){return this.fixedtime};c.prototype.getElapsedTime=function(){return this.elapsed_time};c.prototype.sendEventToAllNodes=function(a, -b,d){d=d||e.ALWAYS;var f=this._nodes_in_order?this._nodes_in_order:this._nodes;if(f)for(var g=0,q=f.length;g=e.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(e.use_uuids){if(null==a.id||-1==a.id)a.id=e.uuidv4()}else null==a.id||-1==a.id? -a.id=++this.last_node_id:this.last_node_ida.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=e.use_uuids?e.uuidv4():-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.flags={}};n.prototype.configure=function(a){this.graph&& -this.graph._version++;for(var b in a)if("properties"==b)for(var d in a.properties){if(this.properties[d]=a.properties[d],this.onPropertyChanged)this.onPropertyChanged(d,a.properties[d])}else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=e.cloneObject(a[b],this[b]):this[b]=a[b]);a.title||(this.title=this.constructor.title);if(this.inputs)for(d=0;d=this.outputs.length)){var d=this.outputs[a];if(d&&(d._data=b,this.outputs[a].links))for(d=0;d=this.outputs.length)){var d=this.outputs[a];if(d&&(d.type=b,this.outputs[a].links))for(d=0;d=this.inputs.length||null==this.inputs[a].link)){a=this.graph.links[this.inputs[a].link];if(!a)return null;if(!b)return a.data;b=this.graph.getNodeById(a.origin_id);if(!b)return a.data;if(b.updateOutputData)b.updateOutputData(a.origin_slot);else if(b.onExecute)b.onExecute();return a.data}};n.prototype.getInputDataType=function(a){if(!this.inputs||a>=this.inputs.length||null==this.inputs[a].link)return null;a=this.graph.links[this.inputs[a].link];if(!a)return null; -var b=this.graph.getNodeById(a.origin_id);return b?(a=b.outputs[a.origin_slot])?a.type:null:a.type};n.prototype.getInputDataByName=function(a,b){a=this.findInputSlot(a);return-1==a?null:this.getInputData(a,b)};n.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};n.prototype.getInputOrProperty=function(a){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[a]:null;for(var b=0,d=this.inputs.length;b=this.outputs.length?null:this.outputs[a]._data};n.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null;for(var b=[],d=0;da&&this.pos[1]-g-db)return!0;return!1};n.prototype.getSlotInPosition=function(a,b){var d=new Float32Array(2);if(this.inputs)for(var f=0,g=this.inputs.length;f=this.outputs.length)return e.debug&&console.log("Connect: Error, slot number not found"),null;b&&b.constructor=== -Number&&(b=this.graph.getNodeById(b));if(!b)throw"target node is null";if(b==this)return null;if(d.constructor===String){if(d=b.findInputSlot(d),-1==d)return e.debug&&console.log("Connect: Error, no slot of name "+d),null}else if(d===e.EVENT)if(e.do_add_triggers_slots)b.changeMode(e.ON_TRIGGER),d=b.findInputSlot("onTrigger");else return null;else if(!b.inputs||d>=b.inputs.length)return e.debug&&console.log("Connect: Error, slot number not found"),null;var f=b.inputs[d],g=this.outputs[a];if(!this.outputs[a])return null; -b.onBeforeConnectInput&&(d=b.onBeforeConnectInput(d));if(!1===d||null===d||!e.isValidConnection(g.type,f.type))return this.setDirtyCanvas(!1,!0),null;if(b.onConnectInput&&!1===b.onConnectInput(d,g.type,g,this,a)||this.onConnectOutput&&!1===this.onConnectOutput(a,f.type,f,b,d))return null;b.inputs[d]&&null!=b.inputs[d].link&&(this.graph.beforeChange(),b.disconnectInput(d,{doProcessChange:!1}));if(null!==g.links&&g.links.length)switch(g.type){case e.EVENT:e.allow_multi_output_for_events||(this.graph.beforeChange(), -this.disconnectOutput(a,!1,{doProcessChange:!1}))}var q=e.use_uuids?e.uuidv4():++this.graph.last_link_id;q=new k(q,f.type||g.type,this.id,a,b.id,d);this.graph.links[q.id]=q;null==g.links&&(g.links=[]);g.links.push(q.id);b.inputs[d].link=q.id;this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(e.OUTPUT,a,!0,q,g);if(b.onConnectionsChange)b.onConnectionsChange(e.INPUT,d,!0,q,f);this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(e.INPUT, -b,d,this,a),this.graph.onNodeConnectionChange(e.OUTPUT,this,a,b,d));this.setDirtyCanvas(!1,!0);this.graph.afterChange();this.graph.connectionChange(this,q);return q};n.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return e.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return e.debug&&console.log("Connect: Error, slot number not found"),!1;var d=this.outputs[a];if(!d||!d.links||0==d.links.length)return!1; -if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var f=0,g=d.links.length;f=this.inputs.length)return e.debug&&console.log("Connect: Error, slot number not found"),!1;var b=this.inputs[a];if(!b)return!1;var d=this.inputs[a].link;if(null!=d){this.inputs[a].link=null;var f=this.graph.links[d];if(f){var g=this.graph.getNodeById(f.origin_id);if(!g)return!1;var q=g.outputs[f.origin_slot];if(!q||!q.links||0==q.links.length)return!1;for(var c=0,l=q.links.length;cb&&this.inputs[b].pos)return d[0]=this.pos[0]+this.inputs[b].pos[0],d[1]=this.pos[1]+ -this.inputs[b].pos[1],d;if(!a&&f>b&&this.outputs[b].pos)return d[0]=this.pos[0]+this.outputs[b].pos[0],d[1]=this.pos[1]+this.outputs[b].pos[1],d;if(this.horizontal)return d[0]=this.pos[0]+this.size[0]/f*(b+.5),d[1]=a?this.pos[1]-e.NODE_TITLE_HEIGHT:this.pos[1]+this.size[1],d;d[0]=a?this.pos[0]+g:this.pos[0]+this.size[0]+1-g;d[1]=this.pos[1]+(b+.7)*e.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0);return d};n.prototype.alignToGrid=function(){this.pos[0]=e.CANVAS_GRID_SIZE*Math.round(this.pos[0]/ -e.CANVAS_GRID_SIZE);this.pos[1]=e.CANVAS_GRID_SIZE*Math.round(this.pos[1]/e.CANVAS_GRID_SIZE)};n.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>n.MAX_CONSOLE&&this.console.shift();if(this.graph.onNodeTrace)this.graph.onNodeTrace(this,a)};n.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};n.prototype.loadImage=function(a){var b=new Image;b.src=e.node_images_path+a;b.ready=!1;var d=this;b.onload= -function(){this.ready=!0;d.setDirtyCanvas(!0)};return b};n.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,d=0;da.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});Object.defineProperty(this,"size",{set:function(a){!a||2>a.length||(this._size[0]=Math.max(140,a[0]),this._size[1]=Math.max(80,a[1]))},get:function(){return this._size},enumerable:!0})};r.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding);this.color=a.color;this.font_size=a.font_size};r.prototype.serialize=function(){var a= -this._bounding;return{title:this.title,bounding:[Math.round(a[0]),Math.round(a[1]),Math.round(a[2]),Math.round(a[3])],color:this.color,font_size:this.font_size}};r.prototype.move=function(a,b,d){this._pos[0]+=a;this._pos[1]+=b;if(!d)for(d=0;d=this.viewport[0]&&f=this.viewport[1]&&dthis.max_scale&&(a=this.max_scale);if(a!=this.scale&&this.element){var d=this.element.getBoundingClientRect();if(d&&(b= -b||[.5*d.width,.5*d.height],d=this.convertCanvasToOffset(b),this.scale=a,.01>Math.abs(this.scale-1)&&(this.scale=1),a=this.convertCanvasToOffset(b),a=[a[0]-d[0],a[1]-d[1]],this.offset[0]+=a[0],this.offset[1]+=a[1],this.onredraw))this.onredraw(this)}};u.prototype.changeDeltaScale=function(a,b){this.changeScale(this.scale*a,b)};u.prototype.reset=function(){this.scale=1;this.offset[0]=0;this.offset[1]=0};y.LGraphCanvas=e.LGraphCanvas=h;h.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; -h.link_type_colors={"-1":e.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"};h.gradients={};h.prototype.clear=function(){this.fps=this.render_time=this.last_draw_time=this.frame=0;this.dragging_rectangle=null;this.selected_nodes={};this.selected_group=null;this.visible_nodes=[];this.connecting_node=this.node_capturing_input=this.node_over=this.node_dragged=null;this.highlighted_links={};this.dragging_canvas=!1;this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_widget=this.node_in_panel=this.dirty_area= -null;this.last_mouse=[0,0];this.last_mouseclick=0;this.pointer_is_double=this.pointer_is_down=!1;this.visible_area.set([0,0,0,0]);if(this.onClear)this.onClear()};h.prototype.setGraph=function(a,b){this.graph!=a&&(b||this.clear(),!a&&this.graph?this.graph.detachCanvas(this):(a.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))};h.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph};h.prototype.openSubgraph=function(a){if(!a)throw"graph cannot be null"; -if(this.graph==a)throw"graph cannot be the same";this.clear();this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph));a.attachCanvas(this);this.checkPanels();this.setDirty(!0,!0)};h.prototype.closeSubgraph=function(){if(this._graph_stack&&0!=this._graph_stack.length){var a=this.graph._subgraph_node,b=this._graph_stack.pop();this.selected_nodes={};this.highlighted_links={};b.attachCanvas(this);this.setDirty(!0,!0);a&&(this.centerOnNode(a),this.selectNodes([a]));this.ds.offset= -[0,0];this.ds.scale=1}};h.prototype.getCurrentGraph=function(){return this.graph};h.prototype.setCanvas=function(a,b){if(a&&a.constructor===String&&(a=document.getElementById(a),!a))throw"Error creating LiteGraph canvas: Canvas not found";if(a!==this.canvas&&(!a&&this.canvas&&(b||this.unbindEvents()),this.canvas=a,this.ds.element=a)){a.className+=" lgraphcanvas";a.data=this;a.tabindex="1";this.bgcanvas=null;this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width, -this.bgcanvas.height=this.canvas.height);if(null==a.getContext){if("canvas"!=a.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+a.localName;throw"This browser doesn't support Canvas";}null==(this.ctx=a.getContext("2d"))&&(a.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL());b||this.bindEvents()}};h.prototype._doNothing=function(a){a.preventDefault();return!1};h.prototype._doReturnTrue=function(a){a.preventDefault(); -return!0};h.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas,b=this.getCanvasWindow().document;this._mousedown_callback=this.processMouseDown.bind(this);this._mousewheel_callback=this.processMouseWheel.bind(this);this._mousemove_callback=this.processMouseMove.bind(this);this._mouseup_callback=this.processMouseUp.bind(this);e.pointerListenerAdd(a,"down",this._mousedown_callback,!0);a.addEventListener("mousewheel",this._mousewheel_callback, -!1);e.pointerListenerAdd(a,"up",this._mouseup_callback,!0);e.pointerListenerAdd(a,"move",this._mousemove_callback);a.addEventListener("contextmenu",this._doNothing);a.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1);this._key_callback=this.processKey.bind(this);a.addEventListener("keydown",this._key_callback,!0);b.addEventListener("keyup",this._key_callback,!0);this._ondrop_callback=this.processDrop.bind(this);a.addEventListener("dragover",this._doNothing,!1);a.addEventListener("dragend", -this._doNothing,!1);a.addEventListener("drop",this._ondrop_callback,!1);a.addEventListener("dragenter",this._doReturnTrue,!1);this._events_binded=!0}};h.prototype.unbindEvents=function(){if(this._events_binded){var a=this.getCanvasWindow().document;e.pointerListenerRemove(this.canvas,"move",this._mousedown_callback);e.pointerListenerRemove(this.canvas,"up",this._mousedown_callback);e.pointerListenerRemove(this.canvas,"down",this._mousedown_callback);this.canvas.removeEventListener("mousewheel",this._mousewheel_callback); -this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback);this.canvas.removeEventListener("keydown",this._key_callback);a.removeEventListener("keyup",this._key_callback);this.canvas.removeEventListener("contextmenu",this._doNothing);this.canvas.removeEventListener("drop",this._ondrop_callback);this.canvas.removeEventListener("dragenter",this._doReturnTrue);this._ondrop_callback=this._key_callback=this._mousewheel_callback=this._mousedown_callback=null;this._events_binded=!1}else console.warn("LGraphCanvas: no events binded")}; -h.getFileExtension=function(a){var b=a.indexOf("?");-1!=b&&(a=a.substr(0,b));b=a.lastIndexOf(".");return-1==b?"":a.substr(b+1).toLowerCase()};h.prototype.enableWebGL=function(){if("undefined"===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0;this.bgcanvas=this.canvas;this.bgctx=this.gl;this.canvas.webgl_enabled=!0}; -h.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};h.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};h.prototype.startRendering=function(){function a(){this.pause_rendering||this.draw();var b=this.getCanvasWindow();this.is_rendering&&b.requestAnimationFrame(a.bind(this))}this.is_rendering||(this.is_rendering=!0,a.call(this))};h.prototype.stopRendering=function(){this.is_rendering= -!1};h.prototype.blockClick=function(){this.block_click=!0;this.last_mouseclick=0};h.prototype.processMouseDown=function(a){this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();h.active_canvas=this;var d=this,f=a.clientX,g=a.clientY;this.ds.viewport=this.viewport;f=!this.viewport||this.viewport&&f>=this.viewport[0]&&f=this.viewport[1]&&gg-this.last_mouseclick&&c;this.mouse[0]=a.clientX;this.mouse[1]=a.clientY;this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;this.last_click_position= -[this.mouse[0],this.mouse[1]];this.pointer_is_double=this.pointer_is_down&&c?!0:!1;this.pointer_is_down=!0;this.canvas.focus();e.closeAllContextMenus(b);if(!this.onMouse||1!=this.onMouse(a)){if(1!=a.which||this.pointer_is_double)if(2==a.which)if(e.middle_click_slot_add_default_node){if(q&&this.allow_interaction&&!f&&!this.read_only&&!this.connecting_node&&!q.flags.collapsed&&!this.live_mode){g=f=c=!1;if(q.outputs)for(v=0,l=q.outputs.length;vq.size[0]-e.NODE_TITLE_HEIGHT&& -0>l[1]&&(d=this,setTimeout(function(){d.openSubgraph(q.subgraph)},10)),this.live_mode&&(v=c=!0));v?q.is_selected||this.processNodeSelected(q,a):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=q),this.processNodeSelected(q,a));this.dirty_canvas=!0}}else if(!f){if(!this.read_only)for(v=0;vl[0]+4||a.canvasYl[1]+4)){this.showLinkMenu(c,a);this.over_link_center=null; -break}this.selected_group=this.graph.getGroupOnPos(a.canvasX,a.canvasY);this.selected_group_resizing=!1;this.selected_group&&!this.read_only&&(a.ctrlKey&&(this.dragging_rectangle=null),10>E([a.canvasX,a.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes());g&&!this.read_only&&this.allow_searchbox&&(this.showSearchBox(a),a.preventDefault(),a.stopPropagation()); -c=!0}!f&&c&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}this.last_mouse[0]=a.clientX;this.last_mouse[1]=a.clientY;this.last_mouseclick=e.getTime();this.last_mouse_dragging=!0;this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&&a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}}}};h.prototype.processMouseMove=function(a){this.autoresize&&this.resize(); -this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){h.active_canvas=this;this.adjustMouseEvent(a);var b=[a.clientX,a.clientY];this.mouse[0]=b[0];this.mouse[1]=b[1];var d=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;if(this.block_click)return a.preventDefault(),!1;a.dragging=this.last_mouse_dragging;this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.graph_mouse,a,this.node_widget[1]), -this.dirty_canvas=!0);var f=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=a.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=a.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[a.canvasX-this.selected_group.pos[0],a.canvasY-this.selected_group.pos[1]]:(this.selected_group.move(d[0]/this.ds.scale,d[1]/this.ds.scale, -a.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=d[0]/this.ds.scale,this.ds.offset[1]+=d[1]/this.ds.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else if((this.allow_interaction||f&&f.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);b=0;for(var g=this.graph._nodes.length;bc[0]+4||a.canvasYc[1]+4)){g=q;break}g!=this.over_link_center&&(this.over_link_center=g,this.dirty_canvas=!0);this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&& -this.node_capturing_input!=f&&this.node_capturing_input.onMouseMove)this.node_capturing_input.onMouseMove(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]],this);if(this.node_dragged&&!this.live_mode){for(b in this.selected_nodes)f=this.selected_nodes[b],f.pos[0]+=d[0]/this.ds.scale,f.pos[1]+=d[1]/this.ds.scale,f.is_selected||this.processNodeSelected(f,a);this.dirty_bgcanvas=this.dirty_canvas=!0}this.resizing_node&&!this.live_mode&&(d=[a.canvasX-this.resizing_node.pos[0], -a.canvasY-this.resizing_node.pos[1]],b=this.resizing_node.computeSize(),d[0]=Math.max(b[0],d[0]),d[1]=Math.max(b[1],d[1]),this.resizing_node.setSize(d),this.canvas.style.cursor="se-resize",this.dirty_bgcanvas=this.dirty_canvas=!0)}a.preventDefault();return!1}};h.prototype.processMouseUp=function(a){var b=void 0===a.isPrimary||a.isPrimary;if(!b)return!1;this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){var d=this.getCanvasWindow().document;h.active_canvas=this;this.options.skip_events|| -(e.pointerListenerRemove(d,"move",this._mousemove_callback,!0),e.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),e.pointerListenerRemove(d,"up",this._mouseup_callback,!0));this.adjustMouseEvent(a);d=e.getTime();a.click_time=d-this.last_mouseclick;this.last_mouse_dragging=!1;this.last_click_position=null;this.block_click&&(this.block_click=!1);if(1==a.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,a);this.node_widget=null;this.selected_group&& -(this.selected_group.move(this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),a.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null);this.selected_group_resizing=!1;var f=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){d= -this.graph._nodes;var g=new Float32Array(4),q=Math.abs(this.dragging_rectangle[2]),c=Math.abs(this.dragging_rectangle[3]),l=0>this.dragging_rectangle[3]?this.dragging_rectangle[1]-c:this.dragging_rectangle[1];this.dragging_rectangle[0]=0>this.dragging_rectangle[2]?this.dragging_rectangle[0]-q:this.dragging_rectangle[0];this.dragging_rectangle[1]=l;this.dragging_rectangle[2]=q;this.dragging_rectangle[3]=c;if(!f||10a.click_time&&D(a.canvasX,a.canvasY,f.pos[0],f.pos[1]-e.NODE_TITLE_HEIGHT,e.NODE_TITLE_HEIGHT,e.NODE_TITLE_HEIGHT)&&f.collapse();this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_dragged.pos[0]=Math.round(this.node_dragged.pos[0]);this.node_dragged.pos[1]=Math.round(this.node_dragged.pos[1]);(this.graph.config.align_to_grid||this.align_to_grid)&&this.node_dragged.alignToGrid();if(this.onNodeMoved)this.onNodeMoved(this.node_dragged);this.graph.afterChange(this.node_dragged); -this.node_dragged=null}else{f=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);!f&&300>a.click_time&&this.deselectAllNodes();this.dirty_canvas=!0;this.dragging_canvas=!1;if(this.node_over&&this.node_over.onMouseUp)this.node_over.onMouseUp(a,[a.canvasX-this.node_over.pos[0],a.canvasY-this.node_over.pos[1]],this);if(this.node_capturing_input&&this.node_capturing_input.onMouseUp)this.node_capturing_input.onMouseUp(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]])}}else 2== -a.which?(this.dirty_canvas=!0,this.dragging_canvas=!1):3==a.which&&(this.dirty_canvas=!0,this.dragging_canvas=!1);b&&(this.pointer_is_double=this.pointer_is_down=!1);this.graph.change();a.stopPropagation();a.preventDefault();return!1}};h.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var d=a.clientX,f=a.clientY;if(!this.viewport||this.viewport&&d>=this.viewport[0]&&d=this.viewport[1]&&fb&&(d*=1/1.1),this.ds.changeScale(d,[a.clientX,a.clientY]),this.graph.change(),a.preventDefault(),!1}};h.prototype.isOverNodeBox=function(a,b,d){var f=e.NODE_TITLE_HEIGHT;return D(b,d,a.pos[0]+2,a.pos[1]+2-f,f-4,f-4)?!0:!1};h.prototype.isOverNodeInput=function(a,b,d,f){if(a.inputs)for(var g=0,e=a.inputs.length;gb.nodes[g].pos[0]&&(d[0]=b.nodes[g].pos[0],f[0]=g),d[1]>b.nodes[g].pos[1]&&(d[1]=b.nodes[g].pos[1],f[1]=g)):(d=[b.nodes[g].pos[0],b.nodes[g].pos[1]],f=[g,g]);f=[];for(g=0;g=this.viewport[0]&&b=this.viewport[1]&&dd-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};h.prototype.drawFrontCanvas= -function(){this.dirty_canvas=!1;this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){var b=this.canvas;a.start2D&&!this.viewport&&(a.start2D(),a.restore(),a.setTransform(1,0,0,1,0,0));var d=this.viewport||this.dirty_area;d&&(a.save(),a.beginPath(),a.rect(d[0],d[1],d[2],d[3]),a.clip());this.clear_background&&(d?a.clearRect(d[0],d[1],d[2],d[3]):a.clearRect(0,0,b.width,b.height));this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b, -a);this.show_info&&this.renderInfo(a,d?d[0]:0,d?d[1]:0);if(this.graph){a.save();this.ds.toCanvasContext(a);b=this.computeVisibleNodes(null,this.visible_nodes);for(var f=0;f> ";b.fillText(f+d.getTitle(),.5*a.width,40);b.restore()}d=!1;this.onRenderBackground&&(d=this.onRenderBackground(a,b));this.viewport||(b.restore(),b.setTransform(1,0,0,1,0,0));this.visible_links.length=0;if(this.graph){b.save();this.ds.toCanvasContext(b);1.5>this.ds.scale&&!d&&this.clear_background_color&& -(b.fillStyle=this.clear_background_color,b.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]));if(this.background_image&&.5this.ds.scale;if(this.live_mode){if(!a.flags.collapsed&&(b.shadowColor="transparent",a.onDrawForeground))a.onDrawForeground(b,this,this.canvas)}else{var c=this.editor_alpha;b.globalAlpha=c;this.render_shadows&&!g?(b.shadowColor=e.DEFAULT_SHADOW_COLOR,b.shadowOffsetX=2*this.ds.scale, -b.shadowOffsetY=2*this.ds.scale,b.shadowBlur=3*this.ds.scale):b.shadowColor="transparent";if(!a.flags.collapsed||!a.onDrawCollapsed||1!=a.onDrawCollapsed(b,this)){var l=a._shape||e.BOX_SHAPE;B.set(a.size);var m=a.horizontal;if(a.flags.collapsed){b.font=this.inner_text_font;var h=a.getTitle?a.getTitle():a.title;null!=h&&(a._collapsed_width=Math.min(a.size[0],b.measureText(h).width+2*e.NODE_TITLE_HEIGHT),B[0]=a._collapsed_width,B[1]=0)}a.clip_area&&(b.save(),b.beginPath(),l==e.BOX_SHAPE?b.rect(0,0, -B[0],B[1]):l==e.ROUND_SHAPE?b.roundRect(0,0,B[0],B[1],[10]):l==e.CIRCLE_SHAPE&&b.arc(.5*B[0],.5*B[1],.5*B[0],0,2*Math.PI),b.clip());a.has_errors&&(f="red");this.drawNodeShape(a,b,B,d,f,a.is_selected,a.mouseOver);b.shadowColor="transparent";if(a.onDrawForeground)a.onDrawForeground(b,this,this.canvas);b.textAlign=m?"center":"left";b.font=this.inner_text_font;f=!g;var t=this.connecting_output;l=this.connecting_input;b.lineWidth=1;h=0;var v=new Float32Array(2);if(!a.flags.collapsed){if(a.inputs)for(d= -0;dthis.ds.scale,z=a._shape||a.constructor.shape||e.ROUND_SHAPE,t=a.constructor.title_mode,v=!0;t==e.TRANSPARENT_TITLE||t==e.NO_TITLE?v=!1:t==e.AUTOHIDE_TITLE&&l&&(v=!0);M[0]=0;M[1]=v?-g:0;M[2]=d[0]+1;M[3]=v?d[1]+g:d[1];l=b.globalAlpha;b.beginPath();z==e.BOX_SHAPE||q?b.fillRect(M[0],M[1],M[2],M[3]):z==e.ROUND_SHAPE||z==e.CARD_SHAPE?b.roundRect(M[0],M[1],M[2],M[3],z==e.CARD_SHAPE?[this.round_radius, -this.round_radius,0,0]:[this.round_radius]):z==e.CIRCLE_SHAPE&&b.arc(.5*d[0],.5*d[1],.5*d[0],0,2*Math.PI);b.fill();!a.flags.collapsed&&v&&(b.shadowColor="transparent",b.fillStyle="rgba(0,0,0,0.2)",b.fillRect(0,-1,M[2],2));b.shadowColor="transparent";if(a.onDrawBackground)a.onDrawBackground(b,this,this.canvas,this.graph_mouse);if(v||t==e.TRANSPARENT_TITLE){if(a.onDrawTitleBar)a.onDrawTitleBar(b,g,d,this.ds.scale,f);else if(t!=e.TRANSPARENT_TITLE&&(a.constructor.title_color||this.render_title_colored)){v= -a.constructor.title_color||f;a.flags.collapsed&&(b.shadowColor=e.DEFAULT_SHADOW_COLOR);if(this.use_gradients){var C=h.gradients[v];C||(C=h.gradients[v]=b.createLinearGradient(0,0,400,0),C.addColorStop(0,v),C.addColorStop(1,"#000"));b.fillStyle=C}else b.fillStyle=v;b.beginPath();z==e.BOX_SHAPE||q?b.rect(0,-g,d[0]+1,g):(z==e.ROUND_SHAPE||z==e.CARD_SHAPE)&&b.roundRect(0,-g,d[0]+1,g,a.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]);b.fill();b.shadowColor="transparent"}v= -!1;e.node_box_coloured_by_mode&&e.NODE_MODES_COLORS[a.mode]&&(v=e.NODE_MODES_COLORS[a.mode]);e.node_box_coloured_when_on&&(v=a.action_triggered?"#FFF":a.execute_triggered?"#AAA":v);if(a.onDrawTitleBox)a.onDrawTitleBox(b,g,d,this.ds.scale);else z==e.ROUND_SHAPE||z==e.CIRCLE_SHAPE||z==e.CARD_SHAPE?(q&&(b.fillStyle="black",b.beginPath(),b.arc(.5*g,-.5*g,6,0,2*Math.PI),b.fill()),b.fillStyle=a.boxcolor||v||e.NODE_DEFAULT_BOXCOLOR,q?b.fillRect(.5*g-5,-.5*g-5,10,10):(b.beginPath(),b.arc(.5*g,-.5*g,5,0,2* -Math.PI),b.fill())):(q&&(b.fillStyle="black",b.fillRect(.5*(g-10)-1,-.5*(g+10)-1,12,12)),b.fillStyle=a.boxcolor||v||e.NODE_DEFAULT_BOXCOLOR,b.fillRect(.5*(g-10),-.5*(g+10),10,10));b.globalAlpha=l;if(a.onDrawTitleText)a.onDrawTitleText(b,g,d,this.ds.scale,this.title_text_font,c);!q&&(b.font=this.title_text_font,l=String(a.getTitle()))&&(b.fillStyle=c?e.NODE_SELECTED_TITLE_COLOR:a.constructor.title_text_color||this.node_title_color,a.flags.collapsed?(b.textAlign="left",b.measureText(l),b.fillText(l.substr(0, -20),g,e.NODE_TITLE_TEXT_Y-g),b.textAlign="left"):(b.textAlign="left",b.fillText(l,g,e.NODE_TITLE_TEXT_Y-g)));a.flags.collapsed||!a.subgraph||a.skip_subgraph_button||(l=e.NODE_TITLE_HEIGHT,v=a.size[0]-l,C=e.isInsideRectangle(this.graph_mouse[0]-a.pos[0],this.graph_mouse[1]-a.pos[1],v+2,-l+2,l-4,l-4),b.fillStyle=C?"#888":"#555",z==e.BOX_SHAPE||q?b.fillRect(v+2,-l+2,l-4,l-4):(b.beginPath(),b.roundRect(v+2,-l+2,l-4,l-4,[4]),b.fill()),b.fillStyle="#333",b.beginPath(),b.moveTo(v+.2*l,.6*-l),b.lineTo(v+ -.8*l,.6*-l),b.lineTo(v+.5*l,.3*-l),b.fill());if(a.onDrawTitle)a.onDrawTitle(b)}if(c){if(a.onBounding)a.onBounding(M);t==e.TRANSPARENT_TITLE&&(M[1]-=g,M[3]+=g);b.lineWidth=1;b.globalAlpha=.8;b.beginPath();z==e.BOX_SHAPE?b.rect(-6+M[0],-6+M[1],12+M[2],12+M[3]):z==e.ROUND_SHAPE||z==e.CARD_SHAPE&&a.flags.collapsed?b.roundRect(-6+M[0],-6+M[1],12+M[2],12+M[3],[2*this.round_radius]):z==e.CARD_SHAPE?b.roundRect(-6+M[0],-6+M[1],12+M[2],12+M[3],[2*this.round_radius,2,2*this.round_radius,2]):z==e.CIRCLE_SHAPE&& -b.arc(.5*d[0],.5*d[1],.5*d[0]+6,0,2*Math.PI);b.strokeStyle=e.NODE_BOX_OUTLINE_COLOR;b.stroke();b.strokeStyle=f;b.globalAlpha=1}0m[2]&&(m[0]+=m[2],m[2]=Math.abs(m[2]));0>m[3]&& -(m[1]+=m[3],m[3]=Math.abs(m[3]));if(J(m,l)){var x=k.outputs[t];t=c.inputs[z];if(x&&t&&(k=x.dir||(k.horizontal?e.DOWN:e.RIGHT),t=t.dir||(c.horizontal?e.UP:e.LEFT),this.renderLink(a,v,C,h,!1,0,null,k,t),h&&h._last_time&&1E3>b-h._last_time)){x=2-.002*(b-h._last_time);var G=a.globalAlpha;a.globalAlpha=G*x;this.renderLink(a,v,C,h,!0,x,"white",k,t);a.globalAlpha=G}}}}}}a.globalAlpha=1};h.prototype.renderLink=function(a,b,d,f,g,c,l,m,w,t){f&&this.visible_links.push(f);!l&&f&&(l=f.color||h.link_type_colors[f.type]); -l||(l=this.default_link_color);null!=f&&this.highlighted_links[f.id]&&(l="#FFF");m=m||e.RIGHT;w=w||e.LEFT;var q=E(b,d);this.render_connections_border&&.6b[1]?0:Math.PI,a.save(),a.translate(C[0],C[1]),a.rotate(q),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5, --3),a.fill(),a.restore(),a.save(),a.translate(f[0],f[1]),a.rotate(t),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore()),a.beginPath(),a.arc(g[0],g[1],5,0,2*Math.PI),a.fill());if(c)for(a.fillStyle=l,C=0;5>C;++C)c=(.001*e.getTime()+.2*C)%1,g=this.computeConnectionPoint(b,d,c,m,w),a.beginPath(),a.arc(g[0],g[1],5,0,2*Math.PI),a.fill()};h.prototype.computeConnectionPoint=function(a,b,d,f,g){f=f||e.RIGHT;g=g||e.LEFT;var c=E(a,b),l=[a[0],a[1]],m=[b[0],b[1]];switch(f){case e.LEFT:l[0]+= --.25*c;break;case e.RIGHT:l[0]+=.25*c;break;case e.UP:l[1]+=-.25*c;break;case e.DOWN:l[1]+=.25*c}switch(g){case e.LEFT:m[0]+=-.25*c;break;case e.RIGHT:m[0]+=.25*c;break;case e.UP:m[1]+=-.25*c;break;case e.DOWN:m[1]+=.25*c}f=(1-d)*(1-d)*(1-d);g=3*(1-d)*(1-d)*d;c=3*(1-d)*d*d;d*=d*d;return[f*a[0]+g*l[0]+c*m[0]+d*b[0],f*a[1]+g*l[1]+c*m[1]+d*b[1]]};h.prototype.drawExecutionOrder=function(a){a.shadowColor="transparent";a.globalAlpha=.25;a.textAlign="center";a.strokeStyle="white";a.globalAlpha=.75;for(var b= -this.visible_nodes,d=0;dn&&(n=0);1k&&(k=0),1c||c>A-12||lx.last_y+G||void 0===x.last_y)){f=x.value;switch(x.type){case "button":d.type===e.pointerevents_method+"down"&&(x.callback&&setTimeout(function(){x.callback(x,t,a,b,d)},20),this.dirty_canvas=x.clicked=!0);break;case "slider":f=x.value;v=F((c-15)/(A-30),0,1);if(x.options.read_only)break;x.value=x.options.min+(x.options.max-x.options.min)*v;f!=x.value&&setTimeout(function(){g(x,x.value)},20);this.dirty_canvas= -!0;break;case "number":case "combo":f=x.value;if(d.type==e.pointerevents_method+"move"&&"number"==x.type)h&&(x.value+=.1*h*(x.options.step||1)),null!=x.options.min&&x.valuex.options.max&&(x.value=x.options.max);else if(d.type==e.pointerevents_method+"down"){var w=x.options.values;w&&w.constructor===Function&&(w=x.options.values(x,a));var k=null;"number"!=x.type&&(k=w.constructor===Array?w:Object.keys(w));c=40>c?-1:c>A-40?1:0;if("number"== -x.type)x.value+=.1*c*(x.options.step||1),null!=x.options.min&&x.valuex.options.max&&(x.value=x.options.max);else if(c)v=-1,this.last_mouseclick=0,v=w.constructor===Object?k.indexOf(String(x.value))+c:k.indexOf(x.value)+c,v>=k.length&&(v=k.length-1),0>v&&(v=0),x.value=w.constructor===Array?w[v]:v;else{var n=w!=k?Object.values(w):w;new e.ContextMenu(n,{scale:Math.max(1,this.ds.scale),event:d,className:"dark",callback:function(a,b, -d){w!=k&&(a=n.indexOf(a));this.value=a;g(this,a);t.dirty_canvas=!0;return!1}.bind(x)},v)}}else d.type==e.pointerevents_method+"up"&&"number"==x.type&&(c=40>c?-1:c>A-40?1:0,200>d.click_time&&0==c&&this.prompt("Value",x.value,function(a){if(/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(a))try{a=eval(a)}catch(V){}this.value=Number(a);g(this,this.value)}.bind(x),d));f!=x.value&&setTimeout(function(){g(this,this.value)}.bind(x),20);this.dirty_canvas=!0;break;case "toggle":d.type==e.pointerevents_method+"down"&&(x.value= -!x.value,setTimeout(function(){g(x,x.value)},20));break;case "string":case "text":d.type==e.pointerevents_method+"down"&&this.prompt("Value",x.value,function(a){g(this,a)}.bind(x),d,x.options?x.options.multiline:!1);break;default:x.mouse&&(this.dirty_canvas=x.mouse(d,[c,l],a))}if(f!=x.value){if(a.onWidgetChanged)a.onWidgetChanged(x.name,x.value,f,x);a.graph._version++}return x}}}return null};h.prototype.drawGroups=function(a,b){if(this.graph){a=this.graph._groups;b.save();b.globalAlpha=.5*this.editor_alpha; -for(var d=0;dd&&.01>b.editor_alpha&&(clearInterval(f),1>d&&(b.live_mode=!0));1d.pos[0]+d.size[0])d=c;if(null===f||l+t>f.pos[1]+f.size[1])f=c;if(null===g||m"+(w.label?w.label:m)+""+a+"",value:m})}if(l.length)return new e.ContextMenu(l,{event:d,callback:function(a,b,d,f){g&&(b=this.getBoundingClientRect(),c.showEditPropertyValue(g,a.value,{position:[b.left,b.top]}))},parentMenu:f,allow_html:!0, -node:g},b),!1}};h.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};h.onMenuResizeNode=function(a,b,d,f,g){if(g){a=function(a){a.size=a.computeSize();if(a.onResize)a.onResize(a.size)};b=h.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(g);else for(var e in b.selected_nodes)a(b.selected_nodes[e]);g.setDirtyCanvas(!0,!0)}};h.prototype.showLinkMenu=function(a,b){var d=this,f=d.graph.getNodeById(a.origin_id),g=d.graph.getNodeById(a.target_id), -c=!1;f&&f.outputs&&f.outputs[a.origin_slot]&&(c=f.outputs[a.origin_slot].type);var l=!1;g&&g.outputs&&g.outputs[a.target_slot]&&(l=g.inputs[a.target_slot].type);var m=new e.ContextMenu(["Add Node",null,"Delete",null],{event:b,title:null!=a.data?a.data.constructor.name:null,callback:function(b,e,q){switch(b){case "Add Node":h.onMenuAdd(null,null,q,m,function(b){b.inputs&&b.inputs.length&&b.outputs&&b.outputs.length&&f.connectByType(a.origin_slot,b,c)&&(b.connectByType(a.target_slot,g,l),b.pos[0]-= -.5*b.size[0])});break;case "Delete":d.graph.removeLink(a.id)}}});return!1};h.prototype.createDefaultNodeForSlot=function(a){a=a||{};a=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},a);var b=a.nodeFrom&&null!==a.slotFrom,d=!b&&a.nodeTo&&null!==a.slotTo;if(!b&&!d)return console.warn("No data passed to createDefaultNodeForSlot "+a.nodeFrom+" "+a.slotFrom+" "+a.nodeTo+" "+a.slotTo),!1;if(!a.nodeType)return console.warn("No type to createDefaultNodeForSlot"), -!1;var f=b?a.nodeFrom:a.nodeTo,g=b?a.slotFrom:a.slotTo;switch(typeof g){case "string":d=b?f.findOutputSlot(g,!1):f.findInputSlot(g,!1);g=b?f.outputs[g]:f.inputs[g];break;case "object":d=b?f.findOutputSlot(g.name):f.findInputSlot(g.name);break;case "number":d=g;g=b?f.outputs[g]:f.inputs[g];break;default:return console.warn("Cant get slot information "+g),!1}!1!==g&&!1!==d||console.warn("createDefaultNodeForSlot bad slotX "+g+" "+d);f=g.type==e.EVENT?"_event_":g.type;if((g=b?e.slot_types_default_out: -e.slot_types_default_in)&&g[f]){nodeNewType=!1;if("object"==typeof g[f]||"array"==typeof g[f])for(var c in g[f]){if(a.nodeType==g[f][c]||"AUTO"==a.nodeType){nodeNewType=g[f][c];break}}else if(a.nodeType==g[f]||"AUTO"==a.nodeType)nodeNewType=g[f];if(nodeNewType){c=!1;"object"==typeof nodeNewType&&nodeNewType.node&&(c=nodeNewType,nodeNewType=nodeNewType.node);if(g=e.createNode(nodeNewType)){if(c){if(c.properties)for(var l in c.properties)g.addProperty(l,c.properties[l]);if(c.inputs)for(l in g.inputs= -[],c.inputs)g.addOutput(c.inputs[l][0],c.inputs[l][1]);if(c.outputs)for(l in g.outputs=[],c.outputs)g.addOutput(c.outputs[l][0],c.outputs[l][1]);c.title&&(g.title=c.title);c.json&&g.configure(c.json)}this.graph.add(g);g.pos=[a.position[0]+a.posAdd[0]+(a.posSizeFix[0]?a.posSizeFix[0]*g.size[0]:0),a.position[1]+a.posAdd[1]+(a.posSizeFix[1]?a.posSizeFix[1]*g.size[1]:0)];b?a.nodeFrom.connectByType(d,g,f):a.nodeTo.connectByTypeOutput(d,g,f);return!0}console.log("failed creating "+nodeNewType)}}return!1}; -h.prototype.showConnectionMenu=function(a){a=a||{};var b=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},a),d=this,f=b.nodeFrom&&b.slotFrom;a=!f&&b.nodeTo&&b.slotTo;if(!f&&!a)return console.warn("No data passed to showConnectionMenu"),!1;a=f?b.nodeFrom:b.nodeTo;var g=f?b.slotFrom:b.slotTo,c=!1;switch(typeof g){case "string":c=f?a.findOutputSlot(g,!1):a.findInputSlot(g,!1);g=f?a.outputs[g]:a.inputs[g];break;case "object":c=f?a.findOutputSlot(g.name):a.findInputSlot(g.name); -break;case "number":c=g;g=f?a.outputs[g]:a.inputs[g];break;default:return console.warn("Cant get slot information "+g),!1}a=["Add Node",null];d.allow_searchbox&&(a.push("Search"),a.push(null));var l=g.type==e.EVENT?"_event_":g.type,m=f?e.slot_types_default_out:e.slot_types_default_in;if(m&&m[l])if("object"==typeof m[l]||"array"==typeof m[l])for(var w in m[l])a.push(m[l][w]);else a.push(m[l]);var t=new e.ContextMenu(a,{event:b.e,title:(g&&""!=g.name?g.name+(l?" | ":""):"")+(g&&l?l:""),callback:function(a, -e,x){switch(a){case "Add Node":h.onMenuAdd(null,null,x,t,function(a){f?b.nodeFrom.connectByType(c,a,l):b.nodeTo.connectByTypeOutput(c,a,l)});break;case "Search":f?d.showSearchBox(x,{node_from:b.nodeFrom,slot_from:g,type_filter_in:l}):d.showSearchBox(x,{node_to:b.nodeTo,slot_from:g,type_filter_out:l});break;default:d.createDefaultNodeForSlot(Object.assign(b,{position:[b.e.canvasX,b.e.canvasY],nodeType:a}))}}});return!1};h.onShowPropertyEditor=function(a,b,d,f,g){function c(){if(w){var b=w.value;"Number"== -a.type?b=Number(b):"Boolean"==a.type&&(b=!!b);g[l]=b;m.parentNode&&m.parentNode.removeChild(m);g.setDirtyCanvas(!0,!0)}}var l=a.property||"title";b=g[l];var m=document.createElement("div");m.is_modified=!1;m.className="graphdialog";m.innerHTML="";m.close=function(){m.parentNode&&m.parentNode.removeChild(m)};m.querySelector(".name").innerText=l;var w=m.querySelector(".value");w&&(w.value=b,w.addEventListener("blur", -function(a){this.focus()}),w.addEventListener("keydown",function(a){m.is_modified=!0;if(27==a.keyCode)m.close();else if(13==a.keyCode)c();else if(13!=a.keyCode&&"textarea"!=a.target.localName)return;a.preventDefault();a.stopPropagation()}));b=h.active_canvas.canvas;d=b.getBoundingClientRect();var t=f=-20;d&&(f-=d.left,t-=d.top);event?(m.style.left=event.clientX+f+"px",m.style.top=event.clientY+t+"px"):(m.style.left=.5*b.width+f+"px",m.style.top=.5*b.height+t+"px");m.querySelector("button").addEventListener("click", -c);b.parentNode.appendChild(m);w&&w.focus();var v=null;m.addEventListener("mouseleave",function(a){e.dialog_close_on_mouse_leave&&!m.is_modified&&e.dialog_close_on_mouse_leave&&(v=setTimeout(m.close,e.dialog_close_on_mouse_leave_delay))});m.addEventListener("mouseenter",function(a){e.dialog_close_on_mouse_leave&&v&&clearTimeout(v)})};h.prototype.prompt=function(a,b,d,f,g){var c=this;a=a||"";var l=document.createElement("div");l.is_modified=!1;l.className="graphdialog rounded";l.innerHTML=g?" ": -" ";l.close=function(){c.prompt_box=null;l.parentNode&&l.parentNode.removeChild(l)};g=h.active_canvas.canvas;g.parentNode.appendChild(l);1h.search_limit))break}}q=null;if(Array.prototype.filter)q=Object.keys(e.registered_node_types).filter(g);else for(x in q=[],e.registered_node_types)g(x)&&q.push(x);for(x=0;xh.search_limit);x++);if(b.show_general_after_typefiltered&&(G.value|| -Q.value)){filtered_extra=[];for(x in e.registered_node_types)g(x,{inTypeOverride:G&&G.value?"*":!1,outTypeOverride:Q&&Q.value?"*":!1})&&filtered_extra.push(x);for(x=0;xh.search_limit);x++);}if((G.value||Q.value)&&0==A.childNodes.length&&b.show_general_if_none_on_typefilter){filtered_extra=[];for(x in e.registered_node_types)g(x,{skipFilter:!0})&&filtered_extra.push(x);for(x=0;xh.search_limit);x++);}}}b=Object.assign({slot_from:null,node_from:null,node_to:null,do_type_filter:e.search_filter_enabled,type_filter_in:!1,type_filter_out:!1,show_general_if_none_on_typefilter:!0,show_general_after_typefiltered:!0,hide_on_mouse_leave:e.search_hide_on_mouse_leave,show_all_if_empty:!0,show_all_on_open:e.search_show_all_on_open},b||{});var c=this,l=h.active_canvas,m=l.canvas,w=m.ownerDocument||document,t=document.createElement("div");t.className= -"litegraph litesearchbox graphdialog rounded";t.innerHTML="Search ";b.do_type_filter&&(t.innerHTML+="",t.innerHTML+="");t.innerHTML+="
";w.fullscreenElement?w.fullscreenElement.appendChild(t):(w.body.appendChild(t),w.body.style.overflow="hidden");if(b.do_type_filter)var v= -t.querySelector(".slot_in_type_filter"),C=t.querySelector(".slot_out_type_filter");t.close=function(){c.search_box=null;this.blur();m.focus();w.body.style.overflow="";setTimeout(function(){c.canvas.focus()},20);t.parentNode&&t.parentNode.removeChild(t)};1v.height-200&&(A.style.maxHeight=v.height-a.layerY-20+"px");K.focus();b.show_all_on_open&&g();return t};h.prototype.showEditPropertyValue=function(a,b,d){function f(){g(C.value)}function g(f){e&&e.values&&e.values.constructor===Object&&void 0!=e.values[f]&&(f=e.values[f]);"number"==typeof a.properties[b]&& -(f=Number(f));if("array"==c||"object"==c)f=JSON.parse(f);a.properties[b]=f;a.graph&&a.graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b,f);if(d.onclose)d.onclose();v.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){d=d||{};var e=a.getPropertyInfo(b),c=e.type,l="";if("string"==c||"number"==c||"array"==c||"object"==c)l="";else if("enum"!=c&&"combo"!=c||!e.values)if("boolean"==c||"toggle"==c)l="";else{console.warn("unknown type: "+c);return}else{l=""}var v=this.createDialog(""+(e.label?e.label:b)+""+l+"",d),C=!1;if("enum"!=c&&"combo"!=c||!e.values)if("boolean"==c||"toggle"==c)(C=v.querySelector("input"))&& -C.addEventListener("click",function(a){v.modified();g(!!C.checked)});else{if(C=v.querySelector("input"))C.addEventListener("blur",function(a){this.focus()}),t=void 0!==a.properties[b]?a.properties[b]:"","string"!==c&&(t=JSON.stringify(t)),C.value=t,C.addEventListener("keydown",function(a){if(27==a.keyCode)v.close();else if(13==a.keyCode)f();else if(13!=a.keyCode){v.modified();return}a.preventDefault();a.stopPropagation()})}else C=v.querySelector("select"),C.addEventListener("change",function(a){v.modified(); -g(a.target.value)});C&&C.focus();v.querySelector("button").addEventListener("click",f);return v}};h.prototype.createDialog=function(a,b){b=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},b||{});var d=document.createElement("div");d.className="graphdialog";d.innerHTML=a;d.is_modified=!1;a=this.canvas.getBoundingClientRect();var f=-20,g=-20;a&&(f-=a.left,g-=a.top);b.position?(f+=b.position[0],g+=b.position[1]):b.event?(f+=b.event.clientX,g+=b.event.clientY):(f+=.5*this.canvas.width, -g+=.5*this.canvas.height);d.style.left=f+"px";d.style.top=g+"px";this.canvas.parentNode.appendChild(d);b.checkForInput&&(a=[],(a=d.querySelectorAll("input"))&&a.forEach(function(a){a.addEventListener("keydown",function(a){d.modified();if(27==a.keyCode)d.close();else if(13!=a.keyCode)return;a.preventDefault();a.stopPropagation()});a.focus()}));d.modified=function(){d.is_modified=!0};d.close=function(){d.parentNode&&d.parentNode.removeChild(d)};var c=null,l=!1;d.addEventListener("mouseleave",function(a){l|| -(b.closeOnLeave||e.dialog_close_on_mouse_leave)&&!d.is_modified&&e.dialog_close_on_mouse_leave&&(c=setTimeout(d.close,e.dialog_close_on_mouse_leave_delay))});d.addEventListener("mouseenter",function(a){(b.closeOnLeave||e.dialog_close_on_mouse_leave)&&c&&clearTimeout(c)});(a=d.querySelectorAll("select"))&&a.forEach(function(a){a.addEventListener("click",function(a){l++});a.addEventListener("blur",function(a){l=0});a.addEventListener("change",function(a){l=-1})});return d};h.prototype.createPanel=function(a, -b){b=b||{};var d=b.window||window,f=document.createElement("div");f.className="litegraph dialog";f.innerHTML="
";f.header=f.querySelector(".dialog-header");b.width&&(f.style.width=b.width+(b.width.constructor===Number?"px":""));b.height&&(f.style.height=b.height+(b.height.constructor===Number?"px":""));b.closable&& -(b=document.createElement("span"),b.innerHTML="✕",b.classList.add("close"),b.addEventListener("click",function(){f.close()}),f.header.appendChild(b));f.title_element=f.querySelector(".dialog-title");f.title_element.innerText=a;f.content=f.querySelector(".dialog-content");f.alt_content=f.querySelector(".dialog-alt-content");f.footer=f.querySelector(".dialog-footer");f.close=function(){if(f.onClose&&"function"==typeof f.onClose)f.onClose();f.parentNode&&f.parentNode.removeChild(f);this.parentNode&& -this.parentNode.removeChild(this)};f.toggleAltContent=function(a){if("undefined"!=typeof a){var b=a?"block":"none";a=a?"none":"block"}else b="block"!=f.alt_content.style.display?"block":"none",a="block"!=f.alt_content.style.display?"none":"block";f.alt_content.style.display=b;f.content.style.display=a};f.toggleFooterVisibility=function(a){f.footer.style.display="undefined"!=typeof a?a?"block":"none":"block"!=f.footer.style.display?"block":"none"};f.clear=function(){this.content.innerHTML=""};f.addHTML= -function(a,b,d){var g=document.createElement("div");b&&(g.className=b);g.innerHTML=a;d?f.footer.appendChild(g):f.content.appendChild(g);return g};f.addButton=function(a,b,d){var g=document.createElement("button");g.innerText=a;g.options=d;g.classList.add("btn");g.addEventListener("click",b);f.footer.appendChild(g);return g};f.addSeparator=function(){var a=document.createElement("div");a.className="separator";f.content.appendChild(a)};f.addWidget=function(a,b,c,l,m){function g(a,b){l.callback&&l.callback(a, -b,l);m&&m(a,b,l)}l=l||{};var q=String(c);a=a.toLowerCase();"number"==a&&(q=c.toFixed(3));var C=document.createElement("div");C.className="property";C.innerHTML="";C.querySelector(".property_name").innerText=l.label||b;var x=C.querySelector(".property_value");x.innerText=q;C.dataset.property=b;C.dataset.type=l.type||a;C.options=l;C.value=c;if("code"==a)C.addEventListener("click",function(a){f.inner_showCodePad(this.dataset.property)}); -else if("boolean"==a)C.classList.add("boolean"),c&&C.classList.add("bool-on"),C.addEventListener("click",function(){var a=this.dataset.property;this.value=!this.value;this.classList.toggle("bool-on");this.querySelector(".property_value").innerText=this.value?"true":"false";g(a,this.value)});else if("string"==a||"number"==a)x.setAttribute("contenteditable",!0),x.addEventListener("keydown",function(b){"Enter"!=b.code||"string"==a&&b.shiftKey||(b.preventDefault(),this.blur())}),x.addEventListener("blur", -function(){var a=this.innerText,b=this.parentNode.dataset.property;"number"==this.parentNode.dataset.type&&(a=Number(a));g(b,a)});else if("enum"==a||"combo"==a)q=h.getPropertyPrintableValue(c,l.values),x.innerText=q,x.addEventListener("click",function(a){var b=this.parentNode.dataset.property,f=this;new e.ContextMenu(l.values||[],{event:a,className:"dark",callback:function(a,d,c){f.innerText=a;g(b,a);return!1}},d)});f.content.appendChild(C);return C};if(f.onOpen&&"function"==typeof f.onOpen)f.onOpen(); -return f};h.getPropertyPrintableValue=function(a,b){if(!b||b.constructor===Array)return String(a);if(b.constructor===Object){var d="",f;for(f in b)if(b[f]==a){d=f;break}return String(a)+" ("+d+")"}};h.prototype.closePanels=function(){var a=document.querySelector("#node-panel");a&&a.close();(a=document.querySelector("#option-panel"))&&a.close()};h.prototype.showShowGraphOptionsPanel=function(a,b,d,f){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(b&&b.event&&b.event.target&&b.event.target.lgraphcanvas)){console.warn("Canvas not found"); -return}var g=b.event.target.lgraphcanvas}else g=this;g.closePanels();a=g.getCanvasWindow();panel=g.createPanel("Options",{closable:!0,window:a,onOpen:function(){g.OPTIONPANEL_IS_OPEN=!0},onClose:function(){g.OPTIONPANEL_IS_OPEN=!1;g.options_panel=null}});g.options_panel=panel;panel.id="option-panel";panel.classList.add("settings");(function(){panel.content.innerHTML="";var a=function(a,b,d){d&&d.key&&(a=d.key);d.values&&(b=Object.values(d.values).indexOf(b));g[a]=b},b=e.availableCanvasOptions;b.sort(); -for(var d in b){var f=b[d];panel.addWidget("boolean",f,g[f],{key:f,on:"True",off:"False"},a)}panel.addWidget("combo","Render mode",e.LINK_RENDER_MODES[g.links_render_mode],{key:"links_render_mode",values:e.LINK_RENDER_MODES},a);panel.addSeparator();panel.footer.innerHTML=""})();g.canvas.parentNode.appendChild(panel)};h.prototype.showShowNodePanel=function(a){function b(){g.content.innerHTML="";g.addHTML(""+a.type+""+(a.constructor.desc||"")+""); -g.addHTML("

Properties

");var b=function(b,d){f.graph.beforeChange(a);switch(b){case "Title":a.title=d;break;case "Mode":b=Object.values(e.NODE_MODES).indexOf(d);0<=b&&e.NODE_MODES[b]?a.changeMode(b):console.warn("unexpected mode: "+d);break;case "Color":h.node_colors[d]?(a.color=h.node_colors[d].color,a.bgcolor=h.node_colors[d].bgcolor):console.warn("unexpected color: "+d);break;default:a.setProperty(b,d)}f.graph.afterChange();f.dirty_canvas=!0};g.addWidget("string","Title",a.title,{},b); -g.addWidget("combo","Mode",e.NODE_MODES[a.mode],{values:e.NODE_MODES},b);var d="";void 0!==a.color&&(d=Object.keys(h.node_colors).filter(function(b){return h.node_colors[b].color==a.color}));g.addWidget("combo","Color",d,{values:Object.keys(h.node_colors)},b);for(var c in a.properties){d=a.properties[c];var l=a.getPropertyInfo(c);a.onAddPropertyToPanel&&a.onAddPropertyToPanel(c,g)||g.addWidget(l.widget||l.type,c,d,l,b)}g.addSeparator();if(a.onShowCustomPanelInfo)a.onShowCustomPanelInfo(g);g.footer.innerHTML= -"";g.addButton("Delete",function(){a.block_delete||(a.graph.remove(a),g.close())}).classList.add("delete")}this.SELECTED_NODE=a;this.closePanels();var d=this.getCanvasWindow(),f=this,g=this.createPanel(a.title||"",{closable:!0,window:d,onOpen:function(){f.NODEPANEL_IS_OPEN=!0},onClose:function(){f.NODEPANEL_IS_OPEN=!1;f.node_panel=null}});f.node_panel=g;g.id="node-panel";g.node=a;g.classList.add("settings");g.inner_showCodePad=function(d){g.classList.remove("settings");g.classList.add("centered"); -g.alt_content.innerHTML="";var f=g.alt_content.querySelector("textarea"),c=function(){g.toggleAltContent(!1);g.toggleFooterVisibility(!0);f.parentNode.removeChild(f);g.classList.add("settings");g.classList.remove("centered");b()};f.value=a.properties[d];f.addEventListener("keydown",function(b){"Enter"==b.code&&b.ctrlKey&&(a.setProperty(d,f.value),c())});g.toggleAltContent(!0);g.toggleFooterVisibility(!1);f.style.height="calc(100% - 40px)";var e=g.addButton("Assign", -function(){a.setProperty(d,f.value);c()});g.alt_content.appendChild(e);e=g.addButton("Close",c);e.style.float="right";g.alt_content.appendChild(e)};b();this.canvas.parentNode.appendChild(g)};h.prototype.showSubgraphPropertiesDialog=function(a){function b(){f.clear();if(a.inputs)for(var d=0;d","subgraph_property"); -e.dataset.name=c.name;e.dataset.slot=d;e.querySelector(".name").innerText=c.name;e.querySelector(".type").innerText=c.type;e.querySelector("button").addEventListener("click",function(d){a.removeInput(Number(this.parentNode.dataset.slot));b()})}}}console.log("showing subgraph properties dialog");var d=this.canvas.parentNode.querySelector(".subgraph_dialog");d&&d.close();var f=this.createPanel("Subgraph Inputs",{closable:!0,width:500});f.node=a;f.classList.add("subgraph_dialog");f.addHTML(" + NameType", -"subgraph_property extra",!0).querySelector("button").addEventListener("click",function(d){d=this.parentNode;var f=d.querySelector(".name").value,g=d.querySelector(".type").value;f&&-1==a.findInputSlot(f)&&(a.addInput(f,g),d.querySelector(".name").value="",d.querySelector(".type").value="",b())});b();this.canvas.parentNode.appendChild(f);return f};h.prototype.showSubgraphPropertiesDialogRight=function(a){function b(){g.clear();if(a.outputs)for(var d=0;d","subgraph_property");c.dataset.name=f.name;c.dataset.slot=d;c.querySelector(".name").innerText=f.name;c.querySelector(".type").innerText=f.type;c.querySelector("button").addEventListener("click",function(d){a.removeOutput(Number(this.parentNode.dataset.slot));b()})}}}function d(){var d=this.parentNode,f=d.querySelector(".name").value,g=d.querySelector(".type").value;f&&-1==a.findOutputSlot(f)&& -(a.addOutput(f,g),d.querySelector(".name").value="",d.querySelector(".type").value="",b())}var f=this.canvas.parentNode.querySelector(".subgraph_dialog");f&&f.close();var g=this.createPanel("Subgraph Outputs",{closable:!0,width:500});g.node=a;g.classList.add("subgraph_dialog");f=g.addHTML(" + NameType","subgraph_property extra",!0);f.querySelector(".name").addEventListener("keydown", -function(a){13==a.keyCode&&d.apply(this)});f.querySelector("button").addEventListener("click",function(a){d.apply(this)});b();this.canvas.parentNode.appendChild(g);return g};h.prototype.checkPanels=function(){if(this.canvas)for(var a=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),b=0;b=Object.keys(a.selected_nodes).length)g.collapse(); -else for(var c in a.selected_nodes)a.selected_nodes[c].collapse();g.graph.afterChange()};h.onMenuNodePin=function(a,b,d,f,g){g.pin()};h.onMenuNodeMode=function(a,b,d,f,g){new e.ContextMenu(e.NODE_MODES,{event:d,callback:function(a){if(g){var b=Object.values(e.NODE_MODES).indexOf(a),d=function(d){0<=b&&e.NODE_MODES[b]?d.changeMode(b):(console.warn("unexpected mode: "+a),d.changeMode(e.ALWAYS))},f=h.active_canvas;if(!f.selected_nodes||1>=Object.keys(f.selected_nodes).length)d(g);else for(var c in f.selected_nodes)d(f.selected_nodes[c])}}, -parentMenu:f,node:g});return!1};h.onMenuNodeColors=function(a,b,d,f,g){if(!g)throw"no node for color";b=[];b.push({value:null,content:"No color"});for(var c in h.node_colors)a=h.node_colors[c],a={value:c,content:""+c+""},b.push(a);new e.ContextMenu(b,{event:d,callback:function(a){if(g){var b=a.value?h.node_colors[a.value]: -null;a=function(a){b?a.constructor===e.LGraphGroup?a.color=b.groupcolor:(a.color=b.color,a.bgcolor=b.bgcolor):(delete a.color,delete a.bgcolor)};var d=h.active_canvas;if(!d.selected_nodes||1>=Object.keys(d.selected_nodes).length)a(g);else for(var f in d.selected_nodes)a(d.selected_nodes[f]);g.setDirtyCanvas(!0,!0)}},parentMenu:f,node:g});return!1};h.onMenuNodeShapes=function(a,b,d,f,g){if(!g)throw"no node passed";new e.ContextMenu(e.VALID_SHAPES,{event:d,callback:function(a){if(g){g.graph.beforeChange(); -var b=h.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)g.shape=a;else for(var d in b.selected_nodes)b.selected_nodes[d].shape=a;g.graph.afterChange();g.setDirtyCanvas(!0)}},parentMenu:f,node:g});return!1};h.onMenuNodeRemove=function(a,b,d,f,g){if(!g)throw"no node passed";a=g.graph;a.beforeChange();b=h.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)!1!==g.removable&&a.remove(g);else for(var c in b.selected_nodes)d=b.selected_nodes[c],!1!==d.removable&& -a.remove(d);a.afterChange();g.setDirtyCanvas(!0,!0)};h.onMenuNodeToSubgraph=function(a,b,d,f,g){a=g.graph;if(b=h.active_canvas)d=Object.values(b.selected_nodes||{}),d.length||(d=[g]),f=e.createNode("graph/subgraph"),f.pos=g.pos.concat(),a.add(f),f.buildFromNodes(d),b.deselectAllNodes(),g.setDirtyCanvas(!0,!0)};h.onMenuNodeClone=function(a,b,d,f,g){g.graph.beforeChange();var c={};a=function(a){if(!1!==a.clonable){var b=a.clone();b&&(b.pos=[a.pos[0]+5,a.pos[1]+5],a.graph.add(b),c[b.id]=b)}};b=h.active_canvas; -if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(g);else for(var e in b.selected_nodes)a(b.selected_nodes[e]);Object.keys(c).length&&b.selectNodes(c);g.graph.afterChange();g.setDirtyCanvas(!0,!0)};h.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"}, -cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}};h.prototype.getCanvasMenuOptions=function(){if(this.getMenuOptions)var a=this.getMenuOptions();else a=[{content:"Add Node",has_submenu:!0,callback:h.onMenuAdd},{content:"Add Group",callback:h.onGroupAdd}],1Name", -f),l=e.querySelector("input");l&&c&&(l.value=c.label||"");var t=function(){a.graph.beforeChange();l.value&&(c&&(c.label=l.value),d.setDirty(!0));e.close();a.graph.afterChange()};e.querySelector("button").addEventListener("click",t);l.addEventListener("keydown",function(a){e.is_modified=!0;if(27==a.keyCode)e.close();else if(13==a.keyCode)t();else if(13!=a.keyCode&&"textarea"!=a.target.localName)return;a.preventDefault();a.stopPropagation()});l.focus()}},extra:a};a&&(c.title=a.type);var l=null;a&&(l= -a.getSlotInPosition(b.canvasX,b.canvasY),h.active_node=a);l?(g=[],a.getSlotMenuOptions?g=a.getSlotMenuOptions(l):(l&&l.output&&l.output.links&&l.output.links.length&&g.push({content:"Disconnect Links",slot:l}),b=l.input||l.output,b.removable&&g.push(b.locked?"Cannot remove":{content:"Remove Slot",slot:l}),b.nameLocked||g.push({content:"Rename Slot",slot:l})),c.title=(l.input?l.input.type:l.output.type)||"*",l.input&&l.input.type==e.ACTION&&(c.title="Action"),l.output&&l.output.type==e.EVENT&&(c.title= -"Event")):a?g=this.getNodeMenuOptions(a):(g=this.getCanvasMenuOptions(),(l=this.graph.getGroupOnPos(b.canvasX,b.canvasY))&&g.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:l,options:this.getGroupMenuOptions(l)}}));g&&new e.ContextMenu(g,c,f)};e.compareObjects=function(a,b){for(var d in a)if(a[d]!=b[d])return!1;return!0};e.distance=E;e.colorToString=function(a){return"rgba("+Math.round(255*a[0]).toFixed()+","+Math.round(255*a[1]).toFixed()+","+Math.round(255*a[2]).toFixed()+ -","+(4==a.length?a[3].toFixed(2):"1.0")+")"};e.isInsideRectangle=D;e.growBounding=function(a,b,d){ba[2]&&(a[2]=b);da[3]&&(a[3]=d)};e.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};e.overlapBounding=J;e.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),d=0,f,g,c=0;6>c;c+=2)f="0123456789ABCDEF".indexOf(a.charAt(c)),g="0123456789ABCDEF".indexOf(a.charAt(c+1)),b[d]=16*f+g,d++;return b}; -e.num2hex=function(a){for(var b="#",d,f,g=0;3>g;g++)d=a[g]/16,f=a[g]%16,b+="0123456789ABCDEF".charAt(d)+"0123456789ABCDEF".charAt(f);return b};L.prototype.addItem=function(a,b,d){function f(a){var b=this.value;b&&b.has_submenu&&g.call(this,a)}function g(a){var b=this.value,f=!0;c.current_submenu&&c.current_submenu.close(a);if(d.callback){var g=d.callback.call(this,b,d,a,c,d.node);!0===g&&(f=!1)}if(b&&(b.callback&&!d.ignore_item_callbacks&&!0!==b.disabled&&(g=b.callback.call(this,b,d,a,c,d.extra), -!0===g&&(f=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new c.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:c,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,extra:b.submenu.extra,autoopen:d.autoopen});f=!1}f&&!c.lock&&c.close()}var c=this;d=d||{};var l=document.createElement("div");l.className="litemenu-entry submenu";var m=!1;if(null===b)l.classList.add("separator");else{l.innerHTML=b&&b.title?b.title: -a;if(l.value=b)b.disabled&&(m=!0,l.classList.add("disabled")),(b.submenu||b.has_submenu)&&l.classList.add("has_submenu");"function"==typeof b?(l.dataset.value=a,l.onclick_callback=b):l.dataset.value=b;b.className&&(l.className+=" "+b.className)}this.root.appendChild(l);m||l.addEventListener("click",g);!m&&d.autoopen&&e.pointerListenerAdd(l,"enter",f);return l};L.prototype.close=function(a,b){this.root.parentNode&&this.root.parentNode.removeChild(this.root);this.parentMenu&&!b&&(this.parentMenu.lock= -!1,this.parentMenu.current_submenu=null,void 0===a?this.parentMenu.close():a&&!L.isCursorOverElement(a,this.parentMenu.root)&&L.trigger(this.parentMenu.root,e.pointerevents_method+"leave",a));this.current_submenu&&this.current_submenu.close(a,!0);this.root.closing_timer&&clearTimeout(this.root.closing_timer)};L.trigger=function(a,b,d,f){var g=document.createEvent("CustomEvent");g.initCustomEvent(b,!0,!0,d);g.srcElement=f;a.dispatchEvent?a.dispatchEvent(g):a.__events&&a.__events.dispatchEvent(g);return g}; -L.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};L.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};L.isCursorOverElement=function(a,b){var d=a.clientX;a=a.clientY;return(b=b.getBoundingClientRect())?a>b.top&&ab.left&&dMath.abs(b))return f[1];a=(a-f[0])/b;return f[1]*(1-a)+g[1]*a}}return 0}};H.prototype.draw=function(a,b,d,f,g,c){if(d=this.points){this.size=b;var e=b[0]-2*this.margin;b=b[1]-2*this.margin;g=g||"#666";a.save();a.translate(this.margin,this.margin);f&&(a.fillStyle="#111",a.fillRect(0,0,e,b),a.fillStyle="#222",a.fillRect(.5*e,0,1,b),a.strokeStyle="#333", -a.strokeRect(0,0,e,b));a.strokeStyle=g;c&&(a.globalAlpha=.5);a.beginPath();for(f=0;fa[1])){var f=this.size[0]-2*this.margin,g=this.size[1]-2*this.margin,c=a[0]-this.margin;a=a[1]-this.margin; -this.selected=this.getCloserPoint([c,a],30/b.ds.scale);-1==this.selected&&(b=[c/f,1-a/g],d.push(b),d.sort(function(a,b){return a[0]-b[0]}),this.selected=d.indexOf(b),this.must_update=!0);if(-1!=this.selected)return!0}};H.prototype.onMouseMove=function(a,b){var d=this.points;if(d){var f=this.selected;if(!(0>f)){var g=(a[0]-this.margin)/(this.size[0]-2*this.margin),c=(a[1]-this.margin)/(this.size[1]-2*this.margin);this._nearest=this.getCloserPoint([a[0]-this.margin,a[1]-this.margin],30/b.ds.scale); -if(b=d[f]){var e=0==f||f==d.length-1;!e&&(-10>a[0]||a[0]>this.size[0]+10||-10>a[1]||a[1]>this.size[1]+10)?(d.splice(f,1),this.selected=-1):(b[0]=e?0==f?0:1:F(g,0,1),b[1]=1-F(c,0,1),d.sort(function(a,b){return a[0]-b[0]}),this.selected=d.indexOf(b),this.must_update=!0)}}}};H.prototype.onMouseUp=function(a,b){this.selected=-1;return!1};H.prototype.getCloserPoint=function(a,b){var d=this.points;if(!d)return-1;b=b||30;for(var f=this.size[0]-2*this.margin,g=this.size[1]-2*this.margin,c=d.length,e=[0,0], -l=1E6,m=-1,t=0;tl||w>b||(m=t,l=w)}return m};e.CurveEditor=H;e.getParameterNames=function(a){return(a+"").replace(/[/][/].*$/gm,"").replace(/\s+/g,"").replace(/[/][*][^/*]*[*][/]/g,"").split("){",1)[0].replace(/^[^(]*[(]/,"").replace(/=[^,]+/g,"").split(",").filter(Boolean)};e.pointerListenerAdd=function(a,b,d,f){f=void 0===f?!1:f;if(a&&a.addEventListener&&b&&"function"===typeof d){var g=e.pointerevents_method;if("pointer"==g&&!window.PointerEvent)switch(console.warn("sMethod=='pointer' && !window.PointerEvent"), -console.log("Converting pointer["+b+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."),b){case "down":g="touch";b="start";break;case "move":g="touch";break;case "up":g="touch";b="end";break;case "cancel":g="touch";break;case "enter":console.log("debug: Should I send a move event?");break;default:console.warn("PointerEvent not available in this browser ? The event "+b+" would not be called")}switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":a.addEventListener(g+ -b,d,f);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("mouse"!=g)return a.addEventListener(g+b,d,f);default:return a.addEventListener(b,d,f)}}};e.pointerListenerRemove=function(a,b,d,f){f=void 0===f?!1:f;if(a&&a.removeEventListener&&b&&"function"===typeof d)switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":"pointer"!=e.pointerevents_method&&"mouse"!=e.pointerevents_method||a.removeEventListener(e.pointerevents_method+b,d,f);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("pointer"== -e.pointerevents_method)return a.removeEventListener(e.pointerevents_method+b,d,f);default:return a.removeEventListener(b,d,f)}};y.clamp=F;"undefined"==typeof window||window.requestAnimationFrame||(window.requestAnimationFrame=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)})})(this); -"undefined"!=typeof exports&&(exports.LiteGraph=this.LiteGraph,exports.LGraph=this.LGraph,exports.LLink=this.LLink,exports.LGraphNode=this.LGraphNode,exports.LGraphGroup=this.LGraphGroup,exports.DragAndScale=this.DragAndScale,exports.LGraphCanvas=this.LGraphCanvas,exports.ContextMenu=this.ContextMenu); -(function(y){function c(){this.addOutput("in ms","number");this.addOutput("in sec","number")}function k(){this.size=[140,80];this.properties={enabled:!0};this.enabled=!0;this.subgraph=new I.LGraph;this.subgraph._subgraph_node=this;this.subgraph._is_subgraph=!0;this.subgraph.onTrigger=this.onSubgraphTrigger.bind(this);this.subgraph.onInputAdded=this.onSubgraphNewInput.bind(this);this.subgraph.onInputRenamed=this.onSubgraphRenamedInput.bind(this);this.subgraph.onInputTypeChanged=this.onSubgraphTypeChangeInput.bind(this); -this.subgraph.onInputRemoved=this.onSubgraphRemovedInput.bind(this);this.subgraph.onOutputAdded=this.onSubgraphNewOutput.bind(this);this.subgraph.onOutputRenamed=this.onSubgraphRenamedOutput.bind(this);this.subgraph.onOutputTypeChanged=this.onSubgraphTypeChangeOutput.bind(this);this.subgraph.onOutputRemoved=this.onSubgraphRemovedOutput.bind(this)}function n(){this.addOutput("","number");this.name_in_graph="";this.properties={name:"",type:"number",value:0};var a=this;this.name_widget=this.addWidget("text", -"Name",this.properties.name,function(b){b&&a.setProperty("name",b)});this.type_widget=this.addWidget("text","Type",this.properties.type,function(b){a.setProperty("type",b)});this.value_widget=this.addWidget("number","Value",this.properties.value,function(b){a.setProperty("value",b)});this.widgets_up=!0;this.size=[180,90]}function r(){this.addInput("","");this.name_in_graph="";this.properties={name:"",type:""};this.name_widget=this.addWidget("text","Name",this.properties.name,"name");this.type_widget= -this.addWidget("text","Type",this.properties.type,"type");this.widgets_up=!0;this.size=[180,60]}function u(){this.addOutput("value","number");this.addProperty("value",1);this.widget=this.addWidget("number","value",1,"value");this.widgets_up=!0;this.size=[180,30]}function h(){this.addOutput("bool","boolean");this.addProperty("value",!0);this.widget=this.addWidget("toggle","value",!0,"value");this.widgets_up=this.serialize_widgets=!0;this.size=[140,30]}function E(){this.addOutput("string","string"); -this.addProperty("value","");this.widget=this.addWidget("text","value","","value");this.widgets_up=!0;this.size=[180,30]}function D(){this.addOutput("obj","object");this.size=[120,30];this._object={}}function J(){this.addInput("url","string");this.addOutput("file","string");this.addProperty("url","");this.addProperty("type","text");this.widget=this.addWidget("text","url","","url");this._data=null}function L(){this.addInput("parse",I.ACTION);this.addInput("json","string");this.addOutput("done",I.EVENT); -this.addOutput("object","object");this.widget=this.addWidget("button","parse","",this.parse.bind(this));this._obj=this._str=null}function H(){this.addOutput("data","object");this.addProperty("value","");this.widget=this.addWidget("text","json","","value");this.widgets_up=!0;this.size=[140,30];this._value=null}function F(){this._value=[];this.addInput("json","");this.addOutput("arrayOut","array");this.addOutput("length","number");this.addProperty("value","[]");this.widget=this.addWidget("text","array", -this.properties.value,"value");this.widgets_up=!0;this.size=[140,50]}function e(){this.addInput("arr","array");this.addInput("value","");this.addOutput("arr","array");this.properties={index:0};this.widget=this.addWidget("number","i",this.properties.index,"index",{precision:0,step:10,min:0})}function K(){this.addInput("array","array,table,string");this.addInput("index","number");this.addOutput("value","");this.addProperty("index",0)}function B(){this.addInput("table","table");this.addInput("row","number"); -this.addInput("col","number");this.addOutput("value","");this.addProperty("row",0);this.addProperty("column",0)}function M(){this.addInput("obj","object");this.addOutput("property",0);this.addProperty("value",0);this.widget=this.addWidget("text","prop.","",this.setValue.bind(this));this.widgets_up=!0;this.size=[140,30];this._value=null}function l(){this.addInput("obj","");this.addOutput("keys","array");this.size=[140,30]}function m(){this.addInput("obj","");this.addInput("value","");this.addOutput("obj", -"");this.properties={property:""};this.name_widget=this.addWidget("text","prop.",this.properties.property,"property")}function w(){this.addInput("A","object");this.addInput("B","object");this.addOutput("out","object");this._result={};var a=this;this.addWidget("button","clear","",function(){a._result={}});this.size=this.computeSize()}function N(){this.size=[60,30];this.addInput("in");this.addOutput("out");this.properties={varname:"myname",container:N.LITEGRAPH};this.value=null}function a(a){return a&& -null!=a.length?Number(a.length):0}function a(a){return a&&null!=a.length?Number(a.length):0}function b(){this.size=[60,30];this.addInput("data",0);this.addInput("download",I.ACTION);this.properties={filename:"data.json"};this.value=null;var a=this;this.addWidget("button","Download","",function(b){a.value&&a.downloadAsFile()})}function d(){this.size=[60,30];this.addInput("value",0,{label:""});this.value=0}function f(){this.addInput("in",0);this.addOutput("out",0);this.size=[40,30]}function g(){this.mode= -I.ON_EVENT;this.size=[80,30];this.addProperty("msg","");this.addInput("log",I.EVENT);this.addInput("msg",0)}function q(){this.mode=I.ON_EVENT;this.addProperty("msg","");this.addInput("",I.EVENT);this.widget=this.addWidget("text","Text","","msg");this.widgets_up=!0;this.size=[200,30]}function z(){this.size=[60,30];this.addProperty("onExecute","return A;");this.addInput("A",0);this.addInput("B",0);this.addOutput("out",0);this._func=null;this.data={}}function P(){this.addInput("A",0);this.addInput("B", -0);this.addOutput("true","boolean");this.addOutput("false","boolean");this.addProperty("A",1);this.addProperty("B",1);this.addProperty("OP","==","enum",{values:P.values});this.addWidget("combo","Op.",this.properties.OP,{property:"OP",values:P.values});this.size=[80,60]}var I=y.LiteGraph;c.title="Time";c.desc="Time";c.prototype.onExecute=function(){this.setOutputData(0,1E3*this.graph.globaltime);this.setOutputData(1,this.graph.globaltime)};I.registerNodeType("basic/time",c);k.title="Subgraph";k.desc= -"Graph inside a node";k.title_color="#334";k.prototype.onGetInputs=function(){return[["enabled","boolean"]]};k.prototype.onDblClick=function(a,b,d){var f=this;setTimeout(function(){d.openSubgraph(f.subgraph)},10)};k.prototype.onAction=function(a,b){this.subgraph.onAction(a,b)};k.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled")){if(this.inputs)for(var a=0;aa&&(b[0]=c?this.trigger(null,e,h):this._pending.push([c,e])};D.prototype.onExecute= -function(c,e){c=1E3*this.graph.elapsed_time;this.isInputConnected(1)&&(this.properties.time_in_ms=this.getInputData(1));for(var h=0;he[1]))return this.old_y=c.canvasY,this.captureInput(!0),this.mouse_captured=!0};n.prototype.onMouseMove=function(c){if(this.mouse_captured){var e=this.old_y-c.canvasY;c.shiftKey&&(e*=10);if(c.metaKey||c.altKey)e*=.1;this.old_y=c.canvasY;c=this._remainder+e/n.pixels_threshold;this._remainder=c%1;c=clamp(this.properties.value+ -(c|0)*this.properties.step,this.properties.min,this.properties.max);this.properties.value=c;this.graph._version++;this.setDirtyCanvas(!0)}};n.prototype.onMouseUp=function(c,e){200>c.click_time&&(this.properties.value=clamp(this.properties.value+(e[1]>.5*this.size[1]?-1:1)*this.properties.step,this.properties.min,this.properties.max),this.graph._version++,this.setDirtyCanvas(!0));this.mouse_captured&&(this.mouse_captured=!1,this.captureInput(!1))};H.registerNodeType("widget/number",n);r.title="Combo"; -r.desc="Widget to select from a list";r.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};r.prototype.onPropertyChanged=function(c,e){"values"==c?(this._values=e.split(";"),this.widget.options.values=this._values):"value"==c&&(this.widget.value=e)};H.registerNodeType("widget/combo",r);u.title="Knob";u.desc="Circular controller";u.size=[80,100];u.prototype.onDrawForeground=function(c){if(!this.flags.collapsed){-1==this.value&&(this.value=(this.properties.value-this.properties.min)/ -(this.properties.max-this.properties.min));var e=.5*this.size[0],h=.5*this.size[1],k=.5*Math.min(this.size[0],this.size[1])-5;c.globalAlpha=1;c.save();c.translate(e,h);c.rotate(.75*Math.PI);c.fillStyle="rgba(0,0,0,0.5)";c.beginPath();c.moveTo(0,0);c.arc(0,0,k,0,1.5*Math.PI);c.fill();c.strokeStyle="black";c.fillStyle=this.properties.color;c.lineWidth=2;c.beginPath();c.moveTo(0,0);c.arc(0,0,k-4,0,1.5*Math.PI*Math.max(.01,this.value));c.closePath();c.fill();c.lineWidth=1;c.globalAlpha=1;c.restore(); -c.fillStyle="black";c.beginPath();c.arc(e,h,.75*k,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":this.properties.color;c.beginPath();var n=this.value*Math.PI*1.5+.75*Math.PI;c.arc(e+Math.cos(n)*k*.65,h+Math.sin(n)*k*.65,.05*k,0,2*Math.PI,!0);c.fill();c.fillStyle=this.mouseOver?"white":"#AAA";c.font=Math.floor(.5*k)+"px Arial";c.textAlign="center";c.fillText(this.properties.value.toFixed(this.properties.precision),e,h+.15*k)}};u.prototype.onExecute=function(){this.setOutputData(0,this.properties.value); -this.boxcolor=H.colorToString([this.value,this.value,this.value])};u.prototype.onMouseDown=function(c){this.center=[.5*this.size[0],.5*this.size[1]+20];this.radius=.5*this.size[0];if(20>c.canvasY-this.pos[1]||H.distance([c.canvasX,c.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];this.captureInput(!0);return!0};u.prototype.onMouseMove=function(c){if(this.oldmouse){c=[c.canvasX-this.pos[0],c.canvasY- -this.pos[1]];var e=this.value;e-=.01*(c[1]-this.oldmouse[1]);1e&&(e=0);this.value=e;this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=c;this.setDirtyCanvas(!0)}};u.prototype.onMouseUp=function(c){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};u.prototype.onPropertyChanged=function(c,e){if("min"==c||"max"==c||"value"==c)return this.properties[c]=parseFloat(e),!0};H.registerNodeType("widget/knob",u);h.title="Inner Slider"; -h.prototype.onPropertyChanged=function(c,e){"value"==c&&(this.slider.value=e)};h.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)};H.registerNodeType("widget/internal_slider",h);E.title="H.Slider";E.desc="Linear slider controller";E.prototype.onDrawForeground=function(c){-1==this.value&&(this.value=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min));c.globalAlpha=1;c.lineWidth=1;c.fillStyle="#000";c.fillRect(2,2,this.size[0]-4,this.size[1]- -4);c.fillStyle=this.properties.color;c.beginPath();c.rect(4,4,(this.size[0]-8)*this.value,this.size[1]-8);c.fill()};E.prototype.onExecute=function(){this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value;this.setOutputData(0,this.properties.value);this.boxcolor=H.colorToString([this.value,this.value,this.value])};E.prototype.onMouseDown=function(c){if(0>c.canvasY-this.pos[1])return!1;this.oldmouse=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];this.captureInput(!0); -return!0};E.prototype.onMouseMove=function(c){if(this.oldmouse){c=[c.canvasX-this.pos[0],c.canvasY-this.pos[1]];var e=this.value;e+=(c[0]-this.oldmouse[0])/this.size[0];1e&&(e=0);this.value=e;this.oldmouse=c;this.setDirtyCanvas(!0)}};E.prototype.onMouseUp=function(c){this.oldmouse=null;this.captureInput(!1)};E.prototype.onMouseLeave=function(c){};H.registerNodeType("widget/hslider",E);D.title="Progress";D.desc="Shows data in linear progress";D.prototype.onExecute=function(){var c=this.getInputData(0); -void 0!=c&&(this.properties.value=c)};D.prototype.onDrawForeground=function(c){c.lineWidth=1;c.fillStyle=this.properties.color;var e=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min);e=Math.min(1,e);e=Math.max(0,e);c.fillRect(2,2,(this.size[0]-4)*e,this.size[1]-4)};H.registerNodeType("widget/progress",D);J.title="Text";J.desc="Shows the input value";J.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text", -text:"Normal",type:"minibutton"}];J.prototype.onDrawForeground=function(c){c.fillStyle=this.properties.color;var e=this.properties.value;this.properties.glowSize?(c.shadowColor=this.properties.color,c.shadowOffsetX=0,c.shadowOffsetY=0,c.shadowBlur=this.properties.glowSize):c.shadowColor="transparent";var h=this.properties.fontsize;c.textAlign=this.properties.align;c.font=h.toString()+"px "+this.properties.font;this.str="number"==typeof e?e.toFixed(this.properties.decimals):e;if("string"==typeof this.str){e= -this.str.replace(/[\r\n]/g,"\\n").split("\\n");for(var k=0;kr?k.xbox.axes.lx:0,this._left_axis[1]=Math.abs(k.xbox.axes.ly)>r?k.xbox.axes.ly:0,this._right_axis[0]=Math.abs(k.xbox.axes.rx)>r?k.xbox.axes.rx:0,this._right_axis[1]=Math.abs(k.xbox.axes.ry)>r?k.xbox.axes.ry:0,this._triggers[0]=Math.abs(k.xbox.axes.ltrigger)>r?k.xbox.axes.ltrigger: -0,this._triggers[1]=Math.abs(k.xbox.axes.rtrigger)>r?k.xbox.axes.rtrigger:0);if(this.outputs)for(r=0;rr;r++)if(k[r]){k=k[r];r=this.xbox_mapping;r||(r=this.xbox_mapping={axes:[], -buttons:{},hat:"",hatmap:c.CENTER});r.axes.lx=k.axes[0];r.axes.ly=k.axes[1];r.axes.rx=k.axes[2];r.axes.ry=k.axes[3];r.axes.ltrigger=k.buttons[6].value;r.axes.rtrigger=k.buttons[7].value;r.hat="";r.hatmap=c.CENTER;for(var u=0;uu)r.buttons[c.mapping_array[u]]=k.buttons[u].pressed,k.buttons[u].was_pressed&&this.trigger(c.mapping_array[u]+"_button_event");else switch(u){case 12:k.buttons[u].pressed&&(r.hat+="up",r.hatmap|=c.UP); -break;case 13:k.buttons[u].pressed&&(r.hat+="down",r.hatmap|=c.DOWN);break;case 14:k.buttons[u].pressed&&(r.hat+="left",r.hatmap|=c.LEFT);break;case 15:k.buttons[u].pressed&&(r.hat+="right",r.hatmap|=c.RIGHT);break;case 16:r.buttons.home=k.buttons[u].pressed}k.xbox=r;return k}};c.prototype.onDrawBackground=function(c){if(!this.flags.collapsed){var k=this._left_axis,n=this._right_axis;c.strokeStyle="#88A";c.strokeRect(.5*(k[0]+1)*this.size[0]-4,.5*(k[1]+1)*this.size[1]-4,8,8);c.strokeStyle="#8A8"; -c.strokeRect(.5*(n[0]+1)*this.size[0]-4,.5*(n[1]+1)*this.size[1]-4,8,8);k=this.size[1]/this._current_buttons.length;c.fillStyle="#AEB";for(n=0;n","enum",{values:N.values});this.addWidget("combo", -"Cond.",this.properties.OP,{property:"OP",values:N.values});this.size=[80,60]}function a(){this.addInput("in",0);this.addInput("cond","boolean");this.addOutput("true",0);this.addOutput("false",0);this.size=[80,60]}function b(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment",1);this.addProperty("value",0)}function d(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"} -function f(){this.addInput("x","number");this.addInput("y","number");this.addOutput("","number");this.properties={x:1,y:1,formula:"x+y"};this.code_widget=this.addWidget("text","F(x,y)",this.properties.formula,function(a,b,d){d.properties.formula=a});this.addWidget("toggle","allow",v.allow_scripts,function(a){v.allow_scripts=a});this._func=null}function g(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function q(){this.addInputs([["x","number"],["y","number"]]); -this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function z(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function P(){this.addInputs([["x","number"],["y","number"],["z","number"]]);this.addOutput("vec3","vec3");this.properties={x:0,y:0,z:0};this._data=new Float32Array(3)}function I(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z", -"number");this.addOutput("w","number")}function t(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);this.addOutput("vec4","vec4");this.properties={x:0,y:0,z:0,w:0};this._data=new Float32Array(4)}var v=y.LiteGraph;c.title="Converter";c.desc="type A to type B";c.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a&&this.outputs)for(var b=0;ba&&(a+=1024);var c=Math.floor(a);a-=c;d=h.data[c];c=h.data[1023==c?0:c+1];b&&(a=a*a*a*(a*(6*a-15)+10));return d*(1-a)+c*a};h.prototype.onExecute=function(){var a=this.getInputData(0)|| -0,b=this.properties.octaves||1,d=0,c=1;a+=this.properties.seed||0;for(var f=this.properties.speed||1,g=0,e=0;ec);++e);a=this.properties.min;this._last_v=d/g*(this.properties.max-a)+a;this.setOutputData(0,this._last_v)};h.prototype.onDrawBackground=function(a){this.outputs[0].label=(this._last_v||0).toFixed(3)};v.registerNodeType("math/noise",h);E.title="Spikes";E.desc="spike every random time";E.prototype.onExecute= -function(){var a=this.graph.elapsed_time;this._remaining_time-=a;this._blink_time-=a;a=0;0this._remaining_time?(this._remaining_time=Math.random()*(this.properties.max_time-this.properties.min_time)+this.properties.min_time,this._blink_time=this.properties.duration,this.boxcolor="#FFF"):this.boxcolor="#000";this.setOutputData(0,a)};v.registerNodeType("math/spikes",E);D.title="Clamp";D.desc="Clamp number between min and max"; -D.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(a=Math.max(this.properties.min,a),a=Math.min(this.properties.max,a),this.setOutputData(0,a))};D.prototype.getCode=function(a){a="";this.isInputConnected(0)&&(a+="clamp({{0}},"+this.properties.min+","+this.properties.max+")");return a};v.registerNodeType("math/clamp",D);J.title="Lerp";J.desc="Linear Interpolation";J.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.getInputData(1);null==b&&(b=0); -var d=this.properties.f,c=this.getInputData(2);void 0!==c&&(d=c);this.setOutputData(0,a*(1-d)+b*d)};J.prototype.onGetInputs=function(){return[["f","number"]]};v.registerNodeType("math/lerp",J);L.title="Abs";L.desc="Absolute";L.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.abs(a))};v.registerNodeType("math/abs",L);H.title="Floor";H.desc="Floor number to remove fractional part";H.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0, -Math.floor(a))};v.registerNodeType("math/floor",H);F.title="Frac";F.desc="Returns fractional part";F.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a%1)};v.registerNodeType("math/frac",F);e.title="Smoothstep";e.desc="Smoothstep";e.prototype.onExecute=function(){var a=this.getInputData(0);if(void 0!==a){var b=this.properties.A;a=clamp((a-b)/(this.properties.B-b),0,1);this.setOutputData(0,a*a*(3-2*a))}};v.registerNodeType("math/smoothstep",e);K.title="Scale"; -K.desc="v * factor";K.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a*this.properties.factor)};v.registerNodeType("math/scale",K);B.title="Gate";B.desc="if v is true, then outputs A, otherwise B";B.prototype.onExecute=function(){var a=this.getInputData(0);this.setOutputData(0,this.getInputData(a?1:2))};v.registerNodeType("math/gate",B);M.title="Average";M.desc="Average Filter";M.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b= -this._values.length;this._values[this._current%b]=a;this._current+=1;this._current>b&&(this._current=0);for(var d=a=0;db&&(b=1);this.properties.samples=Math.round(b);a=this._values;this._values=new Float32Array(this.properties.samples);a.length<=this._values.length?this._values.set(a):this._values.set(a.subarray(0,this._values.length))};v.registerNodeType("math/average",M);l.title="TendTo";l.desc="moves the output value always closer to the input"; -l.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.factor;this._value=null==this._value?a:this._value*(1-b)+a*b;this.setOutputData(0,this._value)};v.registerNodeType("math/tendTo",l);m.values="+ - * / % ^ max min".split(" ");m.funcs={"+":function(a,b){return a+b},"-":function(a,b){return a-b},x:function(a,b){return a*b},X:function(a,b){return a*b},"*":function(a,b){return a*b},"/":function(a,b){return a/b},"%":function(a,b){return a%b},"^":function(a, -b){return Math.pow(a,b)},max:function(a,b){return Math.max(a,b)},min:function(a,b){return Math.min(a,b)}};m.title="Operation";m.desc="Easy math operators";m["@OP"]={type:"enum",title:"operation",values:m.values};m.size=[100,60];m.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"};m.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};m.prototype.onPropertyChanged= -function(a,b){"OP"==a&&(this._func=m.funcs[this.properties.OP],this._func||(console.warn("Unknown operation: "+this.properties.OP),this._func=function(a){return a}))};m.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);null!=a?a.constructor===Number&&(this.properties.A=a):a=this.properties.A;null!=b?this.properties.B=b:b=this.properties.B;var d=m.funcs[this.properties.OP];if(a.constructor===Number)var c=d(a,b);else if(a.constructor===Array){c=this._result;c.length=a.length; -for(var f=0;fB":g=a>b;break;case "A=B":g=a>=b}this.setOutputData(d,g)}}};w.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};v.registerNodeType("math/compare",w);v.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"});v.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"});v.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]], -title:"A>B"});v.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"});v.registerSearchboxExtra("math/compare","<=",{outputs:[["A<=B","boolean"]],title:"A<=B"});N.values="> < == != <= >= || &&".split(" ");N["@OP"]={type:"enum",title:"operation",values:N.values};N.title="Condition";N.desc="evaluates condition between A and B";N.prototype.getTitle=function(){return"A "+this.properties.OP+ -" B"};N.prototype.onExecute=function(){var a=this.getInputData(0);void 0===a?a=this.properties.A:this.properties.A=a;var b=this.getInputData(1);void 0===b?b=this.properties.B:this.properties.B=b;var d=!0;switch(this.properties.OP){case ">":d=a>b;break;case "<":d=a=":d=a>=b;break;case "||":d=a||b;break;case "&&":d=a&&b}this.setOutputData(0,d);this.setOutputData(1,!d)};v.registerNodeType("math/condition",N);a.title= -"Branch";a.desc="If condition is true, outputs IN in true, otherwise in false";a.prototype.onExecute=function(){var a=this.getInputData(0);this.getInputData(1)?(this.setOutputData(0,a),this.setOutputData(1,null)):(this.setOutputData(0,null),this.setOutputData(1,a))};v.registerNodeType("math/branch",a);b.title="Accumulate";b.desc="Increments a value every time";b.prototype.onExecute=function(){null===this.properties.value&&(this.properties.value=0);var a=this.getInputData(0);this.properties.value= -null!==a?this.properties.value+a:this.properties.value+this.properties.increment;this.setOutputData(0,this.properties.value)};v.registerNodeType("math/accumulate",b);d.title="Trigonometry";d.desc="Sin Cos Tan";d.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.amplitude,d=this.findInputSlot("amplitude");-1!=d&&(b=this.getInputData(d));var c=this.properties.offset;d=this.findInputSlot("offset");-1!=d&&(c=this.getInputData(d));d=0;for(var f=this.outputs.length;d< -f;++d){switch(this.outputs[d].name){case "sin":var g=Math.sin(a);break;case "cos":g=Math.cos(a);break;case "tan":g=Math.tan(a);break;case "asin":g=Math.asin(a);break;case "acos":g=Math.acos(a);break;case "atan":g=Math.atan(a)}this.setOutputData(d,b*g+c)}};d.prototype.onGetInputs=function(){return[["v","number"],["amplitude","number"],["offset","number"]]};d.prototype.onGetOutputs=function(){return[["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]]}; -v.registerNodeType("math/trigonometry",d);v.registerSearchboxExtra("math/trigonometry","SIN()",{outputs:[["sin","number"]],title:"SIN()"});v.registerSearchboxExtra("math/trigonometry","COS()",{outputs:[["cos","number"]],title:"COS()"});v.registerSearchboxExtra("math/trigonometry","TAN()",{outputs:[["tan","number"]],title:"TAN()"});f.title="Formula";f.desc="Compute formula";f.size=[160,100];M.prototype.onPropertyChanged=function(a,b){"formula"==a&&(this.code_widget.value=b)};f.prototype.onExecute= -function(){if(v.allow_scripts){var a=this.getInputData(0),b=this.getInputData(1);null!=a?this.properties.x=a:a=this.properties.x;null!=b?this.properties.y=b:b=this.properties.y;try{this._func&&this._func_code==this.properties.formula||(this._func=new Function("x","y","TIME","return "+this.properties.formula),this._func_code=this.properties.formula);var d=this._func(a,b,this.graph.globaltime);this.boxcolor=null}catch(A){this.boxcolor="red"}this.setOutputData(0,d)}};f.prototype.getTitle=function(){return this._func_code|| -"Formula"};f.prototype.onDrawBackground=function(){var a=this.properties.formula;this.outputs&&this.outputs.length&&(this.outputs[0].label=a)};v.registerNodeType("math/formula",f);g.title="Vec2->XY";g.desc="vector 2 to components";g.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};v.registerNodeType("math3d/vec2-to-xy",g);q.title="XY->Vec2";q.desc="components to vector2";q.prototype.onExecute=function(){var a=this.getInputData(0); -null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var d=this._data;d[0]=a;d[1]=b;this.setOutputData(0,d)};v.registerNodeType("math3d/xy-to-vec2",q);z.title="Vec3->XYZ";z.desc="vector 3 to components";z.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]))};v.registerNodeType("math3d/vec3-to-xyz",z);P.title="XYZ->Vec3";P.desc="components to vector3";P.prototype.onExecute= -function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var d=this.getInputData(2);null==d&&(d=this.properties.z);var c=this._data;c[0]=a;c[1]=b;c[2]=d;this.setOutputData(0,c)};v.registerNodeType("math3d/xyz-to-vec3",P);I.title="Vec4->XYZW";I.desc="vector 4 to components";I.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]),this.setOutputData(3, -a[3]))};v.registerNodeType("math3d/vec4-to-xyzw",I);t.title="XYZW->Vec4";t.desc="components to vector4";t.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var d=this.getInputData(2);null==d&&(d=this.properties.z);var c=this.getInputData(3);null==c&&(c=this.properties.w);var f=this._data;f[0]=a;f[1]=b;f[2]=d;f[3]=c;this.setOutputData(0,f)};v.registerNodeType("math3d/xyzw-to-vec4",t)})(this); -(function(y){function c(){this.addInput("T","vec3");this.addInput("R","vec3");this.addInput("S","vec3");this.addOutput("mat4","mat4");this.properties={T:[0,0,0],R:[0,0,0],S:[1,1,1],R_in_degrees:!0};this._result=mat4.create();this._must_update=!0}function k(){this.addInput("A","number,vec3");this.addInput("B","number,vec3");this.addOutput("=","number,vec3");this.addProperty("OP","+","enum",{values:k.values});this._result=vec3.create()}function n(){this.addInput("in","vec3");this.addInput("f","number"); -this.addOutput("out","vec3");this.properties={f:1};this._data=new Float32Array(3)}function r(){this.addInput("in","vec3");this.addOutput("out","number")}function u(){this.addInput("in","vec3");this.addOutput("out","vec3");this._data=new Float32Array(3)}function h(){this.addInput("A","vec3");this.addInput("B","vec3");this.addInput("f","vec3");this.addOutput("out","vec3");this.properties={f:.5};this._data=new Float32Array(3)}function E(){this.addInput("A","vec3");this.addInput("B","vec3");this.addOutput("out", -"number")}var D=y.LiteGraph;c.title="mat4";c.temp_quat=new Float32Array([0,0,0,1]);c.temp_mat4=new Float32Array(16);c.temp_vec3=new Float32Array(3);c.prototype.onPropertyChanged=function(c,e){this._must_update=!0};c.prototype.onExecute=function(){var e=this._result,l=c.temp_quat,m=c.temp_mat4,h=c.temp_vec3,k=this.getInputData(0),a=this.getInputData(1),b=this.getInputData(2);if(this._must_update||k||a||b)k=k||this.properties.T,a=a||this.properties.R,b=b||this.properties.S,mat4.identity(e),mat4.translate(e, -e,k),this.properties.R_in_degrees?(h.set(a),vec3.scale(h,h,DEG2RAD),quat.fromEuler(l,h)):quat.fromEuler(l,a),mat4.fromQuat(m,l),mat4.multiply(e,e,m),mat4.scale(e,e,b);this.setOutputData(0,e)};D.registerNodeType("math3d/mat4",c);k.values="+ - * / % ^ max min dot cross".split(" ");D.registerSearchboxExtra("math3d/operation","CROSS()",{properties:{OP:"cross"},title:"CROSS()"});D.registerSearchboxExtra("math3d/operation","DOT()",{properties:{OP:"dot"},title:"DOT()"});k.title="Operation";k.desc="Easy math 3D operators"; -k["@OP"]={type:"enum",title:"operation",values:k.values};k.size=[100,60];k.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"};k.prototype.onExecute=function(){var c=this.getInputData(0),e=this.getInputData(1);if(null!=c&&null!=e){c.constructor===Number&&(c=[c,c,c]);e.constructor===Number&&(e=[e,e,e]);var m=this._result;switch(this.properties.OP){case "+":m=vec3.add(m,c,e);break;case "-":m=vec3.sub(m,c,e); -break;case "x":case "X":case "*":m=vec3.mul(m,c,e);break;case "/":m=vec3.div(m,c,e);break;case "%":m[0]=c[0]%e[0];m[1]=c[1]%e[1];m[2]=c[2]%e[2];break;case "^":m[0]=Math.pow(c[0],e[0]);m[1]=Math.pow(c[1],e[1]);m[2]=Math.pow(c[2],e[2]);break;case "max":m[0]=Math.max(c[0],e[0]);m[1]=Math.max(c[1],e[1]);m[2]=Math.max(c[2],e[2]);break;case "min":m[0]=Math.min(c[0],e[0]),m[1]=Math.min(c[1],e[1]),m[2]=Math.min(c[2],e[2]);case "dot":m=vec3.dot(c,e);break;case "cross":vec3.cross(m,c,e);break;default:console.warn("Unknown operation: "+ -this.properties.OP)}this.setOutputData(0,m)}};k.prototype.onDrawBackground=function(c){this.flags.collapsed||(c.font="40px Arial",c.fillStyle="#666",c.textAlign="center",c.fillText(this.properties.OP,.5*this.size[0],.5*(this.size[1]+D.NODE_TITLE_HEIGHT)),c.textAlign="left")};D.registerNodeType("math3d/operation",k);n.title="vec3_scale";n.desc="scales the components of a vec3";n.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null==e&&(e=this.properties.f); -var m=this._data;m[0]=c[0]*e;m[1]=c[1]*e;m[2]=c[2]*e;this.setOutputData(0,m)}};D.registerNodeType("math3d/vec3-scale",n);r.title="vec3_length";r.desc="returns the module of a vector";r.prototype.onExecute=function(){var c=this.getInputData(0);null!=c&&this.setOutputData(0,Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]))};D.registerNodeType("math3d/vec3-length",r);u.title="vec3_normalize";u.desc="returns the vector normalized";u.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e= -Math.sqrt(c[0]*c[0]+c[1]*c[1]+c[2]*c[2]),m=this._data;m[0]=c[0]/e;m[1]=c[1]/e;m[2]=c[2]/e;this.setOutputData(0,m)}};D.registerNodeType("math3d/vec3-normalize",u);h.title="vec3_lerp";h.desc="returns the interpolated vector";h.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);if(null!=e){var m=this.getInputOrProperty("f"),h=this._data;h[0]=c[0]*(1-m)+e[0]*m;h[1]=c[1]*(1-m)+e[1]*m;h[2]=c[2]*(1-m)+e[2]*m;this.setOutputData(0,h)}}};D.registerNodeType("math3d/vec3-lerp", -h);E.title="vec3_dot";E.desc="returns the dot product";E.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null!=e&&this.setOutputData(0,c[0]*e[0]+c[1]*e[1]+c[2]*e[2])}};D.registerNodeType("math3d/vec3-dot",E);if(y.glMatrix){y=function(){this.addInput("vec3","vec3");this.addOutput("remap","vec3");this.addOutput("clamped","vec3");this.properties={clamp:!0,range_min:[-1,-1,0],range_max:[1,1,0],target_min:[-1,-1,0],target_max:[1,1,0]};this._value=vec3.create(); -this._clamped=vec3.create()};var J=function(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]);this.addOutput("slerp","quat");this.addProperty("factor",.5);this._value=quat.create()},L=function(){this.addInputs([["A","quat"],["B","quat"]]);this.addOutput("A*B","quat");this._value=quat.create()},H=function(){this.addInputs([["vec3","vec3"],["quat","quat"]]);this.addOutput("result","vec3");this.properties={vec:[0,0,1]}},F=function(){this.addInput(["quat","quat"]);this.addOutput("euler", -"vec3");this._value=vec3.create()},e=function(){this.addInput("euler","vec3");this.addOutput("quat","quat");this.properties={euler:[0,0,0],use_yaw_pitch_roll:!1};this._degs=vec3.create();this._value=quat.create()},K=function(){this.addInputs([["degrees","number"],["axis","vec3"]]);this.addOutput("quat","quat");this.properties={angle:90,axis:vec3.fromValues(0,1,0)};this._value=quat.create()},B=function(){this.addOutput("quat","quat");this.properties={x:0,y:0,z:0,w:1,normalize:!1};this._value=quat.create()}; -B.title="Quaternion";B.desc="quaternion";B.prototype.onExecute=function(){this._value[0]=this.getInputOrProperty("x");this._value[1]=this.getInputOrProperty("y");this._value[2]=this.getInputOrProperty("z");this._value[3]=this.getInputOrProperty("w");this.properties.normalize&&quat.normalize(this._value,this._value);this.setOutputData(0,this._value)};B.prototype.onGetInputs=function(){return[["x","number"],["y","number"],["z","number"],["w","number"]]};D.registerNodeType("math3d/quaternion",B);K.title= -"Rotation";K.desc="quaternion rotation";K.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.angle);var e=this.getInputData(1);null==e&&(e=this.properties.axis);c=quat.setAxisAngle(this._value,e,.0174532925*c);this.setOutputData(0,c)};D.registerNodeType("math3d/rotation",K);e.title="Euler->Quat";e.desc="Converts euler angles (in degrees) to quaternion";e.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.euler);vec3.scale(this._degs, -c,DEG2RAD);this.properties.use_yaw_pitch_roll&&(this._degs=[this._degs[2],this._degs[0],this._degs[1]]);c=quat.fromEuler(this._value,this._degs);this.setOutputData(0,c)};D.registerNodeType("math3d/euler_to_quat",e);F.title="Euler->Quat";F.desc="Converts rotX,rotY,rotZ in degrees to quat";F.prototype.onExecute=function(){var c=this.getInputData(0);c&&(quat.toEuler(this._value,c),vec3.scale(this._value,this._value,DEG2RAD),this.setOutputData(0,this._value))};D.registerNodeType("math3d/quat_to_euler", -F);H.title="Rot. Vec3";H.desc="rotate a point";H.prototype.onExecute=function(){var c=this.getInputData(0);null==c&&(c=this.properties.vec);var e=this.getInputData(1);null==e?this.setOutputData(c):this.setOutputData(0,vec3.transformQuat(vec3.create(),c,e))};D.registerNodeType("math3d/rotate_vec3",H);L.title="Mult. Quat";L.desc="rotate quaternion";L.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);null!=e&&(c=quat.multiply(this._value,c,e),this.setOutputData(0, -c))}};D.registerNodeType("math3d/mult-quat",L);J.title="Quat Slerp";J.desc="quaternion spherical interpolation";J.prototype.onExecute=function(){var c=this.getInputData(0);if(null!=c){var e=this.getInputData(1);if(null!=e){var m=this.properties.factor;null!=this.getInputData(2)&&(m=this.getInputData(2));c=quat.slerp(this._value,c,e,m);this.setOutputData(0,c)}}};D.registerNodeType("math3d/quat-slerp",J);y.title="Remap Range";y.desc="remap a 3D range";y.prototype.onExecute=function(){var c=this.getInputData(0); -c&&this._value.set(c);c=this.properties.range_min;for(var e=this.properties.range_max,m=this.properties.target_min,h=this.properties.target_max,k=0;3>k;++k){var a=e[k]-c[k];this._clamped[k]=clamp(this._value[k],c[k],e[k]);0==a?this._value[k]=.5*(m[k]+h[k]):(a=(this._value[k]-c[k])/a,this.properties.clamp&&(a=clamp(a,0,1)),this._value[k]=m[k]+a*(h[k]-m[k]))}this.setOutputData(0,this._value);this.setOutputData(1,this._clamped)};D.registerNodeType("math3d/remap_range",y)}else D.debug&&console.warn("No glmatrix found, some Math3D nodes may not work")})(this); -(function(y){function c(){this.addInput("","string");this.addOutput("table","table");this.addOutput("rows","number");this.addProperty("value","");this.addProperty("separator",",");this._table=null}y=y.LiteGraph;y.wrapFunctionAsNode("string/toString",function(c){if(c&&c.constructor===Object)try{return JSON.stringify(c)}catch(n){}return String(c)},[""],"string");y.wrapFunctionAsNode("string/compare",function(c,n){return c==n},["string","string"],"boolean");y.wrapFunctionAsNode("string/concatenate", -function(c,n){return void 0===c?n:void 0===n?c:c+n},["string","string"],"string");y.wrapFunctionAsNode("string/contains",function(c,n){return void 0===c||void 0===n?!1:-1!=c.indexOf(n)},["string","string"],"boolean");y.wrapFunctionAsNode("string/toUpperCase",function(c){return null!=c&&c.constructor===String?c.toUpperCase():c},["string"],"string");y.wrapFunctionAsNode("string/split",function(c,n){null==n&&(n=this.properties.separator);if(null==c)return[];if(c.constructor===String)return c.split(n|| -" ");if(c.constructor===Array){for(var k=[],u=0;ue;++e){var h=this.getInputData(e);if(null!=h){var k=this.values[e];k.push(h);k.length>c[0]&&k.shift()}}}};c.prototype.onDrawBackground=function(e){if(!this.flags.collapsed){var h= -this.size,k=.5*h[1]/this.properties.scale,n=c.colors,l=.5*h[1];e.fillStyle="#000";e.fillRect(0,0,h[0],h[1]);e.strokeStyle="#555";e.beginPath();e.moveTo(0,l);e.lineTo(h[0],l);e.stroke();if(this.inputs)for(var m=0;4>m;++m){var w=this.values[m];if(this.inputs[m]&&this.inputs[m].link){e.strokeStyle=n[m];e.beginPath();var r=w[0]*k*-1+l;e.moveTo(0,clamp(r,0,h[1]));for(var a=1;ah&&(h=0);if(0!=c.length){var k=[0,0,0];if(0==h)k=c[0];else if(1==h)k=c[c.length-1];else{var n= -(c.length-1)*h;h=c[Math.floor(n)];c=c[Math.floor(n)+1];n-=Math.floor(n);k[0]=h[0]*(1-n)+c[0]*n;k[1]=h[1]*(1-n)+c[1]*n;k[2]=h[2]*(1-n)+c[2]*n}for(h=0;h=c&&(this._video.currentTime=c*this._video.duration,this._video.pause()); -this._video.dirty=!0;this.setOutputData(0,this._video);this.setOutputData(1,this._video.currentTime);this.setOutputData(2,this._video.duration);this.setDirtyCanvas(!0)}};L.prototype.onStart=function(){this.play()};L.prototype.onStop=function(){this.stop()};L.prototype.loadVideo=function(c){this._video_url=c;var e=c.substr(0,10).indexOf(":"),h="";-1!=e&&(h=c.substr(0,e));e="";h&&(e=c.substr(0,c.indexOf("/",h.length+3)),e=e.substr(h.length+3));this.properties.use_proxy&&h&&F.proxy&&e!=location.host&& -(c=F.proxy+c.substr(c.indexOf(":")+3));this._video=document.createElement("video");this._video.src=c;this._video.type="type=video/mp4";this._video.muted=!0;this._video.autoplay=!0;var k=this;this._video.addEventListener("loadedmetadata",function(c){console.log("Duration: "+this.duration+" seconds");console.log("Size: "+this.videoWidth+","+this.videoHeight);k.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this._video.addEventListener("progress",function(c){console.log("video loading...")}); -this._video.addEventListener("error",function(c){console.error("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:console.error("You stopped the video.");break;case this.error.MEDIA_ERR_NETWORK:console.error("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:console.error("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:console.error("Sorry, your browser can't play this video.")}});this._video.addEventListener("ended", -function(c){console.log("Video Ended.");this.play()})};L.prototype.onPropertyChanged=function(c,h){this.properties[c]=h;"url"==c&&""!=h&&this.loadVideo(h);return!0};L.prototype.play=function(){this._video&&this._video.videoWidth&&this._video.play()};L.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};L.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};L.prototype.pause=function(){this._video&&(console.log("Video paused"), -this._video.pause())};L.prototype.onWidget=function(c,h){};F.registerNodeType("graphics/video",L);H.title="Webcam";H.desc="Webcam image";H.is_webcam_open=!1;H.prototype.openStream=function(){if(navigator.mediaDevices.getUserMedia){this._waiting_confirmation=!0;navigator.mediaDevices.getUserMedia({audio:!1,video:this.properties.filterFacingMode?{facingMode:this.properties.facingMode}:!0}).then(this.streamReady.bind(this)).catch(function(e){console.log("Webcam rejected",e);c._webcam_stream=!1;H.is_webcam_open= -!1;c.boxcolor="red";c.trigger("stream_error")});var c=this}else console.log("getUserMedia() is not supported in your browser, use chrome and enable WebRTC from about://flags")};H.prototype.closeStream=function(){if(this._webcam_stream){var c=this._webcam_stream.getTracks();if(c.length)for(var h=0;h=this.size[1]||!this.properties.show||!this._video||(c.save(),c.drawImage(this._video,0,0,this.size[0],this.size[1]),c.restore())};H.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["stream_ready",F.EVENT],["stream_closed",F.EVENT],["stream_error",F.EVENT]]};F.registerNodeType("graphics/webcam",H)})(this); -(function(y){function c(){this.addOutput("tex","Texture");this.addOutput("name","string");this.properties={name:"",filter:!0};this.size=[c.image_preview_size,c.image_preview_size]}function k(){this.addInput("Texture","Texture");this.properties={flipY:!1};this.size=[c.image_preview_size,c.image_preview_size]}function n(){this.addInput("Texture","Texture");this.addOutput("tex","Texture");this.addOutput("name","string");this.properties={name:"",generate_mipmaps:!1}}function r(){this.addInput("Texture", -"Texture");this.addInput("TextureB","Texture");this.addInput("value","number");this.addOutput("Texture","Texture");this.help="

pixelcode must be vec3, uvcode must be vec2, is optional

\t\t

uv: tex. coords

color: texture colorB: textureB

time: scene time value: input value

For multiline you must type: result = ...

";this.properties={value:1,pixelcode:"color + colorB * value",uvcode:"",precision:c.DEFAULT}; -this.has_error=!1}function u(){this.addOutput("out","Texture");this.properties={code:"",u_value:1,u_color:[1,1,1,1],width:512,height:512,precision:c.DEFAULT};this.properties.code=u.pixel_shader;this._uniforms={u_value:1,u_color:vec4.create(),in_texture:0,texSize:vec4.create(),time:0}}function h(){this.addInput("in","Texture");this.addInput("scale","vec2");this.addInput("offset","vec2");this.addOutput("out","Texture");this.properties={offset:vec2.fromValues(0,0),scale:vec2.fromValues(1,1),precision:c.DEFAULT}} -function E(){this.addInput("in","Texture");this.addInput("warp","Texture");this.addInput("factor","number");this.addOutput("out","Texture");this.properties={factor:.01,scale:[1,1],offset:[0,0],precision:c.DEFAULT};this._uniforms={u_texture:0,u_textureB:1,u_factor:1,u_scale:vec2.create(),u_offset:vec2.create()}}function D(){this.addInput("Texture","Texture");this.properties={additive:!1,antialiasing:!1,filter:!0,disable_alpha:!1,gamma:1,viewport:[0,0,1,1]};this.size[0]=130}function J(){this.addInput("Texture", -"Texture");this.addOutput("","Texture");this.properties={size:0,generate_mipmaps:!1,precision:c.DEFAULT}}function L(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={iterations:1,generate_mipmaps:!1,precision:c.DEFAULT}}function H(){this.addInput("Texture","Texture");this.addOutput("","Texture");this.properties={size:[512,512],generate_mipmaps:!1,precision:c.DEFAULT}}function F(){this.addInput("Texture","Texture");this.addOutput("tex","Texture");this.addOutput("avg", -"vec4");this.addOutput("lum","number");this.properties={use_previous_frame:!0,high_quality:!1};this._uniforms={u_texture:0,u_mipmap_offset:0};this._luminance=new Float32Array(4)}function e(){this.addInput("in","Texture");this.addInput("factor","Number");this.addOutput("out","Texture");this.properties={factor:.5};this._uniforms={u_texture:0,u_textureB:1,u_factor:this.properties.factor}}function K(){this.addInput("in","Texture");this.addOutput("avg","Texture");this.addOutput("array","Texture");this.properties= -{samples:64,frames_interval:1};this._uniforms={u_texture:0,u_textureB:1,u_samples:this.properties.samples,u_isamples:1/this.properties.samples};this.frame=0}function B(){this.addInput("Image","image");this.addOutput("","Texture");this.properties={}}function M(){this.addInput("Texture","Texture");this.addInput("LUT","Texture");this.addInput("Intensity","number");this.addOutput("","Texture");this.properties={enabled:!0,intensity:1,precision:c.DEFAULT,texture:null};M._shader||(M._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER, -M.pixel_shader))}function l(){this.addInput("Texture","Texture");this.addInput("Atlas","Texture");this.addOutput("","Texture");this.properties={enabled:!0,num_row_symbols:4,symbol_size:16,brightness:1,colorize:!1,filter:!1,invert:!1,precision:c.DEFAULT,generate_mipmaps:!1,texture:null};l._shader||(l._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,l.pixel_shader));this._uniforms={u_texture:0,u_textureB:1,u_row_simbols:4,u_simbol_size:16,u_res:vec2.create()}}function m(){this.addInput("Texture","Texture"); -this.addOutput("R","Texture");this.addOutput("G","Texture");this.addOutput("B","Texture");this.addOutput("A","Texture");m._shader||(m._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,m.pixel_shader))}function w(){this.addInput("R","Texture");this.addInput("G","Texture");this.addInput("B","Texture");this.addInput("A","Texture");this.addOutput("Texture","Texture");this.properties={precision:c.DEFAULT,R:1,G:1,B:1,A:1};this._color=vec4.create();this._uniforms={u_textureR:0,u_textureG:1,u_textureB:2, -u_textureA:3,u_color:this._color}}function N(){this.addOutput("Texture","Texture");this._tex_color=vec4.create();this.properties={color:vec4.create(),precision:c.DEFAULT}}function a(){this.addInput("A","color");this.addInput("B","color");this.addOutput("Texture","Texture");this.properties={angle:0,scale:1,A:[0,0,0],B:[1,1,1],texture_size:32};a._shader||(a._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,a.pixel_shader));this._uniforms={u_angle:0,u_colorA:vec3.create(),u_colorB:vec3.create()}}function b(){this.addInput("A", -"Texture");this.addInput("B","Texture");this.addInput("Mixer","Texture");this.addOutput("Texture","Texture");this.properties={factor:.5,size_from_biggest:!0,invert:!1,precision:c.DEFAULT};this._uniforms={u_textureA:0,u_textureB:1,u_textureMix:2,u_mix:vec4.create()}}function d(){this.addInput("Tex.","Texture");this.addOutput("Edges","Texture");this.properties={invert:!0,threshold:!1,factor:1,precision:c.DEFAULT};d._shader||(d._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,d.pixel_shader))}function f(){this.addInput("Texture", -"Texture");this.addInput("Distance","number");this.addInput("Range","number");this.addOutput("Texture","Texture");this.properties={distance:100,range:50,only_depth:!1,high_precision:!1};this._uniforms={u_texture:0,u_distance:100,u_range:50,u_camera_planes:null}}function g(){this.addInput("Texture","Texture");this.addOutput("Texture","Texture");this.properties={precision:c.DEFAULT,invert:!1};this._uniforms={u_texture:0,u_camera_planes:null,u_ires:vec2.create()}}function q(){this.addInput("Texture", -"Texture");this.addInput("Iterations","number");this.addInput("Intensity","number");this.addOutput("Blurred","Texture");this.properties={intensity:1,iterations:1,preserve_aspect:!1,scale:[1,1],precision:c.DEFAULT}}function z(){this.intensity=.5;this.persistence=.6;this.iterations=8;this.threshold=.8;this.scale=1;this.dirt_texture=null;this.dirt_factor=.5;this._textures=[];this._uniforms={u_intensity:1,u_texture:0,u_glow_texture:1,u_threshold:0,u_texel_size:vec2.create()}}function P(){this.addInput("in", -"Texture");this.addInput("dirt","Texture");this.addOutput("out","Texture");this.addOutput("glow","Texture");this.properties={enabled:!0,intensity:1,persistence:.99,iterations:16,threshold:0,scale:1,dirt_factor:.5,precision:c.DEFAULT};this.fx=new z}function I(){this.addInput("Texture","Texture");this.addOutput("Filtered","Texture");this.properties={intensity:1,radius:5}}function t(){this.addInput("Texture","Texture");this.addOutput("Filtered","Texture");this.properties={sigma:1.4,k:1.6,p:21.7,epsilon:79, -phi:.017}}function v(){this.addOutput("Webcam","Texture");this.properties={texture_name:"",facingMode:"user"};this.boxcolor="black";this.version=0}function C(){this.addInput("in","Texture");this.addInput("f","number");this.addOutput("out","Texture");this.properties={enabled:!0,factor:1,precision:c.LOW};this._uniforms={u_texture:0,u_factor:1}}function x(){this.addInput("in","");this.properties={precision:c.LOW,width:0,height:0,channels:1};this.addOutput("out","Texture")}function G(){this.addInput("in", -"Texture");this.addOutput("out","Texture");this.properties={precision:c.LOW,split_channels:!1};this._values=new Uint8Array(1024);this._values.fill(255);this._curve_texture=null;this._uniforms={u_texture:0,u_curve:1,u_range:1};this._must_update=!0;this._points={RGB:[[0,0],[1,1]],R:[[0,0],[1,1]],G:[[0,0],[1,1]],B:[[0,0],[1,1]]};this.curve_editor=null;this.addWidget("toggle","Split Channels",!1,"split_channels");this.addWidget("combo","Channel","RGB",{values:["RGB","R","G","B"]});this.curve_offset=68; -this.size=[240,160]}function A(){this.addInput("in","Texture");this.addInput("exp","number");this.addOutput("out","Texture");this.properties={exposition:1,precision:c.LOW};this._uniforms={u_texture:0,u_exposition:1}}function R(){this.addInput("in","Texture");this.addInput("avg","number,Texture");this.addOutput("out","Texture");this.properties={enabled:!0,scale:1,gamma:1,average_lum:1,lum_white:1,precision:c.LOW};this._uniforms={u_texture:0,u_lumwhite2:1,u_igamma:1,u_scale:1,u_average_lum:1}}function T(){this.addOutput("out", -"Texture");this.properties={width:512,height:512,seed:0,persistence:.1,octaves:8,scale:1,offset:[0,0],amplitude:1,precision:c.DEFAULT};this._key=0;this._texture=null;this._uniforms={u_persistence:.1,u_seed:0,u_offset:vec2.create(),u_scale:1,u_viewport:vec2.create()}}function S(){this.addInput("v");this.addOutput("out","Texture");this.properties={code:S.default_code,width:512,height:512,clear:!0,precision:c.DEFAULT,use_html_canvas:!1};this._temp_texture=this._func=null;this.compileCode()}function U(){this.addInput("in", -"Texture");this.addOutput("out","Texture");this.properties={key_color:vec3.fromValues(0,1,0),threshold:.8,slope:.2,precision:c.DEFAULT}}function V(){this.addInput("in","texture");this.addInput("yaw","number");this.addOutput("out","texture");this.properties={yaw:0}}var O=y.LiteGraph,aa=y.LGraphCanvas;y.LGraphTexture=null;"undefined"!=typeof GL&&(aa.link_type_colors.Texture="#987",y.LGraphTexture=c,c.title="Texture",c.desc="Texture",c.widgets_info={name:{widget:"texture"},filter:{widget:"checkbox"}}, -c.loadTextureCallback=null,c.image_preview_size=256,c.UNDEFINED=0,c.PASS_THROUGH=1,c.COPY=2,c.LOW=3,c.HIGH=4,c.REUSE=5,c.DEFAULT=2,c.MODE_VALUES={undefined:c.UNDEFINED,"pass through":c.PASS_THROUGH,copy:c.COPY,low:c.LOW,high:c.HIGH,reuse:c.REUSE,default:c.DEFAULT},c.getTexturesContainer=function(){return gl.textures},c.loadTexture=function(a,b){b=b||{};var d=a;"http://"==d.substr(0,7)&&O.proxy&&(d=O.proxy+d.substr(7));return c.getTexturesContainer()[a]=GL.Texture.fromURL(d,b)},c.getTexture=function(a){var b= -this.getTexturesContainer();if(!b)throw"Cannot load texture, container of textures not found";b=b[a];return!b&&a&&":"!=a[0]?this.loadTexture(a):b},c.getTargetTexture=function(a,b,d){if(!a)throw"LGraphTexture.getTargetTexture expects a reference texture";switch(d){case c.LOW:d=gl.UNSIGNED_BYTE;break;case c.HIGH:d=gl.HIGH_PRECISION_FORMAT;break;case c.REUSE:return a;default:d=a?a.type:gl.UNSIGNED_BYTE}b&&b.width==a.width&&b.height==a.height&&b.type==d&&b.format==a.format||(b=new GL.Texture(a.width, -a.height,{type:d,format:a.format,filter:gl.LINEAR}));return b},c.getTextureType=function(a,b){b=b?b.type:gl.UNSIGNED_BYTE;switch(a){case c.HIGH:b=gl.HIGH_PRECISION_FORMAT;break;case c.LOW:b=gl.UNSIGNED_BYTE}return b},c.getWhiteTexture=function(){return this._white_texture?this._white_texture:this._white_texture=GL.Texture.fromMemory(1,1,[255,255,255,255],{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST})},c.getNoiseTexture=function(){if(this._noise_texture)return this._noise_texture;for(var a=new Uint8Array(1048576), -b=0;1048576>b;++b)a[b]=255*Math.random();return this._noise_texture=a=GL.Texture.fromMemory(512,512,a,{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST})},c.prototype.onDropFile=function(a,b,c){a?("string"==typeof a?a=GL.Texture.fromURL(a):-1!=b.toLowerCase().indexOf(".dds")?a=GL.Texture.fromDDSInMemory(a):(a=new Blob([c]),a=URL.createObjectURL(a),a=GL.Texture.fromURL(a)),this._drop_texture=a,this.properties.name=b):(this._drop_texture=null,this.properties.name="")},c.prototype.getExtraMenuOptions= -function(a){var b=this;if(this._drop_texture)return[{content:"Clear",callback:function(){b._drop_texture=null;b.properties.name=""}}]},c.prototype.onExecute=function(){var a=null;this.isOutputConnected(1)&&(a=this.getInputData(0));!a&&this._drop_texture&&(a=this._drop_texture);!a&&this.properties.name&&(a=c.getTexture(this.properties.name));if(a){this._last_tex=a;!1===this.properties.filter?a.setParameter(gl.TEXTURE_MAG_FILTER,gl.NEAREST):a.setParameter(gl.TEXTURE_MAG_FILTER,gl.LINEAR);this.setOutputData(0, -a);this.setOutputData(1,a.fullpath||a.filename);for(var b=2;b=this.size[1]))if(this._drop_texture&& -a.webgl)a.drawImage(this._drop_texture,0,0,this.size[0],this.size[1]);else{if(this._last_preview_tex!=this._last_tex)if(a.webgl)this._canvas=this._last_tex;else{var b=c.generateLowResTexturePreview(this._last_tex);if(!b)return;this._last_preview_tex=this._last_tex;this._canvas=cloneCanvas(b)}this._canvas&&(a.save(),a.webgl||(a.translate(0,this.size[1]),a.scale(1,-1)),a.drawImage(this._canvas,0,0,this.size[0],this.size[1]),a.restore())}},c.generateLowResTexturePreview=function(a){if(!a)return null; -var b=c.image_preview_size,d=a;if(a.format==gl.DEPTH_COMPONENT)return null;if(a.width>b||a.height>b)d=this._preview_temp_tex,this._preview_temp_tex||(this._preview_temp_tex=d=new GL.Texture(b,b,{minFilter:gl.NEAREST})),a.copyTo(d);a=this._preview_canvas;a||(this._preview_canvas=a=createCanvas(b,b));d&&d.toCanvas(a);return a},c.prototype.getResources=function(a){this.properties.name&&(a[this.properties.name]=GL.Texture);return a},c.prototype.onGetInputs=function(){return[["in","Texture"]]},c.prototype.onGetOutputs= -function(){return[["width","number"],["height","number"],["aspect","number"]]},c.replaceCode=function(a,b){return a.replace(/\{\{[a-zA-Z0-9_]*\}\}/g,function(a){a=a.replace(/[\{\}]/g,"");return b[a]||""})},O.registerNodeType("texture/texture",c),k.title="Preview",k.desc="Show a texture in the graph canvas",k.allow_preview=!1,k.prototype.onDrawBackground=function(a){if(!this.flags.collapsed&&(a.webgl||k.allow_preview)){var b=this.getInputData(0);b&&(b=!b.handle&&a.webgl?b:c.generateLowResTexturePreview(b), -a.save(),this.properties.flipY&&(a.translate(0,this.size[1]),a.scale(1,-1)),a.drawImage(b,0,0,this.size[0],this.size[1]),a.restore())}},O.registerNodeType("texture/preview",k),n.title="Save",n.desc="Save a texture in the repository",n.prototype.getPreviewTexture=function(){return this._texture},n.prototype.onExecute=function(){var a=this.getInputData(0);a&&(this.properties.generate_mipmaps&&(a.bind(0),a.setParameter(gl.TEXTURE_MIN_FILTER,gl.LINEAR_MIPMAP_LINEAR),gl.generateMipmap(a.texture_type), -a.unbind(0)),this.properties.name&&(c.storeTexture?c.storeTexture(this.properties.name,a):c.getTexturesContainer()[this.properties.name]=a),this._texture=a,this.setOutputData(0,a),this.setOutputData(1,this.properties.name))},O.registerNodeType("texture/save",n),r.widgets_info={uvcode:{widget:"code"},pixelcode:{widget:"code"},precision:{widget:"combo",values:c.MODE_VALUES}},r.title="Operation",r.desc="Texture shader operation",r.presets={},r.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show? -"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]},r.prototype.onPropertyChanged=function(){this.has_error=!1},r.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._tex||this._tex.gl!=a||(a.save(),a.drawImage(this._tex,0,0,this.size[0],this.size[1]),a.restore())},r.prototype.onExecute=function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===c.PASS_THROUGH)this.setOutputData(0, -a);else{var b=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var d=512,f=512;a?(d=a.width,f=a.height):b&&(d=b.width,f=b.height);b||(b=GL.Texture.getWhiteTexture());var g=c.getTextureType(this.properties.precision,a);this._tex=a||this._tex?c.getTargetTexture(a||this._tex,this._tex,this.properties.precision):new GL.Texture(d,f,{type:g,format:gl.RGBA,filter:gl.LINEAR});g="";this.properties.uvcode&&(g="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&& -(g=this.properties.uvcode));var e="";this.properties.pixelcode&&(e="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&&(e=this.properties.pixelcode));var l=this._shader;if(!(this.has_error||l&&this._shader_code==g+"|"+e)){var h=c.replaceCode(r.pixel_shader,{UV_CODE:g,PIXEL_CODE:e});try{l=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,h),this.boxcolor="#00FF00"}catch(Z){GL.Shader.dumpErrorToConsole(Z,Shader.SCREEN_VERTEX_SHADER,h);this.boxcolor="#FF0000";this.has_error=!0; -return}this._shader=l;this._shader_code=g+"|"+e}if(this._shader){var m=this.getInputData(2);null!=m?this.properties.value=m:m=parseFloat(this.properties.value);var k=this.graph.getTime();this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST);gl.disable(gl.CULL_FACE);gl.disable(gl.BLEND);a&&a.bind(0);b&&b.bind(1);var c=Mesh.getScreenQuad();l.uniforms({u_texture:0,u_textureB:1,value:m,texSize:[d,f,1/d,1/f],time:k}).draw(c)});this.setOutputData(0,this._tex)}}}},r.pixel_shader="precision highp float;\n\t\t\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tvarying vec2 v_coord;\n\t\tuniform vec4 texSize;\n\t\tuniform float time;\n\t\tuniform float value;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord;\n\t\t\t{{UV_CODE}};\n\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\t\t\tvec3 color = color4.rgb;\n\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\t\t\tvec3 colorB = color4B.rgb;\n\t\t\tvec3 result = color;\n\t\t\tfloat alpha = 1.0;\n\t\t\t{{PIXEL_CODE}};\n\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t}\n\t\t", -r.registerPreset=function(a,b){r.presets[a]=b},r.registerPreset("",""),r.registerPreset("bypass","color"),r.registerPreset("add","color + colorB * value"),r.registerPreset("substract","(color - colorB) * value"),r.registerPreset("mate","mix( color, colorB, color4B.a * value)"),r.registerPreset("invert","vec3(1.0) - color"),r.registerPreset("multiply","color * colorB * value"),r.registerPreset("divide","(color / colorB) / value"),r.registerPreset("difference","abs(color - colorB) * value"),r.registerPreset("max", -"max(color, colorB) * value"),r.registerPreset("min","min(color, colorB) * value"),r.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"),r.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"),r.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"),r.registerPreset("normalmap","\n\t\tfloat z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\t\tfloat z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\t\tfloat z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\t\tfloat z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\t\tfloat z4 = color.x;\n\t\tfloat z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\t\tfloat z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\t\tfloat z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\t\tfloat z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\t\tvec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\t\tnormal.xy *= value;\n\t\tresult.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\t"), -r.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"),r.prototype.onInspect=function(a){var b=this;a.addCombo("Presets","",{values:Object.keys(r.presets),callback:function(d){var c=r.presets[d];c&&(b.setProperty("pixelcode",c),b.title=d,a.refresh())}})},O.registerNodeType("texture/operation",r),u.title="Shader",u.desc="Texture shader",u.widgets_info={code:{type:"code",lang:"glsl"},precision:{widget:"combo", -values:c.MODE_VALUES}},u.prototype.onPropertyChanged=function(a,b){if("code"==a&&(a=this.getShader())){b=a.uniformInfo;if(this.inputs)for(var d={},c=0;c=this.size[1])){var b=this.getInputData(0);b&&a.drawImage(a==gl?b:gl.canvas,10,30,this.size[0]-20,this.size[1]-40)}},D.prototype.onExecute=function(){var a=this.getInputData(0);if(a){this.properties.disable_alpha?gl.disable(gl.BLEND):(gl.enable(gl.BLEND),this.properties.additive?gl.blendFunc(gl.SRC_ALPHA, -gl.ONE):gl.blendFunc(gl.SRC_ALPHA,gl.ONE_MINUS_SRC_ALPHA));gl.disable(gl.DEPTH_TEST);var b=this.properties.gamma||1;this.isInputConnected(1)&&(b=this.getInputData(1));a.setParameter(gl.TEXTURE_MAG_FILTER,this.properties.filter?gl.LINEAR:gl.NEAREST);var d=D._prev_viewport;d.set(gl.viewport_data);var c=this.properties.viewport;gl.viewport(d[0]+d[2]*c[0],d[1]+d[3]*c[1],d[2]*c[2],d[3]*c[3]);gl.getViewport();this.properties.antialiasing?(D._shader||(D._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER, -D.aa_pixel_shader)),c=Mesh.getScreenQuad(),a.bind(0),D._shader.uniforms({u_texture:0,uViewportSize:[a.width,a.height],u_igamma:1/b,inverseVP:[1/a.width,1/a.height]}).draw(c)):1!=b?(D._gamma_shader||(D._gamma_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,D.gamma_pixel_shader)),a.toViewport(D._gamma_shader,{u_texture:0,u_igamma:1/b})):a.toViewport();gl.viewport(d[0],d[1],d[2],d[3])}},D.prototype.onGetInputs=function(){return[["gamma","number"]]},D.aa_pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 uViewportSize;\n\t\tuniform vec2 inverseVP;\n\t\tuniform float u_igamma;\n\t\t#define FXAA_REDUCE_MIN (1.0/ 128.0)\n\t\t#define FXAA_REDUCE_MUL (1.0 / 8.0)\n\t\t#define FXAA_SPAN_MAX 8.0\n\t\t\n\t\t/* from mitsuhiko/webgl-meincraft based on the code on geeks3d.com */\n\t\tvec4 applyFXAA(sampler2D tex, vec2 fragCoord)\n\t\t{\n\t\t\tvec4 color = vec4(0.0);\n\t\t\t/*vec2 inverseVP = vec2(1.0 / uViewportSize.x, 1.0 / uViewportSize.y);*/\n\t\t\tvec3 rgbNW = texture2D(tex, (fragCoord + vec2(-1.0, -1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbNE = texture2D(tex, (fragCoord + vec2(1.0, -1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbSW = texture2D(tex, (fragCoord + vec2(-1.0, 1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbSE = texture2D(tex, (fragCoord + vec2(1.0, 1.0)) * inverseVP).xyz;\n\t\t\tvec3 rgbM = texture2D(tex, fragCoord * inverseVP).xyz;\n\t\t\tvec3 luma = vec3(0.299, 0.587, 0.114);\n\t\t\tfloat lumaNW = dot(rgbNW, luma);\n\t\t\tfloat lumaNE = dot(rgbNE, luma);\n\t\t\tfloat lumaSW = dot(rgbSW, luma);\n\t\t\tfloat lumaSE = dot(rgbSE, luma);\n\t\t\tfloat lumaM = dot(rgbM, luma);\n\t\t\tfloat lumaMin = min(lumaM, min(min(lumaNW, lumaNE), min(lumaSW, lumaSE)));\n\t\t\tfloat lumaMax = max(lumaM, max(max(lumaNW, lumaNE), max(lumaSW, lumaSE)));\n\t\t\t\n\t\t\tvec2 dir;\n\t\t\tdir.x = -((lumaNW + lumaNE) - (lumaSW + lumaSE));\n\t\t\tdir.y = ((lumaNW + lumaSW) - (lumaNE + lumaSE));\n\t\t\t\n\t\t\tfloat dirReduce = max((lumaNW + lumaNE + lumaSW + lumaSE) * (0.25 * FXAA_REDUCE_MUL), FXAA_REDUCE_MIN);\n\t\t\t\n\t\t\tfloat rcpDirMin = 1.0 / (min(abs(dir.x), abs(dir.y)) + dirReduce);\n\t\t\tdir = min(vec2(FXAA_SPAN_MAX, FXAA_SPAN_MAX), max(vec2(-FXAA_SPAN_MAX, -FXAA_SPAN_MAX), dir * rcpDirMin)) * inverseVP;\n\t\t\t\n\t\t\tvec3 rgbA = 0.5 * (texture2D(tex, fragCoord * inverseVP + dir * (1.0 / 3.0 - 0.5)).xyz + \n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * (2.0 / 3.0 - 0.5)).xyz);\n\t\t\tvec3 rgbB = rgbA * 0.5 + 0.25 * (texture2D(tex, fragCoord * inverseVP + dir * -0.5).xyz + \n\t\t\t\ttexture2D(tex, fragCoord * inverseVP + dir * 0.5).xyz);\n\t\t\t\n\t\t\t//return vec4(rgbA,1.0);\n\t\t\tfloat lumaB = dot(rgbB, luma);\n\t\t\tif ((lumaB < lumaMin) || (lumaB > lumaMax))\n\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\telse\n\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\tif(u_igamma != 1.0)\n\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\treturn color;\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t}\n\t\t", -D.gamma_pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_igamma;\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\t\t gl_FragColor = color;\n\t\t}\n\t\t",O.registerNodeType("texture/toviewport",D),J.title="Copy",J.desc="Copy Texture",J.widgets_info={size:{widget:"combo",values:[0,32,64,128,256,512,1024,2048]},precision:{widget:"combo", -values:c.MODE_VALUES}},J.prototype.onExecute=function(){var a=this.getInputData(0);if((a||this._temp_texture)&&this.isOutputConnected(0)){if(a){var b=a.width,d=a.height;0!=this.properties.size&&(d=b=this.properties.size);var f=this._temp_texture,g=a.type;this.properties.precision===c.LOW?g=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(g=gl.HIGH_PRECISION_FORMAT);f&&f.width==b&&f.height==d&&f.type==g||(f=gl.LINEAR,this.properties.generate_mipmaps&&isPowerOfTwo(b)&&isPowerOfTwo(d)&&(f=gl.LINEAR_MIPMAP_LINEAR), -this._temp_texture=new GL.Texture(b,d,{type:g,format:gl.RGBA,minFilter:f,magFilter:gl.LINEAR}));a.copyTo(this._temp_texture);this.properties.generate_mipmaps&&(this._temp_texture.bind(0),gl.generateMipmap(this._temp_texture.texture_type),this._temp_texture.unbind(0))}this.setOutputData(0,this._temp_texture)}},O.registerNodeType("texture/copy",J),L.title="Downsample",L.desc="Downsample Texture",L.widgets_info={iterations:{type:"number",step:1,precision:0,min:0},precision:{widget:"combo",values:c.MODE_VALUES}}, -L.prototype.onExecute=function(){var a=this.getInputData(0);if((a||this._temp_texture)&&this.isOutputConnected(0)&&a&&a.texture_type===GL.TEXTURE_2D)if(1>this.properties.iterations)this.setOutputData(0,a);else{var b=L._shader;b||(L._shader=b=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,L.pixel_shader));var d=a.width|0,f=a.height|0,g=a.type;this.properties.precision===c.LOW?g=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(g=gl.HIGH_PRECISION_FORMAT);var e=this.properties.iterations||1,l=a,h= -[];g={type:g,format:a.format};var m=vec2.create(),k={u_offset:m};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var w=0;w>1||0;f=f>>1||0;a=GL.Texture.getTemporary(d,f,g);h.push(a);l.setParameter(GL.TEXTURE_MAG_FILTER,GL.NEAREST);l.copyTo(a,b,k);if(1==d&&1==f)break;l=a}this._texture=h.pop();for(w=0;wc;c++)this.isOutputConnected(c)?(this._channels[c]&&this._channels[c].width==a.width&&this._channels[c].height==a.height&&this._channels[c].type==a.type&&this._channels[c].format==b||(this._channels[c]=new GL.Texture(a.width,a.height,{type:a.type,format:b,filter:gl.LINEAR})), -d++):this._channels[c]=null;if(d){gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var f=Mesh.getScreenQuad(),g=m._shader,e=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]];for(c=0;4>c;c++)this._channels[c]&&(this._channels[c].drawTo(function(){a.bind(0);g.uniforms({u_texture:0,u_mask:e[c]}).draw(f)}),this.setOutputData(c,this._channels[c]))}}},m.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec4 u_mask;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\t\t}\n\t\t", -O.registerNodeType("texture/textureChannels",m),w.title="Channels to Texture",w.desc="Split texture channels",w.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},w.prototype.onExecute=function(){var a=c.getWhiteTexture(),b=this.getInputData(0)||a,d=this.getInputData(1)||a,f=this.getInputData(2)||a,g=this.getInputData(3)||a;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad();w._shader||(w._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,w.pixel_shader));var l=w._shader; -a=Math.max(b.width,d.width,f.width,g.width);var h=Math.max(b.height,d.height,f.height,g.height),m=this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE;this._texture&&this._texture.width==a&&this._texture.height==h&&this._texture.type==m||(this._texture=new GL.Texture(a,h,{type:m,format:gl.RGBA,filter:gl.LINEAR}));a=this._color;a[0]=this.properties.R;a[1]=this.properties.G;a[2]=this.properties.B;a[3]=this.properties.A;var k=this._uniforms;this._texture.drawTo(function(){b.bind(0); -d.bind(1);f.bind(2);g.bind(3);l.uniforms(k).draw(e)});this.setOutputData(0,this._texture)},w.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureR;\n\t\tuniform sampler2D u_textureG;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform vec4 u_color;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = u_color * vec4( \t\t\t\t\ttexture2D(u_textureR, v_coord).r,\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\n\t\t}\n\t\t", -O.registerNodeType("texture/channelsTexture",w),N.title="Color",N.desc="Generates a 1x1 texture with a constant color",N.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},N.prototype.onDrawBackground=function(a){var b=this.properties.color;a.fillStyle="rgb("+Math.floor(255*clamp(b[0],0,1))+","+Math.floor(255*clamp(b[1],0,1))+","+Math.floor(255*clamp(b[2],0,1))+")";this.flags.collapsed?this.boxcolor=a.fillStyle:a.fillRect(0,0,this.size[0],this.size[1])},N.prototype.onExecute=function(){var a= -this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE;this._tex&&this._tex.type==a||(this._tex=new GL.Texture(1,1,{format:gl.RGBA,type:a,minFilter:gl.NEAREST}));a=this.properties.color;if(this.inputs)for(var b=0;ba.width?d: -a,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad(),l=null,h=this._uniforms;f?(l=b._shader_tex,l||(l=b._shader_tex=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader,{MIX_TEX:""}))):(l=b._shader_factor,l||(l=b._shader_factor=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader)),g=null==g?this.properties.factor:g,h.u_mix.set([g,g,g,g]));var m=this.properties.invert;this._tex.drawTo(function(){a.bind(m?1:0);d.bind(m?0:1);f&&f.bind(2); -l.uniforms(h).draw(e)});this.setOutputData(0,this._tex)}}},b.prototype.onGetInputs=function(){return[["factor","number"]]},b.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform sampler2D u_textureB;\n\t\t#ifdef MIX_TEX\n\t\t\tuniform sampler2D u_textureMix;\n\t\t#else\n\t\t\tuniform vec4 u_mix;\n\t\t#endif\n\t\t\n\t\tvoid main() {\n\t\t\t#ifdef MIX_TEX\n\t\t\t vec4 f = texture2D(u_textureMix, v_coord);\n\t\t\t#else\n\t\t\t vec4 f = u_mix;\n\t\t\t#endif\n\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\t\t}\n\t\t", -O.registerNodeType("texture/mix",b),d.title="Edges",d.desc="Detects edges",d.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},d.prototype.onExecute=function(){if(this.isOutputConnected(0)){var a=this.getInputData(0);if(this.properties.precision===c.PASS_THROUGH)this.setOutputData(0,a);else if(a){this._tex=c.getTargetTexture(a,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var b=Mesh.getScreenQuad(),f=d._shader,g=this.properties.invert,e=this.properties.factor, -l=this.properties.threshold?1:0;this._tex.drawTo(function(){a.bind(0);f.uniforms({u_texture:0,u_isize:[1/a.width,1/a.height],u_factor:e,u_threshold:l,u_invert:g?1:0}).draw(b)});this.setOutputData(0,this._tex)}}},d.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_isize;\n\t\tuniform int u_invert;\n\t\tuniform float u_factor;\n\t\tuniform float u_threshold;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\t\t\tdiff *= u_factor;\n\t\t\tif(u_invert == 1)\n\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\t\t\tif( u_threshold == 0.0 )\n\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\n\t\t\telse\n\t\t\t\tgl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\t\t}\n\t\t", -O.registerNodeType("texture/edges",d),f.title="Depth Range",f.desc="Generates a texture with a depth range",f.prototype.onExecute=function(){if(this.isOutputConnected(0)){var a=this.getInputData(0);if(a){var b=gl.UNSIGNED_BYTE;this.properties.high_precision&&(b=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT);this._temp_texture&&this._temp_texture.type==b&&this._temp_texture.width==a.width&&this._temp_texture.height==a.height||(this._temp_texture=new GL.Texture(a.width,a.height,{type:b,format:gl.RGBA, -filter:gl.LINEAR}));var d=this._uniforms;b=this.properties.distance;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.distance=b);var c=this.properties.range;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.range=c);d.u_distance=b;d.u_range=c;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var g=Mesh.getScreenQuad();f._shader||(f._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,f.pixel_shader),f._shader_onlydepth=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,f.pixel_shader, -{ONLY_DEPTH:""}));var e=this.properties.only_depth?f._shader_onlydepth:f._shader;b=null;b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[.1,1E3];d.u_camera_planes=b;this._temp_texture.drawTo(function(){a.bind(0);e.uniforms(d).draw(g)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}},f.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_camera_planes;\n\t\tuniform float u_distance;\n\t\tuniform float u_range;\n\t\t\n\t\tfloat LinearDepth()\n\t\t{\n\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\t\t\tdepth = depth * 2.0 - 1.0;\n\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t\tfloat depth = LinearDepth();\n\t\t\t#ifdef ONLY_DEPTH\n\t\t\t gl_FragColor = vec4(depth);\n\t\t\t#else\n\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\n\t\t\t\tfloat dof = 1.0;\n\t\t\t\tif(diff <= u_range)\n\t\t\t\t\tdof = diff / u_range;\n\t\t\t gl_FragColor = vec4(dof);\n\t\t\t#endif\n\t\t}\n\t\t", -O.registerNodeType("texture/depth_range",f),g.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},g.title="Linear Depth",g.desc="Creates a color texture with linear depth",g.prototype.onExecute=function(){if(this.isOutputConnected(0)){var a=this.getInputData(0);if(a&&(a.format==gl.DEPTH_COMPONENT||a.format==gl.DEPTH_STENCIL)){var b=this.properties.precision==c.HIGH?gl.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE;this._temp_texture&&this._temp_texture.type==b&&this._temp_texture.width==a.width&& -this._temp_texture.height==a.height||(this._temp_texture=new GL.Texture(a.width,a.height,{type:b,format:gl.RGB,filter:gl.LINEAR}));var d=this._uniforms;d.u_invert=this.properties.invert?1:0;gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var f=Mesh.getScreenQuad();g._shader||(g._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,g.pixel_shader));var e=g._shader;b=null;b=a.near_far_planes?a.near_far_planes:window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[.1,1E3]; -d.u_camera_planes=b;d.u_ires.set([0,0]);this._temp_texture.drawTo(function(){a.bind(0);e.uniforms(d).draw(f)});this._temp_texture.near_far_planes=b;this.setOutputData(0,this._temp_texture)}}},g.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_camera_planes;\n\t\tuniform int u_invert;\n\t\tuniform vec2 u_ires;\n\t\t\n\t\tvoid main() {\n\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\tfloat depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\t\t\tfloat f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t\tif( u_invert == 1 )\n\t\t\t\tf = 1.0 - f;\n\t\t\tgl_FragColor = vec4(vec3(f),1.0);\n\t\t}\n\t\t", -O.registerNodeType("texture/linear_depth",g),q.title="Blur",q.desc="Blur a texture",q.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},q.max_iterations=20,q.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b=this._final_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(b=this._final_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));var d=this.properties.iterations;this.isInputConnected(1)&& -(d=this.getInputData(1),this.properties.iterations=d);d=Math.min(Math.floor(d),q.max_iterations);if(0==d)this.setOutputData(0,a);else{var c=this.properties.intensity;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.intensity=c);var f=O.camera_aspect;f||void 0===window.gl||(f=gl.canvas.height/gl.canvas.width);f||(f=1);f=this.properties.preserve_aspect?f:1;var g=this.properties.scale||[1,1];a.applyBlur(f*g[0],g[1],c,b);for(a=1;a>=1;1<(g|0)&&(g>>=1);if(2>f)break;k=h[A]=GL.Texture.getTemporary(f,g,e);q[0]=1/w.width;q[1]=1/w.height;w.blit(k,m.uniforms(l));w=k}c&&(q[0]=1/w.width,q[1]=1/w.height,l.u_intensity=G,l.u_delta=1,w.blit(c,m.uniforms(l)));gl.enable(gl.BLEND);gl.blendFunc(gl.ONE,gl.ONE);l.u_intensity=this.persistence; -l.u_delta=.5;for(A-=2;0<=A;A--)k=h[A],h[A]=null,q[0]=1/w.width,q[1]=1/w.height,w.blit(k,m.uniforms(l)),GL.Texture.releaseTemporary(w),w=k;gl.disable(gl.BLEND);d&&w.blit(d);if(b){var n=this.dirt_texture,t=this.dirt_factor;l.u_intensity=G;m=n?z._dirt_final_shader:z._final_shader;m||(m=n?z._dirt_final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,z.final_pixel_shader,{USE_DIRT:""}):z._final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,z.final_pixel_shader));b.drawTo(function(){a.bind(0); -w.bind(1);n&&(m.setUniform("u_dirt_factor",t),m.setUniform("u_dirt_texture",n.bind(2)));m.toViewport(l)})}GL.Texture.releaseTemporary(w)},z.cut_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform float u_threshold;\n\tvoid main() {\n\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\t}",z.scale_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\n\t}", -z.final_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform sampler2D u_glow_texture;\n\t#ifdef USE_DIRT\n\t\tuniform sampler2D u_dirt_texture;\n\t#endif\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\tuniform float u_dirt_factor;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tvec4 glow = sampleBox( v_coord );\n\t\t#ifdef USE_DIRT\n\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\t\t#endif\n\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\t}", -P.title="Glow",P.desc="Filters a texture giving it a glow effect",P.widgets_info={iterations:{type:"number",min:0,max:16,step:1,precision:0},threshold:{type:"number",min:0,max:10,step:.01,precision:2},precision:{widget:"combo",values:c.MODE_VALUES}},P.prototype.onGetInputs=function(){return[["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]]},P.prototype.onGetOutputs=function(){return[["average","Texture"]]}, -P.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isAnyOutputConnected())if(this.properties.precision===c.PASS_THROUGH||!1===this.getInputOrProperty("enabled"))this.setOutputData(0,a);else{var b=this.fx;b.threshold=this.getInputOrProperty("threshold");b.iterations=this.getInputOrProperty("iterations");b.intensity=this.getInputOrProperty("intensity");b.persistence=this.getInputOrProperty("persistence");b.dirt_texture=this.getInputData(1);b.dirt_factor=this.getInputOrProperty("dirt_factor"); -b.scale=this.properties.scale;var d=c.getTextureType(this.properties.precision,a),f=null;this.isOutputConnected(2)&&(f=this._average_texture,f&&f.type==a.type&&f.format==a.format||(f=this._average_texture=new GL.Texture(1,1,{type:a.type,format:a.format,filter:gl.LINEAR})));var g=null;this.isOutputConnected(1)&&(g=this._glow_texture,g&&g.width==a.width&&g.height==a.height&&g.type==d&&g.format==a.format||(g=this._glow_texture=new GL.Texture(a.width,a.height,{type:d,format:a.format,filter:gl.LINEAR}))); -var e=null;this.isOutputConnected(0)&&(e=this._final_texture,e&&e.width==a.width&&e.height==a.height&&e.type==d&&e.format==a.format||(e=this._final_texture=new GL.Texture(a.width,a.height,{type:d,format:a.format,filter:gl.LINEAR})));b.applyFX(a,e,g,f);this.isOutputConnected(0)&&this.setOutputData(0,e);this.isOutputConnected(1)&&this.setOutputData(1,f);this.isOutputConnected(2)&&this.setOutputData(2,g)}},O.registerNodeType("texture/glow",P),I.title="Kuwahara Filter",I.desc="Filters a texture giving an artistic oil canvas painting", -I.max_radius=10,I._shaders=[],I.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b=this._temp_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(this._temp_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));b=this.properties.radius;b=Math.min(Math.floor(b),I.max_radius);if(0==b)this.setOutputData(0,a);else{var d=this.properties.intensity,c=O.camera_aspect;c||void 0===window.gl||(c=gl.canvas.height/gl.canvas.width); -c||(c=1);c=this.properties.preserve_aspect?c:1;I._shaders[b]||(I._shaders[b]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,I.pixel_shader,{RADIUS:b.toFixed(0)}));var f=I._shaders[b],g=GL.Mesh.getScreenQuad();a.bind(0);this._temp_texture.drawTo(function(){f.uniforms({u_texture:0,u_intensity:d,u_resolution:[a.width,a.height],u_iResolution:[1/a.width,1/a.height]}).draw(g)});this.setOutputData(0,this._temp_texture)}}},I.pixel_shader="\nprecision highp float;\nvarying vec2 v_coord;\nuniform sampler2D u_texture;\nuniform float u_intensity;\nuniform vec2 u_resolution;\nuniform vec2 u_iResolution;\n#ifndef RADIUS\n\t#define RADIUS 7\n#endif\nvoid main() {\n\n\tconst int radius = RADIUS;\n\tvec2 fragCoord = v_coord;\n\tvec2 src_size = u_iResolution;\n\tvec2 uv = v_coord;\n\tfloat n = float((radius + 1) * (radius + 1));\n\tint i;\n\tint j;\n\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\tvec3 c;\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm0 += c;\n\t\t\ts0 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm1 += c;\n\t\t\ts1 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm2 += c;\n\t\t\ts2 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm3 += c;\n\t\t\ts3 += c * c;\n\t\t}\n\t}\n\t\n\tfloat min_sigma2 = 1e+2;\n\tm0 /= n;\n\ts0 = abs(s0 / n - m0 * m0);\n\t\n\tfloat sigma2 = s0.r + s0.g + s0.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m0, 1.0);\n\t}\n\t\n\tm1 /= n;\n\ts1 = abs(s1 / n - m1 * m1);\n\t\n\tsigma2 = s1.r + s1.g + s1.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m1, 1.0);\n\t}\n\t\n\tm2 /= n;\n\ts2 = abs(s2 / n - m2 * m2);\n\t\n\tsigma2 = s2.r + s2.g + s2.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m2, 1.0);\n\t}\n\t\n\tm3 /= n;\n\ts3 = abs(s3 / n - m3 * m3);\n\t\n\tsigma2 = s3.r + s3.g + s3.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m3, 1.0);\n\t}\n}\n", -O.registerNodeType("texture/kuwahara",I),t.title="XDoG Filter",t.desc="Filters a texture giving an artistic ink style",t.max_radius=10,t._shaders=[],t.prototype.onExecute=function(){var a=this.getInputData(0);if(a&&this.isOutputConnected(0)){var b=this._temp_texture;b&&b.width==a.width&&b.height==a.height&&b.type==a.type||(this._temp_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));t._xdog_shader||(t._xdog_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,t.xdog_pixel_shader)); -var d=t._xdog_shader,c=GL.Mesh.getScreenQuad(),f=this.properties.sigma,g=this.properties.k,e=this.properties.p,l=this.properties.epsilon,h=this.properties.phi;a.bind(0);this._temp_texture.drawTo(function(){d.uniforms({src:0,sigma:f,k:g,p:e,epsilon:l,phi:h,cvsWidth:a.width,cvsHeight:a.height}).draw(c)});this.setOutputData(0,this._temp_texture)}},t.xdog_pixel_shader="\nprecision highp float;\nuniform sampler2D src;\n\nuniform float cvsHeight;\nuniform float cvsWidth;\n\nuniform float sigma;\nuniform float k;\nuniform float p;\nuniform float epsilon;\nuniform float phi;\nvarying vec2 v_coord;\n\nfloat cosh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat cosH = (tmp + 1.0 / tmp) / 2.0;\n\treturn cosH;\n}\n\nfloat tanh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\treturn tanH;\n}\n\nfloat sinh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat sinH = (tmp - 1.0 / tmp) / 2.0;\n\treturn sinH;\n}\n\nvoid main(void){\n\tvec3 destColor = vec3(0.0);\n\tfloat tFrag = 1.0 / cvsHeight;\n\tfloat sFrag = 1.0 / cvsWidth;\n\tvec2 Frag = vec2(sFrag,tFrag);\n\tvec2 uv = gl_FragCoord.st;\n\tfloat twoSigmaESquared = 2.0 * sigma * sigma;\n\tfloat twoSigmaRSquared = twoSigmaESquared * k * k;\n\tint halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\tconst int MAX_NUM_ITERATION = 99999;\n\tvec2 sum = vec2(0.0);\n\tvec2 norm = vec2(0.0);\n\n\tfor(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\t\tint i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\t\tint j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\t\tfloat d = length(vec2(i,j));\n\t\tvec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\t\t\t\t\t\t\texp( -d * d / twoSigmaRSquared ));\n\n\t\tvec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\t\tnorm += kernel;\n\t\tsum += kernel * L;\n\t}\n\n\tsum /= norm;\n\n\tfloat H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\tfloat edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\tdestColor = vec3(edge);\n\tgl_FragColor = vec4(destColor, 1.0);\n}", -O.registerNodeType("texture/xDoG",t),v.title="Webcam",v.desc="Webcam texture",v.is_webcam_open=!1,v.prototype.openStream=function(){if(navigator.getUserMedia){this._waiting_confirmation=!0;navigator.mediaDevices.getUserMedia({audio:!1,video:{facingMode:this.properties.facingMode}}).then(this.streamReady.bind(this)).catch(function(b){v.is_webcam_open=!1;console.log("Webcam rejected",b);a._webcam_stream=!1;a.boxcolor="red";a.trigger("stream_error")});var a=this}},v.prototype.closeStream=function(){if(this._webcam_stream){var a= -this._webcam_stream.getTracks();if(a.length)for(var b=0;b=this.size[1]||!this._video||(a.save(),a.webgl?this._video_texture&&a.drawImage(this._video_texture,0,0,this.size[0],this.size[1]):a.drawImage(this._video, -0,0,this.size[0],this.size[1]),a.restore())},v.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();if(this._video&&this._video.videoWidth){var a=this._video.videoWidth,b=this._video.videoHeight,d=this._video_texture;d&&d.width==a&&d.height==b||(this._video_texture=new GL.Texture(a,b,{format:gl.RGB,filter:gl.LINEAR}));this._video_texture.uploadImage(this._video);this._video_texture.version=++this.version;this.properties.texture_name&&(c.getTexturesContainer()[this.properties.texture_name]= -this._video_texture);this.setOutputData(0,this._video_texture);for(a=1;aMath.abs(b))return c[1];a=(a-c[0])/b;return c[1]*(1-a)+f[1]*a}}return 0}},G.prototype.updateCurve=function(){for(var a=this._values,b=a.length/4,d=this.properties.split_channels,c=0;cd+f.NODE_TITLE_HEIGHT&&a.drawImage(b,10,g,this.size[0]-20,this.size[1]-d-f.NODE_TITLE_HEIGHT);var g=this.size[1]-f.NODE_TITLE_HEIGHT+.5;c=f.isInsideRectangle(c[0],c[1],this.pos[0],this.pos[1]+g,this.size[0],f.NODE_TITLE_HEIGHT);a.fillStyle=c?"#555":"#222";a.beginPath();this._shape==f.BOX_SHAPE?a.rect(0,g,this.size[0]+1,f.NODE_TITLE_HEIGHT):a.roundRect(0,g,this.size[0]+1,f.NODE_TITLE_HEIGHT,0,8);a.fill();a.textAlign="center";a.font="24px Arial";a.fillStyle= -c?"#DDD":"#999";a.fillText("+",.5*this.size[0],g+24)}};E.prototype.onMouseDown=function(a,b,d){b[1]>this.size[1]-f.NODE_TITLE_HEIGHT+.5&&d.showSubgraphPropertiesDialog(this)};E.prototype.onDrawSubgraphBackground=function(a){};E.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:"Print Code",callback:function(){var a=b._context.computeShaderCode();console.log(a.vs_code,a.fs_code)}}]};f.registerNodeType("texture/shaderGraph",E);D.title="Uniform";D.desc="Input data for the shader"; -D.prototype.getTitle=function(){return this.properties.name&&this.flags.collapsed?this.properties.type+" "+this.properties.name:"Uniform"};D.prototype.onPropertyChanged=function(a,b){this.outputs[0].name=this.properties.type+" "+this.properties.name};D.prototype.onGetCode=function(a){if(this.shader_destination){var b=this.properties.type;if(!b){if(!a.onGetPropertyInfo)return;b=a.onGetPropertyInfo(this.property.name);if(!b)return;b=b.type}"number"==b?b="float":"texture"==b&&(b="sampler2D");-1!=g.GLSL_types.indexOf(b)&& -(a.addUniform("u_"+this.properties.name,b),this.setOutputData(0,b))}};D.prototype.getOutputVarName=function(a){return"u_"+this.properties.name};k("input/uniform",D);J.title="Attribute";J.desc="Input data from mesh attribute";J.prototype.getTitle=function(){return"att. "+this.properties.name};J.prototype.onGetCode=function(a){if(this.shader_destination){var b=this.properties.type;b&&-1!=g.GLSL_types.indexOf(b)&&("number"==b&&(b="float"),"coord"!=this.properties.name&&a.addCode("varying"," varying "+ -b+" v_"+this.properties.name+";"),this.setOutputData(0,b))}};J.prototype.getOutputVarName=function(a){return"v_"+this.properties.name};k("input/attribute",J);L.title="Sampler2D";L.desc="Reads a pixel from a texture";L.prototype.onGetCode=function(a){if(this.shader_destination){var b=r(this,0),d=n(this),c="vec4 "+d+" = vec4(0.0);\n";if(b){var f=r(this,1)||a.buffer_names.uvs;c+=d+" = texture2D("+b+","+f+");\n"}u(this,0)&&(c+="vec4 "+u(this,0)+" = "+d+";\n");u(this,1)&&(c+="vec3 "+u(this,1)+" = "+d+ -".xyz;\n");a.addCode("code",c,this.shader_destination);this.setOutputData(0,"vec4");this.setOutputData(1,"vec3")}};k("texture/sampler2D",L);H.title="const";H.prototype.getTitle=function(){return this.flags.collapsed?t(this.properties.value,this.properties.type,2):"Const"};H.prototype.onPropertyChanged=function(a,b){"type"==a&&(this.outputs[0].type!=b&&(this.disconnectOutput(0),this.outputs[0].type=b),this.widgets.length=1,this.updateWidgets());"value"==a&&(b.length?(this.widgets[1].value=b[1],2d;++d)b.push({name:r(this,d),type:this.getInputData(d)||"float"});var c=u(this,0);if(c){var f=b[0].type,g=this.properties.operation, -e=[];for(d=0;2>d;++d){var l=b[d].name;null==l&&(l=null!=p.value?p.value:"(1.0)",b[d].type="float");b[d].type!=f&&("float"!=b[d].type||"*"!=g&&"/"!=g)&&(l=C(l,b[d].type,f));e.push(l)}a.addCode("code",f+" "+c+" = "+e[0]+g+e[1]+";",this.shader_destination);this.setOutputData(0,f)}}};k("math/operation",M);l.title="Func";l.prototype.onPropertyChanged=function(a,b){this.graph&&this.graph._version++;if("func"==a&&(a=P[b])){for(b=a.params.length;bd;++d)b.push({name:r(this,d),type:this.getInputData(d)||"float"});var c=u(this,0);if(c){var f=P[this.properties.func];if(f){var g=b[0].type,e=f.return_type;"T"==e&&(e=g);var l=[];for(d= -0;d=c;){g=.5*(e+c)|0;d=a[g];if(d==b)break;if(c==e-1)return c;da&&(a=1);this.points&&this.points.length==3*a||(this.points=new Float32Array(3*a));this.properties.generate_normals?this.normals&&this.normals.length==this.points.length||(this.normals=new Float32Array(this.points.length)):this.normals=null;var b=this._last_radius||this.properties.radius,d=this.properties.mode,c=this.getInputData(0);this._old_obj_version=c?c._version:null;this.points=k.generatePoints(b,a,d,this.points,this.normals, -this.properties.regular,c);this.version++};k.generatePoints=function(a,b,d,c,g,e,l){var f=3*b;c&&c.length==f||(c=new Float32Array(f));var h=new Float32Array(3),m=new Float32Array([0,1,0]);if(e)if(d==k.RECTANGLE){f=Math.floor(Math.sqrt(b));for(b=0;bg||ue&&el))break}this.geometry.indices=this.indices=new Uint32Array(h)}this.indices&&this.indices.length?(this.geometry.indices=this.indices,this.setOutputData(0,this.geometry)):this.setOutputData(0,null)}};B.registerNodeType("geometry/connectPoints",J);"undefined"!=typeof GL&&(L.title="to geometry",L.desc="converts a mesh to geometry",L.prototype.onExecute=function(){var a=this.getInputData(0);if(a){if(a!=this.last_mesh){this.last_mesh=a;for(i in a.vertexBuffers)this.geometry[i]= -a.vertexBuffers[i].data;a.indexBuffers.triangles&&(this.geometry.indices=a.indexBuffers.triangles.data);this.geometry._id=c();this.geometry._version=0}this.setOutputData(0,this.geometry);this.geometry&&this.setOutputData(1,this.geometry.vertices)}},B.registerNodeType("geometry/toGeometry",L),H.title="Geo to Mesh",H.prototype.updateMesh=function(a){this.mesh||(this.mesh=new GL.Mesh);for(var b in a)if("_"!=b[0]){var d=a[b],c=GL.Mesh.common_buffers[b];if(c||"indices"==b){c=c?c.spacing:3;var g=this.mesh.vertexBuffers[b]; -g&&g.data.length==d.length?(g.data.set(d),g.upload(GL.DYNAMIC_DRAW)):g=new GL.Buffer("indices"==b?GL.ELEMENT_ARRAY_BUFFER:GL.ARRAY_BUFFER,d,c,GL.DYNAMIC_DRAW);this.mesh.addBuffer(b,g)}}if(this.mesh.vertexBuffers.normals&&this.mesh.vertexBuffers.normals.data.length!=this.mesh.vertexBuffers.vertices.data.length){d=new Float32Array([0,1,0]);c=new Float32Array(this.mesh.vertexBuffers.vertices.data.length);for(b=0;b=c.NOTEON||l<=c.NOTEOFF)this.channel=e&15};Object.defineProperty(c.prototype,"velocity",{get:function(){return this.cmd==c.NOTEON?this.data[2]:-1},set:function(c){this.data[2]=c},enumerable:!0});c.notes="A A# B C C# D D# E F F# G G#".split(" ");c.note_to_index={A:0,"A#":1,B:2,C:3,"C#":4,D:5,"D#":6,E:7,F:8,"F#":9,G:10,"G#":11};Object.defineProperty(c.prototype,"note",{get:function(){return this.cmd!= -c.NOTEON?-1:c.toNoteString(this.data[1],!0)},set:function(c){throw"notes cannot be assigned this way, must modify the data[1]";},enumerable:!0});Object.defineProperty(c.prototype,"octave",{get:function(){return this.cmd!=c.NOTEON?-1:Math.floor((this.data[1]-24)/12+1)},set:function(c){throw"octave cannot be assigned this way, must modify the data[1]";},enumerable:!0});c.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)};c.computePitch=function(c){return 440*Math.pow(2,(c-69)/ -12)};c.prototype.getCC=function(){return this.data[1]};c.prototype.getCCValue=function(){return this.data[2]};c.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192};c.computePitchBend=function(c,e){return c+(e<<7)-8192};c.prototype.setCommandFromString=function(e){this.cmd=c.computeCommandFromString(e)};c.computeCommandFromString=function(e){if(!e)return 0;if(e&&e.constructor===Number)return e;e=e.toUpperCase();switch(e){case "NOTE ON":case "NOTEON":return c.NOTEON;case "NOTE OFF":case "NOTEOFF":return c.NOTEON; -case "KEY PRESSURE":case "KEYPRESSURE":return c.KEYPRESSURE;case "CONTROLLER CHANGE":case "CONTROLLERCHANGE":case "CC":return c.CONTROLLERCHANGE;case "PROGRAM CHANGE":case "PROGRAMCHANGE":case "PC":return c.PROGRAMCHANGE;case "CHANNEL PRESSURE":case "CHANNELPRESSURE":return c.CHANNELPRESSURE;case "PITCH BEND":case "PITCHBEND":return c.PITCHBEND;case "TIME TICK":case "TIMETICK":return c.TIMETICK;default:return Number(e)}};c.toNoteString=function(e,h){e=Math.round(e);var l=Math.floor((e-24)/12+1);e= -(e-21)%12;0>e&&(e=12+e);return c.notes[e]+(h?"":l)};c.NoteStringToPitch=function(e){e=e.toUpperCase();var l=e[0],h=4;"#"==e[1]?(l+="#",2this.properties.max_value)return;this.trigger("on_midi",h)}};B.registerNodeType("midi/filter",h);E.title="MIDIEvent";E.desc="Create a MIDI Event";E.color="#243";E.prototype.onAction=function(e,h){"assign"==e?(this.properties.channel=h.channel,this.properties.cmd=h.cmd,this.properties.value1=h.data[1],this.properties.value2=h.data[2],h.cmd== -c.NOTEON?this.gate=!0:h.cmd==c.NOTEOFF&&(this.gate=!1)):(h=this.midi_event,h.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?h.setCommandFromString(this.properties.cmd):h.cmd=this.properties.cmd,h.data[0]=h.cmd|h.channel,h.data[1]=Number(this.properties.value1),h.data[2]=Number(this.properties.value2),this.trigger("on_midi",h))};E.prototype.onExecute=function(){var e=this.properties;if(this.inputs)for(var h=0;hc;++c)this.valid_notes[c]=-1!=this.notes_pitches.indexOf(c);for(c=0;12>c;++c)if(this.valid_notes[c])this.offset_notes[c]=0;else for(var e=1;12>e;++e){if(this.valid_notes[(c-e)%12]){this.offset_notes[c]=-e;break}if(this.valid_notes[(c+e)%12]){this.offset_notes[c]=e;break}}};H.prototype.onAction=function(e,h){h&&h.constructor===c&&(h.data[0]==c.NOTEON||h.data[0]==c.NOTEOFF?(this.midi_event=new c,this.midi_event.setup(h.data),this.midi_event.data[1]+=this.offset_notes[c.note_to_index[h.note]], -this.trigger("out",this.midi_event)):this.trigger("out",h))};H.prototype.onExecute=function(){var c=this.getInputData(1);null!=c&&c!=this._current_scale&&this.processScale(c)};B.registerNodeType("midi/quantize",H);F.title="MIDI fromFile";F.desc="Plays a MIDI file";F.color="#243";F.prototype.onAction=function(c){"play"==c?this.play():"pause"==c&&(this._playing=!this._playing)};F.prototype.onPropertyChanged=function(c,e){"url"==c&&this.loadMIDIFile(e)};F.prototype.onExecute=function(){if(this._midi&& -this._playing){this._current_time+=this.graph.elapsed_time;for(var e=100*this._current_time,h=0;ha;a++)for(var b=0;bd+f||c[1]>b))return a}}return-1};K.prototype.onAction=function(e,h){if("reset"==e)for(h=0;hh[1]))return e=this.getKeyIndex(h),this.keys[e]=!0,this._last_key=e,e=12*(this.properties.start_octave-1)+29+e,h=new c,h.setup([c.NOTEON,e,100]),this.trigger("note",h),!0};K.prototype.onMouseMove=function(e,h){if(!(0>h[1]||-1==this._last_key)){this.setDirtyCanvas(!0); -e=this.getKeyIndex(h);if(this._last_key==e)return!0;this.keys[this._last_key]=!1;h=12*(this.properties.start_octave-1)+29+this._last_key;var k=new c;k.setup([c.NOTEOFF,h,100]);this.trigger("note",k);this.keys[e]=!0;h=12*(this.properties.start_octave-1)+29+e;k=new c;k.setup([c.NOTEON,h,100]);this.trigger("note",k);this._last_key=e;return!0}};K.prototype.onMouseUp=function(e,h){if(!(0>h[1]))return e=this.getKeyIndex(h),this.keys[e]=!1,this._last_key=-1,e=12*(this.properties.start_octave-1)+29+e,h=new c, -h.setup([c.NOTEOFF,e,100]),this.trigger("note",h),!0};B.registerNodeType("midi/keys",K)})(this); -(function(y){function c(){this.properties={src:"",gain:.5,loop:!0,autoplay:!0,playbackRate:1};this._loading_audio=!1;this._audiobuffer=null;this._audionodes=[];this._last_sourcenode=null;this.addOutput("out","audio");this.addInput("gain","number");this.audionode=m.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain;this.properties.src&&this.loadSound(this.properties.src)}function k(){this.properties={gain:.5};this._audionodes=[];this._media_stream= -null;this.addOutput("out","audio");this.addInput("gain","number");this.audionode=m.getAudioContext().createGain();this.audionode.graphnode=this;this.audionode.gain.value=this.properties.gain}function n(){this.properties={fftSize:2048,minDecibels:-100,maxDecibels:-10,smoothingTimeConstant:.5};this.audionode=m.getAudioContext().createAnalyser();this.audionode.graphnode=this;this.audionode.fftSize=this.properties.fftSize;this.audionode.minDecibels=this.properties.minDecibels;this.audionode.maxDecibels= -this.properties.maxDecibels;this.audionode.smoothingTimeConstant=this.properties.smoothingTimeConstant;this.addInput("in","audio");this.addOutput("freqs","array");this.addOutput("samples","array");this._time_bin=this._freq_bin=null}function r(){this.properties={gain:1};this.audionode=m.getAudioContext().createGain();this.addInput("in","audio");this.addInput("gain","number");this.addOutput("out","audio")}function u(){this.properties={impulse_src:"",normalize:!0};this.audionode=m.getAudioContext().createConvolver(); -this.addInput("in","audio");this.addOutput("out","audio")}function h(){this.properties={threshold:-50,knee:40,ratio:12,reduction:-20,attack:0,release:.25};this.audionode=m.getAudioContext().createDynamicsCompressor();this.addInput("in","audio");this.addOutput("out","audio")}function E(){this.properties={};this.audionode=m.getAudioContext().createWaveShaper();this.addInput("in","audio");this.addInput("shape","waveshape");this.addOutput("out","audio")}function D(){this.properties={gain1:.5,gain2:.5}; -this.audionode=m.getAudioContext().createGain();this.audionode1=m.getAudioContext().createGain();this.audionode1.gain.value=this.properties.gain1;this.audionode2=m.getAudioContext().createGain();this.audionode2.gain.value=this.properties.gain2;this.audionode1.connect(this.audionode);this.audionode2.connect(this.audionode);this.addInput("in1","audio");this.addInput("in1 gain","number");this.addInput("in2","audio");this.addInput("in2 gain","number");this.addOutput("out","audio")}function J(){this.properties= -{A:.1,D:.1,S:.1,R:.1};this.audionode=m.getAudioContext().createGain();this.audionode.gain.value=0;this.addInput("in","audio");this.addInput("gate","boolean");this.addOutput("out","audio");this.gate=!1}function L(){this.properties={delayTime:.5};this.audionode=m.getAudioContext().createDelay(10);this.audionode.delayTime.value=this.properties.delayTime;this.addInput("in","audio");this.addInput("time","number");this.addOutput("out","audio")}function H(){this.properties={frequency:350,detune:0,Q:1};this.addProperty("type", -"lowpass","enum",{values:"lowpass highpass bandpass lowshelf highshelf peaking notch allpass".split(" ")});this.audionode=m.getAudioContext().createBiquadFilter();this.addInput("in","audio");this.addOutput("out","audio")}function F(){this.properties={frequency:440,detune:0,type:"sine"};this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]});this.audionode=m.getAudioContext().createOscillator();this.addOutput("out","audio")}function e(){this.properties={continuous:!0, -mark:-1};this.addInput("data","array");this.addInput("mark","number");this.size=[300,200];this._last_buffer=null}function K(){this.properties={band:440,amplitude:1};this.addInput("freqs","array");this.addOutput("signal","number")}function B(){if(!B.default_code){var c=B.default_function.toString(),e=c.indexOf("{")+1,a=c.lastIndexOf("}");B.default_code=c.substr(e,a-e)}this.properties={code:B.default_code};c=m.getAudioContext();c.createScriptProcessor?this.audionode=c.createScriptProcessor(4096,1,1): -(console.warn("ScriptProcessorNode deprecated"),this.audionode=c.createGain());this.processCode();B._bypass_function||(B._bypass_function=this.audionode.onaudioprocess);this.addInput("in","audio");this.addOutput("out","audio")}function M(){this.audionode=m.getAudioContext().destination;this.addInput("in","audio")}var l=y.LiteGraph,m={};y.LGAudio=m;m.getAudioContext=function(){if(!this._audio_context){window.AudioContext=window.AudioContext||window.webkitAudioContext;if(!window.AudioContext)return console.error("AudioContext not supported by browser"), -null;this._audio_context=new AudioContext;this._audio_context.onmessage=function(c){console.log("msg",c)};this._audio_context.onended=function(c){console.log("ended",c)};this._audio_context.oncomplete=function(c){console.log("complete",c)}}return this._audio_context};m.connect=function(c,e){try{c.connect(e)}catch(a){console.warn("LGraphAudio:",a)}};m.disconnect=function(c,e){try{c.disconnect(e)}catch(a){console.warn("LGraphAudio:",a)}};m.changeAllAudiosConnections=function(c,e){if(c.inputs)for(var a= -0;a=this.size[0]&&(d=this.size[0]-1),c.strokeStyle= -"red",c.beginPath(),c.moveTo(d,b),c.lineTo(d,0),c.stroke())}};e.title="Visualization";e.desc="Audio Visualization";l.registerNodeType("audio/visualization",e);K.prototype.onExecute=function(){if(this._freqs=this.getInputData(0)){var c=this.properties.band,e=this.getInputData(1);void 0!==e&&(c=e);e=m.getAudioContext().sampleRate/this._freqs.length;e=c/e*2;e>=this._freqs.length?e=this._freqs[this._freqs.length-1]:(c=e|0,e-=c,e=this._freqs[c]*(1-e)+this._freqs[c+1]*e);this.setOutputData(0,e/255*this.properties.amplitude)}}; -K.prototype.onGetInputs=function(){return[["band","number"]]};K.title="Signal";K.desc="extract the signal of some frequency";l.registerNodeType("audio/signal",K);B.prototype.onAdded=function(c){c.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)};B["@code"]={widget:"code",type:"code"};B.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback};B.prototype.onStop=function(){this.audionode.onaudioprocess=B._bypass_function};B.prototype.onPause=function(){this.audionode.onaudioprocess= -B._bypass_function};B.prototype.onUnpause=function(){this.audionode.onaudioprocess=this._callback};B.prototype.onExecute=function(){};B.prototype.onRemoved=function(){this.audionode.onaudioprocess=B._bypass_function};B.prototype.processCode=function(){try{this._script=new (new Function("properties",this.properties.code))(this.properties),this._old_code=this.properties.code,this._callback=this._script.onaudioprocess}catch(w){console.error("Error in onaudioprocess code",w),this._callback=B._bypass_function, -this.audionode.onaudioprocess=this._callback}};B.prototype.onPropertyChanged=function(c,e){"code"==c&&(this.properties.code=e,this.processCode(),this.graph&&this.graph.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback))};B.default_function=function(){this.onaudioprocess=function(c){var e=c.inputBuffer;c=c.outputBuffer;for(var a=0;a(t^16*Math.random()>>t/4).toString(16))},isValidConnection:function(t,e){if(""!=e&&"*"!==e||(e=0),!(t=""!=t&&"*"!==t?t:0)||!e||t==e||t==LiteGraph.EVENT&&e==LiteGraph.ACTION)return!0;if(t=String(t),e=String(e),t=t.toLowerCase(),e=e.toLowerCase(),-1==t.indexOf(",")&&-1==e.indexOf(","))return t==e;for(var i=t.split(","),s=e.split(","),o=0;oe&&(e=h.size[p]),o==LiteGraph.VERTICAL_LAYOUT?0:1);i+=h.size[p]+s+LiteGraph.NODE_TITLE_HEIGHT}a+=e+s}}this.setDirtyCanvas(!0,!0)},LGraph.prototype.getTime=function(){return this.globaltime},LGraph.prototype.getFixedTime=function(){return this.fixedtime},LGraph.prototype.getElapsedTime=function(){return this.elapsed_time},LGraph.prototype.sendEventToAllNodes=function(t,e,i){i=i||LiteGraph.ALWAYS;var s=this._nodes_in_order||this._nodes;if(s)for(var o=0,n=s.length;o=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";return LiteGraph.use_uuids?null!=t.id&&-1!=t.id||(t.id=LiteGraph.uuidv4()):null==t.id||-1==t.id?t.id=++this.last_node_id:this.last_node_id=this.outputs.length)){var i=this.outputs[t];if(i&&(i._data=e,this.outputs[t].links))for(var s=0;s=this.outputs.length)){var i=this.outputs[t];if(i&&(i.type=e,this.outputs[t].links))for(var s=0;s=this.inputs.length||null==this.inputs[t].link))return t=this.inputs[t].link,(t=this.graph.links[t])?(e&&(e=this.graph.getNodeById(t.origin_id))&&(e.updateOutputData?e.updateOutputData(t.origin_slot):e.onExecute&&e.onExecute()),t.data):null},LGraphNode.prototype.getInputDataType=function(t){var e;return this.inputs&&!(t>=this.inputs.length||null==this.inputs[t].link)&&(t=this.inputs[t].link,t=this.graph.links[t])?(e=this.graph.getNodeById(t.origin_id))?(e=e.outputs[t.origin_slot])?e.type:null:t.type:null},LGraphNode.prototype.getInputDataByName=function(t,e){t=this.findInputSlot(t);return-1==t?null:this.getInputData(t,e)},LGraphNode.prototype.isInputConnected=function(t){return!!this.inputs&&t=this.inputs.length)&&(t=this.inputs[t])&&null!==t.link&&(t=this.graph.links[t.link])?this.graph.getNodeById(t.origin_id):null},LGraphNode.prototype.getInputOrProperty=function(t){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[t]:null;for(var e=0,i=this.inputs.length;e=this.outputs.length?null:this.outputs[t]._data},LGraphNode.prototype.getOutputInfo=function(t){return this.outputs&&t=this.outputs.length)return null;var e=this.outputs[t];if(!e.links||0==e.links.length)return null;for(var i=[],s=0;st&&this.pos[1]-o-ie)return!0;return!1},LGraphNode.prototype.getSlotInPosition=function(t,e){var i=new Float32Array(2);if(this.inputs)for(var s=0,o=this.inputs.length;s=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;if(!(e=e&&e.constructor===Number?this.graph.getNodeById(e):e))throw"target node is null";if(e==this)return null;if(i.constructor===String){if(-1==(i=e.findInputSlot(i)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+i),null}else if(i===LiteGraph.EVENT){if(!LiteGraph.do_add_triggers_slots)return null;e.changeMode(LiteGraph.ON_TRIGGER),i=e.findInputSlot("onTrigger")}else if(!e.inputs||i>=e.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;var s,o=!1,n=e.inputs[i],r=this.outputs[t];return this.outputs[t]?!1!==(i=e.onBeforeConnectInput?e.onBeforeConnectInput(i):i)&&null!==i&&LiteGraph.isValidConnection(r.type,n.type)?e.onConnectInput&&!1===e.onConnectInput(i,r.type,r,this,t)||this.onConnectOutput&&!1===this.onConnectOutput(t,n.type,n,e,i)?null:(e.inputs[i]&&null!=e.inputs[i].link&&(this.graph.beforeChange(),e.disconnectInput(i,{doProcessChange:!1}),o=!0),null!==r.links&&r.links.length&&r.type===LiteGraph.EVENT&&!LiteGraph.allow_multi_output_for_events&&(this.graph.beforeChange(),this.disconnectOutput(t,!1,{doProcessChange:!1}),o=!0),s=new LLink(LiteGraph.use_uuids?LiteGraph.uuidv4():++this.graph.last_link_id,n.type||r.type,this.id,t,e.id,i),this.graph.links[s.id]=s,null==r.links&&(r.links=[]),r.links.push(s.id),e.inputs[i].link=s.id,this.graph&&this.graph._version++,this.onConnectionsChange&&this.onConnectionsChange(LiteGraph.OUTPUT,t,!0,s,r),e.onConnectionsChange&&e.onConnectionsChange(LiteGraph.INPUT,i,!0,s,n),this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(LiteGraph.INPUT,e,i,this,t),this.graph.onNodeConnectionChange(LiteGraph.OUTPUT,this,t,e,i)),this.setDirtyCanvas(!1,!0),this.graph.afterChange(),this.graph.connectionChange(this,s),s):(this.setDirtyCanvas(!1,!0),o&&this.graph.connectionChange(this,null),null):null},LGraphNode.prototype.disconnectOutput=function(t,e){if(t.constructor===String){if(-1==(t=this.findOutputSlot(t)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+t),!1}else if(!this.outputs||t>=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var i=this.outputs[t];if(!i||!i.links||0==i.links.length)return!1;if(e){if(!(e=e.constructor===Number?this.graph.getNodeById(e):e))throw"Target Node not found";for(var s=0,o=i.links.length;s=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var e=this.inputs[t];if(!e)return!1;var i=this.inputs[t].link;if(null!=i){this.inputs[t].link=null;var s=this.graph.links[i];if(s){var o=this.graph.getNodeById(s.origin_id);if(!o)return!1;var n=o.outputs[s.origin_slot];if(!n||!n.links||0==n.links.length)return!1;for(var r=0,a=n.links.length;rLGraphNode.MAX_CONSOLE&&this.console.shift(),this.graph.onNodeTrace&&this.graph.onNodeTrace(this,t)},LGraphNode.prototype.setDirtyCanvas=function(t,e){this.graph&&this.graph.sendActionToCanvas("setDirty",[t,e])},LGraphNode.prototype.loadImage=function(t){var e=new Image,i=(e.src=LiteGraph.node_images_path+t,e.ready=!1,this);return e.onload=function(){this.ready=!0,i.setDirtyCanvas(!0)},e},LGraphNode.prototype.captureInput=function(t){if(this.graph&&this.graph.list_of_graphcanvas)for(var e=this.graph.list_of_graphcanvas,i=0;i=this.viewport[0]&&i=this.viewport[1]&&sthis.max_scale&&(t=this.max_scale),t!=this.scale&&this.element&&(i=this.element.getBoundingClientRect())&&(e=e||[.5*i.width,.5*i.height],i=this.convertCanvasToOffset(e),this.scale=t,Math.abs(this.scale-1)<.01&&(this.scale=1),e=[(t=this.convertCanvasToOffset(e))[0]-i[0],t[1]-i[1]],this.offset[0]+=e[0],this.offset[1]+=e[1],this.onredraw)&&this.onredraw(this)},DragAndScale.prototype.changeDeltaScale=function(t,e){this.changeScale(this.scale*t,e)},DragAndScale.prototype.reset=function(){this.scale=1,this.offset[0]=0,this.offset[1]=0},global.LGraphCanvas=LiteGraph.LGraphCanvas=LGraphCanvas,LGraphCanvas.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",LGraphCanvas.link_type_colors={"-1":LiteGraph.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"},LGraphCanvas.gradients={},LGraphCanvas.prototype.clear=function(){this.frame=0,this.last_draw_time=0,this.render_time=0,this.fps=0,this.dragging_rectangle=null,this.selected_nodes={},this.selected_group=null,this.visible_nodes=[],this.node_dragged=null,this.node_over=null,this.node_capturing_input=null,this.connecting_node=null,this.highlighted_links={},this.dragging_canvas=!1,this.dirty_canvas=!0,this.dirty_bgcanvas=!0,this.dirty_area=null,this.node_in_panel=null,this.node_widget=null,this.last_mouse=[0,0],this.last_mouseclick=0,this.pointer_is_down=!1,this.pointer_is_double=!1,this.visible_area.set([0,0,0,0]),this.onClear&&this.onClear()},LGraphCanvas.prototype.setGraph=function(t,e){this.graph!=t&&(e||this.clear(),!t&&this.graph?this.graph.detachCanvas(this):(t.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))},LGraphCanvas.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph},LGraphCanvas.prototype.openSubgraph=function(t){if(!t)throw"graph cannot be null";if(this.graph==t)throw"graph cannot be the same";this.clear(),this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph)),t.attachCanvas(this),this.checkPanels(),this.setDirty(!0,!0)},LGraphCanvas.prototype.closeSubgraph=function(){var t,e;this._graph_stack&&0!=this._graph_stack.length&&(t=this.graph._subgraph_node,e=this._graph_stack.pop(),this.selected_nodes={},this.highlighted_links={},e.attachCanvas(this),this.setDirty(!0,!0),t&&(this.centerOnNode(t),this.selectNodes([t])),this.ds.offset=[0,0],this.ds.scale=1)},LGraphCanvas.prototype.getCurrentGraph=function(){return this.graph},LGraphCanvas.prototype.setCanvas=function(t,e){if(t&&t.constructor===String&&!(t=document.getElementById(t)))throw"Error creating LiteGraph canvas: Canvas not found";if(t!==this.canvas&&(t||!this.canvas||e||this.unbindEvents(),this.canvas=t,this.ds.element=t)){if(t.className+=" lgraphcanvas",t.data=this,t.tabindex="1",this.bgcanvas=null,this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height),null==t.getContext){if("canvas"!=t.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+t.localName;throw"This browser doesn't support Canvas"}null==(this.ctx=t.getContext("2d"))&&(t.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL()),e||this.bindEvents()}},LGraphCanvas.prototype._doNothing=function(t){return t.preventDefault(),!1},LGraphCanvas.prototype._doReturnTrue=function(t){return t.preventDefault(),!0},LGraphCanvas.prototype.bindEvents=function(){var t,e;this._events_binded?console.warn("LGraphCanvas: events already binded"):(t=this.canvas,e=this.getCanvasWindow().document,this._mousedown_callback=this.processMouseDown.bind(this),this._mousewheel_callback=this.processMouseWheel.bind(this),this._mousemove_callback=this.processMouseMove.bind(this),this._mouseup_callback=this.processMouseUp.bind(this),LiteGraph.pointerListenerAdd(t,"down",this._mousedown_callback,!0),t.addEventListener("mousewheel",this._mousewheel_callback,!1),LiteGraph.pointerListenerAdd(t,"up",this._mouseup_callback,!0),LiteGraph.pointerListenerAdd(t,"move",this._mousemove_callback),t.addEventListener("contextmenu",this._doNothing),t.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1),this._key_callback=this.processKey.bind(this),t.setAttribute("tabindex",1),t.addEventListener("keydown",this._key_callback,!0),e.addEventListener("keyup",this._key_callback,!0),this._ondrop_callback=this.processDrop.bind(this),t.addEventListener("dragover",this._doNothing,!1),t.addEventListener("dragend",this._doNothing,!1),t.addEventListener("drop",this._ondrop_callback,!1),t.addEventListener("dragenter",this._doReturnTrue,!1),this._events_binded=!0)},LGraphCanvas.prototype.unbindEvents=function(){var t;this._events_binded?(t=this.getCanvasWindow().document,LiteGraph.pointerListenerRemove(this.canvas,"move",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"up",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"down",this._mousedown_callback),this.canvas.removeEventListener("mousewheel",this._mousewheel_callback),this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback),this.canvas.removeEventListener("keydown",this._key_callback),t.removeEventListener("keyup",this._key_callback),this.canvas.removeEventListener("contextmenu",this._doNothing),this.canvas.removeEventListener("drop",this._ondrop_callback),this.canvas.removeEventListener("dragenter",this._doReturnTrue),this._mousedown_callback=null,this._mousewheel_callback=null,this._key_callback=null,this._ondrop_callback=null,this._events_binded=!1):console.warn("LGraphCanvas: no events binded")},LGraphCanvas.getFileExtension=function(t){var e=t.indexOf("?"),e=(t=-1!=e?t.substr(0,e):t).lastIndexOf(".");return-1==e?"":t.substr(e+1).toLowerCase()},LGraphCanvas.prototype.enableWebGL=function(){if("undefined"==typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"==typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas),this.ctx.webgl=!0,this.bgcanvas=this.canvas,this.bgctx=this.gl,this.canvas.webgl_enabled=!0},LGraphCanvas.prototype.setDirty=function(t,e){t&&(this.dirty_canvas=!0),e&&(this.dirty_bgcanvas=!0)},LGraphCanvas.prototype.getCanvasWindow=function(){var t;return this.canvas?(t=this.canvas.ownerDocument).defaultView||t.parentWindow:window},LGraphCanvas.prototype.startRendering=function(){function e(){this.pause_rendering||this.draw();var t=this.getCanvasWindow();this.is_rendering&&t.requestAnimationFrame(e.bind(this))}this.is_rendering||(this.is_rendering=!0,e.call(this))},LGraphCanvas.prototype.stopRendering=function(){this.is_rendering=!1},LGraphCanvas.prototype.blockClick=function(){this.block_click=!0,this.last_mouseclick=0},LGraphCanvas.prototype.processMouseDown=function(t){if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){this.adjustMouseEvent(t);var e=this.getCanvasWindow(),i=(e.document,LGraphCanvas.active_canvas=this),s=t.clientX,o=t.clientY,s=(this.ds.viewport=this.viewport,!this.viewport||(this.viewport,s>=this.viewport[0]&&s=this.viewport[1]&&og[0]+4||t.canvasYg[1]+4)){this.showLinkMenu(_,t),this.over_link_center=null;break}}this.selected_group=this.graph.getGroupOnPos(t.canvasX,t.canvasY),this.selected_group_resizing=!1,this.selected_group&&!this.read_only&&(t.ctrlKey&&(this.dragging_rectangle=null),distance([t.canvasX,t.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale<10?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes()),a&&!this.read_only&&this.allow_searchbox&&(this.showSearchBox(t),t.preventDefault(),t.stopPropagation()),s=!0}}else{if(this.live_mode||n.flags.pinned||this.bringToFront(n),this.allow_interaction&&!this.connecting_node&&!n.flags.collapsed&&!this.live_mode)if(!r&&!1!==n.resizable&&isInsideRectangle(t.canvasX,t.canvasY,n.pos[0]+n.size[0]-5,n.pos[1]+n.size[1]-5,10,10))this.graph.beforeChange(),this.resizing_node=n,this.canvas.style.cursor="se-resize",r=!0;else{if(n.outputs)for(var c=0,f=n.outputs.length;cn.size[0]-LiteGraph.NODE_TITLE_HEIGHT&&o[1]<0&&(i=this,setTimeout(function(){i.openSubgraph(n.subgraph)},10)),this.live_mode&&(l=s=!0)),l?n.is_selected||this.processNodeSelected(n,t):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=n),this.processNodeSelected(n,t)),this.dirty_canvas=!0)}!r&&s&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}return this.last_mouse[0]=t.clientX,this.last_mouse[1]=t.clientY,this.last_mouseclick=LiteGraph.getTime(),this.last_mouse_dragging=!0,this.graph.change(),e.document.activeElement&&("input"==e.document.activeElement.nodeName.toLowerCase()||"textarea"==e.document.activeElement.nodeName.toLowerCase())||t.preventDefault(),t.stopPropagation(),this.onMouseDown&&this.onMouseDown(t),!1}}}},LGraphCanvas.prototype.processMouseMove=function(t){if(this.autoresize&&this.resize(),this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){(LGraphCanvas.active_canvas=this).adjustMouseEvent(t);var e=[t.clientX,t.clientY],i=(this.mouse[0]=e[0],this.mouse[1]=e[1],[e[0]-this.last_mouse[0],e[1]-this.last_mouse[1]]);if(this.last_mouse=e,this.graph_mouse[0]=t.canvasX,this.graph_mouse[1]=t.canvasY,!this.block_click){t.dragging=this.last_mouse_dragging,this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t,this.node_widget[1]),this.dirty_canvas=!0);var s=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=t.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=t.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[t.canvasX-this.selected_group.pos[0],t.canvasY-this.selected_group.pos[1]]:(e=i[0]/this.ds.scale,a=i[1]/this.ds.scale,this.selected_group.move(e,a,t.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=i[0]/this.ds.scale,this.ds.offset[1]+=i[1]/this.ds.scale,this.dirty_canvas=!0,this.dirty_bgcanvas=!0;else if((this.allow_interaction||s&&s.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);for(var o,n,r,a,u=0,h=this.graph._nodes.length;ud[0]+4||t.canvasYd[1]+4)){p=l;break}}p!=this.over_link_center&&(this.over_link_center=p,this.dirty_canvas=!0),this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&&this.node_capturing_input!=s&&this.node_capturing_input.onMouseMove&&this.node_capturing_input.onMouseMove(t,[t.canvasX-this.node_capturing_input.pos[0],t.canvasY-this.node_capturing_input.pos[1]],this),this.node_dragged&&!this.live_mode){for(var u in this.selected_nodes){var c=this.selected_nodes[u];c.pos[0]+=i[0]/this.ds.scale,c.pos[1]+=i[1]/this.ds.scale,c.is_selected||this.processNodeSelected(c,t)}this.dirty_canvas=!0,this.dirty_bgcanvas=!0}this.resizing_node&&!this.live_mode&&(e=[t.canvasX-this.resizing_node.pos[0],t.canvasY-this.resizing_node.pos[1]],a=this.resizing_node.computeSize(),e[0]=Math.max(a[0],e[0]),e[1]=Math.max(a[1],e[1]),this.resizing_node.setSize(e),this.canvas.style.cursor="se-resize",this.dirty_canvas=!0,this.dirty_bgcanvas=!0)}}return t.preventDefault(),!1}},LGraphCanvas.prototype.processMouseUp=function(t){var e=void 0===t.isPrimary||t.isPrimary;if(!e)return!1;if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){var i=this.getCanvasWindow().document,i=((LGraphCanvas.active_canvas=this).options.skip_events||(LiteGraph.pointerListenerRemove(i,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerRemove(i,"up",this._mouseup_callback,!0)),this.adjustMouseEvent(t),LiteGraph.getTime());if(t.click_time=i-this.last_mouseclick,this.last_mouse_dragging=!1,this.last_click_position=null,this.block_click&&(this.block_click=!1),1==t.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t),this.node_widget=null,this.selected_group&&(i=this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),r=this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),this.selected_group.move(i,r,t.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null),this.selected_group_resizing=!1;var s,i=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){var o=this.graph._nodes,n=new Float32Array(4),r=Math.abs(this.dragging_rectangle[2]),a=Math.abs(this.dragging_rectangle[3]),u=this.dragging_rectangle[2]<0?this.dragging_rectangle[0]-r:this.dragging_rectangle[0],h=this.dragging_rectangle[3]<0?this.dragging_rectangle[1]-a:this.dragging_rectangle[1];if(this.dragging_rectangle[0]=u,this.dragging_rectangle[1]=h,this.dragging_rectangle[2]=r,this.dragging_rectangle[3]=a,!i||10=this.viewport[0]&&i=this.viewport[1]&&si.nodes[n].pos[0]&&(s[0]=i.nodes[n].pos[0],o[0]=n),s[1]>i.nodes[n].pos[1]&&(s[1]=i.nodes[n].pos[1],o[1]=n)):(s=[i.nodes[n].pos[0],i.nodes[n].pos[1]],o=[n,n]);for(var r=[],n=0;n=this.viewport[0]&&e=this.viewport[1]&&i> ";e.fillText(s+i.getTitle(),.5*t.width,40),e.restore()}var n,i=!1;this.onRenderBackground&&(i=this.onRenderBackground(t,e)),this.viewport||(e.restore(),e.setTransform(1,0,0,1,0,0)),this.visible_links.length=0,this.graph&&(e.save(),this.ds.toCanvasContext(e),this.ds.scale<1.5&&!i&&this.clear_background_color&&(e.fillStyle=this.clear_background_color,e.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3])),this.background_image&&.5t[2]&&(t[2]=e),it[3]&&(t[3]=i)}function isInsideBounding(t,e){return!(t[0]e[1][0]||t[1]>e[1][1])}function overlapBounding(t,e){var i=t[0]+t[2];return!(e[0]+e[2]e[1]+e[3]||ih.width-p.width-10&&(s=h.width-p.width-10),h.height)&&l>h.height-p.height-10&&(l=h.height-p.height-10),o.style.left=s+"px",o.style.top=l+"px",i.scale&&(o.style.transform="scale("+i.scale+")")}function CurveEditor(t){this.points=t,this.selected=-1,this.nearest=-1,this.size=null,this.must_update=!0,this.margin=5}function clamp(t,e,i){return te[1]?0:Math.PI,t.save(),t.translate(c[0],c[1]),t.rotate(w),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore(),t.save(),t.translate(o[0],o[1]),t.rotate(L),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore()),t.beginPath(),t.arc(O[0],O[1],5,0,2*Math.PI),t.fill()),n){t.fillStyle=r;for(_=0;_<5;++_){var I=(.001*LiteGraph.getTime()+.2*_)%1,O=this.computeConnectionPoint(e,i,I,a,u);t.beginPath(),t.arc(O[0],O[1],5,0,2*Math.PI),t.fill()}}},LGraphCanvas.prototype.computeConnectionPoint=function(t,e,i,s,o){s=s||LiteGraph.RIGHT,o=o||LiteGraph.LEFT;var n=distance(t,e),r=t,a=[t[0],t[1]],u=[e[0],e[1]],t=e;switch(s){case LiteGraph.LEFT:a[0]+=-.25*n;break;case LiteGraph.RIGHT:a[0]+=.25*n;break;case LiteGraph.UP:a[1]+=-.25*n;break;case LiteGraph.DOWN:a[1]+=.25*n}switch(o){case LiteGraph.LEFT:u[0]+=-.25*n;break;case LiteGraph.RIGHT:u[0]+=.25*n;break;case LiteGraph.UP:u[1]+=-.25*n;break;case LiteGraph.DOWN:u[1]+=.25*n}e=(1-i)*(1-i)*(1-i),s=(1-i)*(1-i)*3*i,o=3*(1-i)*(i*i),i*=i*i;return[e*r[0]+s*a[0]+o*u[0]+i*t[0],e*r[1]+s*a[1]+o*u[1]+i*t[1]]},LGraphCanvas.prototype.drawExecutionOrder=function(t){t.shadowColor="transparent",t.globalAlpha=.25,t.textAlign="center",t.strokeStyle="white",t.globalAlpha=.75;for(var e=this.visible_nodes,i=0;iw.last_y+widget_height||void 0===w.last_y)){var old_value=w.value;switch(w.type){case"button":event.type===LiteGraph.pointerevents_method+"down"&&(w.callback&&setTimeout(function(){w.callback(w,that,node,pos,event)},20),w.clicked=!0,this.dirty_canvas=!0);break;case"slider":var old_value=w.value,nvalue=clamp((x-15)/(widget_width-30),0,1);w.options.read_only||(w.value=w.options.min+(w.options.max-w.options.min)*nvalue,old_value!=w.value&&setTimeout(function(){inner_value_change(w,w.value)},20),this.dirty_canvas=!0);break;case"number":case"combo":var old_value=w.value,values,values_list,delta,index,text_values,menu,delta;function inner_clicked(t,e,i){return values!=values_list&&(t=text_values.indexOf(t)),this.value=t,inner_value_change(this,t),!(that.dirty_canvas=!0)}event.type==LiteGraph.pointerevents_method+"move"&&"number"==w.type?(deltaX&&(w.value+=.1*deltaX*(w.options.step||1)),null!=w.options.min&&w.valuew.options.max&&(w.value=w.options.max)):event.type==LiteGraph.pointerevents_method+"down"?(values=w.options.values,values&&values.constructor===Function&&(values=w.options.values(w,node)),values_list=null,"number"!=w.type&&(values_list=values.constructor===Array?values:Object.keys(values)),delta=x<40?-1:widget_width-40w.options.max&&(w.value=w.options.max)):delta?(index=-1,this.last_mouseclick=0,index=values.constructor===Object?values_list.indexOf(String(w.value))+delta:values_list.indexOf(w.value)+delta,index>=values_list.length&&(index=values_list.length-1),index<0&&(index=0),values.constructor===Array?w.value=values[index]:w.value=index):(text_values=values!=values_list?Object.values(values):values,menu=new LiteGraph.ContextMenu(text_values,{scale:Math.max(1,this.ds.scale),event:event,className:"dark",callback:inner_clicked.bind(w)},ref_window))):event.type==LiteGraph.pointerevents_method+"up"&&"number"==w.type&&(delta=x<40?-1:widget_width-40i.pos[0]+i.size[0])&&(i=n),(null===s||a+h>s.pos[1]+s.size[1])&&(s=n),(null===o||r"+(h.label||o)+""+t+"",value:o})}if(u.length)return new LiteGraph.ContextMenu(u,{event:i,callback:function(t,e,i,s){var o;n&&(o=this.getBoundingClientRect(),r.showEditPropertyValue(n,t.value,{position:[o.left,o.top]}))},parentMenu:s,allow_html:!0,node:n},a),!1}},LGraphCanvas.decodeHTML=function(t){var e=document.createElement("div");return e.innerText=t,e.innerHTML},LGraphCanvas.onMenuResizeNode=function(t,e,i,s,o){if(o){var n=function(t){t.size=t.computeSize(),t.onResize&&t.onResize(t.size)},r=LGraphCanvas.active_canvas;if(!r.selected_nodes||Object.keys(r.selected_nodes).length<=1)n(o);else for(var a in r.selected_nodes)n(r.selected_nodes[a]);o.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.showLinkMenu=function(s,t){var o=this,n=o.graph.getNodeById(s.origin_id),r=o.graph.getNodeById(s.target_id),a=!1,u=(n&&n.outputs&&n.outputs[s.origin_slot]&&(a=n.outputs[s.origin_slot].type),!1),h=(r&&r.outputs&&r.outputs[s.target_slot]&&(u=r.inputs[s.target_slot].type),new LiteGraph.ContextMenu(["Add Node",null,"Delete",null],{event:t,title:null!=s.data?s.data.constructor.name:null,callback:function(t,e,i){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,i,h,function(t){t.inputs&&t.inputs.length&&t.outputs&&t.outputs.length&&n.connectByType(s.origin_slot,t,a)&&(t.connectByType(s.target_slot,r,u),t.pos[0]-=.5*t.size[0])});break;case"Delete":o.graph.removeLink(s.id)}}}));return!1},LGraphCanvas.prototype.createDefaultNodeForSlot=function(t){var t=t||{},e=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},t),i=e.nodeFrom&&null!==e.slotFrom,t=!i&&e.nodeTo&&null!==e.slotTo;if(i||t)if(e.nodeType){var s=i?e.nodeFrom:e.nodeTo,o=i?e.slotFrom:e.slotTo,n=!1;switch(typeof o){case"string":n=i?s.findOutputSlot(o,!1):s.findInputSlot(o,!1),o=(i?s.outputs:s.inputs)[o];break;case"object":n=i?s.findOutputSlot(o.name):s.findInputSlot(o.name);break;case"number":n=o,o=(i?s.outputs:s.inputs)[o];break;default:return console.warn("Cant get slot information "+o),!1}!1!==o&&!1!==n||console.warn("createDefaultNodeForSlot bad slotX "+o+" "+n);var r=o.type==LiteGraph.EVENT?"_event_":o.type,a=i?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(a&&a[r]){if(o.link,nodeNewType=!1,"object"==typeof a[r]||"array"==typeof a[r]){for(var u in a[r])if(e.nodeType==a[r][u]||"AUTO"==e.nodeType){nodeNewType=a[r][u];break}}else e.nodeType!=a[r]&&"AUTO"!=e.nodeType||(nodeNewType=a[r]);if(nodeNewType){var h=!1,p=("object"==typeof nodeNewType&&nodeNewType.node&&(nodeNewType=(h=nodeNewType).node),LiteGraph.createNode(nodeNewType));if(p){if(h){if(h.properties)for(var l in h.properties)p.addProperty(l,h.properties[l]);if(h.inputs)for(var l in p.inputs=[],h.inputs)p.addOutput(h.inputs[l][0],h.inputs[l][1]);if(h.outputs)for(var l in p.outputs=[],h.outputs)p.addOutput(h.outputs[l][0],h.outputs[l][1]);h.title&&(p.title=h.title),h.json&&p.configure(h.json)}return this.graph.add(p),p.pos=[e.position[0]+e.posAdd[0]+(e.posSizeFix[0]?e.posSizeFix[0]*p.size[0]:0),e.position[1]+e.posAdd[1]+(e.posSizeFix[1]?e.posSizeFix[1]*p.size[1]:0)],i?e.nodeFrom.connectByType(n,p,r):e.nodeTo.connectByTypeOutput(n,p,r),!0}console.log("failed creating "+nodeNewType)}}}else console.warn("No type to createDefaultNodeForSlot");else console.warn("No data passed to createDefaultNodeForSlot "+e.nodeFrom+" "+e.slotFrom+" "+e.nodeTo+" "+e.slotTo);return!1},LGraphCanvas.prototype.showConnectionMenu=function(t){var t=t||{},s=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},t),o=this,n=s.nodeFrom&&s.slotFrom,t=!n&&s.nodeTo&&s.slotTo;if(n||t){var e=n?s.nodeFrom:s.nodeTo,r=n?s.slotFrom:s.slotTo,a=!1;switch(typeof r){case"string":a=n?e.findOutputSlot(r,!1):e.findInputSlot(r,!1),r=(n?e.outputs:e.inputs)[r];break;case"object":a=n?e.findOutputSlot(r.name):e.findInputSlot(r.name);break;case"number":a=r,r=(n?e.outputs:e.inputs)[r];break;default:return console.warn("Cant get slot information "+r),!1}var i=["Add Node",null],u=(o.allow_searchbox&&(i.push("Search"),i.push(null)),r.type==LiteGraph.EVENT?"_event_":r.type),h=n?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(h&&h[u])if("object"==typeof h[u]||"array"==typeof h[u])for(var p in h[u])i.push(h[u][p]);else i.push(h[u]);var l=new LiteGraph.ContextMenu(i,{event:s.e,title:(r&&""!=r.name?r.name+(u?" | ":""):"")+(r&&u?u:""),callback:function(t,e,i){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,i,l,function(t){n?s.nodeFrom.connectByType(a,t,u):s.nodeTo.connectByTypeOutput(a,t,u)});break;case"Search":n?o.showSearchBox(i,{node_from:s.nodeFrom,slot_from:r,type_filter_in:u}):o.showSearchBox(i,{node_to:s.nodeTo,slot_from:r,type_filter_out:u});break;default:o.createDefaultNodeForSlot(Object.assign(s,{position:[s.e.canvasX,s.e.canvasY],nodeType:t}))}}})}else console.warn("No data passed to showConnectionMenu");return!1},LGraphCanvas.onShowPropertyEditor=function(e,t,i,s,o){var n=e.property||"title",r=o[n],a=document.createElement("div");a.is_modified=!1,a.className="graphdialog",a.innerHTML="",a.close=function(){a.parentNode&&a.parentNode.removeChild(a)};a.querySelector(".name").innerText=n;var u=a.querySelector(".value");u&&(u.value=r,u.addEventListener("blur",function(t){this.focus()}),u.addEventListener("keydown",function(t){if(a.is_modified=!0,27==t.keyCode)a.close();else if(13==t.keyCode)c();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}));var r=LGraphCanvas.active_canvas.canvas,h=r.getBoundingClientRect(),p=-20,l=-20;h&&(p-=h.left,l-=h.top),event?(a.style.left=event.clientX+p+"px",a.style.top=event.clientY+l+"px"):(a.style.left=.5*r.width+p+"px",a.style.top=.5*r.height+l+"px");a.querySelector("button").addEventListener("click",c),r.parentNode.appendChild(a),u&&u.focus();var d=null;function c(){var t;u&&(t=u.value,"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t)),o[n]=t,a.parentNode&&a.parentNode.removeChild(a),o.setDirtyCanvas(!0,!0))}a.addEventListener("mouseleave",function(t){LiteGraph.dialog_close_on_mouse_leave&&!a.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(d=setTimeout(a.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),a.addEventListener("mouseenter",function(t){LiteGraph.dialog_close_on_mouse_leave&&d&&clearTimeout(d)})},LGraphCanvas.prototype.prompt=function(t,e,i,s,o){var n=this,r=(t=t||"",document.createElement("div"));r.is_modified=!1,r.className="graphdialog rounded",r.innerHTML=o?" ":" ",r.close=function(){n.prompt_box=null,r.parentNode&&r.parentNode.removeChild(r)};var o=LGraphCanvas.active_canvas.canvas,a=(o.parentNode.appendChild(r),1LGraphCanvas.search_limit)break}}var h=null;if(Array.prototype.filter)h=Object.keys(LiteGraph.registered_node_types).filter(p);else for(var e in h=[],LiteGraph.registered_node_types)p(e)&&h.push(e);for(e=0;eLGraphCanvas.search_limit));e++);if(d.show_general_after_typefiltered&&(n.value||r.value)){for(var e in filtered_extra=[],LiteGraph.registered_node_types)p(e,{inTypeOverride:!(!n||!n.value)&&"*",outTypeOverride:!(!r||!r.value)&&"*"})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}if((n.value||r.value)&&0==g.childNodes.length&&d.show_general_if_none_on_typefilter){for(var e in filtered_extra=[],LiteGraph.registered_node_types)p(e,{skipFilter:!0})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}function p(t,e){var e=e||{},e=Object.assign({skipFilter:!1,inTypeOverride:!1,outTypeOverride:!1},e),i=LiteGraph.registered_node_types[t];if(a&&i.filter!=a)return!1;if((!d.show_all_if_empty||o)&&-1===t.toLowerCase().indexOf(o))return!1;if(d.do_type_filter&&!e.skipFilter){i=t,t=n.value;if(!1!==e.inTypeOverride&&(t=e.inTypeOverride),n&&t&&LiteGraph.registered_slot_in_types[t]&&LiteGraph.registered_slot_in_types[t].nodes){var s=LiteGraph.registered_slot_in_types[t].nodes.includes(i);if(!1===s)return!1}t=r.value;if(!1!==e.outTypeOverride&&(t=e.outTypeOverride),r&&t&&LiteGraph.registered_slot_out_types[t]&&LiteGraph.registered_slot_out_types[t].nodes){s=LiteGraph.registered_slot_out_types[t].nodes.includes(i);if(!1===s)return!1}}return!0}}function l(t,e){var i=document.createElement("div");f=f||t,i.innerText=t,i.dataset.type=escape(t),i.className="litegraph lite-search-item",e&&(i.className+=" "+e),i.addEventListener("click",function(t){E(unescape(this.dataset.type))}),g.appendChild(i)}}return u.style.left=b+"px",u.style.top=T+"px",n.layerY>o.height-200&&(g.style.maxHeight=o.height-n.layerY-20+"px"),m.focus(),d.show_all_on_open&&L(),u},LGraphCanvas.prototype.showEditPropertyValue=function(e,i,s){if(e&&void 0!==e.properties[i]){s=s||{};var o=e.getPropertyInfo(i),n=o.type,t="";if("string"==n||"number"==n||"array"==n||"object"==n)t="";else if("enum"!=n&&"combo"!=n||!o.values){if("boolean"!=n&&"toggle"!=n)return void console.warn("unknown type: "+n);t=""}else{for(var r in t=""}var u=this.createDialog(""+(o.label||i)+""+t+"",s),h=!1;return"enum"!=n&&"combo"!=n||!o.values?"boolean"==n||"toggle"==n?(h=u.querySelector("input"))&&h.addEventListener("click",function(t){u.modified(),l(!!h.checked)}):(h=u.querySelector("input"))&&(h.addEventListener("blur",function(t){this.focus()}),a=void 0!==e.properties[i]?e.properties[i]:"","string"!==n&&(a=JSON.stringify(a)),h.value=a,h.addEventListener("keydown",function(t){if(27==t.keyCode)u.close();else if(13==t.keyCode)p();else if(13!=t.keyCode)return void u.modified();t.preventDefault(),t.stopPropagation()})):(h=u.querySelector("select")).addEventListener("change",function(t){u.modified(),l(t.target.value)}),h&&h.focus(),u.querySelector("button").addEventListener("click",p),u}function p(){l(h.value)}function l(t){o&&o.values&&o.values.constructor===Object&&null!=o.values[t]&&(t=o.values[t]),"number"==typeof e.properties[i]&&(t=Number(t)),"array"!=n&&"object"!=n||(t=JSON.parse(t)),e.properties[i]=t,e.graph&&e.graph._version++,e.onPropertyChanged&&e.onPropertyChanged(i,t),s.onclose&&s.onclose(),u.close(),e.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.createDialog=function(t,e){e=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},e||{});var i=document.createElement("div"),t=(i.className="graphdialog",i.innerHTML=t,i.is_modified=!1,this.canvas.getBoundingClientRect()),s=-20,o=-20,n=(t&&(s-=t.left,o-=t.top),e.position?(s+=e.position[0],o+=e.position[1]):e.event?(s+=e.event.clientX,o+=e.event.clientY):(s+=.5*this.canvas.width,o+=.5*this.canvas.height),i.style.left=s+"px",i.style.top=o+"px",this.canvas.parentNode.appendChild(i),e.checkForInput&&(t=[],t=i.querySelectorAll("input"))&&t.forEach(function(t){t.addEventListener("keydown",function(t){if(i.modified(),27==t.keyCode)i.close();else if(13!=t.keyCode)return;t.preventDefault(),t.stopPropagation()}),t.focus()}),i.modified=function(){i.is_modified=!0},i.close=function(){i.parentNode&&i.parentNode.removeChild(i)},null),r=!1,s=(i.addEventListener("mouseleave",function(t){r||(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&!i.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(n=setTimeout(i.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),i.addEventListener("mouseenter",function(t){(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&n&&clearTimeout(n)}),i.querySelectorAll("select"));return s&&s.forEach(function(t){t.addEventListener("click",function(t){r++}),t.addEventListener("blur",function(t){r=0}),t.addEventListener("change",function(t){r=-1})}),i},LGraphCanvas.prototype.createPanel=function(t,e){var h=(e=e||{}).window||window,p=document.createElement("div");return p.className="litegraph dialog",p.innerHTML="
",p.header=p.querySelector(".dialog-header"),e.width&&(p.style.width=e.width+(e.width.constructor===Number?"px":"")),e.height&&(p.style.height=e.height+(e.height.constructor===Number?"px":"")),e.closable&&((e=document.createElement("span")).innerHTML="✕",e.classList.add("close"),e.addEventListener("click",function(){p.close()}),p.header.appendChild(e)),p.title_element=p.querySelector(".dialog-title"),p.title_element.innerText=t,p.content=p.querySelector(".dialog-content"),p.alt_content=p.querySelector(".dialog-alt-content"),p.footer=p.querySelector(".dialog-footer"),p.close=function(){p.onClose&&"function"==typeof p.onClose&&p.onClose(),p.parentNode&&p.parentNode.removeChild(p),this.parentNode&&this.parentNode.removeChild(this)},p.toggleAltContent=function(t){var e;t=void 0!==t?(e=t?"block":"none",t?"none":"block"):(e="block"!=p.alt_content.style.display?"block":"none","block"!=p.alt_content.style.display?"none":"block"),p.alt_content.style.display=e,p.content.style.display=t},p.toggleFooterVisibility=function(t){t=void 0!==t?t?"block":"none":"block"!=p.footer.style.display?"block":"none",p.footer.style.display=t},p.clear=function(){this.content.innerHTML=""},p.addHTML=function(t,e,i){var s=document.createElement("div");return e&&(s.className=e),s.innerHTML=t,(i?p.footer:p.content).appendChild(s),s},p.addButton=function(t,e,i){var s=document.createElement("button");return s.innerText=t,s.options=i,s.classList.add("btn"),s.addEventListener("click",e),p.footer.appendChild(s),s},p.addSeparator=function(){var t=document.createElement("div");t.className="separator",p.content.appendChild(t)},p.addWidget=function(e,t,i,n,s){n=n||{};var o=String(i),r=("number"==(e=e.toLowerCase())&&(o=i.toFixed(3)),document.createElement("div")),a=(r.className="property",r.innerHTML="",r.querySelector(".property_name").innerText=n.label||t,r.querySelector(".property_value"));function u(t,e){n.callback&&n.callback(t,e,n),s&&s(t,e,n)}return a.innerText=o,r.dataset.property=t,r.dataset.type=n.type||e,r.options=n,r.value=i,"code"==e?r.addEventListener("click",function(t){p.inner_showCodePad(this.dataset.property)}):"boolean"==e?(r.classList.add("boolean"),i&&r.classList.add("bool-on"),r.addEventListener("click",function(){var t=this.dataset.property;this.value=!this.value,this.classList.toggle("bool-on"),this.querySelector(".property_value").innerText=this.value?"true":"false",u(t,this.value)})):"string"==e||"number"==e?(a.setAttribute("contenteditable",!0),a.addEventListener("keydown",function(t){"Enter"!=t.code||"string"==e&&t.shiftKey||(t.preventDefault(),this.blur())}),a.addEventListener("blur",function(){var t=this.innerText;u(this.parentNode.dataset.property,t="number"==this.parentNode.dataset.type?Number(t):t)})):"enum"!=e&&"combo"!=e||(o=LGraphCanvas.getPropertyPrintableValue(i,n.values),a.innerText=o,a.addEventListener("click",function(t){var e=n.values||[],s=this.parentNode.dataset.property,o=this;new LiteGraph.ContextMenu(e,{event:t,className:"dark",callback:function(t,e,i){return o.innerText=t,u(s,t),!1}},h)})),p.content.appendChild(r),r},p.onOpen&&"function"==typeof p.onOpen&&p.onOpen(),p},LGraphCanvas.getPropertyPrintableValue=function(t,e){if(!e)return String(t);if(e.constructor===Array)return String(t);if(e.constructor===Object){var i,s="";for(i in e)if(e[i]==t){s=i;break}return String(t)+" ("+s+")"}},LGraphCanvas.prototype.closePanels=function(){var t=document.querySelector("#node-panel");t&&t.close(),(t=document.querySelector("#option-panel"))&&t.close()},LGraphCanvas.prototype.showShowGraphOptionsPanel=function(t,e,i,s){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(e&&e.event&&e.event.target&&e.event.target.lgraphcanvas))return void console.warn("Canvas not found");var o=e.event.target.lgraphcanvas}else o=this;o.closePanels();e=o.getCanvasWindow();function n(t,e,i){i&&i.key&&(t=i.key),i.values&&(e=Object.values(i.values).indexOf(e)),o[t]=e}panel=o.createPanel("Options",{closable:!0,window:e,onOpen:function(){o.OPTIONPANEL_IS_OPEN=!0},onClose:function(){o.OPTIONPANEL_IS_OPEN=!1,o.options_panel=null}}),(o.options_panel=panel).id="option-panel",panel.classList.add("settings"),panel.content.innerHTML="";var r,a=LiteGraph.availableCanvasOptions;for(r in a.sort(),a){var u=a[r];panel.addWidget("boolean",u,o[u],{key:u,on:"True",off:"False"},n)}o.links_render_mode,panel.addWidget("combo","Render mode",LiteGraph.LINK_RENDER_MODES[o.links_render_mode],{key:"links_render_mode",values:LiteGraph.LINK_RENDER_MODES},n),panel.addSeparator(),panel.footer.innerHTML="",o.canvas.parentNode.appendChild(panel)},LGraphCanvas.prototype.showShowNodePanel=function(n){this.SELECTED_NODE=n,this.closePanels();var t=this.getCanvasWindow(),r=this,a=this.createPanel(n.title||"",{closable:!0,window:t,onOpen:function(){r.NODEPANEL_IS_OPEN=!0},onClose:function(){r.NODEPANEL_IS_OPEN=!1,r.node_panel=null}});function o(){a.content.innerHTML="",a.addHTML(""+n.type+""+(n.constructor.desc||"")+""),a.addHTML("

Properties

");function t(t,e){switch(r.graph.beforeChange(n),t){case"Title":n.title=e;break;case"Mode":var i=Object.values(LiteGraph.NODE_MODES).indexOf(e);0<=i&&LiteGraph.NODE_MODES[i]?n.changeMode(i):console.warn("unexpected mode: "+e);break;case"Color":LGraphCanvas.node_colors[e]?(n.color=LGraphCanvas.node_colors[e].color,n.bgcolor=LGraphCanvas.node_colors[e].bgcolor):console.warn("unexpected color: "+e);break;default:n.setProperty(t,e)}r.graph.afterChange(),r.dirty_canvas=!0}a.addWidget("string","Title",n.title,{},t),a.addWidget("combo","Mode",LiteGraph.NODE_MODES[n.mode],{values:LiteGraph.NODE_MODES},t);var e,i="";for(e in void 0!==n.color&&(i=Object.keys(LGraphCanvas.node_colors).filter(function(t){return LGraphCanvas.node_colors[t].color==n.color})),a.addWidget("combo","Color",i,{values:Object.keys(LGraphCanvas.node_colors)},t),n.properties){var s=n.properties[e],o=n.getPropertyInfo(e);o.type;n.onAddPropertyToPanel&&n.onAddPropertyToPanel(e,a)||a.addWidget(o.widget||o.type,e,s,o,t)}a.addSeparator(),n.onShowCustomPanelInfo&&n.onShowCustomPanelInfo(a),a.footer.innerHTML="",a.addButton("Delete",function(){n.block_delete||(n.graph.remove(n),a.close())}).classList.add("delete")}(r.node_panel=a).id="node-panel",a.node=n,a.classList.add("settings"),a.inner_showCodePad=function(e){a.classList.remove("settings"),a.classList.add("centered"),a.alt_content.innerHTML="";function i(){a.toggleAltContent(!1),a.toggleFooterVisibility(!0),s.parentNode.removeChild(s),a.classList.add("settings"),a.classList.remove("centered"),o()}var s=a.alt_content.querySelector("textarea"),t=(s.value=n.properties[e],s.addEventListener("keydown",function(t){"Enter"==t.code&&t.ctrlKey&&(n.setProperty(e,s.value),i())}),a.toggleAltContent(!0),a.toggleFooterVisibility(!1),s.style.height="calc(100% - 40px)",a.addButton("Assign",function(){n.setProperty(e,s.value),i()})),t=(a.alt_content.appendChild(t),a.addButton("Close",i));t.style.float="right",a.alt_content.appendChild(t)},o(),this.canvas.parentNode.appendChild(a)},LGraphCanvas.prototype.showSubgraphPropertiesDialog=function(o){console.log("showing subgraph properties dialog");var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),s=(t&&t.close(),this.createPanel("Subgraph Inputs",{closable:!0,width:500}));function n(){if(s.clear(),o.inputs)for(var t=0;t","subgraph_property")).dataset.name=i.name,e.dataset.slot=t,e.querySelector(".name").innerText=i.name,e.querySelector(".type").innerText=i.type,e.querySelector("button").addEventListener("click",function(t){o.removeInput(Number(this.parentNode.dataset.slot)),n()}))}}s.node=o,s.classList.add("subgraph_dialog");return s.addHTML(" + NameType","subgraph_property extra",!0).querySelector("button").addEventListener("click",function(t){var e=this.parentNode,i=e.querySelector(".name").value,s=e.querySelector(".type").value;i&&-1==o.findInputSlot(i)&&(o.addInput(i,s),e.querySelector(".name").value="",e.querySelector(".type").value="",n())}),n(),this.canvas.parentNode.appendChild(s),s},LGraphCanvas.prototype.showSubgraphPropertiesDialogRight=function(s){var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),o=(t&&t.close(),this.createPanel("Subgraph Outputs",{closable:!0,width:500}));function n(){if(o.clear(),s.outputs)for(var t=0;t","subgraph_property")).dataset.name=i.name,e.dataset.slot=t,e.querySelector(".name").innerText=i.name,e.querySelector(".type").innerText=i.type,e.querySelector("button").addEventListener("click",function(t){s.removeOutput(Number(this.parentNode.dataset.slot)),n()}))}}o.node=s,o.classList.add("subgraph_dialog");t=o.addHTML(" + NameType","subgraph_property extra",!0);function e(){var t=this.parentNode,e=t.querySelector(".name").value,i=t.querySelector(".type").value;e&&-1==s.findOutputSlot(e)&&(s.addOutput(e,i),t.querySelector(".name").value="",t.querySelector(".type").value="",n())}return t.querySelector(".name").addEventListener("keydown",function(t){13==t.keyCode&&e.apply(this)}),t.querySelector("button").addEventListener("click",function(t){e.apply(this)}),n(),this.canvas.parentNode.appendChild(o),o},LGraphCanvas.prototype.checkPanels=function(){if(this.canvas)for(var t=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),e=0;eNo color"}),LGraphCanvas.node_colors){var a=LGraphCanvas.node_colors[o],t={value:o,content:""+o+""};r.push(t)}return new LiteGraph.ContextMenu(r,{event:i,callback:function(t){if(n){var e=t.value?LGraphCanvas.node_colors[t.value]:null,i=function(t){e?t.constructor===LiteGraph.LGraphGroup?t.color=e.groupcolor:(t.color=e.color,t.bgcolor=e.bgcolor):(delete t.color,delete t.bgcolor)},s=LGraphCanvas.active_canvas;if(!s.selected_nodes||Object.keys(s.selected_nodes).length<=1)i(n);else for(var o in s.selected_nodes)i(s.selected_nodes[o]);n.setDirtyCanvas(!0,!0)}},parentMenu:s,node:n}),!1},LGraphCanvas.onMenuNodeShapes=function(t,e,i,s,o){if(o)return new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES,{event:i,callback:function(e){if(o){o.graph.beforeChange();var t=function(t){t.shape=e},i=LGraphCanvas.active_canvas;if(!i.selected_nodes||Object.keys(i.selected_nodes).length<=1)t(o);else for(var s in i.selected_nodes)t(i.selected_nodes[s]);o.graph.afterChange(),o.setDirtyCanvas(!0)}},parentMenu:s,node:o}),!1;throw"no node passed"},LGraphCanvas.onMenuNodeRemove=function(t,e,i,s,o){if(!o)throw"no node passed";function n(t){!1!==t.removable&&r.remove(t)}var r=o.graph,a=(r.beforeChange(),LGraphCanvas.active_canvas);if(!a.selected_nodes||Object.keys(a.selected_nodes).length<=1)n(o);else for(var u in a.selected_nodes)n(a.selected_nodes[u]);r.afterChange(),o.setDirtyCanvas(!0,!0)},LGraphCanvas.onMenuNodeToSubgraph=function(t,e,i,s,o){var n,r,a=o.graph,u=LGraphCanvas.active_canvas;u&&((n=Object.values(u.selected_nodes||{})).length||(n=[o]),(r=LiteGraph.createNode("graph/subgraph")).pos=o.pos.concat(),a.add(r),r.buildFromNodes(n),u.deselectAllNodes(),o.setDirtyCanvas(!0,!0))},LGraphCanvas.onMenuNodeClone=function(t,e,i,s,o){o.graph.beforeChange();function n(t){var e;!1!==t.clonable&&(e=t.clone())&&(e.pos=[t.pos[0]+5,t.pos[1]+5],t.graph.add(e),r[e.id]=e)}var r={},a=LGraphCanvas.active_canvas;if(!a.selected_nodes||Object.keys(a.selected_nodes).length<=1)n(o);else for(var u in a.selected_nodes)n(a.selected_nodes[u]);Object.keys(r).length&&a.selectNodes(r),o.graph.afterChange(),o.setDirtyCanvas(!0,!0)},LGraphCanvas.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"},cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}},LGraphCanvas.prototype.getCanvasMenuOptions=function(){var t,e=null;return this.getMenuOptions?e=this.getMenuOptions():(e=[{content:"Add Node",has_submenu:!0,callback:LGraphCanvas.onMenuAdd},{content:"Add Group",callback:LGraphCanvas.onGroupAdd}],1Name",e),(r=n.querySelector("input"))&&o&&(r.value=o.label||""),a=function(){u.graph.beforeChange(),r.value&&(o&&(o.label=r.value),h.setDirty(!0)),n.close(),u.graph.afterChange()},n.querySelector("button").addEventListener("click",a),r.addEventListener("keydown",function(t){if(n.is_modified=!0,27==t.keyCode)n.close();else if(13==t.keyCode)a();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}),r.focus()))},extra:u},n=(u&&(o.title=u.type),null);u&&(n=u.getSlotInPosition(t.canvasX,t.canvasY),LGraphCanvas.active_node=u),n?(s=[],u.getSlotMenuOptions?s=u.getSlotMenuOptions(n):(n&&n.output&&n.output.links&&n.output.links.length&&s.push({content:"Disconnect Links",slot:n}),(e=n.input||n.output).removable&&s.push(e.locked?"Cannot remove":{content:"Remove Slot",slot:n}),e.nameLocked||s.push({content:"Rename Slot",slot:n})),o.title=(n.input||n.output).type||"*",n.input&&n.input.type==LiteGraph.ACTION&&(o.title="Action"),n.output&&n.output.type==LiteGraph.EVENT&&(o.title="Event")):u?s=this.getNodeMenuOptions(u):(s=this.getCanvasMenuOptions(),(e=this.graph.getGroupOnPos(t.canvasX,t.canvasY))&&s.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:e,options:this.getGroupMenuOptions(e)}})),s&&new LiteGraph.ContextMenu(s,o,i)},LiteGraph.compareObjects=compareObjects,LiteGraph.distance=distance,LiteGraph.colorToString=colorToString,LiteGraph.isInsideRectangle=isInsideRectangle,LiteGraph.growBounding=growBounding,LiteGraph.isInsideBounding=isInsideBounding,LiteGraph.overlapBounding=overlapBounding,LiteGraph.hex2num=hex2num,LiteGraph.num2hex=num2hex,ContextMenu.prototype.addItem=function(t,e,s){var o=this,i=(s=s||{},document.createElement("div")),n=!(i.className="litemenu-entry submenu");function r(t){var e=this.value,i=!0;if((o.current_submenu&&o.current_submenu.close(t),s.callback&&!0===s.callback.call(this,e,s,t,o,s.node)&&(i=!1),e)&&(e.callback&&!s.ignore_item_callbacks&&!0!==e.disabled&&!0===e.callback.call(this,e,s,t,o,s.extra)&&(i=!1),e.submenu)){if(!e.submenu.options)throw"ContextMenu submenu needs options";new o.constructor(e.submenu.options,{callback:e.submenu.callback,event:t,parentMenu:o,ignore_item_callbacks:e.submenu.ignore_item_callbacks,title:e.submenu.title,extra:e.submenu.extra,autoopen:s.autoopen});i=!1}i&&!o.lock&&o.close()}return null===e?i.classList.add("separator"):(i.innerHTML=e&&e.title?e.title:t,(i.value=e)&&(e.disabled&&(n=!0,i.classList.add("disabled")),e.submenu||e.has_submenu)&&i.classList.add("has_submenu"),"function"==typeof e?(i.dataset.value=t,i.onclick_callback=e):i.dataset.value=e,e.className&&(i.className+=" "+e.className)),this.root.appendChild(i),n||i.addEventListener("click",r),!n&&s.autoopen&&LiteGraph.pointerListenerAdd(i,"enter",function(t){var e=this.value;e&&e.has_submenu&&r.call(this,t)}),i},ContextMenu.prototype.close=function(t,e){this.root.parentNode&&this.root.parentNode.removeChild(this.root),this.parentMenu&&!e&&(this.parentMenu.lock=!1,this.parentMenu.current_submenu=null,void 0===t?this.parentMenu.close():t&&!ContextMenu.isCursorOverElement(t,this.parentMenu.root)&&ContextMenu.trigger(this.parentMenu.root,LiteGraph.pointerevents_method+"leave",t)),this.current_submenu&&this.current_submenu.close(t,!0),this.root.closing_timer&&clearTimeout(this.root.closing_timer)},ContextMenu.trigger=function(t,e,i,s){var o=document.createEvent("CustomEvent");return o.initCustomEvent(e,!0,!0,i),o.srcElement=s,t.dispatchEvent?t.dispatchEvent(o):t.__events&&t.__events.dispatchEvent(o),o},ContextMenu.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this},ContextMenu.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event},ContextMenu.isCursorOverElement=function(t,e){var i=t.clientX,t=t.clientY,e=e.getBoundingClientRect();return!!e&&t>e.top&&te.left&&ithis.size[0]+10||t[1]<-10||t[1]>this.size[1]+10)?(r.splice(i,1),this.selected=-1):(n[0]=e?0==i?0:1:clamp(s,0,1),n[1]=1-clamp(o,0,1),r.sort(function(t,e){return t[0]-e[0]}),this.selected=r.indexOf(n),this.must_update=!0)))},CurveEditor.prototype.onMouseUp=function(t,e){return!(this.selected=-1)},CurveEditor.prototype.getCloserPoint=function(t,e){var i=this.points;if(!i)return-1;e=e||30;for(var s=this.size[0]-2*this.margin,o=this.size[1]-2*this.margin,n=i.length,r=[0,0],a=1e6,u=-1,h=0;h{var d=t.LiteGraph;function e(){this.addOutput("in ms","number"),this.addOutput("in sec","number")}function i(){this.size=[140,80],this.properties={enabled:!0},this.enabled=!0,this.subgraph=new d.LGraph,(this.subgraph._subgraph_node=this).subgraph._is_subgraph=!0,this.subgraph.onTrigger=this.onSubgraphTrigger.bind(this),this.subgraph.onInputAdded=this.onSubgraphNewInput.bind(this),this.subgraph.onInputRenamed=this.onSubgraphRenamedInput.bind(this),this.subgraph.onInputTypeChanged=this.onSubgraphTypeChangeInput.bind(this),this.subgraph.onInputRemoved=this.onSubgraphRemovedInput.bind(this),this.subgraph.onOutputAdded=this.onSubgraphNewOutput.bind(this),this.subgraph.onOutputRenamed=this.onSubgraphRenamedOutput.bind(this),this.subgraph.onOutputTypeChanged=this.onSubgraphTypeChangeOutput.bind(this),this.subgraph.onOutputRemoved=this.onSubgraphRemovedOutput.bind(this)}function s(){this.addOutput("","number"),this.name_in_graph="",this.properties={name:"",type:"number",value:0};var e=this;this.name_widget=this.addWidget("text","Name",this.properties.name,function(t){t&&e.setProperty("name",t)}),this.type_widget=this.addWidget("text","Type",this.properties.type,function(t){e.setProperty("type",t)}),this.value_widget=this.addWidget("number","Value",this.properties.value,function(t){e.setProperty("value",t)}),this.widgets_up=!0,this.size=[180,90]}function o(){this.addInput("",""),this.name_in_graph="",this.properties={name:"",type:""};this.name_widget=this.addWidget("text","Name",this.properties.name,"name"),this.type_widget=this.addWidget("text","Type",this.properties.type,"type"),this.widgets_up=!0,this.size=[180,60]}function n(){this.addOutput("value","number"),this.addProperty("value",1),this.widget=this.addWidget("number","value",1,"value"),this.widgets_up=!0,this.size=[180,30]}function r(){this.addOutput("bool","boolean"),this.addProperty("value",!0),this.widget=this.addWidget("toggle","value",!0,"value"),this.serialize_widgets=!0,this.widgets_up=!0,this.size=[140,30]}function a(){this.addOutput("string","string"),this.addProperty("value",""),this.widget=this.addWidget("text","value","","value"),this.widgets_up=!0,this.size=[180,30]}function u(){this.addOutput("obj","object"),this.size=[120,30],this._object={}}function h(){this.addInput("url","string"),this.addOutput("file","string"),this.addProperty("url",""),this.addProperty("type","text"),this.widget=this.addWidget("text","url","","url"),this._data=null}function p(){this.addInput("parse",d.ACTION),this.addInput("json","string"),this.addOutput("done",d.EVENT),this.addOutput("object","object"),this.widget=this.addWidget("button","parse","",this.parse.bind(this)),this._str=null,this._obj=null}function l(){this.addOutput("data","object"),this.addProperty("value",""),this.widget=this.addWidget("text","json","","value"),this.widgets_up=!0,this.size=[140,30],this._value=null}function c(){this._value=[],this.addInput("json",""),this.addOutput("arrayOut","array"),this.addOutput("length","number"),this.addProperty("value","[]"),this.widget=this.addWidget("text","array",this.properties.value,"value"),this.widgets_up=!0,this.size=[140,50]}function _(){this.addInput("arr","array"),this.addInput("value",""),this.addOutput("arr","array"),this.properties={index:0},this.widget=this.addWidget("number","i",this.properties.index,"index",{precision:0,step:10,min:0})}function g(){this.addInput("array","array,table,string"),this.addInput("index","number"),this.addOutput("value",""),this.addProperty("index",0)}function f(){this.addInput("table","table"),this.addInput("row","number"),this.addInput("col","number"),this.addOutput("value",""),this.addProperty("row",0),this.addProperty("column",0)}function v(){this.addInput("obj","object"),this.addOutput("property",0),this.addProperty("value",0),this.widget=this.addWidget("text","prop.","",this.setValue.bind(this)),this.widgets_up=!0,this.size=[140,30],this._value=null}function m(){this.addInput("obj",""),this.addOutput("keys","array"),this.size=[140,30]}function y(){this.addInput("obj",""),this.addInput("value",""),this.addOutput("obj",""),this.properties={property:""},this.name_widget=this.addWidget("text","prop.",this.properties.property,"property")}function x(){this.addInput("A","object"),this.addInput("B","object"),this.addOutput("out","object"),this._result={};var t=this;this.addWidget("button","clear","",function(){t._result={}}),this.size=this.computeSize()}function b(){this.size=[60,30],this.addInput("in"),this.addOutput("out"),this.properties={varname:"myname",container:b.LITEGRAPH},this.value=null}function T(){this.size=[60,30],this.addInput("data",0),this.addInput("download",d.ACTION),this.properties={filename:"data.json"},this.value=null;var e=this;this.addWidget("button","Download","",function(t){e.value&&e.downloadAsFile()})}function E(){this.size=[60,30],this.addInput("value",0,{label:""}),this.value=0}function w(){this.addInput("in",0),this.addOutput("out",0),this.size=[40,30]}function L(){this.mode=d.ON_EVENT,this.size=[80,30],this.addProperty("msg",""),this.addInput("log",d.EVENT),this.addInput("msg",0)}function O(){this.mode=d.ON_EVENT,this.addProperty("msg",""),this.addInput("",d.EVENT);this.widget=this.addWidget("text","Text","","msg"),this.widgets_up=!0,this.size=[200,30]}function I(){this.size=[60,30],this.addProperty("onExecute","return A;"),this.addInput("A",0),this.addInput("B",0),this.addOutput("out",0),this._func=null,this.data={}}function C(){this.addInput("A",0),this.addInput("B",0),this.addOutput("true","boolean"),this.addOutput("false","boolean"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP","==","enum",{values:C.values}),this.addWidget("combo","Op.",this.properties.OP,{property:"OP",values:C.values}),this.size=[80,60]}e.title="Time",e.desc="Time",e.prototype.onExecute=function(){this.setOutputData(0,1e3*this.graph.globaltime),this.setOutputData(1,this.graph.globaltime)},d.registerNodeType("basic/time",e),i.title="Subgraph",i.desc="Graph inside a node",i.title_color="#334",i.prototype.onGetInputs=function(){return[["enabled","boolean"]]},i.prototype.onDblClick=function(t,e,i){var s=this;setTimeout(function(){i.openSubgraph(s.subgraph)},10)},i.prototype.onAction=function(t,e){this.subgraph.onAction(t,e)},i.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled"),this.enabled){if(this.inputs)for(var t=0;ts&&(e[0]e.linkIDs[t]))}for(var l of t.nodes)"graph/subgraph"===l.type&&(l=reassignGraphUUIDs(l.subgraph),e.nodeIDs.assign(l.nodeIDs),e.linkIDs.assign(l.linkIDs))},i.prototype.clone=function(){var t,e=d.createNode(this.type),i=this.serialize();return d.use_uuids&&(t=d.cloneObject(i.subgraph),this.reassignSubgraphUUIDs(t),i.subgraph=t),delete i.id,delete i.inputs,delete i.outputs,e.configure(i),e},i.prototype.buildFromNodes=function(t){for(var e={},i=0,s=0;s{var s=t.LiteGraph;function e(){this.size=[60,30],this.addInput("event",s.ACTION)}function i(){this.size=[60,30],this.addInput("if",""),this.addOutput("true",s.EVENT),this.addOutput("change",s.EVENT),this.addOutput("false",s.EVENT),this.properties={only_on_change:!0},this.prev=0}function o(){var t=this;this.addInput("",s.ACTION),this.addInput("",s.ACTION),this.addInput("",s.ACTION),this.addOutput("",s.EVENT),this.addOutput("",s.EVENT),this.addOutput("",s.EVENT),this.addWidget("button","+",null,function(){t.addInput("",s.ACTION),t.addOutput("",s.EVENT)}),this.size=[90,70],this.flags={horizontal:!0,render_box:!1}}function n(){var t=this;this.addInput("",s.ACTION),this.addInput("",s.ACTION),this.addOutput("",s.EVENT),this.addWidget("button","+",null,function(){t.addInput("",s.ACTION),t.size[0]=90}),this.size=[90,70],this.ready=[]}function r(){var t=this;this.properties={index:0},this.addInput("index","number"),this.addInput("step",s.ACTION),this.addInput("reset",s.ACTION),this.addOutput("index","number"),this.addOutput("",s.EVENT),this.addOutput("",s.EVENT),this.addOutput("",s.EVENT,{removable:!0}),this.addWidget("button","+",null,function(){t.addOutput("",s.EVENT,{removable:!0})}),this.size=[120,120],this.flags={render_box:!1}}function a(){this.size=[60,30],this.addInput("event",s.ACTION),this.addOutput("event",s.EVENT),this.properties={equal_to:"",has_property:"",property_equal_to:""}}function u(){this.addInput("in",s.ACTION),this.addInput("cond","boolean"),this.addOutput("true",s.EVENT),this.addOutput("false",s.EVENT),this.size=[120,60],this._value=!1}function h(){this.addInput("inc",s.ACTION),this.addInput("dec",s.ACTION),this.addInput("reset",s.ACTION),this.addOutput("change",s.EVENT),this.addOutput("num","number"),this.addProperty("doCountExecution",!1,"boolean",{name:"Count Executions"}),this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"),this.num=0}function p(){this.size=[60,30],this.addProperty("time_in_ms",1e3),this.addInput("event",s.ACTION),this.addOutput("on_time",s.EVENT),this._pending=[]}function l(){this.addProperty("interval",1e3),this.addProperty("event","tick"),this.addOutput("on_tick",s.EVENT),this.time=0,this.last_interval=1e3,this.triggered=!1}function d(){this.addInput("go",s.ACTION),this.addInput("green",s.ACTION),this.addInput("red",s.ACTION),this.addOutput("continue",s.EVENT),this.addOutput("blocked",s.EVENT),this.addOutput("is_green","boolean"),this._ready=!1,this.properties={};var t=this;this.addWidget("button","reset","",function(){t._ready=!1})}function c(){this.addInput("in",s.ACTION),this.addInput("reset",s.ACTION),this.addOutput("out",s.EVENT),this._once=!1,this.properties={};var t=this;this.addWidget("button","reset","",function(){t._once=!1})}function _(){this.addInput("data",0),this.addInput("assign",s.ACTION),this.addOutput("data",0),this._last_value=null,this.properties={data:null,serialize:!0};var t=this;this.addWidget("button","store","",function(){t.properties.data=t._last_value})}e.title="Log Event",e.desc="Log event in console",e.prototype.onAction=function(t,e,i){console.log(t,e)},s.registerNodeType("events/log",e),i.title="TriggerEvent",i.desc="Triggers event if input evaluates to true",i.prototype.onExecute=function(t,e){var i=this.getInputData(0),s=i!=this.prev,o=(s=0===this.prev?!1:s)&&this.properties.only_on_change||!s&&!this.properties.only_on_change;i&&o&&this.triggerSlot(0,t,null,e),!i&&o&&this.triggerSlot(2,t,null,e),s&&this.triggerSlot(1,t,null,e),this.prev=i},s.registerNodeType("events/trigger",i),o.title="Sequence",o.desc="Triggers a sequence of events when an event arrives",o.prototype.getTitle=function(){return""},o.prototype.onAction=function(t,e,i){if(this.outputs){i=i||{};for(var s=0;s{var i=t.LiteGraph;function s(){this.addOutput("",i.EVENT),this.addOutput("","boolean"),this.addProperty("text","click me"),this.addProperty("font_size",30),this.addProperty("message",""),this.size=[164,84],this.clicked=!1}function e(){this.addInput("","boolean"),this.addInput("e",i.ACTION),this.addOutput("v","boolean"),this.addOutput("e",i.EVENT),this.properties={font:"",value:!1},this.size=[160,44]}function o(){this.addOutput("","number"),this.size=[80,60],this.properties={min:-1e3,max:1e3,value:1,step:1},this.old_y=-1,this._remainder=0,this._precision=0,this.mouse_captured=!1}function n(){this.addOutput("","string"),this.addOutput("change",i.EVENT),this.size=[80,60],this.properties={value:"A",values:"A;B;C"},this.old_y=-1,this.mouse_captured=!1,this._values=this.properties.values.split(";");var e=this;this.widgets_up=!0,this.widget=this.addWidget("combo","",this.properties.value,function(t){e.properties.value=t,e.triggerSlot(1,t)},{property:"value",values:this._values})}function r(){this.addOutput("","number"),this.size=[64,84],this.properties={min:0,max:1,value:.5,color:"#7AF",precision:2},this.value=-1}function a(){this.addOutput("","number"),this.properties={value:.5,min:0,max:1,text:"V"};var e=this;this.size=[140,40],this.slider=this.addWidget("slider","V",this.properties.value,function(t){e.properties.value=t},this.properties),this.widgets_up=!0}function u(){this.size=[160,26],this.addOutput("","number"),this.properties={color:"#7AF",min:0,max:1,value:.5},this.value=-1}function h(){this.size=[160,26],this.addInput("","number"),this.properties={min:0,max:1,value:0,color:"#AAF"}}function p(){this.addInputs("",0),this.properties={value:"...",font:"Arial",fontsize:18,color:"#AAA",align:"left",glowSize:0,decimals:1}}function l(){this.size=[200,100],this.properties={borderColor:"#ffffff",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",shadowSize:2,borderRadius:3}}s.title="Button",s.desc="Triggers an event",s.font="Arial",s.prototype.onDrawForeground=function(t){var e;!this.flags.collapsed&&(t.fillStyle="black",t.fillRect(11,11,this.size[0]-20,this.size[1]-20),t.fillStyle="#AAF",t.fillRect(9,9,this.size[0]-20,this.size[1]-20),t.fillStyle=this.clicked?"white":this.mouseOver?"#668":"#334",t.fillRect(10,10,this.size[0]-20,this.size[1]-20),this.properties.text||0===this.properties.text)&&(e=this.properties.font_size||30,t.textAlign="center",t.fillStyle=this.clicked?"black":"white",t.font=e+"px "+s.font,t.fillText(this.properties.text,.5*this.size[0],.5*this.size[1]+.3*e),t.textAlign="left")},s.prototype.onMouseDown=function(t,e){if(1.5*this.size[1]?-1:1,this.properties.value=clamp(this.properties.value+t*this.properties.step,this.properties.min,this.properties.max),this.graph._version++,this.setDirtyCanvas(!0)),this.mouse_captured&&(this.mouse_captured=!1,this.captureInput(!1))},i.registerNodeType("widget/number",o),n.title="Combo",n.desc="Widget to select from a list",n.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)},n.prototype.onPropertyChanged=function(t,e){"values"==t?(this._values=e.split(";"),this.widget.options.values=this._values):"value"==t&&(this.widget.value=e)},i.registerNodeType("widget/combo",n),r.title="Knob",r.desc="Circular controller",r.size=[80,100],r.prototype.onDrawForeground=function(t){var e,i,s,o;this.flags.collapsed||(-1==this.value&&(this.value=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min)),e=.5*this.size[0],i=.5*this.size[1],s=.5*Math.min(this.size[0],this.size[1])-5,Math.floor(.05*s),t.globalAlpha=1,t.save(),t.translate(e,i),t.rotate(.75*Math.PI),t.fillStyle="rgba(0,0,0,0.5)",t.beginPath(),t.moveTo(0,0),t.arc(0,0,s,0,1.5*Math.PI),t.fill(),t.strokeStyle="black",t.fillStyle=this.properties.color,t.lineWidth=2,t.beginPath(),t.moveTo(0,0),t.arc(0,0,s-4,0,1.5*Math.PI*Math.max(.01,this.value)),t.closePath(),t.fill(),t.lineWidth=1,t.globalAlpha=1,t.restore(),t.fillStyle="black",t.beginPath(),t.arc(e,i,.75*s,0,2*Math.PI,!0),t.fill(),t.fillStyle=this.mouseOver?"white":this.properties.color,t.beginPath(),o=this.value*Math.PI*1.5+.75*Math.PI,t.arc(e+Math.cos(o)*s*.65,i+Math.sin(o)*s*.65,.05*s,0,2*Math.PI,!0),t.fill(),t.fillStyle=this.mouseOver?"white":"#AAA",t.font=Math.floor(.5*s)+"px Arial",t.textAlign="center",t.fillText(this.properties.value.toFixed(this.properties.precision),e,i+.15*s))},r.prototype.onExecute=function(){this.setOutputData(0,this.properties.value),this.boxcolor=i.colorToString([this.value,this.value,this.value])},r.prototype.onMouseDown=function(t){return this.center=[.5*this.size[0],.5*this.size[1]+20],this.radius=.5*this.size[0],!(t.canvasY-this.pos[1]<20||i.distance([t.canvasX,t.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius||(this.oldmouse=[t.canvasX-this.pos[0],t.canvasY-this.pos[1]],this.captureInput(!0),0))},r.prototype.onMouseMove=function(t){var e;this.oldmouse&&(t=[t.canvasX-this.pos[0],t.canvasY-this.pos[1]],e=this.value,1<(e-=.01*(t[1]-this.oldmouse[1]))?e=1:e<0&&(e=0),this.value=e,this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value,this.oldmouse=t,this.setDirtyCanvas(!0))},r.prototype.onMouseUp=function(t){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))},r.prototype.onPropertyChanged=function(t,e){if("min"==t||"max"==t||"value"==t)return this.properties[t]=parseFloat(e),!0},i.registerNodeType("widget/knob",r),a.title="Inner Slider",a.prototype.onPropertyChanged=function(t,e){"value"==t&&(this.slider.value=e)},a.prototype.onExecute=function(){this.setOutputData(0,this.properties.value)},i.registerNodeType("widget/internal_slider",a),u.title="H.Slider",u.desc="Linear slider controller",u.prototype.onDrawForeground=function(t){-1==this.value&&(this.value=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min)),t.globalAlpha=1,t.lineWidth=1,t.fillStyle="#000",t.fillRect(2,2,this.size[0]-4,this.size[1]-4),t.fillStyle=this.properties.color,t.beginPath(),t.rect(4,4,(this.size[0]-8)*this.value,this.size[1]-8),t.fill()},u.prototype.onExecute=function(){this.properties.value=this.properties.min+(this.properties.max-this.properties.min)*this.value,this.setOutputData(0,this.properties.value),this.boxcolor=i.colorToString([this.value,this.value,this.value])},u.prototype.onMouseDown=function(t){return!(t.canvasY-this.pos[1]<0||(this.oldmouse=[t.canvasX-this.pos[0],t.canvasY-this.pos[1]],this.captureInput(!0),0))},u.prototype.onMouseMove=function(t){var e;this.oldmouse&&(t=[t.canvasX-this.pos[0],t.canvasY-this.pos[1]],e=this.value,1<(e+=(t[0]-this.oldmouse[0])/this.size[0])?e=1:e<0&&(e=0),this.value=e,this.oldmouse=t,this.setDirtyCanvas(!0))},u.prototype.onMouseUp=function(t){this.oldmouse=null,this.captureInput(!1)},u.prototype.onMouseLeave=function(t){},i.registerNodeType("widget/hslider",u),h.title="Progress",h.desc="Shows data in linear progress",h.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.properties.value=t)},h.prototype.onDrawForeground=function(t){t.lineWidth=1,t.fillStyle=this.properties.color;var e=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),e=Math.min(1,e);e=Math.max(0,e),t.fillRect(2,2,(this.size[0]-4)*e,this.size[1]-4)},i.registerNodeType("widget/progress",h),p.title="Text",p.desc="Shows the input value",p.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}],p.prototype.onDrawForeground=function(t){t.fillStyle=this.properties.color;var e=this.properties.value,i=(this.properties.glowSize?(t.shadowColor=this.properties.color,t.shadowOffsetX=0,t.shadowOffsetY=0,t.shadowBlur=this.properties.glowSize):t.shadowColor="transparent",this.properties.fontsize);if(t.textAlign=this.properties.align,t.font=i.toString()+"px "+this.properties.font,this.str="number"==typeof e?e.toFixed(this.properties.decimals):e,"string"==typeof this.str)for(var s=this.str.replace(/[\r\n]/g,"\\n").split("\\n"),o=0;o{var e=t.LiteGraph;function r(){this.addOutput("left_x_axis","number"),this.addOutput("left_y_axis","number"),this.addOutput("button_pressed",e.EVENT),this.properties={gamepad_index:0,threshold:.1},this._left_axis=new Float32Array(2),this._right_axis=new Float32Array(2),this._triggers=new Float32Array(2),this._previous_buttons=new Uint8Array(17),this._current_buttons=new Uint8Array(17)}r.title="Gamepad",r.desc="gets the input of the gamepad",r.CENTER=0,r.LEFT=1,r.RIGHT=2,r.UP=4,r.DOWN=8,r.zero=new Float32Array(2),r.buttons=["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"],r.prototype.onExecute=function(){var t=this.getGamepad(),e=this.properties.threshold||0;if(t&&(this._left_axis[0]=Math.abs(t.xbox.axes.lx)>e?t.xbox.axes.lx:0,this._left_axis[1]=Math.abs(t.xbox.axes.ly)>e?t.xbox.axes.ly:0,this._right_axis[0]=Math.abs(t.xbox.axes.rx)>e?t.xbox.axes.rx:0,this._right_axis[1]=Math.abs(t.xbox.axes.ry)>e?t.xbox.axes.ry:0,this._triggers[0]=Math.abs(t.xbox.axes.ltrigger)>e?t.xbox.axes.ltrigger:0,this._triggers[1]=Math.abs(t.xbox.axes.rtrigger)>e?t.xbox.axes.rtrigger:0),this.outputs)for(var i=0;i{var s=t.LiteGraph;function e(){this.addInput("in",0),this.addOutput("out",0),this.size=[80,30]}function i(){this.addInput("in"),this.addOutput("out"),this.size=[80,30]}function o(){this.addInput("in"),this.addOutput("out")}function n(){this.addInput("in","number",{locked:!0}),this.addOutput("out","number",{locked:!0}),this.addOutput("clamped","number",{locked:!0}),this.addProperty("in",0),this.addProperty("in_min",0),this.addProperty("in_max",1),this.addProperty("out_min",0),this.addProperty("out_max",1),this.size=[120,50]}function r(){this.addOutput("value","number"),this.addProperty("min",0),this.addProperty("max",1),this.size=[80,30]}function h(){this.addInput("in","number"),this.addOutput("out","number"),this.addProperty("min",0),this.addProperty("max",1),this.addProperty("smooth",!0),this.addProperty("seed",0),this.addProperty("octaves",1),this.addProperty("persistence",.8),this.addProperty("speed",1),this.size=[90,30]}function a(){this.addOutput("out","number"),this.addProperty("min_time",1),this.addProperty("max_time",2),this.addProperty("duration",.2),this.size=[90,30],this._remaining_time=0,this._blink_time=0}function u(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.addProperty("min",0),this.addProperty("max",1)}function p(){this.properties={f:.5},this.addInput("A","number"),this.addInput("B","number"),this.addOutput("out","number")}function l(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function d(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function c(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function _(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.properties={A:0,B:1}}function g(){this.addInput("in","number",{label:""}),this.addOutput("out","number",{label:""}),this.size=[80,30],this.addProperty("factor",1)}function f(){this.addInput("v","boolean"),this.addInput("A"),this.addInput("B"),this.addOutput("out")}function v(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.addProperty("samples",10),this._values=new Float32Array(10),this._current=0}function m(){this.addInput("in","number"),this.addOutput("out","number"),this.addProperty("factor",.1),this.size=[80,30],this._value=null}function y(){this.addInput("A","number,array,object"),this.addInput("B","number"),this.addOutput("=","number"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP","+","enum",{values:y.values}),this._func=y.funcs[this.properties.OP],this._result=[]}function x(){this.addInput("A","number"),this.addInput("B","number"),this.addOutput("A==B","boolean"),this.addOutput("A!=B","boolean"),this.addProperty("A",0),this.addProperty("B",0)}function b(){this.addInput("A","number"),this.addInput("B","number"),this.addOutput("true","boolean"),this.addOutput("false","boolean"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP",">","enum",{values:b.values}),this.addWidget("combo","Cond.",this.properties.OP,{property:"OP",values:b.values}),this.size=[80,60]}function T(){this.addInput("in",0),this.addInput("cond","boolean"),this.addOutput("true",0),this.addOutput("false",0),this.size=[80,60]}function E(){this.addInput("inc","number"),this.addOutput("total","number"),this.addProperty("increment",1),this.addProperty("value",0)}function w(){this.addInput("v","number"),this.addOutput("sin","number"),this.addProperty("amplitude",1),this.addProperty("offset",0),this.bgImageUrl="nodes/imgs/icon-sin.png"}function L(){this.addInput("x","number"),this.addInput("y","number"),this.addOutput("","number"),this.properties={x:1,y:1,formula:"x+y"},this.code_widget=this.addWidget("text","F(x,y)",this.properties.formula,function(t,e,i){i.properties.formula=t}),this.addWidget("toggle","allow",s.allow_scripts,function(t){s.allow_scripts=t}),this._func=null}function O(){this.addInput("vec2","vec2"),this.addOutput("x","number"),this.addOutput("y","number")}function I(){this.addInputs([["x","number"],["y","number"]]),this.addOutput("vec2","vec2"),this.properties={x:0,y:0},this._data=new Float32Array(2)}function C(){this.addInput("vec3","vec3"),this.addOutput("x","number"),this.addOutput("y","number"),this.addOutput("z","number")}function D(){this.addInputs([["x","number"],["y","number"],["z","number"]]),this.addOutput("vec3","vec3"),this.properties={x:0,y:0,z:0},this._data=new Float32Array(3)}function N(){this.addInput("vec4","vec4"),this.addOutput("x","number"),this.addOutput("y","number"),this.addOutput("z","number"),this.addOutput("w","number")}function G(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]),this.addOutput("vec4","vec4"),this.properties={x:0,y:0,z:0,w:0},this._data=new Float32Array(4)}e.title="Converter",e.desc="type A to type B",e.prototype.onExecute=function(){var t=this.getInputData(0);if(null!=t&&this.outputs)for(var e=0;ee&&(this._current=0),0),s=0;sB":o=e=B":o=e<=t}this.setOutputData(i,o)}}},x.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]},s.registerNodeType("math/compare",x),s.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"}),s.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"}),s.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]],title:"A>B"}),s.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"}),s.registerSearchboxExtra("math/compare","<=",{outputs:[["A<=B","boolean"]],title:"A<=B"}),b["@OP"]={type:"enum",title:"operation",values:b.values=[">","<","==","!=","<=",">=","||","&&"]},b.title="Condition",b.desc="evaluates condition between A and B",b.prototype.getTitle=function(){return"A "+this.properties.OP+" B"},b.prototype.onExecute=function(){var t=this.getInputData(0),e=(void 0===t?t=this.properties.A:this.properties.A=t,this.getInputData(1)),i=(void 0===e?e=this.properties.B:this.properties.B=e,!0);switch(this.properties.OP){case">":i=e=":i=e<=t;break;case"||":i=t||e;break;case"&&":i=t&&e}this.setOutputData(0,i),this.setOutputData(1,!i)},s.registerNodeType("math/condition",b),T.title="Branch",T.desc="If condition is true, outputs IN in true, otherwise in false",T.prototype.onExecute=function(){var t=this.getInputData(0);this.getInputData(1)?(this.setOutputData(0,t),this.setOutputData(1,null)):(this.setOutputData(0,null),this.setOutputData(1,t))},s.registerNodeType("math/branch",T),E.title="Accumulate",E.desc="Increments a value every time",E.prototype.onExecute=function(){null===this.properties.value&&(this.properties.value=0);var t=this.getInputData(0);this.properties.value+=null!==t?t:this.properties.increment,this.setOutputData(0,this.properties.value)},s.registerNodeType("math/accumulate",E),w.title="Trigonometry",w.desc="Sin Cos Tan",w.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=0),this.properties.amplitude),i=this.findInputSlot("amplitude"),s=(-1!=i&&(e=this.getInputData(i)),this.properties.offset);-1!=(i=this.findInputSlot("offset"))&&(s=this.getInputData(i));for(var o,n=0,r=this.outputs.length;nXY",O.desc="vector 2 to components",O.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]))},s.registerNodeType("math3d/vec2-to-xy",O),I.title="XY->Vec2",I.desc="components to vector2",I.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this._data);i[0]=t,i[1]=e,this.setOutputData(0,i)},s.registerNodeType("math3d/xy-to-vec2",I),C.title="Vec3->XYZ",C.desc="vector 3 to components",C.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]),this.setOutputData(2,t[2]))},s.registerNodeType("math3d/vec3-to-xyz",C),D.title="XYZ->Vec3",D.desc="components to vector3",D.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this.getInputData(2)),s=(null==i&&(i=this.properties.z),this._data);s[0]=t,s[1]=e,s[2]=i,this.setOutputData(0,s)},s.registerNodeType("math3d/xyz-to-vec3",D),N.title="Vec4->XYZW",N.desc="vector 4 to components",N.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]),this.setOutputData(2,t[2]),this.setOutputData(3,t[3]))},s.registerNodeType("math3d/vec4-to-xyzw",N),G.title="XYZW->Vec4",G.desc="components to vector4",G.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this.getInputData(2)),s=(null==i&&(i=this.properties.z),this.getInputData(3)),o=(null==s&&(s=this.properties.w),this._data);o[0]=t,o[1]=e,o[2]=i,o[3]=s,this.setOutputData(0,o)},s.registerNodeType("math3d/xyzw-to-vec4",G)})(this),(t=>{var e=t.LiteGraph;function a(){this.addInput("T","vec3"),this.addInput("R","vec3"),this.addInput("S","vec3"),this.addOutput("mat4","mat4"),this.properties={T:[0,0,0],R:[0,0,0],S:[1,1,1],R_in_degrees:!0},this._result=mat4.create(),this._must_update=!0}function i(){this.addInput("A","number,vec3"),this.addInput("B","number,vec3"),this.addOutput("=","number,vec3"),this.addProperty("OP","+","enum",{values:i.values}),this._result=vec3.create()}function s(){this.addInput("in","vec3"),this.addInput("f","number"),this.addOutput("out","vec3"),this.properties={f:1},this._data=new Float32Array(3)}function o(){this.addInput("in","vec3"),this.addOutput("out","number")}function n(){this.addInput("in","vec3"),this.addOutput("out","vec3"),this._data=new Float32Array(3)}function r(){this.addInput("A","vec3"),this.addInput("B","vec3"),this.addInput("f","vec3"),this.addOutput("out","vec3"),this.properties={f:.5},this._data=new Float32Array(3)}function u(){this.addInput("A","vec3"),this.addInput("B","vec3"),this.addOutput("out","number")}function h(){this.addOutput("quat","quat"),this.properties={x:0,y:0,z:0,w:1,normalize:!1},this._value=quat.create()}function p(){this.addInputs([["degrees","number"],["axis","vec3"]]),this.addOutput("quat","quat"),this.properties={angle:90,axis:vec3.fromValues(0,1,0)},this._value=quat.create()}function l(){this.addInput("euler","vec3"),this.addOutput("quat","quat"),this.properties={euler:[0,0,0],use_yaw_pitch_roll:!1},this._degs=vec3.create(),this._value=quat.create()}function d(){this.addInput(["quat","quat"]),this.addOutput("euler","vec3"),this._value=vec3.create()}function c(){this.addInputs([["vec3","vec3"],["quat","quat"]]),this.addOutput("result","vec3"),this.properties={vec:[0,0,1]}}function _(){this.addInputs([["A","quat"],["B","quat"]]),this.addOutput("A*B","quat"),this._value=quat.create()}function g(){this.addInputs([["A","quat"],["B","quat"],["factor","number"]]),this.addOutput("slerp","quat"),this.addProperty("factor",.5),this._value=quat.create()}function f(){this.addInput("vec3","vec3"),this.addOutput("remap","vec3"),this.addOutput("clamped","vec3"),this.properties={clamp:!0,range_min:[-1,-1,0],range_max:[1,1,0],target_min:[-1,-1,0],target_max:[1,1,0]},this._value=vec3.create(),this._clamped=vec3.create()}a.title="mat4",a.temp_quat=new Float32Array([0,0,0,1]),a.temp_mat4=new Float32Array(16),a.temp_vec3=new Float32Array(3),a.prototype.onPropertyChanged=function(t,e){this._must_update=!0},a.prototype.onExecute=function(){var t=this._result,e=a.temp_quat,i=a.temp_mat4,s=a.temp_vec3,o=this.getInputData(0),n=this.getInputData(1),r=this.getInputData(2);(this._must_update||o||n||r)&&(o=o||this.properties.T,n=n||this.properties.R,r=r||this.properties.S,mat4.identity(t),mat4.translate(t,t,o),this.properties.R_in_degrees?(s.set(n),vec3.scale(s,s,DEG2RAD),quat.fromEuler(e,s)):quat.fromEuler(e,n),mat4.fromQuat(i,e),mat4.multiply(t,t,i),mat4.scale(t,t,r)),this.setOutputData(0,t)},e.registerNodeType("math3d/mat4",a),i.values=["+","-","*","/","%","^","max","min","dot","cross"],e.registerSearchboxExtra("math3d/operation","CROSS()",{properties:{OP:"cross"},title:"CROSS()"}),e.registerSearchboxExtra("math3d/operation","DOT()",{properties:{OP:"dot"},title:"DOT()"}),i.title="Operation",i.desc="Easy math 3D operators",i["@OP"]={type:"enum",title:"operation",values:i.values},i.size=[100,60],i.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"},i.prototype.onExecute=function(){var t=this.getInputData(0),e=this.getInputData(1);if(null!=t&&null!=e){t.constructor===Number&&(t=[t,t,t]),e.constructor===Number&&(e=[e,e,e]);var i=this._result;switch(this.properties.OP){case"+":i=vec3.add(i,t,e);break;case"-":i=vec3.sub(i,t,e);break;case"x":case"X":case"*":i=vec3.mul(i,t,e);break;case"/":i=vec3.div(i,t,e);break;case"%":i[0]=t[0]%e[0],i[1]=t[1]%e[1],i[2]=t[2]%e[2];break;case"^":i[0]=Math.pow(t[0],e[0]),i[1]=Math.pow(t[1],e[1]),i[2]=Math.pow(t[2],e[2]);break;case"max":i[0]=Math.max(t[0],e[0]),i[1]=Math.max(t[1],e[1]),i[2]=Math.max(t[2],e[2]);break;case"min":i[0]=Math.min(t[0],e[0]),i[1]=Math.min(t[1],e[1]),i[2]=Math.min(t[2],e[2]);case"dot":i=vec3.dot(t,e);break;case"cross":vec3.cross(i,t,e);break;default:console.warn("Unknown operation: "+this.properties.OP)}this.setOutputData(0,i)}},i.prototype.onDrawBackground=function(t){this.flags.collapsed||(t.font="40px Arial",t.fillStyle="#666",t.textAlign="center",t.fillText(this.properties.OP,.5*this.size[0],.5*(this.size[1]+e.NODE_TITLE_HEIGHT)),t.textAlign="left")},e.registerNodeType("math3d/operation",i),s.title="vec3_scale",s.desc="scales the components of a vec3",s.prototype.onExecute=function(){var t,e,i=this.getInputData(0);null!=i&&(null==(t=this.getInputData(1))&&(t=this.properties.f),(e=this._data)[0]=i[0]*t,e[1]=i[1]*t,e[2]=i[2]*t,this.setOutputData(0,e))},e.registerNodeType("math3d/vec3-scale",s),o.title="vec3_length",o.desc="returns the module of a vector",o.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(t=Math.sqrt(t[0]*t[0]+t[1]*t[1]+t[2]*t[2]),this.setOutputData(0,t))},e.registerNodeType("math3d/vec3-length",o),n.title="vec3_normalize",n.desc="returns the vector normalized",n.prototype.onExecute=function(){var t,e,i=this.getInputData(0);null!=i&&(t=Math.sqrt(i[0]*i[0]+i[1]*i[1]+i[2]*i[2]),(e=this._data)[0]=i[0]/t,e[1]=i[1]/t,e[2]=i[2]/t,this.setOutputData(0,e))},e.registerNodeType("math3d/vec3-normalize",n),r.title="vec3_lerp",r.desc="returns the interpolated vector",r.prototype.onExecute=function(){var t,e,i,s=this.getInputData(0);null!=s&&null!=(t=this.getInputData(1))&&(e=this.getInputOrProperty("f"),(i=this._data)[0]=s[0]*(1-e)+t[0]*e,i[1]=s[1]*(1-e)+t[1]*e,i[2]=s[2]*(1-e)+t[2]*e,this.setOutputData(0,i))},e.registerNodeType("math3d/vec3-lerp",r),u.title="vec3_dot",u.desc="returns the dot product",u.prototype.onExecute=function(){var t,e=this.getInputData(0);null!=e&&null!=(t=this.getInputData(1))&&(e=e[0]*t[0]+e[1]*t[1]+e[2]*t[2],this.setOutputData(0,e))},e.registerNodeType("math3d/vec3-dot",u),t.glMatrix?(h.title="Quaternion",h.desc="quaternion",h.prototype.onExecute=function(){this._value[0]=this.getInputOrProperty("x"),this._value[1]=this.getInputOrProperty("y"),this._value[2]=this.getInputOrProperty("z"),this._value[3]=this.getInputOrProperty("w"),this.properties.normalize&&quat.normalize(this._value,this._value),this.setOutputData(0,this._value)},h.prototype.onGetInputs=function(){return[["x","number"],["y","number"],["z","number"],["w","number"]]},e.registerNodeType("math3d/quaternion",h),p.title="Rotation",p.desc="quaternion rotation",p.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.angle),this.getInputData(1)),e=(null==e&&(e=this.properties.axis),quat.setAxisAngle(this._value,e,.0174532925*t));this.setOutputData(0,e)},e.registerNodeType("math3d/rotation",p),l.title="Euler->Quat",l.desc="Converts euler angles (in degrees) to quaternion",l.prototype.onExecute=function(){var t=this.getInputData(0),t=(null==t&&(t=this.properties.euler),vec3.scale(this._degs,t,DEG2RAD),this.properties.use_yaw_pitch_roll&&(this._degs=[this._degs[2],this._degs[0],this._degs[1]]),quat.fromEuler(this._value,this._degs));this.setOutputData(0,t)},e.registerNodeType("math3d/euler_to_quat",l),d.title="Euler->Quat",d.desc="Converts rotX,rotY,rotZ in degrees to quat",d.prototype.onExecute=function(){var t=this.getInputData(0);t&&(quat.toEuler(this._value,t),vec3.scale(this._value,this._value,DEG2RAD),this.setOutputData(0,this._value))},e.registerNodeType("math3d/quat_to_euler",d),c.title="Rot. Vec3",c.desc="rotate a point",c.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.vec),this.getInputData(1));null==e?this.setOutputData(t):this.setOutputData(0,vec3.transformQuat(vec3.create(),t,e))},e.registerNodeType("math3d/rotate_vec3",c),_.title="Mult. Quat",_.desc="rotate quaternion",_.prototype.onExecute=function(){var t,e=this.getInputData(0);null!=e&&null!=(t=this.getInputData(1))&&(e=quat.multiply(this._value,e,t),this.setOutputData(0,e))},e.registerNodeType("math3d/mult-quat",_),g.title="Quat Slerp",g.desc="quaternion spherical interpolation",g.prototype.onExecute=function(){var t,e,i=this.getInputData(0);null!=i&&null!=(t=this.getInputData(1))&&(e=this.properties.factor,null!=this.getInputData(2)&&(e=this.getInputData(2)),i=quat.slerp(this._value,i,t,e),this.setOutputData(0,i))},e.registerNodeType("math3d/quat-slerp",g),f.title="Remap Range",f.desc="remap a 3D range",f.prototype.onExecute=function(){for(var t=this.getInputData(0),e=(t&&this._value.set(t),this.properties.range_min),i=this.properties.range_max,s=this.properties.target_min,o=this.properties.target_max,n=0;n<3;++n){var r,a=i[n]-e[n];this._clamped[n]=clamp(this._value[n],e[n],i[n]),0==a?this._value[n]=.5*(s[n]+o[n]):(a=(this._value[n]-e[n])/a,r=(this.properties.clamp&&(a=clamp(a,0,1)),o[n]-s[n]),this._value[n]=s[n]+a*r)}this.setOutputData(0,this._value),this.setOutputData(1,this._clamped)},e.registerNodeType("math3d/remap_range",f)):e.debug&&console.warn("No glmatrix found, some Math3D nodes may not work")})(this),(t=>{function e(){this.addInput("","string"),this.addOutput("table","table"),this.addOutput("rows","number"),this.addProperty("value",""),this.addProperty("separator",","),this._table=null}(t=t.LiteGraph).wrapFunctionAsNode("string/toString",function(t){if(t&&t.constructor===Object)try{return JSON.stringify(t)}catch(t){}return String(t)},[""],"string"),t.wrapFunctionAsNode("string/compare",function(t,e){return t==e},["string","string"],"boolean"),t.wrapFunctionAsNode("string/concatenate",function(t,e){return void 0===t?e:void 0===e?t:t+e},["string","string"],"string"),t.wrapFunctionAsNode("string/contains",function(t,e){return void 0!==t&&void 0!==e&&-1!=t.indexOf(e)},["string","string"],"boolean"),t.wrapFunctionAsNode("string/toUpperCase",function(t){return null!=t&&t.constructor===String?t.toUpperCase():t},["string"],"string"),t.wrapFunctionAsNode("string/split",function(t,e){if(null==e&&(e=this.properties.separator),null==t)return[];if(t.constructor===String)return t.split(e||" ");if(t.constructor!==Array)return null;for(var i=[],s=0;s{var i=t.LiteGraph;function e(){this.addInput("sel","number"),this.addInput("A"),this.addInput("B"),this.addInput("C"),this.addInput("D"),this.addOutput("out"),this.selected=0}function s(){this.properties={sequence:"A,B,C"},this.addInput("index","number"),this.addInput("seq"),this.addOutput("out"),this.index=0,this.values=this.properties.sequence.split(",")}function o(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function n(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function r(){this.properties={},this.addInput("in","boolean"),this.addOutput("out","boolean")}function a(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function u(){this.properties={},this.addInput("onTrigger",i.ACTION),this.addInput("condition","boolean"),this.addOutput("true",i.EVENT),this.addOutput("false",i.EVENT),this.mode=i.ON_TRIGGER}e.title="Selector",e.desc="selects an output",e.prototype.onDrawBackground=function(t){var e;this.flags.collapsed||(t.fillStyle="#AFB",e=(this.selected+1)*i.NODE_SLOT_HEIGHT+6,t.beginPath(),t.moveTo(50,e),t.lineTo(50,e+i.NODE_SLOT_HEIGHT),t.lineTo(34,e+.5*i.NODE_SLOT_HEIGHT),t.fill())},e.prototype.onExecute=function(){var t=this.getInputData(0),t=(null!=t&&t.constructor===Number||(t=0),this.selected=t=Math.round(t)%(this.inputs.length-1),this.getInputData(t+1));void 0!==t&&this.setOutputData(0,t)},e.prototype.onGetInputs=function(){return[["E",0],["F",0],["G",0],["H",0]]},i.registerNodeType("logic/selector",e),s.title="Sequence",s.desc="select one element from a sequence from a string",s.prototype.onPropertyChanged=function(t,e){"sequence"==t&&(this.values=e.split(","))},s.prototype.onExecute=function(){var t=this.getInputData(1),t=(t&&t!=this.current_sequence&&(this.values=t.split(","),this.current_sequence=t),this.getInputData(0));null==t&&(t=0),this.index=t=Math.round(t)%this.values.length,this.setOutputData(0,this.values[t])},i.registerNodeType("logic/sequence",s),o.title="AND",o.desc="Return true if all inputs are true",o.prototype.onExecute=function(){var t,e=!0;for(t in this.inputs)if(!this.getInputData(t)){e=!1;break}this.setOutputData(0,e)},o.prototype.onGetInputs=function(){return[["and","boolean"]]},i.registerNodeType("logic/AND",o),n.title="OR",n.desc="Return true if at least one input is true",n.prototype.onExecute=function(){var t,e=!1;for(t in this.inputs)if(this.getInputData(t)){e=!0;break}this.setOutputData(0,e)},n.prototype.onGetInputs=function(){return[["or","boolean"]]},i.registerNodeType("logic/OR",n),r.title="NOT",r.desc="Return the logical negation",r.prototype.onExecute=function(){var t=!this.getInputData(0);this.setOutputData(0,t)},i.registerNodeType("logic/NOT",r),a.title="bool == bool",a.desc="Compare for logical equality",a.prototype.onExecute=function(){var t,e=null,i=!0;for(t in this.inputs)if(null===e)e=this.getInputData(t);else if(e!=this.getInputData(t)){i=!1;break}this.setOutputData(0,i)},a.prototype.onGetInputs=function(){return[["bool","boolean"]]},i.registerNodeType("logic/CompareBool",a),u.title="Branch",u.desc="Branch execution on condition",u.prototype.onExecute=function(t,e){this.getInputData(1)?this.triggerSlot(0):this.triggerSlot(1)},i.registerNodeType("logic/IF",u)})(this),(t=>{var n=t.LiteGraph;function h(){this.addInput("A","Number"),this.addInput("B","Number"),this.addInput("C","Number"),this.addInput("D","Number"),this.values=[[],[],[],[]],this.properties={scale:2}}function e(){this.addOutput("frame","image"),this.properties={url:""}}function i(){this.addInput("f","number"),this.addOutput("Color","color"),this.properties={colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}}function s(){this.addInput("","image,canvas"),this.size=[200,200]}function o(){this.addInputs([["img1","image"],["img2","image"],["fade","number"]]),this.addOutput("","image"),this.properties={fade:.5,width:512,height:512}}function r(){this.addInput("","image"),this.addOutput("","image"),this.properties={width:256,height:256,x:0,y:0,scale:1},this.size=[50,20]}function a(){this.addInput("clear",n.ACTION),this.addOutput("","canvas"),this.properties={width:512,height:512,autoclear:!0},this.canvas=document.createElement("canvas"),this.ctx=this.canvas.getContext("2d")}function u(){this.addInput("canvas","canvas"),this.addInput("img","image,canvas"),this.addInput("x","number"),this.addInput("y","number"),this.properties={x:0,y:0,opacity:1}}function p(){this.addInput("canvas","canvas"),this.addInput("x","number"),this.addInput("y","number"),this.addInput("w","number"),this.addInput("h","number"),this.properties={x:0,y:0,w:10,h:10,color:"white",opacity:1}}function l(){this.addInput("t","number"),this.addOutputs([["frame","image"],["t","number"],["d","number"]]),this.properties={url:"",use_proxy:!0}}function d(){this.addOutput("Webcam","image"),this.properties={filterFacingMode:!1,facingMode:"user"},this.boxcolor="black",this.frame=0}h.title="Plot",h.desc="Plots data over time",h.colors=["#FFF","#F99","#9F9","#99F"],h.prototype.onExecute=function(t){if(!this.flags.collapsed)for(var e=this.size,i=0;i<4;++i){var s,o=this.getInputData(i);null!=o&&((s=this.values[i]).push(o),s.length>e[0])&&s.shift()}},h.prototype.onDrawBackground=function(t){if(!this.flags.collapsed){var e=this.size,i=.5*e[1]/this.properties.scale,s=h.colors,o=.5*e[1];if(t.fillStyle="#000",t.fillRect(0,0,e[0],e[1]),t.strokeStyle="#555",t.beginPath(),t.moveTo(0,o),t.lineTo(e[0],o),t.stroke(),this.inputs)for(var n=0;n<4;++n){var r=this.values[n];if(this.inputs[n]&&this.inputs[n].link){t.strokeStyle=s[n],t.beginPath();var a=r[0]*i*-1+o;t.moveTo(0,clamp(a,0,e[1]));for(var u=1;u{var a=P.LiteGraph,M=P.LGraphCanvas;function c(){this.addOutput("tex","Texture"),this.addOutput("name","string"),this.properties={name:"",filter:!0},this.size=[c.image_preview_size,c.image_preview_size]}function s(){this.addInput("Texture","Texture"),this.properties={flipY:!1},this.size=[c.image_preview_size,c.image_preview_size]}function z(){this.addInput("Texture","Texture"),this.addOutput("tex","Texture"),this.addOutput("name","string"),this.properties={name:"",generate_mipmaps:!1}}function p(){this.addInput("Texture","Texture"),this.addInput("TextureB","Texture"),this.addInput("value","number"),this.addOutput("Texture","Texture"),this.help="

pixelcode must be vec3, uvcode must be vec2, is optional

\t\t

uv: tex. coords

color: texture colorB: textureB

time: scene time value: input value

For multiline you must type: result = ...

",this.properties={value:1,pixelcode:"color + colorB * value",uvcode:"",precision:c.DEFAULT},this.has_error=!1}function t(){this.addOutput("out","Texture"),this.properties={code:"",u_value:1,u_color:[1,1,1,1],width:512,height:512,precision:c.DEFAULT},this.properties.code=t.pixel_shader,this._uniforms={u_value:1,u_color:vec4.create(),in_texture:0,texSize:vec4.create(),time:0}}function u(){this.addInput("in","Texture"),this.addInput("scale","vec2"),this.addInput("offset","vec2"),this.addOutput("out","Texture"),this.properties={offset:vec2.fromValues(0,0),scale:vec2.fromValues(1,1),precision:c.DEFAULT}}function r(){this.addInput("in","Texture"),this.addInput("warp","Texture"),this.addInput("factor","number"),this.addOutput("out","Texture"),this.properties={factor:.01,scale:[1,1],offset:[0,0],precision:c.DEFAULT},this._uniforms={u_texture:0,u_textureB:1,u_factor:1,u_scale:vec2.create(),u_offset:vec2.create()}}function o(){this.addInput("Texture","Texture"),this.properties={additive:!1,antialiasing:!1,filter:!0,disable_alpha:!1,gamma:1,viewport:[0,0,1,1]},this.size[0]=130}function F(){this.addInput("Texture","Texture"),this.addOutput("","Texture"),this.properties={size:0,generate_mipmaps:!1,precision:c.DEFAULT}}function _(){this.addInput("Texture","Texture"),this.addOutput("","Texture"),this.properties={iterations:1,generate_mipmaps:!1,precision:c.DEFAULT}}function B(){this.addInput("Texture","Texture"),this.addOutput("","Texture"),this.properties={size:[512,512],generate_mipmaps:!1,precision:c.DEFAULT}}function h(){this.addInput("Texture","Texture"),this.addOutput("tex","Texture"),this.addOutput("avg","vec4"),this.addOutput("lum","number"),this.properties={use_previous_frame:!0,high_quality:!1},this._uniforms={u_texture:0,u_mipmap_offset:0},this._luminance=new Float32Array(4)}function l(){this.addInput("Texture","Texture"),this.addOutput("min_t","Texture"),this.addOutput("max_t","Texture"),this.addOutput("min","vec4"),this.addOutput("max","vec4"),this.properties={mode:"max",use_previous_frame:!0},this._uniforms={u_texture:0},this._max=new Float32Array(4),this._min=new Float32Array(4),this._textures_chain=[]}function n(){this.addInput("in","Texture"),this.addInput("factor","Number"),this.addOutput("out","Texture"),this.properties={factor:.5},this._uniforms={u_texture:0,u_textureB:1,u_factor:this.properties.factor}}function d(){this.addInput("in","Texture"),this.addOutput("avg","Texture"),this.addOutput("array","Texture"),this.properties={samples:64,frames_interval:1},this._uniforms={u_texture:0,u_textureB:1,u_samples:this.properties.samples,u_isamples:1/this.properties.samples},this.frame=0}function H(){this.addInput("Image","image"),this.addOutput("","Texture"),this.properties={}}function g(){this.addInput("Texture","Texture"),this.addInput("LUT","Texture"),this.addInput("Intensity","number"),this.addOutput("","Texture"),this.properties={enabled:!0,intensity:1,precision:c.DEFAULT,texture:null},g._shader||(g._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,g.pixel_shader))}function f(){this.addInput("Texture","Texture"),this.addInput("Atlas","Texture"),this.addOutput("","Texture"),this.properties={enabled:!0,num_row_symbols:4,symbol_size:16,brightness:1,colorize:!1,filter:!1,invert:!1,precision:c.DEFAULT,generate_mipmaps:!1,texture:null},f._shader||(f._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,f.pixel_shader)),this._uniforms={u_texture:0,u_textureB:1,u_row_simbols:4,u_simbol_size:16,u_res:vec2.create()}}function v(){this.addInput("Texture","Texture"),this.addOutput("R","Texture"),this.addOutput("G","Texture"),this.addOutput("B","Texture"),this.addOutput("A","Texture"),v._shader||(v._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,v.pixel_shader))}function m(){this.addInput("R","Texture"),this.addInput("G","Texture"),this.addInput("B","Texture"),this.addInput("A","Texture"),this.addOutput("Texture","Texture"),this.properties={precision:c.DEFAULT,R:1,G:1,B:1,A:1},this._color=vec4.create(),this._uniforms={u_textureR:0,u_textureG:1,u_textureB:2,u_textureA:3,u_color:this._color}}function e(){this.addOutput("Texture","Texture"),this._tex_color=vec4.create(),this.properties={color:vec4.create(),precision:c.DEFAULT}}function y(){this.addInput("A","color"),this.addInput("B","color"),this.addOutput("Texture","Texture"),this.properties={angle:0,scale:1,A:[0,0,0],B:[1,1,1],texture_size:32},y._shader||(y._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,y.pixel_shader)),this._uniforms={u_angle:0,u_colorA:vec3.create(),u_colorB:vec3.create()}}function x(){this.addInput("A","Texture"),this.addInput("B","Texture"),this.addInput("Mixer","Texture"),this.addOutput("Texture","Texture"),this.properties={factor:.5,size_from_biggest:!0,invert:!1,precision:c.DEFAULT},this._uniforms={u_textureA:0,u_textureB:1,u_textureMix:2,u_mix:vec4.create()}}function b(){this.addInput("Tex.","Texture"),this.addOutput("Edges","Texture"),this.properties={invert:!0,threshold:!1,factor:1,precision:c.DEFAULT},b._shader||(b._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,b.pixel_shader))}function T(){this.addInput("Texture","Texture"),this.addInput("Distance","number"),this.addInput("Range","number"),this.addOutput("Texture","Texture"),this.properties={distance:100,range:50,only_depth:!1,high_precision:!1},this._uniforms={u_texture:0,u_distance:100,u_range:50,u_camera_planes:null}}function E(){this.addInput("Texture","Texture"),this.addOutput("Texture","Texture"),this.properties={precision:c.DEFAULT,invert:!1},this._uniforms={u_texture:0,u_camera_planes:null,u_ires:vec2.create()}}function w(){this.addInput("Texture","Texture"),this.addInput("Iterations","number"),this.addInput("Intensity","number"),this.addOutput("Blurred","Texture"),this.properties={intensity:1,iterations:1,preserve_aspect:!1,scale:[1,1],precision:c.DEFAULT}}function L(){this.intensity=.5,this.persistence=.6,this.iterations=8,this.threshold=.8,this.scale=1,this.dirt_texture=null,this.dirt_factor=.5,this._textures=[],this._uniforms={u_intensity:1,u_texture:0,u_glow_texture:1,u_threshold:0,u_texel_size:vec2.create()}}function i(){this.addInput("in","Texture"),this.addInput("dirt","Texture"),this.addOutput("out","Texture"),this.addOutput("glow","Texture"),this.properties={enabled:!0,intensity:1,persistence:.99,iterations:16,threshold:0,scale:1,dirt_factor:.5,precision:c.DEFAULT},this.fx=new L}function O(){this.addInput("Texture","Texture"),this.addOutput("Filtered","Texture"),this.properties={intensity:1,radius:5}}function I(){this.addInput("Texture","Texture"),this.addOutput("Filtered","Texture"),this.properties={sigma:1.4,k:1.6,p:21.7,epsilon:79,phi:.017}}function C(){this.addOutput("Webcam","Texture"),this.properties={texture_name:"",facingMode:"user"},this.boxcolor="black",this.version=0}function D(){this.addInput("in","Texture"),this.addInput("f","number"),this.addOutput("out","Texture"),this.properties={enabled:!0,factor:1,precision:c.LOW},this._uniforms={u_texture:0,u_factor:1}}function U(){this.addInput("in",""),this.properties={precision:c.LOW,width:0,height:0,channels:1},this.addOutput("out","Texture")}function N(){this.addInput("in","Texture"),this.addOutput("out","Texture"),this.properties={precision:c.LOW,split_channels:!1},this._values=new Uint8Array(1024),this._values.fill(255),this._curve_texture=null,this._uniforms={u_texture:0,u_curve:1,u_range:1},this._must_update=!0,this._points={RGB:[[0,0],[1,1]],R:[[0,0],[1,1]],G:[[0,0],[1,1]],B:[[0,0],[1,1]]},this.curve_editor=null,this.addWidget("toggle","Split Channels",!1,"split_channels"),this.addWidget("combo","Channel","RGB",{values:["RGB","R","G","B"]}),this.curve_offset=68,this.size=[240,160]}function G(){this.addInput("in","Texture"),this.addInput("exp","number"),this.addOutput("out","Texture"),this.properties={exposition:1,precision:c.LOW},this._uniforms={u_texture:0,u_exposition:1}}function S(){this.addInput("in","Texture"),this.addInput("avg","number,Texture"),this.addOutput("out","Texture"),this.properties={enabled:!0,scale:1,gamma:1,average_lum:1,lum_white:1,precision:c.LOW},this._uniforms={u_texture:0,u_lumwhite2:1,u_igamma:1,u_scale:1,u_average_lum:1}}function A(){this.addOutput("out","Texture"),this.properties={width:512,height:512,seed:0,persistence:.1,octaves:8,scale:1,offset:[0,0],amplitude:1,precision:c.DEFAULT},this._key=0,this._texture=null,this._uniforms={u_persistence:.1,u_seed:0,u_offset:vec2.create(),u_scale:1,u_viewport:vec2.create()}}function k(){this.addInput("v"),this.addOutput("out","Texture"),this.properties={code:k.default_code,width:512,height:512,clear:!0,precision:c.DEFAULT,use_html_canvas:!1},this._func=null,this._temp_texture=null,this.compileCode()}function R(){this.addInput("in","Texture"),this.addOutput("out","Texture"),this.properties={key_color:vec3.fromValues(0,1,0),threshold:.8,slope:.2,precision:c.DEFAULT}}function V(){this.addInput("in","texture"),this.addInput("yaw","number"),this.addOutput("out","texture"),this.properties={yaw:0}}P.LGraphTexture=null,"undefined"!=typeof GL&&(M.link_type_colors.Texture="#987",(P.LGraphTexture=c).title="Texture",c.desc="Texture",c.widgets_info={name:{widget:"texture"},filter:{widget:"checkbox"}},c.loadTextureCallback=null,c.image_preview_size=256,c.MODE_VALUES={undefined:c.UNDEFINED=0,"pass through":c.PASS_THROUGH=1,copy:c.COPY=2,low:c.LOW=3,high:c.HIGH=4,reuse:c.REUSE=5,default:c.DEFAULT=2},c.getTexturesContainer=function(){return gl.textures},c.loadTexture=function(t,e){e=e||{};var i=t;return"http://"==i.substr(0,7)&&a.proxy&&(i=a.proxy+i.substr(7)),c.getTexturesContainer()[t]=GL.Texture.fromURL(i,e)},c.getTexture=function(t){var e=this.getTexturesContainer();if(e)return!(e=e[t])&&t&&":"!=t[0]?this.loadTexture(t):e;throw"Cannot load texture, container of textures not found"},c.getTargetTexture=function(t,e,i){if(!t)throw"LGraphTexture.getTargetTexture expects a reference texture";var s=null;switch(i){case c.LOW:s=gl.UNSIGNED_BYTE;break;case c.HIGH:s=gl.HIGH_PRECISION_FORMAT;break;case c.REUSE:return t;default:s=t?t.type:gl.UNSIGNED_BYTE}return e=e&&e.width==t.width&&e.height==t.height&&e.type==s&&e.format==t.format?e:new GL.Texture(t.width,t.height,{type:s,format:t.format,filter:gl.LINEAR})},c.getTextureType=function(t,e){var i=e?e.type:gl.UNSIGNED_BYTE;switch(t){case c.HIGH:i=gl.HIGH_PRECISION_FORMAT;break;case c.LOW:i=gl.UNSIGNED_BYTE}return i},c.getWhiteTexture=function(){return this._white_texture||(this._white_texture=GL.Texture.fromMemory(1,1,[255,255,255,255],{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST}))},c.getNoiseTexture=function(){if(this._noise_texture)return this._noise_texture;for(var t=new Uint8Array(1048576),e=0;e<1048576;++e)t[e]=255*Math.random();var i=GL.Texture.fromMemory(512,512,t,{format:gl.RGBA,wrap:gl.REPEAT,filter:gl.NEAREST});return this._noise_texture=i},c.prototype.onDropFile=function(t,e,i){var s;t?(s=null,s="string"==typeof t?GL.Texture.fromURL(t):-1!=e.toLowerCase().indexOf(".dds")?GL.Texture.fromDDSInMemory(t):(t=new Blob([i]),i=URL.createObjectURL(t),GL.Texture.fromURL(i)),this._drop_texture=s,this.properties.name=e):(this._drop_texture=null,this.properties.name="")},c.prototype.getExtraMenuOptions=function(t){var e=this;if(this._drop_texture)return[{content:"Clear",callback:function(){e._drop_texture=null,e.properties.name=""}}]},c.prototype.onExecute=function(){var t=null;if(t=!(t=!(t=this.isOutputConnected(1)?this.getInputData(0):t)&&this._drop_texture?this._drop_texture:t)&&this.properties.name?c.getTexture(this.properties.name):t){this._last_tex=t,!1===this.properties.filter?t.setParameter(gl.TEXTURE_MAG_FILTER,gl.NEAREST):t.setParameter(gl.TEXTURE_MAG_FILTER,gl.LINEAR),this.setOutputData(0,t),this.setOutputData(1,t.fullpath||t.filename);for(var e=2;ee)&&(i=this._preview_temp_tex,this._preview_temp_tex||(i=new GL.Texture(e,e,{minFilter:gl.NEAREST}),this._preview_temp_tex=i),t.copyTo(i),t=i);t=this._preview_canvas;return t||(t=createCanvas(e,e),this._preview_canvas=t),i&&i.toCanvas(t),t},c.prototype.getResources=function(t){return this.properties.name&&(t[this.properties.name]=GL.Texture),t},c.prototype.onGetInputs=function(){return[["in","Texture"]]},c.prototype.onGetOutputs=function(){return[["width","number"],["height","number"],["aspect","number"]]},c.replaceCode=function(t,e){return t.replace(/\{\{[a-zA-Z0-9_]*\}\}/g,function(t){return t=t.replace(/[\{\}]/g,""),e[t]||""})},a.registerNodeType("texture/texture",c),s.title="Preview",s.desc="Show a texture in the graph canvas",s.allow_preview=!1,s.prototype.onDrawBackground=function(t){var e,i;this.flags.collapsed||(t.webgl||s.allow_preview)&&(e=this.getInputData(0))&&(i=null,i=!e.handle&&t.webgl?e:c.generateLowResTexturePreview(e),t.save(),this.properties.flipY&&(t.translate(0,this.size[1]),t.scale(1,-1)),t.drawImage(i,0,0,this.size[0],this.size[1]),t.restore())},a.registerNodeType("texture/preview",s),z.title="Save",z.desc="Save a texture in the repository",z.prototype.getPreviewTexture=function(){return this._texture},z.prototype.onExecute=function(){var t=this.getInputData(0);t&&(this.properties.generate_mipmaps&&(t.bind(0),t.setParameter(gl.TEXTURE_MIN_FILTER,gl.LINEAR_MIPMAP_LINEAR),gl.generateMipmap(t.texture_type),t.unbind(0)),this.properties.name&&(c.storeTexture?c.storeTexture(this.properties.name,t):c.getTexturesContainer()[this.properties.name]=t),this._texture=t,this.setOutputData(0,t),this.setOutputData(1,this.properties.name))},a.registerNodeType("texture/save",z),p.widgets_info={uvcode:{widget:"code"},pixelcode:{widget:"code"},precision:{widget:"combo",values:c.MODE_VALUES}},p.title="Operation",p.desc="Texture shader operation",p.presets={},p.prototype.getExtraMenuOptions=function(t){var e=this;return[{content:e.properties.show?"Hide Texture":"Show Texture",callback:function(){e.properties.show=!e.properties.show}}]},p.prototype.onPropertyChanged=function(){this.has_error=!1},p.prototype.onDrawBackground=function(t){this.flags.collapsed||this.size[1]<=20||!this.properties.show||this._tex&&this._tex.gl==t&&(t.save(),t.drawImage(this._tex,0,0,this.size[0],this.size[1]),t.restore())},p.prototype.onExecute=function(){var e=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===c.PASS_THROUGH)this.setOutputData(0,e);else{var i=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var s,o,n=512,r=512,t=(e?(n=e.width,r=e.height):i&&(n=i.width,r=i.height),i=i||GL.Texture.getWhiteTexture(),c.getTextureType(this.properties.precision,e)),t=(e||this._tex?this._tex=c.getTargetTexture(e||this._tex,this._tex,this.properties.precision):this._tex=new GL.Texture(n,r,{type:t,format:gl.RGBA,filter:gl.LINEAR}),""),a=(this.properties.uvcode&&(t="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";"))&&(t=this.properties.uvcode),""),u=(this.properties.pixelcode&&(a="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";"))&&(a=this.properties.pixelcode),this._shader);if(!(this.has_error||u&&this._shader_code==t+"|"+a)){var h=c.replaceCode(p.pixel_shader,{UV_CODE:t,PIXEL_CODE:a});try{u=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,h),this.boxcolor="#00FF00"}catch(t){return GL.Shader.dumpErrorToConsole(t,Shader.SCREEN_VERTEX_SHADER,h),this.boxcolor="#FF0000",void(this.has_error=!0)}this._shader=u,this._shader_code=t+"|"+a}this._shader&&(null!=(s=this.getInputData(2))?this.properties.value=s:s=parseFloat(this.properties.value),o=this.graph.getTime(),this._tex.drawTo(function(){gl.disable(gl.DEPTH_TEST),gl.disable(gl.CULL_FACE),gl.disable(gl.BLEND),e&&e.bind(0),i&&i.bind(1);var t=Mesh.getScreenQuad();u.uniforms({u_texture:0,u_textureB:1,value:s,texSize:[n,r,1/n,1/r],time:o}).draw(t)}),this.setOutputData(0,this._tex))}}},p.pixel_shader="precision highp float;\n\t\t\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tvarying vec2 v_coord;\n\t\tuniform vec4 texSize;\n\t\tuniform float time;\n\t\tuniform float value;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 uv = v_coord;\n\t\t\t{{UV_CODE}};\n\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\t\t\tvec3 color = color4.rgb;\n\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\t\t\tvec3 colorB = color4B.rgb;\n\t\t\tvec3 result = color;\n\t\t\tfloat alpha = 1.0;\n\t\t\t{{PIXEL_CODE}};\n\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t}\n\t\t",(p.registerPreset=function(t,e){p.presets[t]=e})("",""),p.registerPreset("bypass","color"),p.registerPreset("add","color + colorB * value"),p.registerPreset("substract","(color - colorB) * value"),p.registerPreset("mate","mix( color, colorB, color4B.a * value)"),p.registerPreset("invert","vec3(1.0) - color"),p.registerPreset("multiply","color * colorB * value"),p.registerPreset("divide","(color / colorB) / value"),p.registerPreset("difference","abs(color - colorB) * value"),p.registerPreset("max","max(color, colorB) * value"),p.registerPreset("min","min(color, colorB) * value"),p.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"),p.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"),p.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"),p.registerPreset("normalmap","\n\t\tfloat z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\t\tfloat z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\t\tfloat z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\t\tfloat z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\t\tfloat z4 = color.x;\n\t\tfloat z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\t\tfloat z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\t\tfloat z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\t\tfloat z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\t\tvec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\t\tnormal.xy *= value;\n\t\tresult.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\t"),p.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"),p.prototype.onInspect=function(i){var s=this;i.addCombo("Presets","",{values:Object.keys(p.presets),callback:function(t){var e=p.presets[t];e&&(s.setProperty("pixelcode",e),s.title=t,i.refresh())}})},a.registerNodeType("texture/operation",p),t.title="Shader",t.desc="Texture shader",t.widgets_info={code:{type:"code",lang:"glsl"},precision:{widget:"combo",values:c.MODE_VALUES}},t.prototype.onPropertyChanged=function(t,e){if("code"==t){var i=this.getShader();if(i){var s=i.uniformInfo;if(this.inputs)for(var o={},n=0;n lumaMax))\n\t\t\t\tcolor = vec4(rgbA, 1.0);\n\t\t\telse\n\t\t\t\tcolor = vec4(rgbB, 1.0);\n\t\t\tif(u_igamma != 1.0)\n\t\t\t\tcolor.xyz = pow( color.xyz, vec3(u_igamma) );\n\t\t\treturn color;\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = applyFXAA( u_texture, v_coord * uViewportSize) ;\n\t\t}\n\t\t",o.gamma_pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_igamma;\n\t\tvoid main() {\n\t\t\tvec4 color = texture2D( u_texture, v_coord);\n\t\t\tcolor.xyz = pow(color.xyz, vec3(u_igamma) );\n\t\t gl_FragColor = color;\n\t\t}\n\t\t",a.registerNodeType("texture/toviewport",o),F.title="Copy",F.desc="Copy Texture",F.widgets_info={size:{widget:"combo",values:[0,32,64,128,256,512,1024,2048]},precision:{widget:"combo",values:c.MODE_VALUES}},F.prototype.onExecute=function(){var t,e,i,s,o=this.getInputData(0);(o||this._temp_texture)&&this.isOutputConnected(0)&&(o&&(t=o.width,e=o.height,0!=this.properties.size&&(t=this.properties.size,e=this.properties.size),s=this._temp_texture,i=o.type,this.properties.precision===c.LOW?i=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(i=gl.HIGH_PRECISION_FORMAT),s&&s.width==t&&s.height==e&&s.type==i||(s=gl.LINEAR,this.properties.generate_mipmaps&&isPowerOfTwo(t)&&isPowerOfTwo(e)&&(s=gl.LINEAR_MIPMAP_LINEAR),this._temp_texture=new GL.Texture(t,e,{type:i,format:gl.RGBA,minFilter:s,magFilter:gl.LINEAR})),o.copyTo(this._temp_texture),this.properties.generate_mipmaps)&&(this._temp_texture.bind(0),gl.generateMipmap(this._temp_texture.texture_type),this._temp_texture.unbind(0)),this.setOutputData(0,this._temp_texture))},a.registerNodeType("texture/copy",F),_.title="Downsample",_.desc="Downsample Texture",_.widgets_info={iterations:{type:"number",step:1,precision:0,min:0},precision:{widget:"combo",values:c.MODE_VALUES}},_.prototype.onExecute=function(){var t=this.getInputData(0);if((t||this._temp_texture)&&this.isOutputConnected(0)&&t&&t.texture_type===GL.TEXTURE_2D)if(this.properties.iterations<1)this.setOutputData(0,t);else{var e,i=_._shader,s=(i||(_._shader=i=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,_.pixel_shader)),0|t.width),o=0|t.height,n=t.type,r=(this.properties.precision===c.LOW?n=gl.UNSIGNED_BYTE:this.properties.precision===c.HIGH&&(n=gl.HIGH_PRECISION_FORMAT),this.properties.iterations||1),a=t,u=[],h={type:n,format:t.format},p=vec2.create(),l={u_offset:p};this._texture&&GL.Texture.releaseTemporary(this._texture);for(var d=0;d>1||0,o=o>>1||0,e=GL.Texture.getTemporary(s,o,h),u.push(e),a.setParameter(GL.TEXTURE_MAG_FILTER,GL.NEAREST),a.copyTo(e,i,l),1!=s||1!=o);++d)a=e;this._texture=u.pop();for(d=0;d>=2)););t.copyTo(this._textures_chain[0]);this._textures_chain[0];for(var s=1;s<=this._textures_chain.length;++s)t=this._textures_chain[s];var o=l._shader,n=this._uniforms;n.u_mipmap_offset=this.properties.mipmap_offset,gl.disable(gl.DEPTH_TEST),gl.disable(gl.BLEND),this._temp_texture.drawTo(function(){t.toViewport(o,n)})}},l.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tuniform mat4 u_samples_a;\n\t\tuniform mat4 u_samples_b;\n\t\tuniform sampler2D u_texture;\n\t\tuniform float u_mipmap_offset;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = vec4(0.0);\n\t\t\t//random average\n\t\t\tfor(int i = 0; i < 4; ++i)\n\t\t\t\tfor(int j = 0; j < 4; ++j)\n\t\t\t\t{\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( u_samples_a[i][j], u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t\tcolor += texture2D(u_texture, vec2( 1.0 - u_samples_a[i][j], 1.0 - u_samples_b[i][j] ), u_mipmap_offset );\n\t\t\t\t}\n\t\t gl_FragColor = color * 0.03125;\n\t\t}\n\t\t",n.title="Smooth",n.desc="Smooth texture over time",n.prototype.onExecute=function(){var t,e,i,s,o=this.getInputData(0);o&&this.isOutputConnected(0)&&(n._shader||(n._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,n.pixel_shader)),(t=this._temp_texture)&&t.type==o.type&&t.width==o.width&&t.height==o.height||(t={type:o.type,format:gl.RGBA,filter:gl.NEAREST},this._temp_texture=new GL.Texture(o.width,o.height,t),this._temp_texture2=new GL.Texture(o.width,o.height,t),o.copyTo(this._temp_texture2)),t=this._temp_texture,e=this._temp_texture2,i=n._shader,(s=this._uniforms).u_factor=1-this.getInputOrProperty("factor"),gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),t.drawTo(function(){e.bind(1),o.toViewport(i,s)}),this.setOutputData(0,t),this._temp_texture=e,this._temp_texture2=t)},n.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_factor;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tgl_FragColor = mix( texture2D( u_texture, v_coord ), texture2D( u_textureB, v_coord ), u_factor );\n\t\t}\n\t\t",a.registerNodeType("texture/temporal_smooth",n),d.title="Lineal Avg Smooth",d.desc="Smooth texture linearly over time",d["@samples"]={type:"number",min:1,max:64,step:1,precision:1},d.prototype.getPreviewTexture=function(){return this._temp_texture2},d.prototype.onExecute=function(){var t,e,i,s,o,n,r,a,u=this.getInputData(0);u&&this.isOutputConnected(0)&&(d._shader||(d._shader_copy=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,d.pixel_shader_copy),d._shader_avg=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,d.pixel_shader_avg)),t=clamp(this.properties.samples,0,64),e=this.frame,0==(i=this.properties.frames_interval)||e%i==0?((e=this._temp_texture)&&e.type==u.type&&e.width==t||(i={type:u.type,format:gl.RGBA,filter:gl.NEAREST},this._temp_texture=new GL.Texture(t,1,i),this._temp_texture2=new GL.Texture(t,1,i),this._temp_texture_out=new GL.Texture(1,1,i)),s=this._temp_texture,o=this._temp_texture2,n=d._shader_copy,r=d._shader_avg,(a=this._uniforms).u_samples=t,a.u_isamples=1/t,gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),s.drawTo(function(){o.bind(1),u.toViewport(n,a)}),this._temp_texture_out.drawTo(function(){s.toViewport(r,a)}),this.setOutputData(0,this._temp_texture_out),this._temp_texture=o,this._temp_texture2=s):this.setOutputData(0,this._temp_texture_out),this.setOutputData(1,this._temp_texture2),this.frame++)},d.pixel_shader_copy="precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_isamples;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tif( v_coord.x <= u_isamples )\n\t\t\t\tgl_FragColor = texture2D( u_texture, vec2(0.5) );\n\t\t\telse\n\t\t\t\tgl_FragColor = texture2D( u_textureB, v_coord - vec2(u_isamples,0.0) );\n\t\t}\n\t\t",d.pixel_shader_avg="precision highp float;\n\t\tprecision highp float;\n\t\tuniform sampler2D u_texture;\n\t\tuniform int u_samples;\n\t\tuniform float u_isamples;\n\t\tvarying vec2 v_coord;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 color = vec4(0.0);\n\t\t\tfor(int i = 0; i < 64; ++i)\n\t\t\t{\n\t\t\t\tcolor += texture2D( u_texture, vec2( float(i)*u_isamples,0.0) );\n\t\t\t\tif(i == (u_samples - 1))\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t\tgl_FragColor = color * u_isamples;\n\t\t}\n\t\t",a.registerNodeType("texture/linear_avg_smooth",d),H.title="Image to Texture",H.desc="Uploads an image to the GPU",H.prototype.onExecute=function(){var t=this.getInputData(0);if(t){var e=t.videoWidth||t.width,i=t.videoHeight||t.height;if(t.gltexture)this.setOutputData(0,t.gltexture);else{var s=this._temp_texture;s&&s.width==e&&s.height==i||(this._temp_texture=new GL.Texture(e,i,{format:gl.RGBA,filter:gl.LINEAR}));try{this._temp_texture.uploadImage(t)}catch(t){return void console.error("image comes from an unsafe location, cannot be uploaded to webgl: "+t)}this.setOutputData(0,this._temp_texture)}}},a.registerNodeType("texture/imageToTexture",H),g.widgets_info={texture:{widget:"texture"},precision:{widget:"combo",values:c.MODE_VALUES}},g.title="LUT",g.desc="Apply LUT to Texture",g.prototype.onExecute=function(){var t,e,i;this.isOutputConnected(0)&&(t=this.getInputData(0),this.properties.precision===c.PASS_THROUGH||!1===this.properties.enabled?this.setOutputData(0,t):t&&((i=(i=this.getInputData(1))||c.getTexture(this.properties.texture))?(i.bind(0),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),gl.bindTexture(gl.TEXTURE_2D,null),e=this.properties.intensity,this.isInputConnected(2)&&(this.properties.intensity=e=this.getInputData(2)),this._tex=c.getTargetTexture(t,this._tex,this.properties.precision),this._tex.drawTo(function(){i.bind(1),t.toViewport(g._shader,{u_texture:0,u_textureB:1,u_amount:e})}),this.setOutputData(0,this._tex)):this.setOutputData(0,t)))},g.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_amount;\n\t\t\n\t\tvoid main() {\n\t\t\t lowp vec4 textureColor = clamp( texture2D(u_texture, v_coord), vec4(0.0), vec4(1.0) );\n\t\t\t mediump float blueColor = textureColor.b * 63.0;\n\t\t\t mediump vec2 quad1;\n\t\t\t quad1.y = floor(floor(blueColor) / 8.0);\n\t\t\t quad1.x = floor(blueColor) - (quad1.y * 8.0);\n\t\t\t mediump vec2 quad2;\n\t\t\t quad2.y = floor(ceil(blueColor) / 8.0);\n\t\t\t quad2.x = ceil(blueColor) - (quad2.y * 8.0);\n\t\t\t highp vec2 texPos1;\n\t\t\t texPos1.x = (quad1.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t texPos1.y = 1.0 - ((quad1.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t highp vec2 texPos2;\n\t\t\t texPos2.x = (quad2.x * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.r);\n\t\t\t texPos2.y = 1.0 - ((quad2.y * 0.125) + 0.5/512.0 + ((0.125 - 1.0/512.0) * textureColor.g));\n\t\t\t lowp vec4 newColor1 = texture2D(u_textureB, texPos1);\n\t\t\t lowp vec4 newColor2 = texture2D(u_textureB, texPos2);\n\t\t\t lowp vec4 newColor = mix(newColor1, newColor2, fract(blueColor));\n\t\t\t gl_FragColor = vec4( mix( textureColor.rgb, newColor.rgb, u_amount), textureColor.w);\n\t\t}\n\t\t",a.registerNodeType("texture/LUT",g),f.widgets_info={texture:{widget:"texture"},precision:{widget:"combo",values:c.MODE_VALUES}},f.title="Encode",f.desc="Apply a texture atlas to encode a texture",f.prototype.onExecute=function(){var t,e,i;this.isOutputConnected(0)&&(t=this.getInputData(0),this.properties.precision===c.PASS_THROUGH||!1===this.properties.enabled?this.setOutputData(0,t):t&&((i=(i=this.getInputData(1))||c.getTexture(this.properties.texture))?(i.bind(0),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,this.properties.filter?gl.LINEAR:gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,this.properties.filter?gl.LINEAR:gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_S,gl.CLAMP_TO_EDGE),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_WRAP_T,gl.CLAMP_TO_EDGE),gl.bindTexture(gl.TEXTURE_2D,null),(e=this._uniforms).u_row_simbols=Math.floor(this.properties.num_row_symbols),e.u_symbol_size=this.properties.symbol_size,e.u_brightness=this.properties.brightness,e.u_invert=this.properties.invert?1:0,e.u_colorize=this.properties.colorize?1:0,this._tex=c.getTargetTexture(t,this._tex,this.properties.precision),e.u_res[0]=this._tex.width,e.u_res[1]=this._tex.height,this._tex.bind(0),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MAG_FILTER,gl.NEAREST),gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.NEAREST),this._tex.drawTo(function(){i.bind(1),t.toViewport(f._shader,e)}),this.properties.generate_mipmaps&&(this._tex.bind(0),gl.generateMipmap(this._tex.texture_type),this._tex.unbind(0)),this.setOutputData(0,this._tex)):this.setOutputData(0,t)))},f.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform float u_row_simbols;\n\t\tuniform float u_symbol_size;\n\t\tuniform float u_brightness;\n\t\tuniform float u_invert;\n\t\tuniform float u_colorize;\n\t\tuniform vec2 u_res;\n\t\t\n\t\tvoid main() {\n\t\t\tvec2 total_symbols = u_res / u_symbol_size;\n\t\t\tvec2 uv = floor(v_coord * total_symbols) / total_symbols; //pixelate \n\t\t\tvec2 local_uv = mod(v_coord * u_res, u_symbol_size) / u_symbol_size;\n\t\t\tlowp vec4 textureColor = texture2D(u_texture, uv );\n\t\t\tfloat lum = clamp(u_brightness * (textureColor.x + textureColor.y + textureColor.z)/3.0,0.0,1.0);\n\t\t\tif( u_invert == 1.0 ) lum = 1.0 - lum;\n\t\t\tfloat index = floor( lum * (u_row_simbols * u_row_simbols - 1.0));\n\t\t\tfloat col = mod( index, u_row_simbols );\n\t\t\tfloat row = u_row_simbols - floor( index / u_row_simbols ) - 1.0;\n\t\t\tvec2 simbol_uv = ( vec2( col, row ) + local_uv ) / u_row_simbols;\n\t\t\tvec4 color = texture2D( u_textureB, simbol_uv );\n\t\t\tif(u_colorize == 1.0)\n\t\t\t\tcolor *= textureColor;\n\t\t\tgl_FragColor = color;\n\t\t}\n\t\t",a.registerNodeType("texture/encode",f),v.title="Texture to Channels",v.desc="Split texture channels",v.prototype.onExecute=function(){var t=this.getInputData(0);if(t){this._channels||(this._channels=Array(4));for(var e=gl.RGB,i=0,s=0;s<4;s++)this.isOutputConnected(s)?(this._channels[s]&&this._channels[s].width==t.width&&this._channels[s].height==t.height&&this._channels[s].type==t.type&&this._channels[s].format==e||(this._channels[s]=new GL.Texture(t.width,t.height,{type:t.type,format:e,filter:gl.LINEAR})),i++):this._channels[s]=null;if(i){gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST);for(var o=Mesh.getScreenQuad(),n=v._shader,r=[[1,0,0,0],[0,1,0,0],[0,0,1,0],[0,0,0,1]],s=0;s<4;s++)this._channels[s]&&(this._channels[s].drawTo(function(){t.bind(0),n.uniforms({u_texture:0,u_mask:r[s]}).draw(o)}),this.setOutputData(s,this._channels[s]))}}},v.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec4 u_mask;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = vec4( vec3( length( texture2D(u_texture, v_coord) * u_mask )), 1.0 );\n\t\t}\n\t\t",a.registerNodeType("texture/textureChannels",v),m.title="Channels to Texture",m.desc="Split texture channels",m.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},m.prototype.onExecute=function(){var t=c.getWhiteTexture(),e=this.getInputData(0)||t,i=this.getInputData(1)||t,s=this.getInputData(2)||t,o=this.getInputData(3)||t,n=(gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),Mesh.getScreenQuad()),r=m._shader=m._shader?m._shader:new GL.Shader(Shader.SCREEN_VERTEX_SHADER,m.pixel_shader),t=Math.max(e.width,i.width,s.width,o.width),a=Math.max(e.height,i.height,s.height,o.height),u=this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE,t=(this._texture&&this._texture.width==t&&this._texture.height==a&&this._texture.type==u||(this._texture=new GL.Texture(t,a,{type:u,format:gl.RGBA,filter:gl.LINEAR})),this._color),h=(t[0]=this.properties.R,t[1]=this.properties.G,t[2]=this.properties.B,t[3]=this.properties.A,this._uniforms);this._texture.drawTo(function(){e.bind(0),i.bind(1),s.bind(2),o.bind(3),r.uniforms(h).draw(n)}),this.setOutputData(0,this._texture)},m.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureR;\n\t\tuniform sampler2D u_textureG;\n\t\tuniform sampler2D u_textureB;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform vec4 u_color;\n\t\t\n\t\tvoid main() {\n\t\t gl_FragColor = u_color * vec4( \t\t\t\t\ttexture2D(u_textureR, v_coord).r,\t\t\t\t\ttexture2D(u_textureG, v_coord).r,\t\t\t\t\ttexture2D(u_textureB, v_coord).r,\t\t\t\t\ttexture2D(u_textureA, v_coord).r);\n\t\t}\n\t\t",a.registerNodeType("texture/channelsTexture",m),e.title="Color",e.desc="Generates a 1x1 texture with a constant color",e.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},e.prototype.onDrawBackground=function(t){var e=this.properties.color;t.fillStyle="rgb("+Math.floor(255*clamp(e[0],0,1))+","+Math.floor(255*clamp(e[1],0,1))+","+Math.floor(255*clamp(e[2],0,1))+")",this.flags.collapsed?this.boxcolor=t.fillStyle:t.fillRect(0,0,this.size[0],this.size[1])},e.prototype.onExecute=function(){var t=this.properties.precision==c.HIGH?c.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE,e=(this._tex&&this._tex.type==t||(this._tex=new GL.Texture(1,1,{format:gl.RGBA,type:t,minFilter:gl.NEAREST})),this.properties.color);if(this.inputs)for(var i=0;ia.width?t:a,this._tex,this.properties.precision),gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),i=Mesh.getScreenQuad(),o=null,s=this._uniforms,e?o=(o=x._shader_tex)||(x._shader_tex=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,x.pixel_shader,{MIX_TEX:""})):(o=(o=x._shader_factor)||(x._shader_factor=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,x.pixel_shader)),n=null==n?this.properties.factor:n,s.u_mix.set([n,n,n,n])),r=this.properties.invert,this._tex.drawTo(function(){a.bind(r?1:0),t.bind(r?0:1),e&&e.bind(2),o.uniforms(s).draw(i)}),this.setOutputData(0,this._tex))))},x.prototype.onGetInputs=function(){return[["factor","number"]]},x.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_textureA;\n\t\tuniform sampler2D u_textureB;\n\t\t#ifdef MIX_TEX\n\t\t\tuniform sampler2D u_textureMix;\n\t\t#else\n\t\t\tuniform vec4 u_mix;\n\t\t#endif\n\t\t\n\t\tvoid main() {\n\t\t\t#ifdef MIX_TEX\n\t\t\t vec4 f = texture2D(u_textureMix, v_coord);\n\t\t\t#else\n\t\t\t vec4 f = u_mix;\n\t\t\t#endif\n\t\t gl_FragColor = mix( texture2D(u_textureA, v_coord), texture2D(u_textureB, v_coord), f );\n\t\t}\n\t\t",a.registerNodeType("texture/mix",x),b.title="Edges",b.desc="Detects edges",b.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},b.prototype.onExecute=function(){var t,e,i,s,o,n;this.isOutputConnected(0)&&(t=this.getInputData(0),this.properties.precision===c.PASS_THROUGH?this.setOutputData(0,t):t&&(this._tex=c.getTargetTexture(t,this._tex,this.properties.precision),gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),e=Mesh.getScreenQuad(),i=b._shader,s=this.properties.invert,o=this.properties.factor,n=this.properties.threshold?1:0,this._tex.drawTo(function(){t.bind(0),i.uniforms({u_texture:0,u_isize:[1/t.width,1/t.height],u_factor:o,u_threshold:n,u_invert:s?1:0}).draw(e)}),this.setOutputData(0,this._tex)))},b.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_isize;\n\t\tuniform int u_invert;\n\t\tuniform float u_factor;\n\t\tuniform float u_threshold;\n\t\t\n\t\tvoid main() {\n\t\t\tvec4 center = texture2D(u_texture, v_coord);\n\t\t\tvec4 up = texture2D(u_texture, v_coord + u_isize * vec2(0.0,1.0) );\n\t\t\tvec4 down = texture2D(u_texture, v_coord + u_isize * vec2(0.0,-1.0) );\n\t\t\tvec4 left = texture2D(u_texture, v_coord + u_isize * vec2(1.0,0.0) );\n\t\t\tvec4 right = texture2D(u_texture, v_coord + u_isize * vec2(-1.0,0.0) );\n\t\t\tvec4 diff = abs(center - up) + abs(center - down) + abs(center - left) + abs(center - right);\n\t\t\tdiff *= u_factor;\n\t\t\tif(u_invert == 1)\n\t\t\t\tdiff.xyz = vec3(1.0) - diff.xyz;\n\t\t\tif( u_threshold == 0.0 )\n\t\t\t\tgl_FragColor = vec4( diff.xyz, center.a );\n\t\t\telse\n\t\t\t\tgl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\t\t}\n\t\t",a.registerNodeType("texture/edges",b),T.title="Depth Range",T.desc="Generates a texture with a depth range",T.prototype.onExecute=function(){var t,e,i,s,o,n;this.isOutputConnected(0)&&(t=this.getInputData(0))&&(n=gl.UNSIGNED_BYTE,this.properties.high_precision&&(n=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT),this._temp_texture&&this._temp_texture.type==n&&this._temp_texture.width==t.width&&this._temp_texture.height==t.height||(this._temp_texture=new GL.Texture(t.width,t.height,{type:n,format:gl.RGBA,filter:gl.LINEAR})),e=this._uniforms,n=this.properties.distance,this.isInputConnected(1)&&(n=this.getInputData(1),this.properties.distance=n),i=this.properties.range,this.isInputConnected(2)&&(i=this.getInputData(2),this.properties.range=i),e.u_distance=n,e.u_range=i,gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),s=Mesh.getScreenQuad(),T._shader||(T._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,T.pixel_shader),T._shader_onlydepth=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,T.pixel_shader,{ONLY_DEPTH:""})),o=this.properties.only_depth?T._shader_onlydepth:T._shader,n=null,n=t.near_far_planes||(window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[.1,1e3]),e.u_camera_planes=n,this._temp_texture.drawTo(function(){t.bind(0),o.uniforms(e).draw(s)}),this._temp_texture.near_far_planes=n,this.setOutputData(0,this._temp_texture))},T.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_camera_planes;\n\t\tuniform float u_distance;\n\t\tuniform float u_range;\n\t\t\n\t\tfloat LinearDepth()\n\t\t{\n\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\tfloat depth = texture2D(u_texture, v_coord).x;\n\t\t\tdepth = depth * 2.0 - 1.0;\n\t\t\treturn zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t}\n\t\t\n\t\tvoid main() {\n\t\t\tfloat depth = LinearDepth();\n\t\t\t#ifdef ONLY_DEPTH\n\t\t\t gl_FragColor = vec4(depth);\n\t\t\t#else\n\t\t\t\tfloat diff = abs(depth * u_camera_planes.y - u_distance);\n\t\t\t\tfloat dof = 1.0;\n\t\t\t\tif(diff <= u_range)\n\t\t\t\t\tdof = diff / u_range;\n\t\t\t gl_FragColor = vec4(dof);\n\t\t\t#endif\n\t\t}\n\t\t",a.registerNodeType("texture/depth_range",T),E.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},E.title="Linear Depth",E.desc="Creates a color texture with linear depth",E.prototype.onExecute=function(){var t,e,i,s,o;!this.isOutputConnected(0)||!(t=this.getInputData(0))||t.format!=gl.DEPTH_COMPONENT&&t.format!=gl.DEPTH_STENCIL||(o=this.properties.precision==c.HIGH?gl.HIGH_PRECISION_FORMAT:gl.UNSIGNED_BYTE,this._temp_texture&&this._temp_texture.type==o&&this._temp_texture.width==t.width&&this._temp_texture.height==t.height||(this._temp_texture=new GL.Texture(t.width,t.height,{type:o,format:gl.RGB,filter:gl.LINEAR})),(e=this._uniforms).u_invert=this.properties.invert?1:0,gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),i=Mesh.getScreenQuad(),s=E._shader=E._shader||new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,E.pixel_shader),o=null,o=t.near_far_planes||(window.LS&&LS.Renderer._main_camera?LS.Renderer._main_camera._uniforms.u_camera_planes:[.1,1e3]),e.u_camera_planes=o,e.u_ires.set([0,0]),this._temp_texture.drawTo(function(){t.bind(0),s.uniforms(e).draw(i)}),this._temp_texture.near_far_planes=o,this.setOutputData(0,this._temp_texture))},E.pixel_shader="precision highp float;\n\t\tprecision highp float;\n\t\tvarying vec2 v_coord;\n\t\tuniform sampler2D u_texture;\n\t\tuniform vec2 u_camera_planes;\n\t\tuniform int u_invert;\n\t\tuniform vec2 u_ires;\n\t\t\n\t\tvoid main() {\n\t\t\tfloat zNear = u_camera_planes.x;\n\t\t\tfloat zFar = u_camera_planes.y;\n\t\t\tfloat depth = texture2D(u_texture, v_coord + u_ires*0.5).x * 2.0 - 1.0;\n\t\t\tfloat f = zNear * (depth + 1.0) / (zFar + zNear - depth * (zFar - zNear));\n\t\t\tif( u_invert == 1 )\n\t\t\t\tf = 1.0 - f;\n\t\t\tgl_FragColor = vec4(vec3(f),1.0);\n\t\t}\n\t\t",a.registerNodeType("texture/linear_depth",E),w.title="Blur",w.desc="Blur a texture",w.widgets_info={precision:{widget:"combo",values:c.MODE_VALUES}},w.max_iterations=20,w.prototype.onExecute=function(){var t=this.getInputData(0);if(t&&this.isOutputConnected(0)){var e=this._final_texture,i=(e&&e.width==t.width&&e.height==t.height&&e.type==t.type||(e=this._final_texture=new GL.Texture(t.width,t.height,{type:t.type,format:gl.RGBA,filter:gl.LINEAR})),this.properties.iterations);if(this.isInputConnected(1)&&(i=this.getInputData(1),this.properties.iterations=i),0==(i=Math.min(Math.floor(i),w.max_iterations)))this.setOutputData(0,t);else{var s=this.properties.intensity,o=(this.isInputConnected(2)&&(s=this.getInputData(2),this.properties.intensity=s),a.camera_aspect),n=(o=(o=o||void 0===window.gl?o:gl.canvas.height/gl.canvas.width)||1,o=this.properties.preserve_aspect?o:1,this.properties.scale||[1,1]);t.applyBlur(o*n[0],n[1],s,e);for(var r=1;r>=1),!((o>>=1)<2));v++)p=u[v]=GL.Texture.getTemporary(o,n,r),c[0]=1/l.width,c[1]=1/l.height,l.blit(p,h.uniforms(a)),l=p;for(s&&(c[0]=1/l.width,c[1]=1/l.height,a.u_intensity=_,a.u_delta=1,l.blit(s,h.uniforms(a))),gl.enable(gl.BLEND),gl.blendFunc(gl.ONE,gl.ONE),a.u_intensity=this.persistence,a.u_delta=.5,v-=2;0<=v;v--)p=u[v],u[v]=null,c[0]=1/l.width,c[1]=1/l.height,l.blit(p,h.uniforms(a)),GL.Texture.releaseTemporary(l),l=p;gl.disable(gl.BLEND),i&&l.blit(i),e&&(s=e,g=this.dirt_texture,f=this.dirt_factor,a.u_intensity=_,h=(h=g?L._dirt_final_shader:L._final_shader)||(g?L._dirt_final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,L.final_pixel_shader,{USE_DIRT:""}):L._final_shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,L.final_pixel_shader)),s.drawTo(function(){t.bind(0),l.bind(1),g&&(h.setUniform("u_dirt_factor",f),h.setUniform("u_dirt_texture",g.bind(2))),h.toViewport(a)})),GL.Texture.releaseTemporary(l)},L.cut_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform float u_threshold;\n\tvoid main() {\n\t\tgl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\t}",L.scale_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tgl_FragColor = u_intensity * sampleBox( v_coord );\n\t}",L.final_pixel_shader="precision highp float;\n\tvarying vec2 v_coord;\n\tuniform sampler2D u_texture;\n\tuniform sampler2D u_glow_texture;\n\t#ifdef USE_DIRT\n\t\tuniform sampler2D u_dirt_texture;\n\t#endif\n\tuniform vec2 u_texel_size;\n\tuniform float u_delta;\n\tuniform float u_intensity;\n\tuniform float u_dirt_factor;\n\t\n\tvec4 sampleBox(vec2 uv) {\n\t\tvec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\t\tvec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\t\treturn s * 0.25;\n\t}\n\tvoid main() {\n\t\tvec4 glow = sampleBox( v_coord );\n\t\t#ifdef USE_DIRT\n\t\t\tglow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\t\t#endif\n\t\tgl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\t}",i.title="Glow",i.desc="Filters a texture giving it a glow effect",i.widgets_info={iterations:{type:"number",min:0,max:16,step:1,precision:0},threshold:{type:"number",min:0,max:10,step:.01,precision:2},precision:{widget:"combo",values:c.MODE_VALUES}},i.prototype.onGetInputs=function(){return[["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]]},i.prototype.onGetOutputs=function(){return[["average","Texture"]]},i.prototype.onExecute=function(){var t,e,i,s,o,n=this.getInputData(0);n&&this.isAnyOutputConnected()&&(this.properties.precision===c.PASS_THROUGH||!1===this.getInputOrProperty("enabled")?this.setOutputData(0,n):(n.width,n.height,(t=this.fx).threshold=this.getInputOrProperty("threshold"),t.iterations=this.getInputOrProperty("iterations"),t.intensity=this.getInputOrProperty("intensity"),t.persistence=this.getInputOrProperty("persistence"),t.dirt_texture=this.getInputData(1),t.dirt_factor=this.getInputOrProperty("dirt_factor"),t.scale=this.properties.scale,e=c.getTextureType(this.properties.precision,n),i=null,!this.isOutputConnected(2)||(i=this._average_texture)&&i.type==n.type&&i.format==n.format||(i=this._average_texture=new GL.Texture(1,1,{type:n.type,format:n.format,filter:gl.LINEAR})),s=null,!this.isOutputConnected(1)||(s=this._glow_texture)&&s.width==n.width&&s.height==n.height&&s.type==e&&s.format==n.format||(s=this._glow_texture=new GL.Texture(n.width,n.height,{type:e,format:n.format,filter:gl.LINEAR})),o=null,!this.isOutputConnected(0)||(o=this._final_texture)&&o.width==n.width&&o.height==n.height&&o.type==e&&o.format==n.format||(o=this._final_texture=new GL.Texture(n.width,n.height,{type:e,format:n.format,filter:gl.LINEAR})),t.applyFX(n,o,s,i),this.isOutputConnected(0)&&this.setOutputData(0,o),this.isOutputConnected(1)&&this.setOutputData(1,i),this.isOutputConnected(2)&&this.setOutputData(2,s)))},a.registerNodeType("texture/glow",i),O.title="Kuwahara Filter",O.desc="Filters a texture giving an artistic oil canvas painting",O.max_radius=10,O._shaders=[],O.prototype.onExecute=function(){var t,e,i,s,o,n=this.getInputData(0);n&&this.isOutputConnected(0)&&((o=this._temp_texture)&&o.width==n.width&&o.height==n.height&&o.type==n.type||(this._temp_texture=new GL.Texture(n.width,n.height,{type:n.type,format:gl.RGBA,filter:gl.LINEAR})),o=this.properties.radius,0==(o=Math.min(Math.floor(o),O.max_radius))?this.setOutputData(0,n):(t=this.properties.intensity,e=(e=(e=a.camera_aspect)||void 0===window.gl?e:gl.canvas.height/gl.canvas.width)||1,e=this.properties.preserve_aspect?e:1,O._shaders[o]||(O._shaders[o]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,O.pixel_shader,{RADIUS:o.toFixed(0)})),i=O._shaders[o],s=GL.Mesh.getScreenQuad(),n.bind(0),this._temp_texture.drawTo(function(){i.uniforms({u_texture:0,u_intensity:t,u_resolution:[n.width,n.height],u_iResolution:[1/n.width,1/n.height]}).draw(s)}),this.setOutputData(0,this._temp_texture)))},O.pixel_shader="\nprecision highp float;\nvarying vec2 v_coord;\nuniform sampler2D u_texture;\nuniform float u_intensity;\nuniform vec2 u_resolution;\nuniform vec2 u_iResolution;\n#ifndef RADIUS\n\t#define RADIUS 7\n#endif\nvoid main() {\n\n\tconst int radius = RADIUS;\n\tvec2 fragCoord = v_coord;\n\tvec2 src_size = u_iResolution;\n\tvec2 uv = v_coord;\n\tfloat n = float((radius + 1) * (radius + 1));\n\tint i;\n\tint j;\n\tvec3 m0 = vec3(0.0); vec3 m1 = vec3(0.0); vec3 m2 = vec3(0.0); vec3 m3 = vec3(0.0);\n\tvec3 s0 = vec3(0.0); vec3 s1 = vec3(0.0); vec3 s2 = vec3(0.0); vec3 s3 = vec3(0.0);\n\tvec3 c;\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm0 += c;\n\t\t\ts0 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = -radius; j <= 0; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm1 += c;\n\t\t\ts1 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = 0; i <= radius; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm2 += c;\n\t\t\ts2 += c * c;\n\t\t}\n\t}\n\t\n\tfor (int j = 0; j <= radius; ++j) {\n\t\tfor (int i = -radius; i <= 0; ++i) {\n\t\t\tc = texture2D(u_texture, uv + vec2(i,j) * src_size).rgb;\n\t\t\tm3 += c;\n\t\t\ts3 += c * c;\n\t\t}\n\t}\n\t\n\tfloat min_sigma2 = 1e+2;\n\tm0 /= n;\n\ts0 = abs(s0 / n - m0 * m0);\n\t\n\tfloat sigma2 = s0.r + s0.g + s0.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m0, 1.0);\n\t}\n\t\n\tm1 /= n;\n\ts1 = abs(s1 / n - m1 * m1);\n\t\n\tsigma2 = s1.r + s1.g + s1.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m1, 1.0);\n\t}\n\t\n\tm2 /= n;\n\ts2 = abs(s2 / n - m2 * m2);\n\t\n\tsigma2 = s2.r + s2.g + s2.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m2, 1.0);\n\t}\n\t\n\tm3 /= n;\n\ts3 = abs(s3 / n - m3 * m3);\n\t\n\tsigma2 = s3.r + s3.g + s3.b;\n\tif (sigma2 < min_sigma2) {\n\t\tmin_sigma2 = sigma2;\n\t\tgl_FragColor = vec4(m3, 1.0);\n\t}\n}\n",a.registerNodeType("texture/kuwahara",O),I.title="XDoG Filter",I.desc="Filters a texture giving an artistic ink style",I.max_radius=10,I._shaders=[],I.prototype.onExecute=function(){var t,e,i,s,o,n,r,a,u=this.getInputData(0);u&&this.isOutputConnected(0)&&((t=this._temp_texture)&&t.width==u.width&&t.height==u.height&&t.type==u.type||(this._temp_texture=new GL.Texture(u.width,u.height,{type:u.type,format:gl.RGBA,filter:gl.LINEAR})),e=I._xdog_shader=I._xdog_shader||new GL.Shader(Shader.SCREEN_VERTEX_SHADER,I.xdog_pixel_shader),i=GL.Mesh.getScreenQuad(),s=this.properties.sigma,o=this.properties.k,n=this.properties.p,r=this.properties.epsilon,a=this.properties.phi,u.bind(0),this._temp_texture.drawTo(function(){e.uniforms({src:0,sigma:s,k:o,p:n,epsilon:r,phi:a,cvsWidth:u.width,cvsHeight:u.height}).draw(i)}),this.setOutputData(0,this._temp_texture))},I.xdog_pixel_shader="\nprecision highp float;\nuniform sampler2D src;\n\nuniform float cvsHeight;\nuniform float cvsWidth;\n\nuniform float sigma;\nuniform float k;\nuniform float p;\nuniform float epsilon;\nuniform float phi;\nvarying vec2 v_coord;\n\nfloat cosh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat cosH = (tmp + 1.0 / tmp) / 2.0;\n\treturn cosH;\n}\n\nfloat tanh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat tanH = (tmp - 1.0 / tmp) / (tmp + 1.0 / tmp);\n\treturn tanH;\n}\n\nfloat sinh(float val)\n{\n\tfloat tmp = exp(val);\n\tfloat sinH = (tmp - 1.0 / tmp) / 2.0;\n\treturn sinH;\n}\n\nvoid main(void){\n\tvec3 destColor = vec3(0.0);\n\tfloat tFrag = 1.0 / cvsHeight;\n\tfloat sFrag = 1.0 / cvsWidth;\n\tvec2 Frag = vec2(sFrag,tFrag);\n\tvec2 uv = gl_FragCoord.st;\n\tfloat twoSigmaESquared = 2.0 * sigma * sigma;\n\tfloat twoSigmaRSquared = twoSigmaESquared * k * k;\n\tint halfWidth = int(ceil( 1.0 * sigma * k ));\n\n\tconst int MAX_NUM_ITERATION = 99999;\n\tvec2 sum = vec2(0.0);\n\tvec2 norm = vec2(0.0);\n\n\tfor(int cnt=0;cnt (2*halfWidth+1)*(2*halfWidth+1)){break;}\n\t\tint i = int(cnt / (2*halfWidth+1)) - halfWidth;\n\t\tint j = cnt - halfWidth - int(cnt / (2*halfWidth+1)) * (2*halfWidth+1);\n\n\t\tfloat d = length(vec2(i,j));\n\t\tvec2 kernel = vec2( exp( -d * d / twoSigmaESquared ), \n\t\t\t\t\t\t\texp( -d * d / twoSigmaRSquared ));\n\n\t\tvec2 L = texture2D(src, (uv + vec2(i,j)) * Frag).xx;\n\n\t\tnorm += kernel;\n\t\tsum += kernel * L;\n\t}\n\n\tsum /= norm;\n\n\tfloat H = 100.0 * ((1.0 + p) * sum.x - p * sum.y);\n\tfloat edge = ( H > epsilon )? 1.0 : 1.0 + tanh( phi * (H - epsilon));\n\tdestColor = vec3(edge);\n\tgl_FragColor = vec4(destColor, 1.0);\n}",a.registerNodeType("texture/xDoG",I),C.title="Webcam",C.desc="Webcam texture",C.is_webcam_open=!1,C.prototype.openStream=function(){var t,e;navigator.getUserMedia&&(t={audio:!(this._waiting_confirmation=!0),video:{facingMode:this.properties.facingMode}},navigator.mediaDevices.getUserMedia(t).then(this.streamReady.bind(this)).catch(function(t){C.is_webcam_open=!1,console.log("Webcam rejected",t),e._webcam_stream=!1,e.boxcolor="red",e.trigger("stream_error")}),e=this)},C.prototype.closeStream=function(){if(this._webcam_stream){var t=this._webcam_stream.getTracks();if(t.length)for(var e=0;e{var a,n,h,u,l,d,c,o,_;function e(){for(var t in d.length=0,u){var e,i=u[t],s=i.indexOf(" "),o=i.substr(0,s),n=i.indexOf("(",s),s=i.substr(s,n-s).trim(),r=i.substr(n+1,i.length-n-2).split(",");for(e in r){var a=r[e].split(" ").filter(function(t){return t});r[e]={type:a[0].trim(),name:a[1].trim()},"="==a[2]&&(r[e].value=a[3].trim())}l[t]={return_type:o,func:s,params:r},d.push(s)}}function i(t,e){e.color="#345",e.filter="shader",e.prototype.clearDestination=function(){this.shader_destination={}},e.prototype.propagateDestination=function(t){if(this.shader_destination[t]=!0,this.inputs)for(var e=0;eo+a.NODE_TITLE_HEIGHT&&t.drawImage(r,10,n,this.size[0]-20,this.size[1]-o-a.NODE_TITLE_HEIGHT),n=this.size[1]-a.NODE_TITLE_HEIGHT+.5,r=a.isInsideRectangle(s[0],s[1],this.pos[0],this.pos[1]+n,this.size[0],a.NODE_TITLE_HEIGHT),t.fillStyle=r?"#555":"#222",t.beginPath(),this._shape==a.BOX_SHAPE?t.rect(0,n,this.size[0]+1,a.NODE_TITLE_HEIGHT):t.roundRect(0,n,this.size[0]+1,a.NODE_TITLE_HEIGHT,0,8),t.fill(),t.textAlign="center",t.font="24px Arial",t.fillStyle=r?"#DDD":"#999",t.fillText("+",.5*this.size[0],n+24))},r.prototype.onMouseDown=function(t,e,i){var s=this.size[1]-a.NODE_TITLE_HEIGHT+.5;e[1]>s&&i.showSubgraphPropertiesDialog(this)},r.prototype.onDrawSubgraphBackground=function(t){},r.prototype.getExtraMenuOptions=function(t){var e=this;return[{content:"Print Code",callback:function(){var t=e._context.computeShaderCode();console.log(t.vs_code,t.fs_code)}}]},a.registerNodeType("texture/shaderGraph",r),m.title="Uniform",m.desc="Input data for the shader",m.prototype.getTitle=function(){return this.properties.name&&this.flags.collapsed?this.properties.type+" "+this.properties.name:"Uniform"},m.prototype.onPropertyChanged=function(t,e){this.outputs[0].name=this.properties.type+" "+this.properties.name},m.prototype.onGetCode=function(t){if(this.shader_destination){var e=this.properties.type;if(!e){if(!t.onGetPropertyInfo)return;var i=t.onGetPropertyInfo(this.property.name);if(!i)return;e=i.type}"number"==e?e="float":"texture"==e&&(e="sampler2D"),-1!=n.GLSL_types.indexOf(e)&&(t.addUniform("u_"+this.properties.name,e),this.setOutputData(0,e))}},m.prototype.getOutputVarName=function(t){return"u_"+this.properties.name},i("input/uniform",m),y.title="Attribute",y.desc="Input data from mesh attribute",y.prototype.getTitle=function(){return"att. "+this.properties.name},y.prototype.onGetCode=function(t){var e;this.shader_destination&&(e=this.properties.type)&&-1!=n.GLSL_types.indexOf(e)&&("number"==e&&(e="float"),"coord"!=this.properties.name&&t.addCode("varying"," varying "+e+" v_"+this.properties.name+";"),this.setOutputData(0,e))},y.prototype.getOutputVarName=function(t){return"v_"+this.properties.name},i("input/attribute",y),x.title="Sampler2D",x.desc="Reads a pixel from a texture",x.prototype.onGetCode=function(t){var e,i,s;this.shader_destination&&(e=f(this,0),s="vec4 "+(i=g(this))+" = vec4(0.0);\n",e&&(s+=i+" = texture2D("+e+","+(f(this,1)||t.buffer_names.uvs)+");\n"),v(this,0)&&(s+="vec4 "+v(this,0)+" = "+i+";\n"),v(this,1)&&(s+="vec3 "+v(this,1)+" = "+i+".xyz;\n"),t.addCode("code",s,this.shader_destination),this.setOutputData(0,"vec4"),this.setOutputData(1,"vec3"))},i("texture/sampler2D",x),b.title="const",b.prototype.getTitle=function(){return this.flags.collapsed?c(this.properties.value,this.properties.type,2):"Const"},b.prototype.onPropertyChanged=function(t,e){"type"==t&&(this.outputs[0].type!=e&&(this.disconnectOutput(0),this.outputs[0].type=e),this.widgets.length=1,this.updateWidgets()),"value"==t&&(e.length?(this.widgets[1].value=e[1],2{var o=t.LiteGraph,n=new Float32Array(16),r=new Float32Array(16),a=new Float32Array(16),s=new Float32Array(16),u={u_view:n,u_projection:r,u_viewprojection:a,u_model:s};function v(){return 1e5*Math.random()|0}function g(){this.addInput("obj",""),this.addInput("radius","number"),this.addOutput("out","geometry"),this.addOutput("points","[vec3]"),this.properties={radius:1,num_points:4096,generate_normals:!0,regular:!1,mode:g.SPHERE,force_update:!1},this.points=new Float32Array(3*this.properties.num_points),this.normals=new Float32Array(3*this.properties.num_points),this.must_update=!0,this.version=0;var t=this;this.addWidget("button","update",null,function(){t.must_update=!0}),this.geometry={vertices:null,_id:v()},this._old_obj=null,this._last_radius=null}function m(){this.addInput("points","geometry"),this.addOutput("instances","[mat4]"),this.properties={mode:1,autoupdate:!0},this.must_update=!0,this.matrices=[],this.first_time=!0}function e(){this.addInput("in","geometry,[mat4]"),this.addInput("mat4","mat4"),this.addOutput("out","geometry"),this.properties={},this.geometry={type:"triangles",vertices:null,_id:v(),_version:0},this._last_geometry_id=-1,this._last_version=-1,this._last_key="",this.must_update=!0}function h(){this.addInput("sides","number"),this.addInput("radius","number"),this.addOutput("out","geometry"),this.properties={sides:6,radius:1,uvs:!1},this.geometry={type:"line_loop",vertices:null,_id:v()},this.geometry_id=-1,this.version=-1,this.must_update=!0,this.last_info={sides:-1,radius:-1}}function p(){this.addInput("","geometry"),this.addOutput("","geometry"),this.properties={top_cap:!0,bottom_cap:!0,offset:[0,100,0]},this.version=-1,this._last_geo_version=-1,this._must_update=!0}function l(){this.addInput("in","geometry"),this.addOutput("out","geometry"),this.properties={code:"V[1] += 0.01 * Math.sin(I + T*0.001);",execute_every_frame:!1},this.geometry=null,this.geometry_id=-1,this.version=-1,this.must_update=!0,this.vertices=null,this.func=null}function d(){this.addInput("in","geometry"),this.addOutput("out","geometry"),this.properties={min_dist:.4,max_dist:.5,max_connections:0,probability:1},this.geometry_id=-1,this.version=-1,this.my_version=1,this.must_update=!0}function c(){this.addInput("mesh","mesh"),this.addOutput("out","geometry"),this.geometry={},this.last_mesh=null}function _(){this.addInput("in","geometry"),this.addOutput("mesh","mesh"),this.properties={},this.version=-1,this.mesh=null}function f(){this.addInput("mesh","mesh"),this.addInput("mat4","mat4"),this.addInput("tex","texture"),this.properties={enabled:!0,primitive:GL.TRIANGLES,additive:!1,color:[1,1,1],opacity:1},this.color=vec4.create([1,1,1,1]),this.model_matrix=mat4.create(),this.uniforms={u_color:this.color,u_model:this.model_matrix}}function y(){this.addInput("size","number"),this.addOutput("out","mesh"),this.properties={type:1,size:1,subdivisions:32},this.version=1e5*Math.random()|0,this.last_info={type:-1,size:-1,subdivisions:-1}}function x(){this.addInput("in","geometry"),this.addInput("mat4","mat4"),this.addInput("tex","texture"),this.properties={enabled:!0,point_size:.1,fixed_size:!1,additive:!0,color:[1,1,1],opacity:1},this.color=vec4.create([1,1,1,1]),this.uniforms={u_point_size:1,u_perspective:1,u_point_perspective:1,u_color:this.color},this.geometry_id=-1,this.version=-1,this.mesh=null}o.LGraphRender={onRequestCameraMatrices:null},(t.LGraphPoints3D=g).widgets_info={mode:{widget:"combo",values:g.MODE_VALUES={rectangle:g.RECTANGLE=1,circle:g.CIRCLE=2,cube:g.CUBE=10,sphere:g.SPHERE=11,hemisphere:g.HEMISPHERE=12,inside_sphere:g.INSIDE_SPHERE=13,object:g.OBJECT=20,object_uniformly:g.OBJECT_UNIFORMLY=21,object_inside:g.OBJECT_INSIDE=22}}},g.title="list of points",g.desc="returns an array of points",g.prototype.onPropertyChanged=function(t,e){this.must_update=!0},g.prototype.onExecute=function(){var t=this.getInputData(0),t=((t!=this._old_obj||t&&t._version!=this._old_obj_version)&&(this._old_obj=t,this.must_update=!0),this.getInputData(1));null==t&&(t=this.properties.radius),this._last_radius!=t&&(this._last_radius=t,this.must_update=!0),(this.must_update||this.properties.force_update)&&(this.must_update=!1,this.updatePoints()),this.geometry.vertices=this.points,this.geometry.normals=this.normals,this.geometry._version=this.version,this.setOutputData(0,this.geometry)},g.prototype.updatePoints=function(){var t=0|this.properties.num_points,e=(t<1&&(t=1),this.points&&this.points.length==3*t||(this.points=new Float32Array(3*t)),this.properties.generate_normals?this.normals&&this.normals.length==this.points.length||(this.normals=new Float32Array(this.points.length)):this.normals=null,this._last_radius||this.properties.radius),i=this.properties.mode,s=this.getInputData(0);this._old_obj_version=s?s._version:null,this.points=g.generatePoints(e,t,i,this.points,this.normals,this.properties.regular,s),this.version++},g.generatePoints=function(t,e,i,s,o,n,r){var a=3*e,u=(s&&s.length==a||(s=new Float32Array(a)),new Float32Array(3)),h=new Float32Array([0,1,0]);if(n){if(i==g.RECTANGLE){for(var p=Math.floor(Math.sqrt(e)),l=0;l{var i=t.length,s=0,o=0,n=i;if(0==i)return-1;if(1==i)return 0;for(;s<=n;){var r=t[o=.5*(n+s)|0];if(r==e)return o;if(s==n-1)return s;r{var t=h.LiteGraph,d=h.LGraphTexture;function r(){this.addInput("Texture","Texture"),this.addInput("Aberration","number"),this.addInput("Distortion","number"),this.addInput("Blur","number"),this.addOutput("Texture","Texture"),this.properties={aberration:1,distortion:1,blur:1,precision:d.DEFAULT},r._shader||(r._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,r.pixel_shader),r._texture=new GL.Texture(3,1,{format:gl.RGB,wrap:gl.CLAMP_TO_EDGE,magFilter:gl.LINEAR,minFilter:gl.LINEAR,pixel_data:[255,0,0,0,255,0,0,0,255]}))}function c(){this.addInput("Texture","Texture"),this.addInput("Blurred","Texture"),this.addInput("Mask","Texture"),this.addInput("Threshold","number"),this.addOutput("Texture","Texture"),this.properties={shape:"",size:10,alpha:1,threshold:1,high_precision:!1}}function p(){this.addInput("Texture","Texture"),this.addInput("value1","number"),this.addInput("value2","number"),this.addOutput("Texture","Texture"),this.properties={fx:"halftone",value1:1,value2:1,precision:d.DEFAULT}}function n(){this.addInput("Tex.","Texture"),this.addInput("intensity","number"),this.addOutput("Texture","Texture"),this.properties={intensity:1,invert:!1,precision:d.DEFAULT},n._shader||(n._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,n.pixel_shader))}"undefined"!=typeof GL&&(r.title="Lens",r.desc="Camera Lens distortion",r.widgets_info={precision:{widget:"combo",values:d.MODE_VALUES}},r.prototype.onExecute=function(){var t,e,i,s,o,n=this.getInputData(0);this.properties.precision===d.PASS_THROUGH?this.setOutputData(0,n):n&&(this._tex=d.getTargetTexture(n,this._tex,this.properties.precision),t=this.properties.aberration,this.isInputConnected(1)&&(t=this.getInputData(1),this.properties.aberration=t),e=this.properties.distortion,this.isInputConnected(2)&&(e=this.getInputData(2),this.properties.distortion=e),i=this.properties.blur,this.isInputConnected(3)&&(i=this.getInputData(3),this.properties.blur=i),gl.disable(gl.BLEND),gl.disable(gl.DEPTH_TEST),s=Mesh.getScreenQuad(),o=r._shader,this._tex.drawTo(function(){n.bind(0),o.uniforms({u_texture:0,u_aberration:t,u_distortion:e,u_blur:i}).draw(s)}),this.setOutputData(0,this._tex))},r.pixel_shader="precision highp float;\n\t\t\tprecision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec2 u_camera_planes;\n\t\t\tuniform float u_aberration;\n\t\t\tuniform float u_distortion;\n\t\t\tuniform float u_blur;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 coord = v_coord;\n\t\t\t\tfloat dist = distance(vec2(0.5), coord);\n\t\t\t\tvec2 dist_coord = coord - vec2(0.5);\n\t\t\t\tfloat percent = 1.0 + ((0.5 - dist) / 0.5) * u_distortion;\n\t\t\t\tdist_coord *= percent;\n\t\t\t\tcoord = dist_coord + vec2(0.5);\n\t\t\t\tvec4 color = texture2D(u_texture,coord, u_blur * dist);\n\t\t\t\tcolor.r = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0+0.01*u_aberration), u_blur * dist ).r;\n\t\t\t\tcolor.b = texture2D(u_texture,vec2(0.5) + dist_coord * (1.0-0.01*u_aberration), u_blur * dist ).b;\n\t\t\t\tgl_FragColor = color;\n\t\t\t}\n\t\t\t",t.registerNodeType("fx/lens",r),h.LGraphFXLens=r,c.title="Bokeh",c.desc="applies an Bokeh effect",c.widgets_info={shape:{widget:"texture"}},c.prototype.onExecute=function(){var t,e,i,s,o,n,r,a,u,h=this.getInputData(0),p=this.getInputData(1),l=this.getInputData(2);h&&l&&this.properties.shape?(p=p||h,(t=d.getTexture(this.properties.shape))&&(e=this.properties.threshold,this.isInputConnected(3)&&(e=this.getInputData(3),this.properties.threshold=e),i=gl.UNSIGNED_BYTE,this.properties.high_precision&&(i=gl.half_float_ext?gl.HALF_FLOAT_OES:gl.FLOAT),this._temp_texture&&this._temp_texture.type==i&&this._temp_texture.width==h.width&&this._temp_texture.height==h.height||(this._temp_texture=new GL.Texture(h.width,h.height,{type:i,format:gl.RGBA,filter:gl.LINEAR})),this.properties.size,s=(s=c._first_shader)||(c._first_shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,c._first_pixel_shader)),o=(o=c._second_shader)||(c._second_shader=new GL.Shader(c._second_vertex_shader,c._second_pixel_shader)),(n=this._points_mesh)&&n._width==h.width&&n._height==h.height&&2==n._spacing||(n=this.createPointsMesh(h.width,h.height,2)),r=Mesh.getScreenQuad(),a=this.properties.size,this.properties.min_light,u=this.properties.alpha,gl.disable(gl.DEPTH_TEST),gl.disable(gl.BLEND),this._temp_texture.drawTo(function(){h.bind(0),p.bind(1),l.bind(2),s.uniforms({u_texture:0,u_texture_blur:1,u_mask:2,u_texsize:[h.width,h.height]}).draw(r)}),this._temp_texture.drawTo(function(){gl.enable(gl.BLEND),gl.blendFunc(gl.ONE,gl.ONE),h.bind(0),t.bind(3),o.uniforms({u_texture:0,u_mask:2,u_shape:3,u_alpha:u,u_threshold:e,u_pointSize:a,u_itexsize:[1/h.width,1/h.height]}).draw(n,gl.POINTS)}),this.setOutputData(0,this._temp_texture))):this.setOutputData(0,h)},c.prototype.createPointsMesh=function(t,e,i){for(var s=Math.round(t/i),o=Math.round(e/i),n=new Float32Array(s*o*2),r=-1,a=2/t*i,u=2/e*i,h=0;h{var e,o=t.LiteGraph,t="#243";function n(t){this.channel=0,this.cmd=0,this.data=new Uint32Array(3),t&&this.setup(t)}for(e in(o.MIDIEvent=n).prototype.fromJSON=function(t){this.setup(t.data)},n.prototype.setup=function(t){var e=t,t=(t.constructor===Object&&(e=t.data),this.data.set(e),e[0]),e=240&(this.status=t);this.cmd=240<=t?t:e,this.cmd==n.NOTEON&&0==this.velocity&&(this.cmd=n.NOTEOFF),this.cmd_str=n.commands[this.cmd]||"",(n.NOTEON<=e||e<=n.NOTEOFF)&&(this.channel=15&t)},Object.defineProperty(n.prototype,"velocity",{get:function(){return this.cmd==n.NOTEON?this.data[2]:-1},set:function(t){this.data[2]=t},enumerable:!0}),n.notes=["A","A#","B","C","C#","D","D#","E","F","F#","G","G#"],n.note_to_index={A:0,"A#":1,B:2,C:3,"C#":4,D:5,"D#":6,E:7,F:8,"F#":9,G:10,"G#":11},Object.defineProperty(n.prototype,"note",{get:function(){return this.cmd!=n.NOTEON?-1:n.toNoteString(this.data[1],!0)},set:function(t){throw"notes cannot be assigned this way, must modify the data[1]"},enumerable:!0}),Object.defineProperty(n.prototype,"octave",{get:function(){var t;return this.cmd!=n.NOTEON?-1:(t=this.data[1]-24,Math.floor(t/12+1))},set:function(t){throw"octave cannot be assigned this way, must modify the data[1]"},enumerable:!0}),n.prototype.getPitch=function(){return 440*Math.pow(2,(this.data[1]-69)/12)},n.computePitch=function(t){return 440*Math.pow(2,(t-69)/12)},n.prototype.getCC=function(){return this.data[1]},n.prototype.getCCValue=function(){return this.data[2]},n.prototype.getPitchBend=function(){return this.data[1]+(this.data[2]<<7)-8192},n.computePitchBend=function(t,e){return t+(e<<7)-8192},n.prototype.setCommandFromString=function(t){this.cmd=n.computeCommandFromString(t)},n.computeCommandFromString=function(t){if(!t)return 0;if(t&&t.constructor===Number)return t;switch(t=t.toUpperCase()){case"NOTE ON":case"NOTEON":return n.NOTEON;case"NOTE OFF":case"NOTEOFF":return n.NOTEON;case"KEY PRESSURE":case"KEYPRESSURE":return n.KEYPRESSURE;case"CONTROLLER CHANGE":case"CONTROLLERCHANGE":case"CC":return n.CONTROLLERCHANGE;case"PROGRAM CHANGE":case"PROGRAMCHANGE":case"PC":return n.PROGRAMCHANGE;case"CHANNEL PRESSURE":case"CHANNELPRESSURE":return n.CHANNELPRESSURE;case"PITCH BEND":case"PITCHBEND":return n.PITCHBEND;case"TIME TICK":case"TIMETICK":return n.TIMETICK;default:return Number(t)}},n.toNoteString=function(t,e){var i=(t=Math.round(t))-21,t=Math.floor((t-24)/12+1);return n.notes[i=(i%=12)<0?12+i:i]+(e?"":t)},n.NoteStringToPitch=function(t){var e=(t=t.toUpperCase())[0],i=4,t=("#"==t[1]?(e+="#",2this.properties.max_value)return}this.trigger("on_midi",e)}},o.registerNodeType("midi/filter",u),h.title="MIDIEvent",h.desc="Create a MIDI Event",h.color=t,h.prototype.onAction=function(t,e){"assign"==t?(this.properties.channel=e.channel,this.properties.cmd=e.cmd,this.properties.value1=e.data[1],this.properties.value2=e.data[2],e.cmd==n.NOTEON?this.gate=!0:e.cmd==n.NOTEOFF&&(this.gate=!1)):((e=this.midi_event).channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?e.setCommandFromString(this.properties.cmd):e.cmd=this.properties.cmd,e.data[0]=e.cmd|e.channel,e.data[1]=Number(this.properties.value1),e.data[2]=Number(this.properties.value2),this.trigger("on_midi",e))},h.prototype.onExecute=function(){var t=this.properties;if(this.inputs)for(var e=0;er+a||t[1]>n))return o}}return-1},f.prototype.onAction=function(t,e){if("reset"==t)for(var i=0;i{var n=t.LiteGraph,p={};function e(){this.properties={src:"",gain:.5,loop:!0,autoplay:!0,playbackRate:1},this._loading_audio=!1,this._audiobuffer=null,this._audionodes=[],this._last_sourcenode=null,this.addOutput("out","audio"),this.addInput("gain","number");var t=p.getAudioContext();this.audionode=t.createGain(),(this.audionode.graphnode=this).audionode.gain.value=this.properties.gain,this.properties.src&&this.loadSound(this.properties.src)}function i(){this.properties={gain:.5},this._audionodes=[],this._media_stream=null,this.addOutput("out","audio"),this.addInput("gain","number");var t=p.getAudioContext();this.audionode=t.createGain(),(this.audionode.graphnode=this).audionode.gain.value=this.properties.gain}function s(){this.properties={fftSize:2048,minDecibels:-100,maxDecibels:-10,smoothingTimeConstant:.5};var t=p.getAudioContext();this.audionode=t.createAnalyser(),(this.audionode.graphnode=this).audionode.fftSize=this.properties.fftSize,this.audionode.minDecibels=this.properties.minDecibels,this.audionode.maxDecibels=this.properties.maxDecibels,this.audionode.smoothingTimeConstant=this.properties.smoothingTimeConstant,this.addInput("in","audio"),this.addOutput("freqs","array"),this.addOutput("samples","array"),this._freq_bin=null,this._time_bin=null}function o(){this.properties={gain:1},this.audionode=p.getAudioContext().createGain(),this.addInput("in","audio"),this.addInput("gain","number"),this.addOutput("out","audio")}function r(){this.properties={impulse_src:"",normalize:!0},this.audionode=p.getAudioContext().createConvolver(),this.addInput("in","audio"),this.addOutput("out","audio")}function a(){this.properties={threshold:-50,knee:40,ratio:12,reduction:-20,attack:0,release:.25},this.audionode=p.getAudioContext().createDynamicsCompressor(),this.addInput("in","audio"),this.addOutput("out","audio")}function u(){this.properties={},this.audionode=p.getAudioContext().createWaveShaper(),this.addInput("in","audio"),this.addInput("shape","waveshape"),this.addOutput("out","audio")}function h(){this.properties={gain1:.5,gain2:.5},this.audionode=p.getAudioContext().createGain(),this.audionode1=p.getAudioContext().createGain(),this.audionode1.gain.value=this.properties.gain1,this.audionode2=p.getAudioContext().createGain(),this.audionode2.gain.value=this.properties.gain2,this.audionode1.connect(this.audionode),this.audionode2.connect(this.audionode),this.addInput("in1","audio"),this.addInput("in1 gain","number"),this.addInput("in2","audio"),this.addInput("in2 gain","number"),this.addOutput("out","audio")}function l(){this.properties={A:.1,D:.1,S:.1,R:.1},this.audionode=p.getAudioContext().createGain(),this.audionode.gain.value=0,this.addInput("in","audio"),this.addInput("gate","boolean"),this.addOutput("out","audio"),this.gate=!1}function d(){this.properties={delayTime:.5},this.audionode=p.getAudioContext().createDelay(10),this.audionode.delayTime.value=this.properties.delayTime,this.addInput("in","audio"),this.addInput("time","number"),this.addOutput("out","audio")}function c(){this.properties={frequency:350,detune:0,Q:1},this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]}),this.audionode=p.getAudioContext().createBiquadFilter(),this.addInput("in","audio"),this.addOutput("out","audio")}function _(){this.properties={frequency:440,detune:0,type:"sine"},this.addProperty("type","sine","enum",{values:["sine","square","sawtooth","triangle","custom"]}),this.audionode=p.getAudioContext().createOscillator(),this.addOutput("out","audio")}function g(){this.properties={continuous:!0,mark:-1},this.addInput("data","array"),this.addInput("mark","number"),this.size=[300,200],this._last_buffer=null}function f(){this.properties={band:440,amplitude:1},this.addInput("freqs","array"),this.addOutput("signal","number")}function v(){v.default_code||(t=(i=v.default_function.toString()).indexOf("{")+1,e=i.lastIndexOf("}"),v.default_code=i.substr(t,e-t)),this.properties={code:v.default_code};var t,e,i=p.getAudioContext();i.createScriptProcessor?this.audionode=i.createScriptProcessor(4096,1,1):(console.warn("ScriptProcessorNode deprecated"),this.audionode=i.createGain()),this.processCode(),v._bypass_function||(v._bypass_function=this.audionode.onaudioprocess),this.addInput("in","audio"),this.addOutput("out","audio")}function m(){this.audionode=p.getAudioContext().destination,this.addInput("in","audio")}(t.LGAudio=p).getAudioContext=function(){if(!this._audio_context){if(window.AudioContext=window.AudioContext||window.webkitAudioContext,!window.AudioContext)return console.error("AudioContext not supported by browser"),null;this._audio_context=new AudioContext,this._audio_context.onmessage=function(t){console.log("msg",t)},this._audio_context.onended=function(t){console.log("ended",t)},this._audio_context.oncomplete=function(t){console.log("complete",t)}}return this._audio_context},p.connect=function(t,e){try{t.connect(e)}catch(t){console.warn("LGraphAudio:",t)}},p.disconnect=function(t,e){try{t.disconnect(e)}catch(t){console.warn("LGraphAudio:",t)}},p.changeAllAudiosConnections=function(t,e){if(t.inputs)for(var i=0;i=this.size[0]&&(n=this.size[0]-1),t.strokeStyle="red",t.beginPath(),t.moveTo(n,o),t.lineTo(n,0),t.stroke())}},g.title="Visualization",g.desc="Audio Visualization",n.registerNodeType("audio/visualization",g),f.prototype.onExecute=function(){var t,e,i;this._freqs=this.getInputData(0),this._freqs&&(i=this.properties.band,(i=(i=void 0!==(t=this.getInputData(1))?t:i)/(p.getAudioContext().sampleRate/this._freqs.length)*2)<(t=0)&&(t=this._freqs[0]),t=i>=this._freqs.length?this._freqs[this._freqs.length-1]:this._freqs[e=0|i]*(1-(i=i-e))+this._freqs[1+e]*i,this.setOutputData(0,t/255*this.properties.amplitude))},f.prototype.onGetInputs=function(){return[["band","number"]]},f.title="Signal",f.desc="extract the signal of some frequency",n.registerNodeType("audio/signal",f),v.prototype.onAdded=function(t){t.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)},v["@code"]={widget:"code",type:"code"},v.prototype.onStart=function(){this.audionode.onaudioprocess=this._callback},v.prototype.onStop=function(){this.audionode.onaudioprocess=v._bypass_function},v.prototype.onPause=function(){this.audionode.onaudioprocess=v._bypass_function},v.prototype.onUnpause=function(){this.audionode.onaudioprocess=this._callback},v.prototype.onExecute=function(){},v.prototype.onRemoved=function(){this.audionode.onaudioprocess=v._bypass_function},v.prototype.processCode=function(){try{var t=new Function("properties",this.properties.code);this._script=new t(this.properties),this._old_code=this.properties.code,this._callback=this._script.onaudioprocess}catch(t){console.error("Error in onaudioprocess code",t),this._callback=v._bypass_function,this.audionode.onaudioprocess=this._callback}},v.prototype.onPropertyChanged=function(t,e){"code"==t&&(this.properties.code=e,this.processCode(),this.graph)&&this.graph.status==LGraph.STATUS_RUNNING&&(this.audionode.onaudioprocess=this._callback)},v.default_function=function(){this.onaudioprocess=function(t){for(var e=t.inputBuffer,i=t.outputBuffer,s=0;s{var n=t.LiteGraph;function e(){this.size=[60,20],this.addInput("send",n.ACTION),this.addOutput("received",n.EVENT),this.addInput("in",0),this.addOutput("out",0),this.properties={url:"",room:"lgraph",only_send_changes:!0},this._ws=null,this._last_sent_data=[],this._last_received_data=[]}function i(){this.room_widget=this.addWidget("text","Room","lgraph",this.setRoom.bind(this)),this.addWidget("button","Reconnect",null,this.connectSocket.bind(this)),this.addInput("send",n.ACTION),this.addOutput("received",n.EVENT),this.addInput("in",0),this.addOutput("out",0),this.properties={url:"tamats.com:55000",room:"lgraph",only_send_changes:!0},this._server=null,this.connectSocket(),this._last_sent_data=[],this._last_received_data=[],"undefined"==typeof SillyClient&&console.warn("remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js")}function s(){this.addInput("request",n.ACTION),this.addInput("url","string"),this.addProperty("url",""),this.addOutput("ready",n.EVENT),this.addOutput("data","string"),this.addWidget("button","Fetch",null,this.fetch.bind(this)),this._data=null,this._fetching=null}e.title="WebSocket",e.desc="Send data through a websocket",e.prototype.onPropertyChanged=function(t,e){"url"==t&&this.connectSocket()},e.prototype.onExecute=function(){if(!this._ws&&this.properties.url&&this.connectSocket(),this._ws&&this._ws.readyState==WebSocket.OPEN){for(var t=this.properties.room,e=this.properties.only_send_changes,i=1;i{if(t.ok)return this.boxcolor="#0F0",t.text();this.boxcolor="#F00",e.trigger("error")}).then(t=>{e._data=t,e._fetching=null,e.trigger("ready")}))},s.prototype.onAction=function(t){"request"==t&&this.fetch()},s.prototype.onExecute=function(){this.setOutputData(1,this._data)},s.prototype.onGetOutputs=function(){return[["error",n.EVENT]]},n.registerNodeType("network/httprequest",s)})(this); \ No newline at end of file diff --git a/build/litegraph_mini.js b/build/litegraph_mini.js index d460fb8b9..6df4bf8a7 100644 --- a/build/litegraph_mini.js +++ b/build/litegraph_mini.js @@ -1,18452 +1,18538 @@ -//packer version - - -(function(global) { - // ************************************************************* - // LiteGraph CLASS ******* - // ************************************************************* - - /** - * The Global Scope. It contains all the registered node classes. - * - * @class LiteGraph - * @constructor - */ - - var LiteGraph = (global.LiteGraph = { - VERSION: 0.4, - - CANVAS_GRID_SIZE: 10, - - NODE_TITLE_HEIGHT: 30, - NODE_TITLE_TEXT_Y: 20, - NODE_SLOT_HEIGHT: 20, - NODE_WIDGET_HEIGHT: 20, - NODE_WIDTH: 140, - NODE_MIN_WIDTH: 50, - NODE_COLLAPSED_RADIUS: 10, - NODE_COLLAPSED_WIDTH: 80, - NODE_TITLE_COLOR: "#999", - NODE_SELECTED_TITLE_COLOR: "#FFF", - NODE_TEXT_SIZE: 14, - NODE_TEXT_COLOR: "#AAA", - NODE_SUBTEXT_SIZE: 12, - NODE_DEFAULT_COLOR: "#333", - NODE_DEFAULT_BGCOLOR: "#353535", - NODE_DEFAULT_BOXCOLOR: "#666", - NODE_DEFAULT_SHAPE: "box", - NODE_BOX_OUTLINE_COLOR: "#FFF", - DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)", - DEFAULT_GROUP_FONT: 24, - - WIDGET_BGCOLOR: "#222", - WIDGET_OUTLINE_COLOR: "#666", - WIDGET_TEXT_COLOR: "#DDD", - WIDGET_SECONDARY_TEXT_COLOR: "#999", - - LINK_COLOR: "#9A9", - EVENT_LINK_COLOR: "#A86", - CONNECTING_LINK_COLOR: "#AFA", - - MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops - DEFAULT_POSITION: [100, 100], //default node position - VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" - - //shapes are used for nodes but also for slots - BOX_SHAPE: 1, - ROUND_SHAPE: 2, - CIRCLE_SHAPE: 3, - CARD_SHAPE: 4, - ARROW_SHAPE: 5, - GRID_SHAPE: 6, // intended for slot arrays - - //enums - INPUT: 1, - OUTPUT: 2, - - EVENT: -1, //for outputs - ACTION: -1, //for inputs - - NODE_MODES: ["Always", "On Event", "Never", "On Trigger"], // helper, will add "On Request" and more in the future - NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode - ALWAYS: 0, - ON_EVENT: 1, - NEVER: 2, - ON_TRIGGER: 3, - - UP: 1, - DOWN: 2, - LEFT: 3, - RIGHT: 4, - CENTER: 5, - - LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper - STRAIGHT_LINK: 0, - LINEAR_LINK: 1, - SPLINE_LINK: 2, - - NORMAL_TITLE: 0, - NO_TITLE: 1, - TRANSPARENT_TITLE: 2, - AUTOHIDE_TITLE: 3, - VERTICAL_LAYOUT: "vertical", // arrange nodes vertically - - proxy: null, //used to redirect calls - node_images_path: "", - - debug: false, - catch_exceptions: true, - throw_errors: true, - allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits - use_deferred_actions: true, //executes actions during the graph execution flow - registered_node_types: {}, //nodetypes by string - node_types_by_file_extension: {}, //used for dropping files in the canvas - Nodes: {}, //node types by classname - Globals: {}, //used to store vars between graphs - - searchbox_extras: {}, //used to add extra features to the search box - auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus - - node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback - node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback - - dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - dialog_close_on_mouse_leave_delay: 500, - - shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys - click_do_break_link_to: false, // [false!]prefer false, way too easy to break links - - search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false - search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] - search_show_all_on_open: true, // [true!] opens the results list when opening the search widget - - auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] - - // set these values if not using auto_load_slot_types - registered_slot_in_types: {}, // slot types for nodeclass - registered_slot_out_types: {}, // slot types for nodeclass - slot_types_in: [], // slot types IN - slot_types_out: [], // slot types OUT - slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - slot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search - - alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node - - do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this - - allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one - - middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel) - - release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults - - pointerevents_method: "mouse", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) - // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - - ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes - - // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. - // use this if you must have node IDs that are unique across all graphs and subgraphs. - use_uuids: false, - - /** - * Register a node class so it can be listed when the user wants to create a new one - * @method registerNodeType - * @param {String} type name of the node and path - * @param {Class} base_class class containing the structure of a node - */ - - registerNodeType: function(type, base_class) { - if (!base_class.prototype) { - throw "Cannot register a simple object, it must be a class with a prototype"; - } - base_class.type = type; - - if (LiteGraph.debug) { - console.log("Node registered: " + type); - } - - const classname = base_class.name; - - const pos = type.lastIndexOf("/"); - base_class.category = type.substring(0, pos); - - if (!base_class.title) { - base_class.title = classname; - } - - //extend class - for (var i in LGraphNode.prototype) { - if (!base_class.prototype[i]) { - base_class.prototype[i] = LGraphNode.prototype[i]; - } - } - - const prev = this.registered_node_types[type]; - if(prev) { - console.log("replacing node type: " + type); - } - if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) { - Object.defineProperty(base_class.prototype, "shape", { - set: function(v) { - switch (v) { - case "default": - delete this._shape; - break; - case "box": - this._shape = LiteGraph.BOX_SHAPE; - break; - case "round": - this._shape = LiteGraph.ROUND_SHAPE; - break; - case "circle": - this._shape = LiteGraph.CIRCLE_SHAPE; - break; - case "card": - this._shape = LiteGraph.CARD_SHAPE; - break; - default: - this._shape = v; - } - }, - get: function() { - return this._shape; - }, - enumerable: true, - configurable: true - }); - - - //used to know which nodes to create when dragging files to the canvas - if (base_class.supported_extensions) { - for (let i in base_class.supported_extensions) { - const ext = base_class.supported_extensions[i]; - if(ext && ext.constructor === String) { - this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; - } - } - } - } - - this.registered_node_types[type] = base_class; - if (base_class.constructor.name) { - this.Nodes[classname] = base_class; - } - if (LiteGraph.onNodeTypeRegistered) { - LiteGraph.onNodeTypeRegistered(type, base_class); - } - if (prev && LiteGraph.onNodeTypeReplaced) { - LiteGraph.onNodeTypeReplaced(type, base_class, prev); - } - - //warnings - if (base_class.prototype.onPropertyChange) { - console.warn( - "LiteGraph node class " + - type + - " has onPropertyChange method, it must be called onPropertyChanged with d at the end" - ); - } - - // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types - if (this.auto_load_slot_types) { - new base_class(base_class.title || "tmpnode"); - } - }, - - /** - * removes a node type from the system - * @method unregisterNodeType - * @param {String|Object} type name of the node or the node constructor itself - */ - unregisterNodeType: function(type) { - const base_class = - type.constructor === String - ? this.registered_node_types[type] - : type; - if (!base_class) { - throw "node type not found: " + type; - } - delete this.registered_node_types[base_class.type]; - if (base_class.constructor.name) { - delete this.Nodes[base_class.constructor.name]; - } - }, - - /** - * Save a slot type and his node - * @method registerSlotType - * @param {String|Object} type name of the node or the node constructor itself - * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. - */ - registerNodeAndSlotType: function(type, slot_type, out){ - out = out || false; - const base_class = - type.constructor === String && - this.registered_node_types[type] !== "anonymous" - ? this.registered_node_types[type] - : type; - - const class_type = base_class.constructor.type; - - let allTypes = []; - if (typeof slot_type === "string") { - allTypes = slot_type.split(","); - } else if (slot_type == this.EVENT || slot_type == this.ACTION) { - allTypes = ["_event_"]; - } else { - allTypes = ["*"]; - } - - for (let i = 0; i < allTypes.length; ++i) { - let slotType = allTypes[i]; - if (slotType === "") { - slotType = "*"; - } - const registerTo = out - ? "registered_slot_out_types" - : "registered_slot_in_types"; - if (this[registerTo][slotType] === undefined) { - this[registerTo][slotType] = { nodes: [] }; - } - if (!this[registerTo][slotType].nodes.includes(class_type)) { - this[registerTo][slotType].nodes.push(class_type); - } - - // check if is a new type - if (!out) { - if (!this.slot_types_in.includes(slotType.toLowerCase())) { - this.slot_types_in.push(slotType.toLowerCase()); - this.slot_types_in.sort(); - } - } else { - if (!this.slot_types_out.includes(slotType.toLowerCase())) { - this.slot_types_out.push(slotType.toLowerCase()); - this.slot_types_out.sort(); - } - } - } - }, - - /** - * Create a new nodetype by passing an object with some properties - * like onCreate, inputs:Array, outputs:Array, properties, onExecute - * @method buildNodeClassFromObject - * @param {String} name node name with namespace (p.e.: 'math/sum') - * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute - */ - buildNodeClassFromObject: function( - name, - object - ) { - var ctor_code = ""; - if(object.inputs) - for(var i=0; i < object.inputs.length; ++i) - { - var _name = object.inputs[i][0]; - var _type = object.inputs[i][1]; - if(_type && _type.constructor === String) - _type = '"'+_type+'"'; - ctor_code += "this.addInput('"+_name+"',"+_type+");\n"; - } - if(object.outputs) - for(var i=0; i < object.outputs.length; ++i) - { - var _name = object.outputs[i][0]; - var _type = object.outputs[i][1]; - if(_type && _type.constructor === String) - _type = '"'+_type+'"'; - ctor_code += "this.addOutput('"+_name+"',"+_type+");\n"; - } - if(object.properties) - for(var i in object.properties) - { - var prop = object.properties[i]; - if(prop && prop.constructor === String) - prop = '"'+prop+'"'; - ctor_code += "this.addProperty('"+i+"',"+prop+");\n"; - } - ctor_code += "if(this.onCreate)this.onCreate()"; - var classobj = Function(ctor_code); - for(var i in object) - if(i!="inputs" && i!="outputs" && i!="properties") - classobj.prototype[i] = object[i]; - classobj.title = object.title || name.split("/").pop(); - classobj.desc = object.desc || "Generated from object"; - this.registerNodeType(name, classobj); - return classobj; - }, - - /** - * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. - * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. - * @method wrapFunctionAsNode - * @param {String} name node name with namespace (p.e.: 'math/sum') - * @param {Function} func - * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type - * @param {String} return_type [optional] string with the return type, otherwise it will be generic - * @param {Object} properties [optional] properties to be configurable - */ - wrapFunctionAsNode: function( - name, - func, - param_types, - return_type, - properties - ) { - var params = Array(func.length); - var code = ""; - if(param_types !== null) //null means no inputs - { - var names = LiteGraph.getParameterNames(func); - for (var i = 0; i < names.length; ++i) { - var type = 0; - if(param_types) - { - //type = param_types[i] != null ? "'" + param_types[i] + "'" : "0"; - if( param_types[i] != null && param_types[i].constructor === String ) - type = "'" + param_types[i] + "'" ; - else if( param_types[i] != null ) - type = param_types[i]; - } - code += - "this.addInput('" + - names[i] + - "'," + - type + - ");\n"; - } - } - if(return_type !== null) //null means no output - code += - "this.addOutput('out'," + - (return_type != null ? (return_type.constructor === String ? "'" + return_type + "'" : return_type) : 0) + - ");\n"; - if (properties) { - code += - "this.properties = " + JSON.stringify(properties) + ";\n"; - } - var classobj = Function(code); - classobj.title = name.split("/").pop(); - classobj.desc = "Generated from " + func.name; - classobj.prototype.onExecute = function onExecute() { - for (var i = 0; i < params.length; ++i) { - params[i] = this.getInputData(i); - } - var r = func.apply(this, params); - this.setOutputData(0, r); - }; - this.registerNodeType(name, classobj); - return classobj; - }, - - /** - * Removes all previously registered node's types - */ - clearRegisteredTypes: function() { - this.registered_node_types = {}; - this.node_types_by_file_extension = {}; - this.Nodes = {}; - this.searchbox_extras = {}; - }, - - /** - * Adds this method to all nodetypes, existing and to be created - * (You can add it to LGraphNode.prototype but then existing node types wont have it) - * @method addNodeMethod - * @param {Function} func - */ - addNodeMethod: function(name, func) { - LGraphNode.prototype[name] = func; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.prototype[name]) { - type.prototype["_" + name] = type.prototype[name]; - } //keep old in case of replacing - type.prototype[name] = func; - } - }, - - /** - * Create a node of a given type with a name. The node is not attached to any graph yet. - * @method createNode - * @param {String} type full name of the node class. p.e. "math/sin" - * @param {String} name a name to distinguish from other nodes - * @param {Object} options to set options - */ - - createNode: function(type, title, options) { - var base_class = this.registered_node_types[type]; - if (!base_class) { - if (LiteGraph.debug) { - console.log( - 'GraphNode type "' + type + '" not registered.' - ); - } - return null; - } - - var prototype = base_class.prototype || base_class; - - title = title || base_class.title || type; - - var node = null; - - if (LiteGraph.catch_exceptions) { - try { - node = new base_class(title); - } catch (err) { - console.error(err); - return null; - } - } else { - node = new base_class(title); - } - - node.type = type; - - if (!node.title && title) { - node.title = title; - } - if (!node.properties) { - node.properties = {}; - } - if (!node.properties_info) { - node.properties_info = []; - } - if (!node.flags) { - node.flags = {}; - } - if (!node.size) { - node.size = node.computeSize(); - //call onresize? - } - if (!node.pos) { - node.pos = LiteGraph.DEFAULT_POSITION.concat(); - } - if (!node.mode) { - node.mode = LiteGraph.ALWAYS; - } - - //extra options - if (options) { - for (var i in options) { - node[i] = options[i]; - } - } - - // callback - if ( node.onNodeCreated ) { - node.onNodeCreated(); - } - - return node; - }, - - /** - * Returns a registered node type with a given name - * @method getNodeType - * @param {String} type full name of the node class. p.e. "math/sin" - * @return {Class} the node class - */ - getNodeType: function(type) { - return this.registered_node_types[type]; - }, - - /** - * Returns a list of node types matching one category - * @method getNodeType - * @param {String} category category name - * @return {Array} array with all the node classes - */ - - getNodeTypesInCategory: function(category, filter) { - var r = []; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if (type.filter != filter) { - continue; - } - - if (category == "") { - if (type.category == null) { - r.push(type); - } - } else if (type.category == category) { - r.push(type); - } - } - - if (this.auto_sort_node_types) { - r.sort(function(a,b){return a.title.localeCompare(b.title)}); - } - - return r; - }, - - /** - * Returns a list with all the node type categories - * @method getNodeTypesCategories - * @param {String} filter only nodes with ctor.filter equal can be shown - * @return {Array} array with all the names of the categories - */ - getNodeTypesCategories: function( filter ) { - var categories = { "": 1 }; - for (var i in this.registered_node_types) { - var type = this.registered_node_types[i]; - if ( type.category && !type.skip_list ) - { - if(type.filter != filter) - continue; - categories[type.category] = 1; - } - } - var result = []; - for (var i in categories) { - result.push(i); - } - return this.auto_sort_node_types ? result.sort() : result; - }, - - //debug purposes: reloads all the js scripts that matches a wildcard - reloadNodes: function(folder_wildcard) { - var tmp = document.getElementsByTagName("script"); - //weird, this array changes by its own, so we use a copy - var script_files = []; - for (var i=0; i < tmp.length; i++) { - script_files.push(tmp[i]); - } - - var docHeadObj = document.getElementsByTagName("head")[0]; - folder_wildcard = document.location.href + folder_wildcard; - - for (var i=0; i < script_files.length; i++) { - var src = script_files[i].src; - if ( - !src || - src.substr(0, folder_wildcard.length) != folder_wildcard - ) { - continue; - } - - try { - if (LiteGraph.debug) { - console.log("Reloading: " + src); - } - var dynamicScript = document.createElement("script"); - dynamicScript.type = "text/javascript"; - dynamicScript.src = src; - docHeadObj.appendChild(dynamicScript); - docHeadObj.removeChild(script_files[i]); - } catch (err) { - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error while reloading " + src); - } - } - } - - if (LiteGraph.debug) { - console.log("Nodes reloaded"); - } - }, - - //separated just to improve if it doesn't work - cloneObject: function(obj, target) { - if (obj == null) { - return null; - } - var r = JSON.parse(JSON.stringify(obj)); - if (!target) { - return r; - } - - for (var i in r) { - target[i] = r[i]; - } - return target; - }, - - /* - * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 - */ - uuidv4: function() { - return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)); - }, - - /** - * Returns if the types of two slots are compatible (taking into account wildcards, etc) - * @method isValidConnection - * @param {String} type_a - * @param {String} type_b - * @return {Boolean} true if they can be connected - */ - isValidConnection: function(type_a, type_b) { - if (type_a=="" || type_a==="*") type_a = 0; - if (type_b=="" || type_b==="*") type_b = 0; - if ( - !type_a //generic output - || !type_b // generic input - || type_a == type_b //same type (is valid for triggers) - || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) - ) { - return true; - } - - // Enforce string type to handle toLowerCase call (-1 number not ok) - type_a = String(type_a); - type_b = String(type_b); - type_a = type_a.toLowerCase(); - type_b = type_b.toLowerCase(); - - // For nodes supporting multiple connection types - if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { - return type_a == type_b; - } - - // Check all permutations to see if one is valid - var supported_types_a = type_a.split(","); - var supported_types_b = type_b.split(","); - for (var i = 0; i < supported_types_a.length; ++i) { - for (var j = 0; j < supported_types_b.length; ++j) { - if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ - //if (supported_types_a[i] == supported_types_b[j]) { - return true; - } - } - } - - return false; - }, - - /** - * Register a string in the search box so when the user types it it will recommend this node - * @method registerSearchboxExtra - * @param {String} node_type the node recommended - * @param {String} description text to show next to it - * @param {Object} data it could contain info of how the node should be configured - * @return {Boolean} true if they can be connected - */ - registerSearchboxExtra: function(node_type, description, data) { - this.searchbox_extras[description.toLowerCase()] = { - type: node_type, - desc: description, - data: data - }; - }, - - /** - * Wrapper to load files (from url using fetch or from file using FileReader) - * @method fetchFile - * @param {String|File|Blob} url the url of the file (or the file itself) - * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" - * @param {Function} on_complete callback(data) - * @param {Function} on_error in case of an error - * @return {FileReader|Promise} returns the object used to - */ - fetchFile: function( url, type, on_complete, on_error ) { - var that = this; - if(!url) - return null; - - type = type || "text"; - if( url.constructor === String ) - { - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); - } - return fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); //it will be catch below - if(type == "arraybuffer") - return response.arrayBuffer(); - else if(type == "text" || type == "string") - return response.text(); - else if(type == "json") - return response.json(); - else if(type == "blob") - return response.blob(); - }) - .then(function(data) { - if(on_complete) - on_complete(data); - }) - .catch(function(error) { - console.error("error fetching file:",url); - if(on_error) - on_error(error); - }); - } - else if( url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.onload = function(e) - { - var v = e.target.result; - if( type == "json" ) - v = JSON.parse(v); - if(on_complete) - on_complete(v); - } - if(type == "arraybuffer") - return reader.readAsArrayBuffer(url); - else if(type == "text" || type == "json") - return reader.readAsText(url); - else if(type == "blob") - return reader.readAsBinaryString(url); - } - return null; - } - }); - - //timer that works everywhere - if (typeof performance != "undefined") { - LiteGraph.getTime = performance.now.bind(performance); - } else if (typeof Date != "undefined" && Date.now) { - LiteGraph.getTime = Date.now.bind(Date); - } else if (typeof process != "undefined") { - LiteGraph.getTime = function() { - var t = process.hrtime(); - return t[0] * 0.001 + t[1] * 1e-6; - }; - } else { - LiteGraph.getTime = function getTime() { - return new Date().getTime(); - }; - } - - //********************************************************************************* - // LGraph CLASS - //********************************************************************************* - - /** - * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. - * supported callbacks: - + onNodeAdded: when a new node is added to the graph - + onNodeRemoved: when a node inside this graph is removed - + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) - * - * @class LGraph - * @constructor - * @param {Object} o data from previous serialization [optional] - */ - - function LGraph(o) { - if (LiteGraph.debug) { - console.log("Graph created"); - } - this.list_of_graphcanvas = null; - this.clear(); - - if (o) { - this.configure(o); - } - } - - global.LGraph = LiteGraph.LGraph = LGraph; - - //default supported types - LGraph.supported_types = ["number", "string", "boolean"]; - - //used to know which types of connections support this graph (some graphs do not allow certain types) - LGraph.prototype.getSupportedTypes = function() { - return this.supported_types || LGraph.supported_types; - }; - - LGraph.STATUS_STOPPED = 1; - LGraph.STATUS_RUNNING = 2; - - /** - * Removes all nodes from this graph - * @method clear - */ - - LGraph.prototype.clear = function() { - this.stop(); - this.status = LGraph.STATUS_STOPPED; - - this.last_node_id = 0; - this.last_link_id = 0; - - this._version = -1; //used to detect changes - - //safe clear - if (this._nodes) { - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - if (node.onRemoved) { - node.onRemoved(); - } - } - } - - //nodes - this._nodes = []; - this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute sorted in execution order - - //other scene stuff - this._groups = []; - - //links - this.links = {}; //container with all the links - - //iterations - this.iteration = 0; - - //custom data - this.config = {}; - this.vars = {}; - this.extra = {}; //to store custom data - - //timing - this.globaltime = 0; - this.runningtime = 0; - this.fixedtime = 0; - this.fixedtime_lapse = 0.01; - this.elapsed_time = 0.01; - this.last_update_time = 0; - this.starttime = 0; - - this.catch_errors = true; - - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - - //subgraph_data - this.inputs = {}; - this.outputs = {}; - - //notify canvas to redraw - this.change(); - - this.sendActionToCanvas("clear"); - }; - - /** - * Attach Canvas to this graph - * @method attachCanvas - * @param {GraphCanvas} graph_canvas - */ - - LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { - throw "attachCanvas expects a LGraphCanvas instance"; - } - if (graphcanvas.graph && graphcanvas.graph != this) { - graphcanvas.graph.detachCanvas(graphcanvas); - } - - graphcanvas.graph = this; - - if (!this.list_of_graphcanvas) { - this.list_of_graphcanvas = []; - } - this.list_of_graphcanvas.push(graphcanvas); - }; - - /** - * Detach Canvas from this graph - * @method detachCanvas - * @param {GraphCanvas} graph_canvas - */ - LGraph.prototype.detachCanvas = function(graphcanvas) { - if (!this.list_of_graphcanvas) { - return; - } - - var pos = this.list_of_graphcanvas.indexOf(graphcanvas); - if (pos == -1) { - return; - } - graphcanvas.graph = null; - this.list_of_graphcanvas.splice(pos, 1); - }; - - /** - * Starts running this graph every interval milliseconds. - * @method start - * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate - */ - - LGraph.prototype.start = function(interval) { - if (this.status == LGraph.STATUS_RUNNING) { - return; - } - this.status = LGraph.STATUS_RUNNING; - - if (this.onPlayEvent) { - this.onPlayEvent(); - } - - this.sendEventToAllNodes("onStart"); - - //launch - this.starttime = LiteGraph.getTime(); - this.last_update_time = this.starttime; - interval = interval || 0; - var that = this; - - //execute once per frame - if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { - function on_frame() { - if (that.execution_timer_id != -1) { - return; - } - window.requestAnimationFrame(on_frame); - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - } - this.execution_timer_id = -1; - on_frame(); - } else { //execute every 'interval' ms - this.execution_timer_id = setInterval(function() { - //execute - if(that.onBeforeStep) - that.onBeforeStep(); - that.runStep(1, !that.catch_errors); - if(that.onAfterStep) - that.onAfterStep(); - }, interval); - } - }; - - /** - * Stops the execution loop of the graph - * @method stop execution - */ - - LGraph.prototype.stop = function() { - if (this.status == LGraph.STATUS_STOPPED) { - return; - } - - this.status = LGraph.STATUS_STOPPED; - - if (this.onStopEvent) { - this.onStopEvent(); - } - - if (this.execution_timer_id != null) { - if (this.execution_timer_id != -1) { - clearInterval(this.execution_timer_id); - } - this.execution_timer_id = null; - } - - this.sendEventToAllNodes("onStop"); - }; - - /** - * Run N steps (cycles) of the graph - * @method runStep - * @param {number} num number of steps to run, default is 1 - * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors - * @param {number} limit max number of nodes to execute (used to execute from start to a node) - */ - - LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { - num = num || 1; - - var start = LiteGraph.getTime(); - this.globaltime = 0.001 * (start - this.starttime); - - //not optimal: executes possible pending actions in node, problem is it is not optimized - //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute - - //from now on it will iterate only on executable nodes which is faster - var nodes = this._nodes_executable - ? this._nodes_executable - : this._nodes; - if (!nodes) { - return; - } - - limit = limit || nodes.length; - - if (do_not_catch_errors) { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) - node.executePendingActions(); - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - //wrap node.onExecute(); - node.doExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - } else { //catch errors - try { - //iterations - for (var i = 0; i < num; i++) { - for (var j = 0; j < limit; ++j) { - var node = nodes[j]; - if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) - node.executePendingActions(); - if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); - } - } - - this.fixedtime += this.fixedtime_lapse; - if (this.onExecuteStep) { - this.onExecuteStep(); - } - } - - if (this.onAfterExecute) { - this.onAfterExecute(); - } - this.errors_in_execution = false; - } catch (err) { - this.errors_in_execution = true; - if (LiteGraph.throw_errors) { - throw err; - } - if (LiteGraph.debug) { - console.log("Error during execution: " + err); - } - this.stop(); - } - } - - var now = LiteGraph.getTime(); - var elapsed = now - start; - if (elapsed == 0) { - elapsed = 1; - } - this.execution_time = 0.001 * elapsed; - this.globaltime += 0.001 * elapsed; - this.iteration += 1; - this.elapsed_time = (now - this.last_update_time) * 0.001; - this.last_update_time = now; - this.nodes_executing = []; - this.nodes_actioning = []; - this.nodes_executedAction = []; - }; - - /** - * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than - * nodes with only inputs. - * @method updateExecutionOrder - */ - LGraph.prototype.updateExecutionOrder = function() { - this._nodes_in_order = this.computeExecutionOrder(false); - this._nodes_executable = []; - for (var i = 0; i < this._nodes_in_order.length; ++i) { - if (this._nodes_in_order[i].onExecute) { - this._nodes_executable.push(this._nodes_in_order[i]); - } - } - }; - - //This is more internal, it computes the executable nodes in order and returns it - LGraph.prototype.computeExecutionOrder = function( - only_onExecute, - set_level - ) { - var L = []; - var S = []; - var M = {}; - var visited_links = {}; //to avoid repeating links - var remaining_links = {}; //to a - - //search for the nodes without inputs (starting nodes) - for (var i = 0, l = this._nodes.length; i < l; ++i) { - var node = this._nodes[i]; - if (only_onExecute && !node.onExecute) { - continue; - } - - M[node.id] = node; //add to pending nodes - - var num = 0; //num of input connections - if (node.inputs) { - for (var j = 0, l2 = node.inputs.length; j < l2; j++) { - if (node.inputs[j] && node.inputs[j].link != null) { - num += 1; - } - } - } - - if (num == 0) { - //is a starting node - S.push(node); - if (set_level) { - node._level = 1; - } - } //num of input links - else { - if (set_level) { - node._level = 0; - } - remaining_links[node.id] = num; - } - } - - while (true) { - if (S.length == 0) { - break; - } - - //get an starting node - var node = S.shift(); - L.push(node); //add to ordered list - delete M[node.id]; //remove from the pending nodes - - if (!node.outputs) { - continue; - } - - //for every output - for (var i = 0; i < node.outputs.length; i++) { - var output = node.outputs[i]; - //not connected - if ( - output == null || - output.links == null || - output.links.length == 0 - ) { - continue; - } - - //for every connection - for (var j = 0; j < output.links.length; j++) { - var link_id = output.links[j]; - var link = this.links[link_id]; - if (!link) { - continue; - } - - //already visited link (ignore it) - if (visited_links[link.id]) { - continue; - } - - var target_node = this.getNodeById(link.target_id); - if (target_node == null) { - visited_links[link.id] = true; - continue; - } - - if ( - set_level && - (!target_node._level || - target_node._level <= node._level) - ) { - target_node._level = node._level + 1; - } - - visited_links[link.id] = true; //mark as visited - remaining_links[target_node.id] -= 1; //reduce the number of links remaining - if (remaining_links[target_node.id] == 0) { - S.push(target_node); - } //if no more links, then add to starters array - } - } - } - - //the remaining ones (loops) - for (var i in M) { - L.push(M[i]); - } - - if (L.length != this._nodes.length && LiteGraph.debug) { - console.warn("something went wrong, nodes missing"); - } - - var l = L.length; - - //save order number in the node - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - //sort now by priority - L = L.sort(function(A, B) { - var Ap = A.constructor.priority || A.priority || 0; - var Bp = B.constructor.priority || B.priority || 0; - if (Ap == Bp) { - //if same priority, sort by order - return A.order - B.order; - } - return Ap - Bp; //sort by priority - }); - - //save order number in the node, again... - for (var i = 0; i < l; ++i) { - L[i].order = i; - } - - return L; - }; - - /** - * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. - * It doesn't include the node itself - * @method getAncestors - * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution - */ - LGraph.prototype.getAncestors = function(node) { - var ancestors = []; - var pending = [node]; - var visited = {}; - - while (pending.length) { - var current = pending.shift(); - if (!current.inputs) { - continue; - } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); - } - - for (var i = 0; i < current.inputs.length; ++i) { - var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); - } - } - } - - ancestors.sort(function(a, b) { - return a.order - b.order; - }); - return ancestors; - }; - - /** - * Positions every node in a more readable manner - * @method arrange - */ - LGraph.prototype.arrange = function (margin, layout) { - margin = margin || 100; - - const nodes = this.computeExecutionOrder(false, true); - const columns = []; - for (let i = 0; i < nodes.length; ++i) { - const node = nodes[i]; - const col = node._level || 1; - if (!columns[col]) { - columns[col] = []; - } - columns[col].push(node); - } - - let x = margin; - - for (let i = 0; i < columns.length; ++i) { - const column = columns[i]; - if (!column) { - continue; - } - let max_size = 100; - let y = margin + LiteGraph.NODE_TITLE_HEIGHT; - for (let j = 0; j < column.length; ++j) { - const node = column[j]; - node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; - node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; - const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; - if (node.size[max_size_index] > max_size) { - max_size = node.size[max_size_index]; - } - const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; - y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; - } - x += max_size + margin; - } - - this.setDirtyCanvas(true, true); - }; - - /** - * Returns the amount of time the graph has been running in milliseconds - * @method getTime - * @return {number} number of milliseconds the graph has been running - */ - LGraph.prototype.getTime = function() { - return this.globaltime; - }; - - /** - * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant - * @method getFixedTime - * @return {number} number of milliseconds the graph has been running - */ - - LGraph.prototype.getFixedTime = function() { - return this.fixedtime; - }; - - /** - * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct - * if the nodes are using graphical actions - * @method getElapsedTime - * @return {number} number of milliseconds it took the last cycle - */ - - LGraph.prototype.getElapsedTime = function() { - return this.elapsed_time; - }; - - /** - * Sends an event to all the nodes, useful to trigger stuff - * @method sendEventToAllNodes - * @param {String} eventname the name of the event (function to be called) - * @param {Array} params parameters in array format - */ - LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { - mode = mode || LiteGraph.ALWAYS; - - var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; - if (!nodes) { - return; - } - - for (var j = 0, l = nodes.length; j < l; ++j) { - var node = nodes[j]; - - if ( - node.constructor === LiteGraph.Subgraph && - eventname != "onExecute" - ) { - if (node.mode == mode) { - node.sendEventToAllNodes(eventname, params, mode); - } - continue; - } - - if (!node[eventname] || node.mode != mode) { - continue; - } - if (params === undefined) { - node[eventname](); - } else if (params && params.constructor === Array) { - node[eventname].apply(node, params); - } else { - node[eventname](params); - } - } - }; - - LGraph.prototype.sendActionToCanvas = function(action, params) { - if (!this.list_of_graphcanvas) { - return; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c[action]) { - c[action].apply(c, params); - } - } - }; - - /** - * Adds a new node instance to this graph - * @method add - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.add = function(node, skip_compute_order) { - if (!node) { - return; - } - - //groups - if (node.constructor === LGraphGroup) { - this._groups.push(node); - this.setDirtyCanvas(true); - this.change(); - node.graph = this; - this._version++; - return; - } - - //nodes - if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); - if (LiteGraph.use_uuids) { - node.id = LiteGraph.uuidv4(); - } - else { - node.id = ++this.last_node_id; - } - } - - if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { - throw "LiteGraph: max number of nodes in a graph reached"; - } - - //give him an id - if (LiteGraph.use_uuids) { - if (node.id == null || node.id == -1) - node.id = LiteGraph.uuidv4(); - } - else { - if (node.id == null || node.id == -1) { - node.id = ++this.last_node_id; - } else if (this.last_node_id < node.id) { - this.last_node_id = node.id; - } - } - - node.graph = this; - this._version++; - - this._nodes.push(node); - this._nodes_by_id[node.id] = node; - - if (node.onAdded) { - node.onAdded(this); - } - - if (this.config.align_to_grid) { - node.alignToGrid(); - } - - if (!skip_compute_order) { - this.updateExecutionOrder(); - } - - if (this.onNodeAdded) { - this.onNodeAdded(node); - } - - this.setDirtyCanvas(true); - this.change(); - - return node; //to chain actions - }; - - /** - * Removes a node from the graph - * @method remove - * @param {LGraphNode} node the instance of the node - */ - - LGraph.prototype.remove = function(node) { - if (node.constructor === LiteGraph.LGraphGroup) { - var index = this._groups.indexOf(node); - if (index != -1) { - this._groups.splice(index, 1); - } - node.graph = null; - this._version++; - this.setDirtyCanvas(true, true); - this.change(); - return; - } - - if (this._nodes_by_id[node.id] == null) { - return; - } //not found - - if (node.ignore_remove) { - return; - } //cannot be removed - - this.beforeChange(); //sure? - almost sure is wrong - - //disconnect inputs - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link != null) { - node.disconnectInput(i); - } - } - } - - //disconnect outputs - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); - } - } - } - - //node.id = -1; //why? - - //callback - if (node.onRemoved) { - node.onRemoved(); - } - - node.graph = null; - this._version++; - - //remove from canvas render - if (this.list_of_graphcanvas) { - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var canvas = this.list_of_graphcanvas[i]; - if (canvas.selected_nodes[node.id]) { - delete canvas.selected_nodes[node.id]; - } - if (canvas.node_dragged == node) { - canvas.node_dragged = null; - } - } - } - - //remove from containers - var pos = this._nodes.indexOf(node); - if (pos != -1) { - this._nodes.splice(pos, 1); - } - delete this._nodes_by_id[node.id]; - - if (this.onNodeRemoved) { - this.onNodeRemoved(node); - } - - //close panels - this.sendActionToCanvas("checkPanels"); - - this.setDirtyCanvas(true, true); - this.afterChange(); //sure? - almost sure is wrong - this.change(); - - this.updateExecutionOrder(); - }; - - /** - * Returns a node by its id. - * @method getNodeById - * @param {Number} id - */ - - LGraph.prototype.getNodeById = function(id) { - if (id == null) { - return null; - } - return this._nodes_by_id[id]; - }; - - /** - * Returns a list of nodes that matches a class - * @method findNodesByClass - * @param {Class} classObject the class itself (not an string) - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByClass = function(classObject, result) { - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].constructor === classObject) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns a list of nodes that matches a type - * @method findNodesByType - * @param {String} type the name of the node type - * @return {Array} a list with all the nodes of this type - */ - LGraph.prototype.findNodesByType = function(type, result) { - var type = type.toLowerCase(); - result = result || []; - result.length = 0; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].type.toLowerCase() == type) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the first node that matches a name in its title - * @method findNodeByTitle - * @param {String} name the name of the node to search - * @return {Node} the node or null - */ - LGraph.prototype.findNodeByTitle = function(title) { - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - return this._nodes[i]; - } - } - return null; - }; - - /** - * Returns a list of nodes that matches a name - * @method findNodesByTitle - * @param {String} name the name of the node to search - * @return {Array} a list with all the nodes with this name - */ - LGraph.prototype.findNodesByTitle = function(title) { - var result = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - if (this._nodes[i].title == title) { - result.push(this._nodes[i]); - } - } - return result; - }; - - /** - * Returns the top-most node in this position of the canvas - * @method getNodeOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph - * @return {LGraphNode} the node at this position or null - */ - LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { - nodes_list = nodes_list || this._nodes; - var nRet = null; - for (var i = nodes_list.length - 1; i >= 0; i--) { - var n = nodes_list[i]; - if (n.isPointInside(x, y, margin)) { - // check for lesser interest nodes (TODO check for overlapping, use the top) - /*if (typeof n == "LGraphGroup"){ - nRet = n; - }else{*/ - return n; - /*}*/ - } - } - return nRet; - }; - - /** - * Returns the top-most group in that position - * @method getGroupOnPos - * @param {number} x the x coordinate in canvas space - * @param {number} y the y coordinate in canvas space - * @return {LGraphGroup} the group or null - */ - LGraph.prototype.getGroupOnPos = function(x, y) { - for (var i = this._groups.length - 1; i >= 0; i--) { - var g = this._groups[i]; - if (g.isPointInside(x, y, 2, true)) { - return g; - } - } - return null; - }; - - /** - * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution - * this replaces the ones using the old version with the new version - * @method checkNodeTypes - */ - LGraph.prototype.checkNodeTypes = function() { - var changes = false; - for (var i = 0; i < this._nodes.length; i++) { - var node = this._nodes[i]; - var ctor = LiteGraph.registered_node_types[node.type]; - if (node.constructor == ctor) { - continue; - } - console.log("node being replaced by newer version: " + node.type); - var newnode = LiteGraph.createNode(node.type); - changes = true; - this._nodes[i] = newnode; - newnode.configure(node.serialize()); - newnode.graph = this; - this._nodes_by_id[newnode.id] = newnode; - if (node.inputs) { - newnode.inputs = node.inputs.concat(); - } - if (node.outputs) { - newnode.outputs = node.outputs.concat(); - } - } - this.updateExecutionOrder(); - }; - - // ********** GLOBALS ***************** - - LGraph.prototype.onAction = function(action, param, options) { - this._input_nodes = this.findNodesByClass( - LiteGraph.GraphInput, - this._input_nodes - ); - for (var i = 0; i < this._input_nodes.length; ++i) { - var node = this._input_nodes[i]; - if (node.properties.name != action) { - continue; - } - //wrap node.onAction(action, param); - node.actionDo(action, param, options); - break; - } - }; - - LGraph.prototype.trigger = function(action, param) { - if (this.onTrigger) { - this.onTrigger(action, param); - } - }; - - /** - * Tell this graph it has a global graph input of this type - * @method addGlobalInput - * @param {String} name - * @param {String} type - * @param {*} value [optional] - */ - LGraph.prototype.addInput = function(name, type, value) { - var input = this.inputs[name]; - if (input) { - //already exist - return; - } - - this.beforeChange(); - this.inputs[name] = { name: name, type: type, value: value }; - this._version++; - this.afterChange(); - - if (this.onInputAdded) { - this.onInputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global graph input - * @method setGlobalInputData - * @param {String} name - * @param {*} data - */ - LGraph.prototype.setInputData = function(name, data) { - var input = this.inputs[name]; - if (!input) { - return; - } - input.value = data; - }; - - /** - * Returns the current value of a global graph input - * @method getInputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getInputData = function(name) { - var input = this.inputs[name]; - if (!input) { - return null; - } - return input.value; - }; - - /** - * Changes the name of a global graph input - * @method renameInput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameInput = function(old_name, name) { - if (name == old_name) { - return; - } - - if (!this.inputs[old_name]) { - return false; - } - - if (this.inputs[name]) { - console.error("there is already one input with that name"); - return false; - } - - this.inputs[name] = this.inputs[old_name]; - delete this.inputs[old_name]; - this._version++; - - if (this.onInputRenamed) { - this.onInputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph input - * @method changeInputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeInputType = function(name, type) { - if (!this.inputs[name]) { - return false; - } - - if ( - this.inputs[name].type && - String(this.inputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.inputs[name].type = type; - this._version++; - if (this.onInputTypeChanged) { - this.onInputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph input - * @method removeInput - * @param {String} name - * @param {String} type - */ - LGraph.prototype.removeInput = function(name) { - if (!this.inputs[name]) { - return false; - } - - delete this.inputs[name]; - this._version++; - - if (this.onInputRemoved) { - this.onInputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - /** - * Creates a global graph output - * @method addOutput - * @param {String} name - * @param {String} type - * @param {*} value - */ - LGraph.prototype.addOutput = function(name, type, value) { - this.outputs[name] = { name: name, type: type, value: value }; - this._version++; - - if (this.onOutputAdded) { - this.onOutputAdded(name, type); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Assign a data to the global output - * @method setOutputData - * @param {String} name - * @param {String} value - */ - LGraph.prototype.setOutputData = function(name, value) { - var output = this.outputs[name]; - if (!output) { - return; - } - output.value = value; - }; - - /** - * Returns the current value of a global graph output - * @method getOutputData - * @param {String} name - * @return {*} the data - */ - LGraph.prototype.getOutputData = function(name) { - var output = this.outputs[name]; - if (!output) { - return null; - } - return output.value; - }; - - /** - * Renames a global graph output - * @method renameOutput - * @param {String} old_name - * @param {String} new_name - */ - LGraph.prototype.renameOutput = function(old_name, name) { - if (!this.outputs[old_name]) { - return false; - } - - if (this.outputs[name]) { - console.error("there is already one output with that name"); - return false; - } - - this.outputs[name] = this.outputs[old_name]; - delete this.outputs[old_name]; - this._version++; - - if (this.onOutputRenamed) { - this.onOutputRenamed(old_name, name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - }; - - /** - * Changes the type of a global graph output - * @method changeOutputType - * @param {String} name - * @param {String} type - */ - LGraph.prototype.changeOutputType = function(name, type) { - if (!this.outputs[name]) { - return false; - } - - if ( - this.outputs[name].type && - String(this.outputs[name].type).toLowerCase() == - String(type).toLowerCase() - ) { - return; - } - - this.outputs[name].type = type; - this._version++; - if (this.onOutputTypeChanged) { - this.onOutputTypeChanged(name, type); - } - }; - - /** - * Removes a global graph output - * @method removeOutput - * @param {String} name - */ - LGraph.prototype.removeOutput = function(name) { - if (!this.outputs[name]) { - return false; - } - delete this.outputs[name]; - this._version++; - - if (this.onOutputRemoved) { - this.onOutputRemoved(name); - } - - if (this.onInputsOutputsChange) { - this.onInputsOutputsChange(); - } - return true; - }; - - LGraph.prototype.triggerInput = function(name, value) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].onTrigger(value); - } - }; - - LGraph.prototype.setCallback = function(name, func) { - var nodes = this.findNodesByTitle(name); - for (var i = 0; i < nodes.length; ++i) { - nodes[i].setTrigger(func); - } - }; - - //used for undo, called before any change is made to the graph - LGraph.prototype.beforeChange = function(info) { - if (this.onBeforeChange) { - this.onBeforeChange(this,info); - } - this.sendActionToCanvas("onBeforeChange", this); - }; - - //used to resend actions, called after any change is made to the graph - LGraph.prototype.afterChange = function(info) { - if (this.onAfterChange) { - this.onAfterChange(this,info); - } - this.sendActionToCanvas("onAfterChange", this); - }; - - LGraph.prototype.connectionChange = function(node, link_info) { - this.updateExecutionOrder(); - if (this.onConnectionChange) { - this.onConnectionChange(node); - } - this._version++; - this.sendActionToCanvas("onConnectionChange"); - }; - - /** - * returns if the graph is in live mode - * @method isLive - */ - - LGraph.prototype.isLive = function() { - if (!this.list_of_graphcanvas) { - return false; - } - - for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { - var c = this.list_of_graphcanvas[i]; - if (c.live_mode) { - return true; - } - } - return false; - }; - - /** - * clears the triggered slot animation in all links (stop visual animation) - * @method clearTriggeredSlots - */ - LGraph.prototype.clearTriggeredSlots = function() { - for (var i in this.links) { - var link_info = this.links[i]; - if (!link_info) { - continue; - } - if (link_info._last_time) { - link_info._last_time = 0; - } - } - }; - - /* Called when something visually changed (not the graph!) */ - LGraph.prototype.change = function() { - if (LiteGraph.debug) { - console.log("Graph changed"); - } - this.sendActionToCanvas("setDirty", [true, true]); - if (this.on_change) { - this.on_change(this); - } - }; - - LGraph.prototype.setDirtyCanvas = function(fg, bg) { - this.sendActionToCanvas("setDirty", [fg, bg]); - }; - - /** - * Destroys a link - * @method removeLink - * @param {Number} link_id - */ - LGraph.prototype.removeLink = function(link_id) { - var link = this.links[link_id]; - if (!link) { - return; - } - var node = this.getNodeById(link.target_id); - if (node) { - node.disconnectInput(link.target_slot); - } - }; - - //save and recover app state *************************************** - /** - * Creates a Object containing all the info about this graph, it can be serialized - * @method serialize - * @return {Object} value of the node - */ - LGraph.prototype.serialize = function() { - var nodes_info = []; - for (var i = 0, l = this._nodes.length; i < l; ++i) { - nodes_info.push(this._nodes[i].serialize()); - } - - //pack link info into a non-verbose format - var links = []; - for (var i in this.links) { - //links is an OBJECT - var link = this.links[i]; - if (!link.serialize) { - //weird bug I havent solved yet - console.warn( - "weird LLink bug, link info is not a LLink but a regular object" - ); - var link2 = new LLink(); - for (var j in link) { - link2[j] = link[j]; - } - this.links[i] = link2; - link = link2; - } - - links.push(link.serialize()); - } - - var groups_info = []; - for (var i = 0; i < this._groups.length; ++i) { - groups_info.push(this._groups[i].serialize()); - } - - var data = { - last_node_id: this.last_node_id, - last_link_id: this.last_link_id, - nodes: nodes_info, - links: links, - groups: groups_info, - config: this.config, - extra: this.extra, - version: LiteGraph.VERSION - }; - - if(this.onSerialize) - this.onSerialize(data); - - return data; - }; - - /** - * Configure a graph from a JSON string - * @method configure - * @param {String} str configure a graph from a JSON string - * @param {Boolean} returns if there was any error parsing - */ - LGraph.prototype.configure = function(data, keep_old) { - if (!data) { - return; - } - - if (!keep_old) { - this.clear(); - } - - var nodes = data.nodes; - - //decode links info (they are very verbose) - if (data.links && data.links.constructor === Array) { - var links = []; - for (var i = 0; i < data.links.length; ++i) { - var link_data = data.links[i]; - if(!link_data) //weird bug - { - console.warn("serialized graph link data contains errors, skipping."); - continue; - } - var link = new LLink(); - link.configure(link_data); - links[link.id] = link; - } - data.links = links; - } - - //copy all stored fields - for (var i in data) { - if(i == "nodes" || i == "groups" ) //links must be accepted - continue; - this[i] = data[i]; - } - - var error = false; - - //create nodes - this._nodes = []; - if (nodes) { - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; //stored info - var node = LiteGraph.createNode(n_info.type, n_info.title); - if (!node) { - if (LiteGraph.debug) { - console.log( - "Node not found or has errors: " + n_info.type - ); - } - - //in case of error we create a replacement node to avoid losing info - node = new LGraphNode(); - node.last_serialization = n_info; - node.has_errors = true; - error = true; - //continue; - } - - node.id = n_info.id; //id it or it will create a new id - this.add(node, true); //add before configure, otherwise configure cannot create links - } - - //configure nodes afterwards so they can reach each other - for (var i = 0, l = nodes.length; i < l; ++i) { - var n_info = nodes[i]; - var node = this.getNodeById(n_info.id); - if (node) { - node.configure(n_info); - } - } - } - - //groups - this._groups.length = 0; - if (data.groups) { - for (var i = 0; i < data.groups.length; ++i) { - var group = new LiteGraph.LGraphGroup(); - group.configure(data.groups[i]); - this.add(group); - } - } - - this.updateExecutionOrder(); - - this.extra = data.extra || {}; - - if(this.onConfigure) - this.onConfigure(data); - - this._version++; - this.setDirtyCanvas(true, true); - return error; - }; - - LGraph.prototype.load = function(url, callback) { - var that = this; - - //from file - if(url.constructor === File || url.constructor === Blob) - { - var reader = new FileReader(); - reader.addEventListener('load', function(event) { - var data = JSON.parse(event.target.result); - that.configure(data); - if(callback) - callback(); - }); - - reader.readAsText(url); - return; - } - - //is a string, then an URL - var req = new XMLHttpRequest(); - req.open("GET", url, true); - req.send(null); - req.onload = function(oEvent) { - if (req.status !== 200) { - console.error("Error loading graph:", req.status, req.response); - return; - } - var data = JSON.parse( req.response ); - that.configure(data); - if(callback) - callback(); - }; - req.onerror = function(err) { - console.error("Error loading graph:", err); - }; - }; - - LGraph.prototype.onNodeTrace = function(node, msg, color) { - //TODO - }; - - //this is the class in charge of storing link information - function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { - this.id = id; - this.type = type; - this.origin_id = origin_id; - this.origin_slot = origin_slot; - this.target_id = target_id; - this.target_slot = target_slot; - - this._data = null; - this._pos = new Float32Array(2); //center - } - - LLink.prototype.configure = function(o) { - if (o.constructor === Array) { - this.id = o[0]; - this.origin_id = o[1]; - this.origin_slot = o[2]; - this.target_id = o[3]; - this.target_slot = o[4]; - this.type = o[5]; - } else { - this.id = o.id; - this.type = o.type; - this.origin_id = o.origin_id; - this.origin_slot = o.origin_slot; - this.target_id = o.target_id; - this.target_slot = o.target_slot; - } - }; - - LLink.prototype.serialize = function() { - return [ - this.id, - this.origin_id, - this.origin_slot, - this.target_id, - this.target_slot, - this.type - ]; - }; - - LiteGraph.LLink = LLink; - - // ************************************************************* - // Node CLASS ******* - // ************************************************************* - - /* - title: string - pos: [x,y] - size: [x,y] - - input|output: every connection - + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); - - general properties: - + clip_area: if you render outside the node, it will be clipped - + unsafe_execution: not allowed for safe execution - + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected - + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally - + widgets_start_y: widgets start at y distance from the top of the node - - flags object: - + collapsed: if it is collapsed - - supported callbacks: - + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) - + onRemoved: when removed from graph - + onStart: when the graph starts playing - + onStop: when the graph stops playing - + onDrawForeground: render the inside widgets inside the node - + onDrawBackground: render the background area inside the node (only in edit mode) - + onMouseDown - + onMouseMove - + onMouseUp - + onMouseEnter - + onMouseLeave - + onExecute: execute the node - + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) - + onGetInputs: returns an array of possible inputs - + onGetOutputs: returns an array of possible outputs - + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) - + onDblClick: double clicked in the node - + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) - + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) - + onConfigure: called after the node has been configured - + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) - + onSelected - + onDeselected - + onDropItem : DOM item dropped over the node - + onDropFile : file dropped over the node - + onConnectInput : if returns false the incoming connection will be canceled - + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) - + onAction: action slot triggered - + getExtraMenuOptions: to add option to context menu -*/ - - /** - * Base Class for all the node type classes - * @class LGraphNode - * @param {String} name a name for the node - */ - - function LGraphNode(title) { - this._ctor(title); - } - - global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - - LGraphNode.prototype._ctor = function(title) { - this.title = title || "Unnamed"; - this.size = [LiteGraph.NODE_WIDTH, 60]; - this.graph = null; - - this._pos = new Float32Array(10, 10); - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - if (LiteGraph.use_uuids) { - this.id = LiteGraph.uuidv4(); - } - else { - this.id = -1; //not know till not added - } - this.type = null; - - //inputs available: array of inputs - this.inputs = []; - this.outputs = []; - this.connections = []; - - //local data - this.properties = {}; //for the values - this.properties_info = []; //for the info - - this.flags = {}; - }; - - /** - * configure a node from an object containing the serialized info - * @method configure - */ - LGraphNode.prototype.configure = function(info) { - if (this.graph) { - this.graph._version++; - } - for (var j in info) { - if (j == "properties") { - //i don't want to clone properties, I want to reuse the old container - for (var k in info.properties) { - this.properties[k] = info.properties[k]; - if (this.onPropertyChanged) { - this.onPropertyChanged( k, info.properties[k] ); - } - } - continue; - } - - if (info[j] == null) { - continue; - } else if (typeof info[j] == "object") { - //object - if (this[j] && this[j].configure) { - this[j].configure(info[j]); - } else { - this[j] = LiteGraph.cloneObject(info[j], this[j]); - } - } //value - else { - this[j] = info[j]; - } - } - - if (!info.title) { - this.title = this.constructor.title; - } - - if (this.inputs) { - for (var i = 0; i < this.inputs.length; ++i) { - var input = this.inputs[i]; - var link_info = this.graph ? this.graph.links[input.link] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - - if( this.onInputAdded ) - this.onInputAdded(input); - - } - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if (!output.links) { - continue; - } - for (var j = 0; j < output.links.length; ++j) { - var link_info = this.graph ? this.graph.links[output.links[j]] : null; - if (this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated - } - - if( this.onOutputAdded ) - this.onOutputAdded(output); - } - } - - if( this.widgets ) - { - for (var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options && w.options.property && (this.properties[ w.options.property ] != undefined)) - w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); - } - if (info.widgets_values) { - for (var i = 0; i < info.widgets_values.length; ++i) { - if (this.widgets[i]) { - this.widgets[i].value = info.widgets_values[i]; - } - } - } - } - - if (this.onConfigure) { - this.onConfigure(info); - } - }; - - /** - * serialize the content - * @method serialize - */ - - LGraphNode.prototype.serialize = function() { - //create serialization object - var o = { - id: this.id, - type: this.type, - pos: this.pos, - size: this.size, - flags: LiteGraph.cloneObject(this.flags), - order: this.order, - mode: this.mode - }; - - //special case for when there were errors - if (this.constructor === LGraphNode && this.last_serialization) { - return this.last_serialization; - } - - if (this.inputs) { - o.inputs = this.inputs; - } - - if (this.outputs) { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - for (var i = 0; i < this.outputs.length; i++) { - delete this.outputs[i]._data; - } - o.outputs = this.outputs; - } - - if (this.title && this.title != this.constructor.title) { - o.title = this.title; - } - - if (this.properties) { - o.properties = LiteGraph.cloneObject(this.properties); - } - - if (this.widgets && this.serialize_widgets) { - o.widgets_values = []; - for (var i = 0; i < this.widgets.length; ++i) { - if(this.widgets[i]) - o.widgets_values[i] = this.widgets[i].value; - else - o.widgets_values[i] = null; - } - } - - if (!o.type) { - o.type = this.constructor.type; - } - - if (this.color) { - o.color = this.color; - } - if (this.bgcolor) { - o.bgcolor = this.bgcolor; - } - if (this.boxcolor) { - o.boxcolor = this.boxcolor; - } - if (this.shape) { - o.shape = this.shape; - } - - if (this.onSerialize) { - if (this.onSerialize(o)) { - console.warn( - "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" - ); - } - } - - return o; - }; - - /* Creates a clone of this node */ - LGraphNode.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - if (!node) { - return null; - } - - //we clone it because serialize returns shared containers - var data = LiteGraph.cloneObject(this.serialize()); - - //remove links - if (data.inputs) { - for (var i = 0; i < data.inputs.length; ++i) { - data.inputs[i].link = null; - } - } - - if (data.outputs) { - for (var i = 0; i < data.outputs.length; ++i) { - if (data.outputs[i].links) { - data.outputs[i].links.length = 0; - } - } - } - - delete data["id"]; - - if (LiteGraph.use_uuids) { - data["id"] = LiteGraph.uuidv4() - } - - //remove links - node.configure(data); - - return node; - }; - - /** - * serialize and stringify - * @method toString - */ - - LGraphNode.prototype.toString = function() { - return JSON.stringify(this.serialize()); - }; - //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - - /** - * get the title string - * @method getTitle - */ - - LGraphNode.prototype.getTitle = function() { - return this.title || this.constructor.title; - }; - - /** - * sets the value of a property - * @method setProperty - * @param {String} name - * @param {*} value - */ - LGraphNode.prototype.setProperty = function(name, value) { - if (!this.properties) { - this.properties = {}; - } - if( value === this.properties[name] ) - return; - var prev_value = this.properties[name]; - this.properties[name] = value; - if (this.onPropertyChanged) { - if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change - this.properties[name] = prev_value; - } - if(this.widgets) //widgets could be linked to properties - for(var i = 0; i < this.widgets.length; ++i) - { - var w = this.widgets[i]; - if(!w) - continue; - if(w.options.property == name) - { - w.value = value; - break; - } - } - }; - - // Execution ************************* - /** - * sets the output data - * @method setOutputData - * @param {number} slot - * @param {*} data - */ - LGraphNode.prototype.setOutputData = function(slot, data) { - if (!this.outputs) { - return; - } - - //this maybe slow and a niche case - //if(slot && slot.constructor === String) - // slot = this.findOutputSlot(slot); - - if (slot == -1 || slot >= this.outputs.length) { - return; - } - - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - - //store data in the output itself in case we want to debug - output_info._data = data; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - var link = this.graph.links[link_id]; - if(link) - link.data = data; - } - } - }; - - /** - * sets the output data type, useful when you want to be able to overwrite the data type - * @method setOutputDataType - * @param {number} slot - * @param {String} datatype - */ - LGraphNode.prototype.setOutputDataType = function(slot, type) { - if (!this.outputs) { - return; - } - if (slot == -1 || slot >= this.outputs.length) { - return; - } - var output_info = this.outputs[slot]; - if (!output_info) { - return; - } - //store data in the output itself in case we want to debug - output_info.type = type; - - //if there are connections, pass the data to the connections - if (this.outputs[slot].links) { - for (var i = 0; i < this.outputs[slot].links.length; i++) { - var link_id = this.outputs[slot].links[i]; - this.graph.links[link_id].type = type; - } - } - }; - - /** - * Retrieves the input data (data traveling through the connection) from one slot - * @method getInputData - * @param {number} slot - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns undefined - */ - LGraphNode.prototype.getInputData = function(slot, force_update) { - if (!this.inputs) { - return; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return; - } - - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - - if (!force_update) { - return link.data; - } - - //special case: used to extract data from the incoming connection before the graph has been executed - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.data; - } - - if (node.updateOutputData) { - node.updateOutputData(link.origin_slot); - } else if (node.onExecute) { - node.onExecute(); - } - - return link.data; - }; - - /** - * Retrieves the input data type (in case this supports multiple input types) - * @method getInputDataType - * @param {number} slot - * @return {String} datatype in string format - */ - LGraphNode.prototype.getInputDataType = function(slot) { - if (!this.inputs) { - return null; - } //undefined; - - if (slot >= this.inputs.length || this.inputs[slot].link == null) { - return null; - } - var link_id = this.inputs[slot].link; - var link = this.graph.links[link_id]; - if (!link) { - //bug: weird case but it happens sometimes - return null; - } - var node = this.graph.getNodeById(link.origin_id); - if (!node) { - return link.type; - } - var output_info = node.outputs[link.origin_slot]; - if (output_info) { - return output_info.type; - } - return null; - }; - - /** - * Retrieves the input data from one slot using its name instead of slot number - * @method getInputDataByName - * @param {String} slot_name - * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link - * @return {*} data or if it is not connected returns null - */ - LGraphNode.prototype.getInputDataByName = function( - slot_name, - force_update - ) { - var slot = this.findInputSlot(slot_name); - if (slot == -1) { - return null; - } - return this.getInputData(slot, force_update); - }; - - /** - * tells you if there is a connection in one input slot - * @method isInputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isInputConnected = function(slot) { - if (!this.inputs) { - return false; - } - return slot < this.inputs.length && this.inputs[slot].link != null; - }; - - /** - * tells you info about an input connection (which node, type, etc) - * @method getInputInfo - * @param {number} slot - * @return {Object} object or null { link: id, name: string, type: string or 0 } - */ - LGraphNode.prototype.getInputInfo = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - return this.inputs[slot]; - } - return null; - }; - - /** - * Returns the link info in the connection of an input slot - * @method getInputLink - * @param {number} slot - * @return {LLink} object or null - */ - LGraphNode.prototype.getInputLink = function(slot) { - if (!this.inputs) { - return null; - } - if (slot < this.inputs.length) { - var slot_info = this.inputs[slot]; - return this.graph.links[ slot_info.link ]; - } - return null; - }; - - /** - * returns the node connected in the input slot - * @method getInputNode - * @param {number} slot - * @return {LGraphNode} node or null - */ - LGraphNode.prototype.getInputNode = function(slot) { - if (!this.inputs) { - return null; - } - if (slot >= this.inputs.length) { - return null; - } - var input = this.inputs[slot]; - if (!input || input.link === null) { - return null; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - return null; - } - return this.graph.getNodeById(link_info.origin_id); - }; - - /** - * returns the value of an input with this name, otherwise checks if there is a property with that name - * @method getInputOrProperty - * @param {string} name - * @return {*} value - */ - LGraphNode.prototype.getInputOrProperty = function(name) { - if (!this.inputs || !this.inputs.length) { - return this.properties ? this.properties[name] : null; - } - - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input_info = this.inputs[i]; - if (name == input_info.name && input_info.link != null) { - var link = this.graph.links[input_info.link]; - if (link) { - return link.data; - } - } - } - return this.properties[name]; - }; - - /** - * tells you the last output data that went in that slot - * @method getOutputData - * @param {number} slot - * @return {Object} object or null - */ - LGraphNode.prototype.getOutputData = function(slot) { - if (!this.outputs) { - return null; - } - if (slot >= this.outputs.length) { - return null; - } - - var info = this.outputs[slot]; - return info._data; - }; - - /** - * tells you info about an output connection (which node, type, etc) - * @method getOutputInfo - * @param {number} slot - * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } - */ - LGraphNode.prototype.getOutputInfo = function(slot) { - if (!this.outputs) { - return null; - } - if (slot < this.outputs.length) { - return this.outputs[slot]; - } - return null; - }; - - /** - * tells you if there is a connection in one output slot - * @method isOutputConnected - * @param {number} slot - * @return {boolean} - */ - LGraphNode.prototype.isOutputConnected = function(slot) { - if (!this.outputs) { - return false; - } - return ( - slot < this.outputs.length && - this.outputs[slot].links && - this.outputs[slot].links.length - ); - }; - - /** - * tells you if there is any connection in the output slots - * @method isAnyOutputConnected - * @return {boolean} - */ - LGraphNode.prototype.isAnyOutputConnected = function() { - if (!this.outputs) { - return false; - } - for (var i = 0; i < this.outputs.length; ++i) { - if (this.outputs[i].links && this.outputs[i].links.length) { - return true; - } - } - return false; - }; - - /** - * retrieves all the nodes connected to this output slot - * @method getOutputNodes - * @param {number} slot - * @return {array} - */ - LGraphNode.prototype.getOutputNodes = function(slot) { - if (!this.outputs || this.outputs.length == 0) { - return null; - } - - if (slot >= this.outputs.length) { - return null; - } - - var output = this.outputs[slot]; - if (!output.links || output.links.length == 0) { - return null; - } - - var r = []; - for (var i = 0; i < output.links.length; i++) { - var link_id = output.links[i]; - var link = this.graph.links[link_id]; - if (link) { - var target_node = this.graph.getNodeById(link.target_id); - if (target_node) { - r.push(target_node); - } - } - } - return r; - }; - - LGraphNode.prototype.addOnTriggerInput = function(){ - var trigS = this.findInputSlot("onTrigger"); - if (trigS == -1){ //!trigS || - var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); - return this.findInputSlot("onTrigger"); - } - return trigS; - } - - LGraphNode.prototype.addOnExecutedOutput = function(){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS == -1){ //!trigS || - var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); - return this.findOutputSlot("onExecuted"); - } - return trigS; - } - - LGraphNode.prototype.onAfterExecuteNode = function(param, options){ - var trigS = this.findOutputSlot("onExecuted"); - if (trigS != -1){ - - //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); - //console.debug(param); - //console.debug(options); - this.triggerSlot(trigS, param, null, options); - - } - } - - LGraphNode.prototype.changeMode = function(modeTo){ - switch(modeTo){ - case LiteGraph.ON_EVENT: - // this.addOnExecutedOutput(); - break; - - case LiteGraph.ON_TRIGGER: - this.addOnTriggerInput(); - this.addOnExecutedOutput(); - break; - - case LiteGraph.NEVER: - break; - - case LiteGraph.ALWAYS: - break; - - case LiteGraph.ON_REQUEST: - break; - - default: - return false; - break; - } - this.mode = modeTo; - return true; - }; - - /** - * Triggers the execution of actions that were deferred when the action was triggered - * @method executePendingActions - */ - LGraphNode.prototype.executePendingActions = function() { - if(!this._waiting_actions || !this._waiting_actions.length) - return; - for(var i = 0; i < this._waiting_actions.length;++i) - { - var p = this._waiting_actions[i]; - this.onAction(p[0],p[1],p[2],p[3],p[4]); - } - this._waiting_actions.length = 0; - } - - - /** - * Triggers the node code execution, place a boolean/counter to mark the node as being executed - * @method doExecute - * @param {*} param - * @param {*} options - */ - LGraphNode.prototype.doExecute = function(param, options) { - options = options || {}; - if (this.onExecute){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); - - this.graph.nodes_executing[this.id] = true; //.push(this.id); - - this.onExecute(param, options); - - this.graph.nodes_executing[this.id] = false; //.pop(); - - // save execution/action ref - this.exec_version = this.graph.iteration; - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - else { - } - this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback - }; - - /** - * Triggers an action, wrapped by logics to control execution flow - * @method actionDo - * @param {String} action name - * @param {*} param - */ - LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) { - options = options || {}; - if (this.onAction){ - - // enable this to give the event an ID - if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); - - this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); - - this.onAction(action, param, options, action_slot); - - this.graph.nodes_actioning[this.id] = false; //.pop(); - - // save execution/action ref - if(options && options.action_call){ - this.action_call = options.action_call; // if (param) - this.graph.nodes_executedAction[this.id] = options.action_call; - } - } - this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event - if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); - }; - - /** - * Triggers an event in this node, this will trigger any output with the same name - * @method trigger - * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all - * @param {*} param - */ - LGraphNode.prototype.trigger = function(action, param, options) { - if (!this.outputs || !this.outputs.length) { - return; - } - - if (this.graph) - this.graph._last_trigger_time = LiteGraph.getTime(); - - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) - continue; - this.triggerSlot(i, param, null, options); - } - }; - - /** - * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes - * @method triggerSlot - * @param {Number} slot the index of the output slot - * @param {*} param - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { - options = options || {}; - if (!this.outputs) { - return; - } - - if(slot == null) - { - console.error("slot must be a number"); - return; - } - - if(slot.constructor !== Number) - console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - if (this.graph) { - this.graph._last_trigger_time = LiteGraph.getTime(); - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = LiteGraph.getTime(); - var node = this.graph.getNodeById(link_info.target_id); - if (!node) { - //node not found? - continue; - } - - //used to mark events in graph - var target_connection = node.inputs[link_info.target_slot]; - - if (node.mode === LiteGraph.ON_TRIGGER) - { - // generate unique trigger ID if not present - if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); - if (node.onExecute) { - // -- wrapping node.onExecute(param); -- - node.doExecute(param, options); - } - } - else if (node.onAction) { - // generate unique action ID if not present - if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); - //pass the action name - var target_connection = node.inputs[link_info.target_slot]; - - //instead of executing them now, it will be executed in the next graph loop, to ensure data flow - if(LiteGraph.use_deferred_actions && node.onExecute) - { - if(!node._waiting_actions) - node._waiting_actions = []; - node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]); - } - else - { - // wrap node.onAction(target_connection.name, param); - node.actionDo( target_connection.name, param, options, link_info.target_slot ); - } - } - } - }; - - /** - * clears the trigger slot animation - * @method clearTriggeredSlot - * @param {Number} slot the index of the output slot - * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot - */ - LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { - if (!this.outputs) { - return; - } - - var output = this.outputs[slot]; - if (!output) { - return; - } - - var links = output.links; - if (!links || !links.length) { - return; - } - - //for every link attached here - for (var k = 0; k < links.length; ++k) { - var id = links[k]; - if (link_id != null && link_id != id) { - //to skip links - continue; - } - var link_info = this.graph.links[links[k]]; - if (!link_info) { - //not connected - continue; - } - link_info._last_time = 0; - } - }; - - /** - * changes node size and triggers callback - * @method setSize - * @param {vec2} size - */ - LGraphNode.prototype.setSize = function(size) - { - this.size = size; - if(this.onResize) - this.onResize(this.size); - } - - /** - * add a new property to this node - * @method addProperty - * @param {string} name - * @param {*} default_value - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) - */ - LGraphNode.prototype.addProperty = function( - name, - default_value, - type, - extra_info - ) { - var o = { name: name, type: type, default_value: default_value }; - if (extra_info) { - for (var i in extra_info) { - o[i] = extra_info[i]; - } - } - if (!this.properties_info) { - this.properties_info = []; - } - this.properties_info.push(o); - if (!this.properties) { - this.properties = {}; - } - this.properties[name] = default_value; - return o; - }; - - //connections - - /** - * add a new output slot to use in this node - * @method addOutput - * @param {string} name - * @param {string} type string defining the output type ("vec3","number",...) - * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) - */ - LGraphNode.prototype.addOutput = function(name, type, extra_info) { - var output = { name: name, type: type, links: null }; - if (extra_info) { - for (var i in extra_info) { - output[i] = extra_info[i]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(output); - if (this.onOutputAdded) { - this.onOutputAdded(output); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - return output; - }; - - /** - * add a new output slot to use in this node - * @method addOutputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addOutputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.outputs) { - this.outputs = []; - } - this.outputs.push(o); - if (this.onOutputAdded) { - this.onOutputAdded(o); - } - - if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); - - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing output slot - * @method removeOutput - * @param {number} slot - */ - LGraphNode.prototype.removeOutput = function(slot) { - this.disconnectOutput(slot); - this.outputs.splice(slot, 1); - for (var i = slot; i < this.outputs.length; ++i) { - if (!this.outputs[i] || !this.outputs[i].links) { - continue; - } - var links = this.outputs[i].links; - for (var j = 0; j < links.length; ++j) { - var link = this.graph.links[links[j]]; - if (!link) { - continue; - } - link.origin_slot -= 1; - } - } - - this.setSize( this.computeSize() ); - if (this.onOutputRemoved) { - this.onOutputRemoved(slot); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add a new input slot to use in this node - * @method addInput - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 - * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) - */ - LGraphNode.prototype.addInput = function(name, type, extra_info) { - type = type || 0; - var input = { name: name, type: type, link: null }; - if (extra_info) { - for (var i in extra_info) { - input[i] = extra_info[i]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - - this.inputs.push(input); - this.setSize( this.computeSize() ); - - if (this.onInputAdded) { - this.onInputAdded(input); - } - - LiteGraph.registerNodeAndSlotType(this,type); - - this.setDirtyCanvas(true, true); - return input; - }; - - /** - * add several new input slots in this node - * @method addInputs - * @param {Array} array of triplets like [[name,type,extra_info],[...]] - */ - LGraphNode.prototype.addInputs = function(array) { - for (var i = 0; i < array.length; ++i) { - var info = array[i]; - var o = { name: info[0], type: info[1], link: null }; - if (array[2]) { - for (var j in info[2]) { - o[j] = info[2][j]; - } - } - - if (!this.inputs) { - this.inputs = []; - } - this.inputs.push(o); - if (this.onInputAdded) { - this.onInputAdded(o); - } - - LiteGraph.registerNodeAndSlotType(this,info[1]); - } - - this.setSize( this.computeSize() ); - this.setDirtyCanvas(true, true); - }; - - /** - * remove an existing input slot - * @method removeInput - * @param {number} slot - */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); - var slot_info = this.inputs.splice(slot, 1); - for (var i = slot; i < this.inputs.length; ++i) { - if (!this.inputs[i]) { - continue; - } - var link = this.graph.links[this.inputs[i].link]; - if (!link) { - continue; - } - link.target_slot -= 1; - } - this.setSize( this.computeSize() ); - if (this.onInputRemoved) { - this.onInputRemoved(slot, slot_info[0] ); - } - this.setDirtyCanvas(true, true); - }; - - /** - * add an special connection to this node (used for special kinds of graphs) - * @method addConnection - * @param {string} name - * @param {string} type string defining the input type ("vec3","number",...) - * @param {[x,y]} pos position of the connection inside the node - * @param {string} direction if is input or output - */ - LGraphNode.prototype.addConnection = function(name, type, pos, direction) { - var o = { - name: name, - type: type, - pos: pos, - direction: direction, - links: null - }; - this.connections.push(o); - return o; - }; - - /** - * computes the minimum size of a node according to its inputs and output slots - * @method computeSize - * @param {vec2} minHeight - * @return {vec2} the total size - */ - LGraphNode.prototype.computeSize = function(out) { - if (this.constructor.size) { - return this.constructor.size.concat(); - } - - var rows = Math.max( - this.inputs ? this.inputs.length : 1, - this.outputs ? this.outputs.length : 1 - ); - var size = out || new Float32Array([0, 0]); - rows = Math.max(rows, 1); - var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - - var title_width = compute_text_size(this.title); - var input_width = 0; - var output_width = 0; - - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - var text = input.label || input.name || ""; - var text_width = compute_text_size(text); - if (input_width < text_width) { - input_width = text_width; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var text = output.label || output.name || ""; - var text_width = compute_text_size(text); - if (output_width < text_width) { - output_width = text_width; - } - } - } - - size[0] = Math.max(input_width + output_width + 10, title_width); - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); - if (this.widgets && this.widgets.length) { - size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); - } - - size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - - var widgets_height = 0; - if (this.widgets && this.widgets.length) { - for (var i = 0, l = this.widgets.length; i < l; ++i) { - if (this.widgets[i].computeSize) - widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; - else - widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; - } - widgets_height += 8; - } - - //compute height using widgets height - if( this.widgets_up ) - size[1] = Math.max( size[1], widgets_height ); - else if( this.widgets_start_y != null ) - size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); - else - size[1] += widgets_height; - - function compute_text_size(text) { - if (!text) { - return 0; - } - return font_size * text.length * 0.6; - } - - if ( - this.constructor.min_height && - size[1] < this.constructor.min_height - ) { - size[1] = this.constructor.min_height; - } - - size[1] += 6; //margin - - return size; - }; - - /** - * returns all the info available about a property of this node. - * - * @method getPropertyInfo - * @param {String} property name of the property - * @return {Object} the object with all the available info - */ - LGraphNode.prototype.getPropertyInfo = function( property ) - { - var info = null; - - //there are several ways to define info about a property - //legacy mode - if (this.properties_info) { - for (var i = 0; i < this.properties_info.length; ++i) { - if (this.properties_info[i].name == property) { - info = this.properties_info[i]; - break; - } - } - } - //litescene mode using the constructor - if(this.constructor["@" + property]) - info = this.constructor["@" + property]; - - if(this.constructor.widgets_info && this.constructor.widgets_info[property]) - info = this.constructor.widgets_info[property]; - - //litescene mode using the constructor - if (!info && this.onGetPropertyInfo) { - info = this.onGetPropertyInfo(property); - } - - if (!info) - info = {}; - if(!info.type) - info.type = typeof this.properties[property]; - if(info.widget == "combo") - info.type = "enum"; - - return info; - } - - /** - * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties - * - * @method addWidget - * @param {String} type the widget type (could be "number","string","combo" - * @param {String} name the text to show on the widget - * @param {String} value the default value - * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) - * @param {Object} options the object that contains special properties of this widget - * @return {Object} the created widget object - */ - LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) - { - if (!this.widgets) { - this.widgets = []; - } - - if(!options && callback && callback.constructor === Object) - { - options = callback; - callback = null; - } - - if(options && options.constructor === String) //options can be the property name - options = { property: options }; - - if(callback && callback.constructor === String) //callback can be the property name - { - if(!options) - options = {}; - options.property = callback; - callback = null; - } - - if(callback && callback.constructor !== Function) - { - console.warn("addWidget: callback must be a function"); - callback = null; - } - - var w = { - type: type.toLowerCase(), - name: name, - value: value, - callback: callback, - options: options || {} - }; - - if (w.options.y !== undefined) { - w.y = w.options.y; - } - - if (!callback && !w.options.callback && !w.options.property) { - console.warn("LiteGraph addWidget(...) without a callback or property assigned"); - } - if (type == "combo" && !w.options.values) { - throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; - } - this.widgets.push(w); - this.setSize( this.computeSize() ); - return w; - }; - - LGraphNode.prototype.addCustomWidget = function(custom_widget) { - if (!this.widgets) { - this.widgets = []; - } - this.widgets.push(custom_widget); - return custom_widget; - }; - - /** - * returns the bounding of the object, used for rendering purposes - * @method getBounding - * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage - * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation - * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] - */ - LGraphNode.prototype.getBounding = function(out, compute_outer) { - out = out || new Float32Array(4); - const nodePos = this.pos; - const isCollapsed = this.flags.collapsed; - const nodeSize = this.size; - - let left_offset = 0; - // 1 offset due to how nodes are rendered - let right_offset = 1 ; - let top_offset = 0; - let bottom_offset = 0; - - if (compute_outer) { - // 4 offset for collapsed node connection points - left_offset = 4; - // 6 offset for right shadow and collapsed node connection points - right_offset = 6 + left_offset; - // 4 offset for collapsed nodes top connection points - top_offset = 4; - // 5 offset for bottom shadow and collapsed node connection points - bottom_offset = 5 + top_offset; - } - - out[0] = nodePos[0] - left_offset; - out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset; - out[2] = isCollapsed ? - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset : - nodeSize[0] + right_offset; - out[3] = isCollapsed ? - LiteGraph.NODE_TITLE_HEIGHT + bottom_offset : - nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset; - - if (this.onBounding) { - this.onBounding(out); - } - return out; - }; - - /** - * checks if a point is inside the shape of a node - * @method isPointInside - * @param {number} x - * @param {number} y - * @return {boolean} - */ - LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { - margin = margin || 0; - - var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; - if (skip_title) { - margin_top = 0; - } - if (this.flags && this.flags.collapsed) { - //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) - if ( - isInsideRectangle( - x, - y, - this.pos[0] - margin, - this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, - (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + - 2 * margin, - LiteGraph.NODE_TITLE_HEIGHT + 2 * margin - ) - ) { - return true; - } - } else if ( - this.pos[0] - 4 - margin < x && - this.pos[0] + this.size[0] + 4 + margin > x && - this.pos[1] - margin_top - margin < y && - this.pos[1] + this.size[1] + margin > y - ) { - return true; - } - return false; - }; - - /** - * checks if a point is inside a node slot, and returns info about which slot - * @method getSlotInPosition - * @param {number} x - * @param {number} y - * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } - */ - LGraphNode.prototype.getSlotInPosition = function(x, y) { - //search for inputs - var link_pos = new Float32Array(2); - if (this.inputs) { - for (var i = 0, l = this.inputs.length; i < l; ++i) { - var input = this.inputs[i]; - this.getConnectionPos(true, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { input: input, slot: i, link_pos: link_pos }; - } - } - } - - if (this.outputs) { - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - this.getConnectionPos(false, i, link_pos); - if ( - isInsideRectangle( - x, - y, - link_pos[0] - 10, - link_pos[1] - 5, - 20, - 10 - ) - ) { - return { output: output, slot: i, link_pos: link_pos }; - } - } - } - - return null; - }; - - /** - * returns the input slot with a given name (used for dynamic slots), -1 if not found - * @method findInputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlot = function(name, returnObj) { - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (name == this.inputs[i].name) { - return !returnObj ? i : this.inputs[i]; - } - } - return -1; - }; - - /** - * returns the output slot with a given name (used for dynamic slots), -1 if not found - * @method findOutputSlot - * @param {string} name the name of the slot - * @param {boolean} returnObj if the obj itself wanted - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlot = function(name, returnObj) { - returnObj = returnObj || false; - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (name == this.outputs[i].name) { - return !returnObj ? i : this.outputs[i]; - } - } - return -1; - }; - - // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options - - /** - * returns the first free input slot - * @method findInputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findInputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = {returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.inputs) { - return -1; - } - for (var i = 0, l = this.inputs.length; i < l; ++i) { - if (this.inputs[i].link && this.inputs[i].link != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.inputs[i]; - } - return -1; - }; - - /** - * returns the first output slot free - * @method findOutputSlotFree - * @param {object} options - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findOutputSlotFree = function(optsIn) { - var optsIn = optsIn || {}; - var optsDef = { returnObj: false - ,typesNotAccepted: [] - }; - var opts = Object.assign(optsDef,optsIn); - if (!this.outputs) { - return -1; - } - for (var i = 0, l = this.outputs.length; i < l; ++i) { - if (this.outputs[i].links && this.outputs[i].links != null) { - continue; - } - if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ - continue; - } - return !opts.returnObj ? i : this.outputs[i]; - } - return -1; - }; - - /** - * findSlotByType for INPUTS - */ - LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * findSlotByType for OUTPUTS - */ - LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { - return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); - }; - - /** - * returns the output (or input) slot with a given type, -1 if not found - * @method findSlotByType - * @param {boolean} input uise inputs instead of outputs - * @param {string} type the type of the slot - * @param {boolean} returnObj if the obj itself wanted - * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) - * @return {number_or_object} the slot (-1 if not found) - */ - LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { - input = input || false; - returnObj = returnObj || false; - preferFreeSlot = preferFreeSlot || false; - doNotUseOccupied = doNotUseOccupied || false; - var aSlots = input ? this.inputs : this.outputs; - if (!aSlots) { - return -1; - } - // !! empty string type is considered 0, * !! - if (type == "" || type == "*") type = 0; - for (var i = 0, l = aSlots.length; i < l; ++i) { - var tFound = false; - var aSource = (type+"").toLowerCase().split(","); - var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; - aDest = (aDest+"").toLowerCase().split(","); - for(var sI=0;sI= 0 && target_slot !== null){ - //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) - return this.connect(slot, target_node, target_slot); - }else{ - //console.log("type "+target_slotType+" not found or not free?") - if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onTrigger IN SLOT - //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); - return this.connect(slot, target_node, -1); - } - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var target_slot = target_node.findInputSlotByType(0, false, true, true); - //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - // connect to the first free input slot if not found a specific type and this output is general - if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ - var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); - if (target_slot >= 0){ - return this.connect(slot, target_node, target_slot); - } - } - - console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); - //TODO filter - - return null; - } - } - - /** - * connect this node input to the output of another node BY TYPE - * @method connectByType - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {string} target_type the output slot type of the target node - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { - var optsIn = optsIn || {}; - var optsDef = { createEventInCase: true - ,firstFreeIfInputGeneralInCase: true - ,generalTypeInCase: true - }; - var opts = Object.assign(optsDef,optsIn); - if (source_node && source_node.constructor === Number) { - source_node = this.graph.getNodeById(source_node); - } - var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); - if (source_slot >= 0 && source_slot !== null){ - //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) - return source_node.connect(source_slot, this, slot); - }else{ - - // connect to the first general output slot if not found a specific type and - if (opts.generalTypeInCase){ - var source_slot = source_node.findOutputSlotByType(0, false, true, true); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ - // WILL CREATE THE onExecuted OUT SLOT - if (LiteGraph.do_add_triggers_slots){ - var source_slot = source_node.addOnExecutedOutput(); - return source_node.connect(source_slot, this, slot); - } - } - // connect to the first free output slot if not found a specific type and this input is general - if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ - var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); - if (source_slot >= 0){ - return source_node.connect(source_slot, this, slot); - } - } - - console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); - //TODO filter - - //console.log("type OUT! "+source_slotType+" not found or not free?") - return null; - } - } - - /** - * connect this node output to the input of another node - * @method connect - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} node the target node - * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) - * @return {Object} the link_info is created, otherwise null - */ - LGraphNode.prototype.connect = function(slot, target_node, target_slot) { - target_slot = target_slot || 0; - - if (!this.graph) { - //could be connected before adding it to a graph - console.log( - "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." - ); //due to link ids being associated with graphs - return null; - } - - //seek for the output slot - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return null; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - if (target_node && target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "target node is null"; - } - - //avoid loopback - if (target_node == this) { - return null; - } - - //you can specify the slot by name - if (target_slot.constructor === String) { - target_slot = target_node.findInputSlot(target_slot); - if (target_slot == -1) { - if (LiteGraph.debug) { - console.log( - "Connect: Error, no slot of name " + target_slot - ); - } - return null; - } - } else if (target_slot === LiteGraph.EVENT) { - - if (LiteGraph.do_add_triggers_slots){ - //search for first slot with event? :: NO this is done outside - //console.log("Connect: Creating triggerEvent"); - // force mode - target_node.changeMode(LiteGraph.ON_TRIGGER); - target_slot = target_node.findInputSlot("onTrigger"); - }else{ - return null; // -- break -- - } - } else if ( - !target_node.inputs || - target_slot >= target_node.inputs.length - ) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return null; - } - - var changed = false; - - var input = target_node.inputs[target_slot]; - var link_info = null; - var output = this.outputs[slot]; - - if (!this.outputs[slot]){ - /*console.debug("Invalid slot passed: "+slot); - console.debug(this.outputs);*/ - return null; - } - - // allow target node to change slot - if (target_node.onBeforeConnectInput) { - // This way node can choose another slot (or make a new one?) - target_slot = target_node.onBeforeConnectInput(target_slot); //callback - } - - //check target_slot and check connection types - if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) - { - this.setDirtyCanvas(false, true); - if(changed) - this.graph.connectionChange(this, link_info); - return null; - }else{ - //console.debug("valid connection",output.type, input.type); - } - - //allows nodes to block connection, callback - if (target_node.onConnectInput) { - if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { - return null; - } - } - if (this.onConnectOutput) { // callback - if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { - return null; - } - } - - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { - this.graph.beforeChange(); - target_node.disconnectInput(target_slot, {doProcessChange: false}); - changed = true; - } - if (output.links !== null && output.links.length){ - switch(output.type){ - case LiteGraph.EVENT: - if (!LiteGraph.allow_multi_output_for_events){ - this.graph.beforeChange(); - this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); - changed = true; - } - break; - default: - break; - } - } - - var nextId - if (LiteGraph.use_uuids) - nextId = LiteGraph.uuidv4(); - else - nextId = ++this.graph.last_link_id; - - //create link class - link_info = new LLink( - nextId, - input.type || output.type, - this.id, - slot, - target_node.id, - target_slot - ); - - //add to graph links list - this.graph.links[link_info.id] = link_info; - - //connect in output - if (output.links == null) { - output.links = []; - } - output.links.push(link_info.id); - //connect in input - target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - true, - link_info, - output - ); - } //link_info has been created now, so its updated - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - target_slot, - true, - link_info, - input - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - target_slot, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot, - target_node, - target_slot - ); - } - - this.setDirtyCanvas(false, true); - this.graph.afterChange(); - this.graph.connectionChange(this, link_info); - - return link_info; - }; - - /** - * disconnect one output to an specific node - * @method disconnectOutput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { - if (slot.constructor === String) { - slot = this.findOutputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.outputs || slot >= this.outputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - //get output slot - var output = this.outputs[slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //one of the output links in this slot - if (target_node) { - if (target_node.constructor === Number) { - target_node = this.graph.getNodeById(target_node); - } - if (!target_node) { - throw "Target Node not found"; - } - - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - - //is the link we are searching for... - if (link_info.target_id == target_node.id) { - output.links.splice(i, 1); //remove here - var input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove there - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.graph) { - this.graph._version++; - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - break; - } - } - } //all the links in this output slot - else { - for (var i = 0, l = output.links.length; i < l; i++) { - var link_id = output.links[i]; - var link_info = this.graph.links[link_id]; - if (!link_info) { - //bug: it happens sometimes - continue; - } - - var target_node = this.graph.getNodeById(link_info.target_id); - var input = null; - if (this.graph) { - this.graph._version++; - } - if (target_node) { - input = target_node.inputs[link_info.target_slot]; - input.link = null; //remove other side link - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.INPUT, - link_info.target_slot, - false, - link_info, - input - ); - } //link_info hasn't been modified so its ok - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - delete this.graph.links[link_id]; //remove the link from the links pool - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.OUTPUT, - slot, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - this, - slot - ); - this.graph.onNodeConnectionChange( - LiteGraph.INPUT, - target_node, - link_info.target_slot - ); - } - } - output.links = null; - } - - this.setDirtyCanvas(false, true); - this.graph.connectionChange(this); - return true; - }; - - /** - * disconnect one input - * @method disconnectInput - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @return {boolean} if it was disconnected successfully - */ - LGraphNode.prototype.disconnectInput = function(slot) { - //seek for the output slot - if (slot.constructor === String) { - slot = this.findInputSlot(slot); - if (slot == -1) { - if (LiteGraph.debug) { - console.log("Connect: Error, no slot of name " + slot); - } - return false; - } - } else if (!this.inputs || slot >= this.inputs.length) { - if (LiteGraph.debug) { - console.log("Connect: Error, slot number not found"); - } - return false; - } - - var input = this.inputs[slot]; - if (!input) { - return false; - } - - var link_id = this.inputs[slot].link; - if(link_id != null) - { - this.inputs[slot].link = null; - - //remove other side - var link_info = this.graph.links[link_id]; - if (link_info) { - var target_node = this.graph.getNodeById(link_info.origin_id); - if (!target_node) { - return false; - } - - var output = target_node.outputs[link_info.origin_slot]; - if (!output || !output.links || output.links.length == 0) { - return false; - } - - //search in the inputs list for this link - for (var i = 0, l = output.links.length; i < l; i++) { - if (output.links[i] == link_id) { - output.links.splice(i, 1); - break; - } - } - - delete this.graph.links[link_id]; //remove from the pool - if (this.graph) { - this.graph._version++; - } - if (this.onConnectionsChange) { - this.onConnectionsChange( - LiteGraph.INPUT, - slot, - false, - link_info, - input - ); - } - if (target_node.onConnectionsChange) { - target_node.onConnectionsChange( - LiteGraph.OUTPUT, - i, - false, - link_info, - output - ); - } - if (this.graph && this.graph.onNodeConnectionChange) { - this.graph.onNodeConnectionChange( - LiteGraph.OUTPUT, - target_node, - i - ); - this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); - } - } - } //link != null - - this.setDirtyCanvas(false, true); - if(this.graph) - this.graph.connectionChange(this); - return true; - }; - - /** - * returns the center of a connection point in canvas coords - * @method getConnectionPos - * @param {boolean} is_input true if if a input slot, false if it is an output - * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) - * @param {vec2} out [optional] a place to store the output, to free garbage - * @return {[x,y]} the position - **/ - LGraphNode.prototype.getConnectionPos = function( - is_input, - slot_number, - out - ) { - out = out || new Float32Array(2); - var num_slots = 0; - if (is_input && this.inputs) { - num_slots = this.inputs.length; - } - if (!is_input && this.outputs) { - num_slots = this.outputs.length; - } - - var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; - - if (this.flags.collapsed) { - var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { - out[0] = this.pos[0] + w * 0.5; - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1]; - } - } else { - if (is_input) { - out[0] = this.pos[0]; - } else { - out[0] = this.pos[0] + w; - } - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; - } - return out; - } - - //weird feature that never got finished - if (is_input && slot_number == -1) { - out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; - return out; - } - - //hard-coded pos - if ( - is_input && - num_slots > slot_number && - this.inputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; - return out; - } else if ( - !is_input && - num_slots > slot_number && - this.outputs[slot_number].pos - ) { - out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; - out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; - return out; - } - - //horizontal distributed slots - if (this.horizontal) { - out[0] = - this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); - if (is_input) { - out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; - } else { - out[1] = this.pos[1] + this.size[1]; - } - return out; - } - - //default vertical slots - if (is_input) { - out[0] = this.pos[0] + offset; - } else { - out[0] = this.pos[0] + this.size[0] + 1 - offset; - } - out[1] = - this.pos[1] + - (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + - (this.constructor.slot_start_y || 0); - return out; - }; - - /* Force align to grid */ - LGraphNode.prototype.alignToGrid = function() { - this.pos[0] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); - this.pos[1] = - LiteGraph.CANVAS_GRID_SIZE * - Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); - }; - - /* Console output */ - LGraphNode.prototype.trace = function(msg) { - if (!this.console) { - this.console = []; - } - - this.console.push(msg); - if (this.console.length > LGraphNode.MAX_CONSOLE) { - this.console.shift(); - } - - if(this.graph.onNodeTrace) - this.graph.onNodeTrace(this, msg); - }; - - /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ - LGraphNode.prototype.setDirtyCanvas = function( - dirty_foreground, - dirty_background - ) { - if (!this.graph) { - return; - } - this.graph.sendActionToCanvas("setDirty", [ - dirty_foreground, - dirty_background - ]); - }; - - LGraphNode.prototype.loadImage = function(url) { - var img = new Image(); - img.src = LiteGraph.node_images_path + url; - img.ready = false; - - var that = this; - img.onload = function() { - this.ready = true; - that.setDirtyCanvas(true); - }; - return img; - }; - - //safe LGraphNode action execution (not sure if safe) - /* -LGraphNode.prototype.executeAction = function(action) -{ - if(action == "") return false; - - if( action.indexOf(";") != -1 || action.indexOf("}") != -1) - { - this.trace("Error: Action contains unsafe characters"); - return false; - } - - var tokens = action.split("("); - var func_name = tokens[0]; - if( typeof(this[func_name]) != "function") - { - this.trace("Error: Action not found on node: " + func_name); - return false; - } - - var code = action; - - try - { - var _foo = eval; - eval = null; - (new Function("with(this) { " + code + "}")).call(this); - eval = _foo; - } - catch (err) - { - this.trace("Error executing action {" + action + "} :" + err); - return false; - } - - return true; -} -*/ - - /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ - LGraphNode.prototype.captureInput = function(v) { - if (!this.graph || !this.graph.list_of_graphcanvas) { - return; - } - - var list = this.graph.list_of_graphcanvas; - - for (var i = 0; i < list.length; ++i) { - var c = list[i]; - //releasing somebody elses capture?! - if (!v && c.node_capturing_input != this) { - continue; - } - - //change - c.node_capturing_input = v ? this : null; - } - }; - - /** - * Collapse the node to make it smaller on the canvas - * @method collapse - **/ - LGraphNode.prototype.collapse = function(force) { - this.graph._version++; - if (this.constructor.collapsable === false && !force) { - return; - } - if (!this.flags.collapsed) { - this.flags.collapsed = true; - } else { - this.flags.collapsed = false; - } - this.setDirtyCanvas(true, true); - }; - - /** - * Forces the node to do not move or realign on Z - * @method pin - **/ - - LGraphNode.prototype.pin = function(v) { - this.graph._version++; - if (v === undefined) { - this.flags.pinned = !this.flags.pinned; - } else { - this.flags.pinned = v; - } - }; - - LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { - return [ - (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], - (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] - ]; - }; - - function LGraphGroup(title) { - this._ctor(title); - } - - global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; - - LGraphGroup.prototype._ctor = function(title) { - this.title = title || "Group"; - this.font_size = 24; - this.color = LGraphCanvas.node_colors.pale_blue - ? LGraphCanvas.node_colors.pale_blue.groupcolor - : "#AAA"; - this._bounding = new Float32Array([10, 10, 140, 80]); - this._pos = this._bounding.subarray(0, 2); - this._size = this._bounding.subarray(2, 4); - this._nodes = []; - this.graph = null; - - Object.defineProperty(this, "pos", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._pos[0] = v[0]; - this._pos[1] = v[1]; - }, - get: function() { - return this._pos; - }, - enumerable: true - }); - - Object.defineProperty(this, "size", { - set: function(v) { - if (!v || v.length < 2) { - return; - } - this._size[0] = Math.max(140, v[0]); - this._size[1] = Math.max(80, v[1]); - }, - get: function() { - return this._size; - }, - enumerable: true - }); - }; - - LGraphGroup.prototype.configure = function(o) { - this.title = o.title; - this._bounding.set(o.bounding); - this.color = o.color; - this.font_size = o.font_size; - }; - - LGraphGroup.prototype.serialize = function() { - var b = this._bounding; - return { - title: this.title, - bounding: [ - Math.round(b[0]), - Math.round(b[1]), - Math.round(b[2]), - Math.round(b[3]) - ], - color: this.color, - font_size: this.font_size - }; - }; - - LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { - this._pos[0] += deltax; - this._pos[1] += deltay; - if (ignore_nodes) { - return; - } - for (var i = 0; i < this._nodes.length; ++i) { - var node = this._nodes[i]; - node.pos[0] += deltax; - node.pos[1] += deltay; - } - }; - - LGraphGroup.prototype.recomputeInsideNodes = function() { - this._nodes.length = 0; - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if (!overlapBounding(this._bounding, node_bounding)) { - continue; - } //out of the visible area - this._nodes.push(node); - } - }; - - LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; - LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; - - //**************************************** - - //Scale and Offset - function DragAndScale(element, skip_events) { - this.offset = new Float32Array([0, 0]); - this.scale = 1; - this.max_scale = 10; - this.min_scale = 0.1; - this.onredraw = null; - this.enabled = true; - this.last_mouse = [0, 0]; - this.element = null; - this.visible_area = new Float32Array(4); - - if (element) { - this.element = element; - if (!skip_events) { - this.bindEvents(element); - } - } - } - - LiteGraph.DragAndScale = DragAndScale; - - DragAndScale.prototype.bindEvents = function(element) { - this.last_mouse = new Float32Array(2); - - this._binded_mouse_callback = this.onMouse.bind(this); - - LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); - - element.addEventListener( - "mousewheel", - this._binded_mouse_callback, - false - ); - element.addEventListener("wheel", this._binded_mouse_callback, false); - }; - - DragAndScale.prototype.computeVisibleArea = function( viewport ) { - if (!this.element) { - this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; - return; - } - var width = this.element.width; - var height = this.element.height; - var startx = -this.offset[0]; - var starty = -this.offset[1]; - if( viewport ) - { - startx += viewport[0] / this.scale; - starty += viewport[1] / this.scale; - width = viewport[2]; - height = viewport[3]; - } - var endx = startx + width / this.scale; - var endy = starty + height / this.scale; - this.visible_area[0] = startx; - this.visible_area[1] = starty; - this.visible_area[2] = endx - startx; - this.visible_area[3] = endy - starty; - }; - - DragAndScale.prototype.onMouse = function(e) { - if (!this.enabled) { - return; - } - - var canvas = this.element; - var rect = canvas.getBoundingClientRect(); - var x = e.clientX - rect.left; - var y = e.clientY - rect.top; - e.canvasx = x; - e.canvasy = y; - e.dragging = this.dragging; - - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); - - var ignore = false; - if (this.onmouse) { - ignore = this.onmouse(e); - } - - if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { - this.dragging = true; - LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); - } else if (e.type == LiteGraph.pointerevents_method+"move") { - if (!ignore) { - var deltax = x - this.last_mouse[0]; - var deltay = y - this.last_mouse[1]; - if (this.dragging) { - this.mouseDrag(deltax, deltay); - } - } - } else if (e.type == LiteGraph.pointerevents_method+"up") { - this.dragging = false; - LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); - LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); - LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); - } else if ( is_inside && - (e.type == "mousewheel" || - e.type == "wheel" || - e.type == "DOMMouseScroll") - ) { - e.eventType = "mousewheel"; - if (e.type == "wheel") { - e.wheel = -e.deltaY; - } else { - e.wheel = - e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - } - - //from stack overflow - e.delta = e.wheelDelta - ? e.wheelDelta / 40 - : e.deltaY - ? -e.deltaY / 3 - : 0; - this.changeDeltaScale(1.0 + e.delta * 0.05); - } - - this.last_mouse[0] = x; - this.last_mouse[1] = y; - - if(is_inside) - { - e.preventDefault(); - e.stopPropagation(); - return false; - } - }; - - DragAndScale.prototype.toCanvasContext = function(ctx) { - ctx.scale(this.scale, this.scale); - ctx.translate(this.offset[0], this.offset[1]); - }; - - DragAndScale.prototype.convertOffsetToCanvas = function(pos) { - //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; - return [ - (pos[0] + this.offset[0]) * this.scale, - (pos[1] + this.offset[1]) * this.scale - ]; - }; - - DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { - out = out || [0, 0]; - out[0] = pos[0] / this.scale - this.offset[0]; - out[1] = pos[1] / this.scale - this.offset[1]; - return out; - }; - - DragAndScale.prototype.mouseDrag = function(x, y) { - this.offset[0] += x / this.scale; - this.offset[1] += y / this.scale; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeScale = function(value, zooming_center) { - if (value < this.min_scale) { - value = this.min_scale; - } else if (value > this.max_scale) { - value = this.max_scale; - } - - if (value == this.scale) { - return; - } - - if (!this.element) { - return; - } - - var rect = this.element.getBoundingClientRect(); - if (!rect) { - return; - } - - zooming_center = zooming_center || [ - rect.width * 0.5, - rect.height * 0.5 - ]; - var center = this.convertCanvasToOffset(zooming_center); - this.scale = value; - if (Math.abs(this.scale - 1) < 0.01) { - this.scale = 1; - } - - var new_center = this.convertCanvasToOffset(zooming_center); - var delta_offset = [ - new_center[0] - center[0], - new_center[1] - center[1] - ]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - - if (this.onredraw) { - this.onredraw(this); - } - }; - - DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { - this.changeScale(this.scale * value, zooming_center); - }; - - DragAndScale.prototype.reset = function() { - this.scale = 1; - this.offset[0] = 0; - this.offset[1] = 0; - }; - - //********************************************************************************* - // LGraphCanvas: LGraph renderer CLASS - //********************************************************************************* - - /** - * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. - * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked - * - * @class LGraphCanvas - * @constructor - * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) - * @param {LGraph} graph [optional] - * @param {Object} options [optional] { skip_rendering, autoresize, viewport } - */ - function LGraphCanvas(canvas, graph, options) { - this.options = options = options || {}; - - //if(graph === undefined) - // throw ("No graph assigned"); - this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; - - if (canvas && canvas.constructor === String) { - canvas = document.querySelector(canvas); - } - - this.ds = new DragAndScale(); - this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much - - this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; - this.inner_text_font = - "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; - this.node_title_color = LiteGraph.NODE_TITLE_COLOR; - this.default_link_color = LiteGraph.LINK_COLOR; - this.default_connection_color = { - input_off: "#778", - input_on: "#7F7", //"#BBD" - output_off: "#778", - output_on: "#7F7" //"#BBD" - }; - this.default_connection_color_byType = { - /*number: "#7F7", - string: "#77F", - boolean: "#F77",*/ - } - this.default_connection_color_byTypeOff = { - /*number: "#474", - string: "#447", - boolean: "#744",*/ - }; - - this.highquality_render = true; - this.use_gradients = false; //set to true to render titlebar with gradients - this.editor_alpha = 1; //used for transition - this.pause_rendering = false; - this.clear_background = true; - this.clear_background_color = "#222"; - - this.read_only = false; //if set to true users cannot modify the graph - this.render_only_selected = true; - this.live_mode = false; - this.show_info = true; - this.allow_dragcanvas = true; - this.allow_dragnodes = true; - this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc - this.multi_select = false; //allow selecting multi nodes without pressing extra keys - this.allow_searchbox = true; - this.allow_reconnect_links = true; //allows to change a connection with having to redo it again - this.align_to_grid = false; //snap to grid - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - - this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything - this.always_render_background = false; - this.render_shadows = true; - this.render_canvas_border = true; - this.render_connections_shadows = false; //too much cpu - this.render_connections_border = true; - this.render_curved_connections = false; - this.render_connection_arrows = false; - this.render_collapsed_slots = true; - this.render_execution_order = false; - this.render_title_colored = true; - this.render_link_tooltip = true; - - this.links_render_mode = LiteGraph.SPLINE_LINK; - - this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle - this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle - this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD - - //to personalize the search box - this.onSearchBox = null; - this.onSearchBoxSelection = null; - - //callbacks - this.onMouse = null; - this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform - this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform - this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) - this.onDrawLinkTooltip = null; //called when rendering a tooltip - this.onNodeMoved = null; //called after moving a node - this.onSelectionChange = null; //called if the selection changes - this.onConnectingChange = null; //called before any link changes - this.onBeforeChange = null; //called before modifying the graph - this.onAfterChange = null; //called after modifying the graph - - this.connections_width = 3; - this.round_radius = 8; - - this.current_node = null; - this.node_widget = null; //used for widgets - this.over_link_center = null; - this.last_mouse_position = [0, 0]; - this.visible_area = this.ds.visible_area; - this.visible_links = []; - - this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas - - //link canvas and graph - if (graph) { - graph.attachCanvas(this); - } - - this.setCanvas(canvas,options.skip_events); - this.clear(); - - if (!options.skip_render) { - this.startRendering(); - } - - this.autoresize = options.autoresize; - } - - global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; - - LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; - - LGraphCanvas.link_type_colors = { - "-1": LiteGraph.EVENT_LINK_COLOR, - number: "#AAA", - node: "#DCA" - }; - LGraphCanvas.gradients = {}; //cache of gradients - - /** - * clears all the data inside - * - * @method clear - */ - LGraphCanvas.prototype.clear = function() { - this.frame = 0; - this.last_draw_time = 0; - this.render_time = 0; - this.fps = 0; - - //this.scale = 1; - //this.offset = [0,0]; - - this.dragging_rectangle = null; - - this.selected_nodes = {}; - this.selected_group = null; - - this.visible_nodes = []; - this.node_dragged = null; - this.node_over = null; - this.node_capturing_input = null; - this.connecting_node = null; - this.highlighted_links = {}; - - this.dragging_canvas = false; - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.dirty_area = null; - - this.node_in_panel = null; - this.node_widget = null; - - this.last_mouse = [0, 0]; - this.last_mouseclick = 0; - this.pointer_is_down = false; - this.pointer_is_double = false; - this.visible_area.set([0, 0, 0, 0]); - - if (this.onClear) { - this.onClear(); - } - }; - - /** - * assigns a graph, you can reassign graphs to the same canvas - * - * @method setGraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { - if (this.graph == graph) { - return; - } - - if (!skip_clear) { - this.clear(); - } - - if (!graph && this.graph) { - this.graph.detachCanvas(this); - return; - } - - graph.attachCanvas(this); - - //remove the graph stack in case a subgraph was open - if (this._graph_stack) - this._graph_stack = null; - - this.setDirty(true, true); - }; - - /** - * returns the top level graph (in case there are subgraphs open on the canvas) - * - * @method getTopGraph - * @return {LGraph} graph - */ - LGraphCanvas.prototype.getTopGraph = function() - { - if(this._graph_stack.length) - return this._graph_stack[0]; - return this.graph; - } - - /** - * opens a graph contained inside a node in the current graph - * - * @method openSubgraph - * @param {LGraph} graph - */ - LGraphCanvas.prototype.openSubgraph = function(graph) { - if (!graph) { - throw "graph cannot be null"; - } - - if (this.graph == graph) { - throw "graph cannot be the same"; - } - - this.clear(); - - if (this.graph) { - if (!this._graph_stack) { - this._graph_stack = []; - } - this._graph_stack.push(this.graph); - } - - graph.attachCanvas(this); - this.checkPanels(); - this.setDirty(true, true); - }; - - /** - * closes a subgraph contained inside a node - * - * @method closeSubgraph - * @param {LGraph} assigns a graph - */ - LGraphCanvas.prototype.closeSubgraph = function() { - if (!this._graph_stack || this._graph_stack.length == 0) { - return; - } - var subgraph_node = this.graph._subgraph_node; - var graph = this._graph_stack.pop(); - this.selected_nodes = {}; - this.highlighted_links = {}; - graph.attachCanvas(this); - this.setDirty(true, true); - if (subgraph_node) { - this.centerOnNode(subgraph_node); - this.selectNodes([subgraph_node]); - } - // when close sub graph back to offset [0, 0] scale 1 - this.ds.offset = [0, 0] - this.ds.scale = 1 - }; - - /** - * returns the visually active graph (in case there are more in the stack) - * @method getCurrentGraph - * @return {LGraph} the active graph - */ - LGraphCanvas.prototype.getCurrentGraph = function() { - return this.graph; - }; - - /** - * assigns a canvas - * - * @method setCanvas - * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) - */ - LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { - var that = this; - - if (canvas) { - if (canvas.constructor === String) { - canvas = document.getElementById(canvas); - if (!canvas) { - throw "Error creating LiteGraph canvas: Canvas not found"; - } - } - } - - if (canvas === this.canvas) { - return; - } - - if (!canvas && this.canvas) { - //maybe detach events from old_canvas - if (!skip_events) { - this.unbindEvents(); - } - } - - this.canvas = canvas; - this.ds.element = canvas; - - if (!canvas) { - return; - } - - //this.canvas.tabindex = "1000"; - canvas.className += " lgraphcanvas"; - canvas.data = this; - canvas.tabindex = "1"; //to allow key events - - //bg canvas: used for non changing stuff - this.bgcanvas = null; - if (!this.bgcanvas) { - this.bgcanvas = document.createElement("canvas"); - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - } - - if (canvas.getContext == null) { - if (canvas.localName != "canvas") { - throw "Element supplied for LGraphCanvas must be a element, you passed a " + - canvas.localName; - } - throw "This browser doesn't support Canvas"; - } - - var ctx = (this.ctx = canvas.getContext("2d")); - if (ctx == null) { - if (!canvas.webgl_enabled) { - console.warn( - "This canvas seems to be WebGL, enabling WebGL renderer" - ); - } - this.enableWebGL(); - } - - //input: (move and up could be unbinded) - // why here? this._mousemove_callback = this.processMouseMove.bind(this); - // why here? this._mouseup_callback = this.processMouseUp.bind(this); - - if (!skip_events) { - this.bindEvents(); - } - }; - - //used in some events to capture them - LGraphCanvas.prototype._doNothing = function doNothing(e) { - //console.log("pointerevents: _doNothing "+e.type); - e.preventDefault(); - return false; - }; - LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { - e.preventDefault(); - return true; - }; - - /** - * binds mouse, keyboard, touch and drag events to the canvas - * @method bindEvents - **/ - LGraphCanvas.prototype.bindEvents = function() { - if (this._events_binded) { - console.warn("LGraphCanvas: events already binded"); - return; - } - - //console.log("pointerevents: bindEvents"); - - var canvas = this.canvas; - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; //hack used when moving canvas between windows - - this._mousedown_callback = this.processMouseDown.bind(this); - this._mousewheel_callback = this.processMouseWheel.bind(this); - // why mousemove and mouseup were not binded here? - this._mousemove_callback = this.processMouseMove.bind(this); - this._mouseup_callback = this.processMouseUp.bind(this); - - //touch events -- TODO IMPLEMENT - //this._touch_callback = this.touchHandler.bind(this); - - LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded - canvas.addEventListener("mousewheel", this._mousewheel_callback, false); - - LiteGraph.pointerListenerAdd(canvas,"up", this._mouseup_callback, true); // CHECK: ??? binded or not - LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); - - canvas.addEventListener("contextmenu", this._doNothing); - canvas.addEventListener( - "DOMMouseScroll", - this._mousewheel_callback, - false - ); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*if( 'touchstart' in document.documentElement ) - { - canvas.addEventListener("touchstart", this._touch_callback, true); - canvas.addEventListener("touchmove", this._touch_callback, true); - canvas.addEventListener("touchend", this._touch_callback, true); - canvas.addEventListener("touchcancel", this._touch_callback, true); - }*/ - - //Keyboard ****************** - this._key_callback = this.processKey.bind(this); - canvas.setAttribute("tabindex",1); //otherwise key events are ignored - canvas.addEventListener("keydown", this._key_callback, true); - document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup - - //Dropping Stuff over nodes ************************************ - this._ondrop_callback = this.processDrop.bind(this); - - canvas.addEventListener("dragover", this._doNothing, false); - canvas.addEventListener("dragend", this._doNothing, false); - canvas.addEventListener("drop", this._ondrop_callback, false); - canvas.addEventListener("dragenter", this._doReturnTrue, false); - - this._events_binded = true; - }; - - /** - * unbinds mouse events from the canvas - * @method unbindEvents - **/ - LGraphCanvas.prototype.unbindEvents = function() { - if (!this._events_binded) { - console.warn("LGraphCanvas: no events binded"); - return; - } - - //console.log("pointerevents: unbindEvents"); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_callback); - LiteGraph.pointerListenerRemove(this.canvas,"down", this._mousedown_callback); - this.canvas.removeEventListener( - "mousewheel", - this._mousewheel_callback - ); - this.canvas.removeEventListener( - "DOMMouseScroll", - this._mousewheel_callback - ); - this.canvas.removeEventListener("keydown", this._key_callback); - document.removeEventListener("keyup", this._key_callback); - this.canvas.removeEventListener("contextmenu", this._doNothing); - this.canvas.removeEventListener("drop", this._ondrop_callback); - this.canvas.removeEventListener("dragenter", this._doReturnTrue); - - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents - /*this.canvas.removeEventListener("touchstart", this._touch_callback ); - this.canvas.removeEventListener("touchmove", this._touch_callback ); - this.canvas.removeEventListener("touchend", this._touch_callback ); - this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ - - this._mousedown_callback = null; - this._mousewheel_callback = null; - this._key_callback = null; - this._ondrop_callback = null; - - this._events_binded = false; - }; - - LGraphCanvas.getFileExtension = function(url) { - var question = url.indexOf("?"); - if (question != -1) { - url = url.substr(0, question); - } - var point = url.lastIndexOf("."); - if (point == -1) { - return ""; - } - return url.substr(point + 1).toLowerCase(); - }; - - /** - * this function allows to render the canvas using WebGL instead of Canvas2D - * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL - * @method enableWebGL - **/ - LGraphCanvas.prototype.enableWebGL = function() { - if (typeof GL === "undefined") { - throw "litegl.js must be included to use a WebGL canvas"; - } - if (typeof enableWebGLCanvas === "undefined") { - throw "webglCanvas.js must be included to use this feature"; - } - - this.gl = this.ctx = enableWebGLCanvas(this.canvas); - this.ctx.webgl = true; - this.bgcanvas = this.canvas; - this.bgctx = this.gl; - this.canvas.webgl_enabled = true; - - /* - GL.create({ canvas: this.bgcanvas }); - this.bgctx = enableWebGLCanvas( this.bgcanvas ); - window.gl = this.gl; - */ - }; - - /** - * marks as dirty the canvas, this way it will be rendered again - * - * @class LGraphCanvas - * @method setDirty - * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) - * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) - */ - LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { - if (fgcanvas) { - this.dirty_canvas = true; - } - if (bgcanvas) { - this.dirty_bgcanvas = true; - } - }; - - /** - * Used to attach the canvas in a popup - * - * @method getCanvasWindow - * @return {window} returns the window where the canvas is attached (the DOM root node) - */ - LGraphCanvas.prototype.getCanvasWindow = function() { - if (!this.canvas) { - return window; - } - var doc = this.canvas.ownerDocument; - return doc.defaultView || doc.parentWindow; - }; - - /** - * starts rendering the content of the canvas when needed - * - * @method startRendering - */ - LGraphCanvas.prototype.startRendering = function() { - if (this.is_rendering) { - return; - } //already rendering - - this.is_rendering = true; - renderFrame.call(this); - - function renderFrame() { - if (!this.pause_rendering) { - this.draw(); - } - - var window = this.getCanvasWindow(); - if (this.is_rendering) { - window.requestAnimationFrame(renderFrame.bind(this)); - } - } - }; - - /** - * stops rendering the content of the canvas (to save resources) - * - * @method stopRendering - */ - LGraphCanvas.prototype.stopRendering = function() { - this.is_rendering = false; - /* - if(this.rendering_timer_id) - { - clearInterval(this.rendering_timer_id); - this.rendering_timer_id = null; - } - */ - }; - - /* LiteGraphCanvas input */ - - //used to block future mouse events (because of im gui) - LGraphCanvas.prototype.blockClick = function() - { - this.block_click = true; - this.last_mouseclick = 0; - } - - LGraphCanvas.prototype.processMouseDown = function(e) { - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - this.adjustMouseEvent(e); - - var ref_window = this.getCanvasWindow(); - var document = ref_window.document; - LGraphCanvas.active_canvas = this; - var that = this; - - var x = e.clientX; - var y = e.clientY; - //console.log(y,this.viewport); - //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); - - this.ds.viewport = this.viewport; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - - //move mouse move event to the window in case it drags outside of the canvas - if(!this.options.skip_events) - { - LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousemove_callback); - LiteGraph.pointerListenerAdd(ref_window.document,"move", this._mousemove_callback,true); //catch for the entire window - LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); - } - - if(!is_inside){ - return; - } - - var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); - var skip_dragging = false; - var skip_action = false; - var now = LiteGraph.getTime(); - var is_primary = (e.isPrimary === undefined || !e.isPrimary); - var is_double_click = (now - this.last_mouseclick < 300) && is_primary; - this.mouse[0] = e.clientX; - this.mouse[1] = e.clientY; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - this.last_click_position = [this.mouse[0],this.mouse[1]]; - - if (this.pointer_is_down && is_primary ){ - this.pointer_is_double = true; - //console.log("pointerevents: pointer_is_double start"); - }else{ - this.pointer_is_double = false; - } - this.pointer_is_down = true; - - - this.canvas.focus(); - - LiteGraph.closeAllContextMenus(ref_window); - - if (this.onMouse) - { - if (this.onMouse(e) == true) - return; - } - - //left button mouse / single finger - if (e.which == 1 && !this.pointer_is_double) - { - if (e.ctrlKey) - { - this.dragging_rectangle = new Float32Array(4); - this.dragging_rectangle[0] = e.canvasX; - this.dragging_rectangle[1] = e.canvasY; - this.dragging_rectangle[2] = 1; - this.dragging_rectangle[3] = 1; - skip_action = true; - } - - // clone node ALT dragging - if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) - { - if (cloned = node.clone()){ - cloned.pos[0] += 5; - cloned.pos[1] += 5; - this.graph.add(cloned,false,{doCalcSize: false}); - node = cloned; - skip_action = true; - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - if (!this.selected_nodes[node.id]) { - this.processNodeSelected(node, e); - } - } - } - } - - var clicking_canvas_bg = false; - - //when clicked on top of a node - //and it is not interactive - if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { - if (!this.live_mode && !node.flags.pinned) { - this.bringToFront(node); - } //if it wasn't selected? - - //not dragging mouse to connect two slots - if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { - //Search for corner for resize - if ( !skip_action && - node.resizable !== false && - isInsideRectangle( e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 10, - 10 - ) - ) { - this.graph.beforeChange(); - this.resizing_node = node; - this.canvas.style.cursor = "se-resize"; - skip_action = true; - } else { - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - this.connecting_node = node; - this.connecting_output = output; - this.connecting_output.slot_index = i; - this.connecting_pos = node.getConnectionPos( false, i ); - this.connecting_slot = i; - - if (LiteGraph.shift_click_do_break_link_from){ - if (e.shiftKey) { - node.disconnectOutput(i); - } - } - - if (is_double_click) { - if (node.onOutputDblClick) { - node.onOutputDblClick(i, e); - } - } else { - if (node.onOutputClick) { - node.onOutputClick(i, e); - } - } - - skip_action = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - link_pos[0] - 15, - link_pos[1] - 10, - 30, - 20 - ) - ) { - if (is_double_click) { - if (node.onInputDblClick) { - node.onInputDblClick(i, e); - } - } else { - if (node.onInputClick) { - node.onInputClick(i, e); - } - } - - if (input.link !== null) { - var link_info = this.graph.links[ - input.link - ]; //before disconnecting - if (LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - this.dirty_bgcanvas = true; - skip_action = true; - }else{ - // do same action as has not node ? - } - - if ( - this.allow_reconnect_links || - //this.move_destination_link_without_shift || - e.shiftKey - ) { - if (!LiteGraph.click_do_break_link_to){ - node.disconnectInput(i); - } - this.connecting_node = this.graph._nodes_by_id[ - link_info.origin_id - ]; - this.connecting_slot = - link_info.origin_slot; - this.connecting_output = this.connecting_node.outputs[ - this.connecting_slot - ]; - this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); - - this.dirty_bgcanvas = true; - skip_action = true; - } - - - }else{ - // has not node - } - - if (!skip_action){ - // connect from in to out, from to to from - this.connecting_node = node; - this.connecting_input = input; - this.connecting_input.slot_index = i; - this.connecting_pos = node.getConnectionPos( true, i ); - this.connecting_slot = i; - - this.dirty_bgcanvas = true; - skip_action = true; - } - } - } - } - } //not resizing - } - - //it wasn't clicked on the links boxes - if (!skip_action) { - var block_drag_node = false; - var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; - - //widgets - var widget = this.processNodeWidgets( node, this.graph_mouse, e ); - if (widget) { - block_drag_node = true; - this.node_widget = [node, widget]; - } - - //double clicking - if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { - //double click node - if (node.onDblClick) { - node.onDblClick( e, pos, this ); - } - this.processNodeDblClicked(node); - block_drag_node = true; - } - - //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { - block_drag_node = true; - } else { - //open subgraph button - if(node.subgraph && !node.skip_subgraph_button) - { - if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { - var that = this; - setTimeout(function() { - that.openSubgraph(node.subgraph); - }, 10); - } - } - - if (this.live_mode) { - clicking_canvas_bg = true; - block_drag_node = true; - } - } - - if (!block_drag_node) { - if (this.allow_dragnodes) { - this.graph.beforeChange(); - this.node_dragged = node; - } - this.processNodeSelected(node, e); - } else { // double-click - /** - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while its panel is open. - */ - if (!node.is_selected) this.processNodeSelected(node, e); - } - - this.dirty_canvas = true; - } - } //clicked outside of nodes - else { - if (!skip_action){ - //search for link connector - if(!this.read_only) { - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; - } - } - - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if (this.selected_group && !this.read_only ) { - if (e.ctrlKey) { - this.dragging_rectangle = null; - } - - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } - - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - e.preventDefault(); - e.stopPropagation(); - } - - clicking_canvas_bg = true; - } - } - - if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start"); - this.dragging_canvas = true; - } - - } else if (e.which == 2) { - //middle button - - if (LiteGraph.middle_click_slot_add_default_node){ - if (node && this.allow_interaction && !skip_action && !this.read_only){ - //not dragging mouse to connect two slots - if ( - !this.connecting_node && - !node.flags.collapsed && - !this.live_mode - ) { - var mClikSlot = false; - var mClikSlot_index = false; - var mClikSlot_isOut = false; - //search for outputs - if (node.outputs) { - for ( var i = 0, l = node.outputs.length; i < l; ++i ) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = output; - mClikSlot_index = i; - mClikSlot_isOut = true; - break; - } - } - } - - //search for inputs - if (node.inputs) { - for ( var i = 0, l = node.inputs.length; i < l; ++i ) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { - mClikSlot = input; - mClikSlot_index = i; - mClikSlot_isOut = false; - break; - } - } - } - //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); - if (mClikSlot && mClikSlot_index!==false){ - - var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); - var node_bounding = node.getBounding(); - // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes - var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 - ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" - ]; - var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node - ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index - ,nodeTo: !mClikSlot_isOut?node:null - ,slotTo: !mClikSlot_isOut?mClikSlot_index:null - ,position: posRef //,e: e - ,nodeType: "AUTO" //nodeNewType - ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] - ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ - }); - - } - } - } - } else if (!skip_action && this.allow_dragcanvas) { - //console.log("pointerevents: dragging_canvas start from middle button"); - this.dragging_canvas = true; - } - - - } else if (e.which == 3 || this.pointer_is_double) { - - //right button - if (this.allow_interaction && !skip_action && !this.read_only){ - - // is it hover a node ? - if (node){ - if(Object.keys(this.selected_nodes).length - && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) - ){ - // is multiselected or using shift to include the now node - if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present - }else{ - // update selection - this.selectNodes([node]); - } - } - - // show menu on this node - this.processContextMenu(node, e); - } - - } - - //TODO - //if(this.node_selected != prev_selected) - // this.onNodeSelectionChange(this.node_selected); - - this.last_mouse[0] = e.clientX; - this.last_mouse[1] = e.clientY; - this.last_mouseclick = LiteGraph.getTime(); - this.last_mouse_dragging = true; - - /* - if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - this.graph.change(); - - //this is to ensure to defocus(blur) if a text input element is on focus - if ( - !ref_window.document.activeElement || - (ref_window.document.activeElement.nodeName.toLowerCase() != - "input" && - ref_window.document.activeElement.nodeName.toLowerCase() != - "textarea") - ) { - e.preventDefault(); - } - e.stopPropagation(); - - if (this.onMouseDown) { - this.onMouseDown(e); - } - - return false; - }; - - /** - * Called when a mouse move event has to be processed - * @method processMouseMove - **/ - LGraphCanvas.prototype.processMouseMove = function(e) { - if (this.autoresize) { - this.resize(); - } - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) { - return; - } - - LGraphCanvas.active_canvas = this; - this.adjustMouseEvent(e); - var mouse = [e.clientX, e.clientY]; - this.mouse[0] = mouse[0]; - this.mouse[1] = mouse[1]; - var delta = [ - mouse[0] - this.last_mouse[0], - mouse[1] - this.last_mouse[1] - ]; - this.last_mouse = mouse; - this.graph_mouse[0] = e.canvasX; - this.graph_mouse[1] = e.canvasY; - - //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); - - if(this.block_click) - { - //console.log("pointerevents: processMouseMove block_click"); - e.preventDefault(); - return false; - } - - e.dragging = this.last_mouse_dragging; - - if (this.node_widget) { - this.processNodeWidgets( - this.node_widget[0], - this.graph_mouse, - e, - this.node_widget[1] - ); - this.dirty_canvas = true; - } - - //get node over - var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); - - if (this.dragging_rectangle) - { - this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; - this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; - this.dirty_canvas = true; - } - else if (this.selected_group && !this.read_only) - { - //moving/resizing a group - if (this.selected_group_resizing) { - this.selected_group.size = [ - e.canvasX - this.selected_group.pos[0], - e.canvasY - this.selected_group.pos[1] - ]; - } else { - var deltax = delta[0] / this.ds.scale; - var deltay = delta[1] / this.ds.scale; - this.selected_group.move(deltax, deltay, e.ctrlKey); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - } - this.dirty_bgcanvas = true; - } else if (this.dragging_canvas) { - ////console.log("pointerevents: processMouseMove is dragging_canvas"); - this.ds.offset[0] += delta[0] / this.ds.scale; - this.ds.offset[1] += delta[1] / this.ds.scale; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { - if (this.connecting_node) { - this.dirty_canvas = true; - } - - //remove mouseover flag - for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { - //mouse leave - this.graph._nodes[i].mouseOver = false; - if (this.node_over && this.node_over.onMouseLeave) { - this.node_over.onMouseLeave(e); - } - this.node_over = null; - this.dirty_canvas = true; - } - } - - //mouse over a node - if (node) { - - if(node.redraw_on_mouse) - this.dirty_canvas = true; - - //this.canvas.style.cursor = "move"; - if (!node.mouseOver) { - //mouse enter - node.mouseOver = true; - this.node_over = node; - this.dirty_canvas = true; - - if (node.onMouseEnter) { - node.onMouseEnter(e); - } - } - - //in case the node wants to do something - if (node.onMouseMove) { - node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); - } - - //if dragging a link - if (this.connecting_node) { - - if (this.connecting_output){ - - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput - - //on top of input - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS - } - } else { - this._highlight_input = null; - this._highlight_input_slot = null; // XXX CHECK THIS - } - } - - }else if(this.connecting_input){ - - var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput - - //on top of output - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.outputs[slot]) { - var slot_type = node.outputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { - this._highlight_output = pos; - } - } else { - this._highlight_output = null; - } - } - } - } - - //Search for corner - if (this.canvas) { - if ( - isInsideRectangle( - e.canvasX, - e.canvasY, - node.pos[0] + node.size[0] - 5, - node.pos[1] + node.size[1] - 5, - 5, - 5 - ) - ) { - this.canvas.style.cursor = "se-resize"; - } else { - this.canvas.style.cursor = "crosshair"; - } - } - } else { //not over a node - - //search for link connector - var over_link = null; - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; - } - over_link = link; - break; - } - if( over_link != this.over_link_center ) - { - this.over_link_center = over_link; - this.dirty_canvas = true; - } - - if (this.canvas) { - this.canvas.style.cursor = ""; - } - } //end - - //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) - if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { - this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); - } - - //node being dragged - if (this.node_dragged && !this.live_mode) { - //console.log("draggin!",this.selected_nodes); - for (var i in this.selected_nodes) { - var n = this.selected_nodes[i]; - n.pos[0] += delta[0] / this.ds.scale; - n.pos[1] += delta[1] / this.ds.scale; - if (!n.is_selected) this.processNodeSelected(n, e); /* - * Don't call the function if the block is already selected. - * Otherwise, it could cause the block to be unselected while dragging. - */ - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - - if (this.resizing_node && !this.live_mode) { - //convert mouse to node space - var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; - var min_size = this.resizing_node.computeSize(); - desired_size[0] = Math.max( min_size[0], desired_size[0] ); - desired_size[1] = Math.max( min_size[1], desired_size[1] ); - this.resizing_node.setSize( desired_size ); - - this.canvas.style.cursor = "se-resize"; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - } - } - - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse up event has to be processed - * @method processMouseUp - **/ - LGraphCanvas.prototype.processMouseUp = function(e) { - - var is_primary = ( e.isPrimary === undefined || e.isPrimary ); - - //early exit for extra pointer - if(!is_primary){ - /*e.stopPropagation(); - e.preventDefault();*/ - //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); - return false; - } - - //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); - - if( this.set_canvas_dirty_on_mouse_event ) - this.dirty_canvas = true; - - if (!this.graph) - return; - - var window = this.getCanvasWindow(); - var document = window.document; - LGraphCanvas.active_canvas = this; - - //restore the mousemove event back to the canvas - if(!this.options.skip_events) - { - //console.log("pointerevents: processMouseUp adjustEventListener"); - LiteGraph.pointerListenerRemove(document,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerAdd(this.canvas,"move", this._mousemove_callback,true); - LiteGraph.pointerListenerRemove(document,"up", this._mouseup_callback,true); - } - - this.adjustMouseEvent(e); - var now = LiteGraph.getTime(); - e.click_time = now - this.last_mouseclick; - this.last_mouse_dragging = false; - this.last_click_position = null; - - if(this.block_click) - { - //console.log("pointerevents: processMouseUp block_clicks"); - this.block_click = false; //used to avoid sending twice a click in a immediate button - } - - //console.log("pointerevents: processMouseUp which: "+e.which); - - if (e.which == 1) { - - if( this.node_widget ) - { - this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); - } - - //left button - this.node_widget = null; - - if (this.selected_group) { - var diffx = - this.selected_group.pos[0] - - Math.round(this.selected_group.pos[0]); - var diffy = - this.selected_group.pos[1] - - Math.round(this.selected_group.pos[1]); - this.selected_group.move(diffx, diffy, e.ctrlKey); - this.selected_group.pos[0] = Math.round( - this.selected_group.pos[0] - ); - this.selected_group.pos[1] = Math.round( - this.selected_group.pos[1] - ); - if (this.selected_group._nodes.length) { - this.dirty_canvas = true; - } - this.selected_group = null; - } - this.selected_group_resizing = false; - - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (this.dragging_rectangle) { - if (this.graph) { - var nodes = this.graph._nodes; - var node_bounding = new Float32Array(4); - - //compute bounding and flip if left to right - var w = Math.abs(this.dragging_rectangle[2]); - var h = Math.abs(this.dragging_rectangle[3]); - var startx = - this.dragging_rectangle[2] < 0 - ? this.dragging_rectangle[0] - w - : this.dragging_rectangle[0]; - var starty = - this.dragging_rectangle[3] < 0 - ? this.dragging_rectangle[1] - h - : this.dragging_rectangle[1]; - this.dragging_rectangle[0] = startx; - this.dragging_rectangle[1] = starty; - this.dragging_rectangle[2] = w; - this.dragging_rectangle[3] = h; - - // test dragging rect size, if minimun simulate a click - if (!node || (w > 10 && h > 10 )){ - //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var nodeX = nodes[i]; - nodeX.getBounding(node_bounding); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(nodeX); - } - if (to_select.length) { - this.selectNodes(to_select,e.shiftKey); // add to selection with shift - } - }else{ - // will select of update selection - this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey - } - - } - this.dragging_rectangle = null; - } else if (this.connecting_node) { - //dragging a connection - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - - var connInOrOut = this.connecting_output || this.connecting_input; - var connType = connInOrOut.type; - - //node below mouse - if (node) { - - /* no need to condition on event type.. just another type - if ( - connType == LiteGraph.EVENT && - this.isOverNodeBox(node, e.canvasX, e.canvasY) - ) { - - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - - } else {*/ - - //slot below mouse? connect - - if (this.connecting_output){ - - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect(this.connecting_slot, node, slot); - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByType(this.connecting_slot,node,connType); - } - - }else if (this.connecting_input){ - - var slot = this.isOverNodeOutput( - node, - e.canvasX, - e.canvasY - ); - - if (slot != -1) { - node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like - } else { - //not on top of an input - // look for a good slot - this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); - } - - } - - - //} - - }else{ - - // add menu when releasing link in empty space - if (LiteGraph.release_link_on_empty_shows_menu){ - if (e.shiftKey && this.allow_searchbox){ - if(this.connecting_output){ - this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); - }else if(this.connecting_input){ - this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); - } - }else{ - if(this.connecting_output){ - this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); - }else if(this.connecting_input){ - this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); - } - } - } - } - - this.connecting_output = null; - this.connecting_input = null; - this.connecting_pos = null; - this.connecting_node = null; - this.connecting_slot = -1; - } //not dragging connection - else if (this.resizing_node) { - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.graph.afterChange(this.resizing_node); - this.resizing_node = null; - } else if (this.node_dragged) { - //node being dragged? - var node = this.node_dragged; - if ( - node && - e.click_time < 300 && - isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) - ) { - node.collapse(); - } - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); - this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); - if (this.graph.config.align_to_grid || this.align_to_grid ) { - this.node_dragged.alignToGrid(); - } - if( this.onNodeMoved ) - this.onNodeMoved( this.node_dragged ); - this.graph.afterChange(this.node_dragged); - this.node_dragged = null; - } //no node being dragged - else { - //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - - if (!node && e.click_time < 300) { - this.deselectAllNodes(); - } - - this.dirty_canvas = true; - this.dragging_canvas = false; - - if (this.node_over && this.node_over.onMouseUp) { - this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); - } - if ( - this.node_capturing_input && - this.node_capturing_input.onMouseUp - ) { - this.node_capturing_input.onMouseUp(e, [ - e.canvasX - this.node_capturing_input.pos[0], - e.canvasY - this.node_capturing_input.pos[1] - ]); - } - } - } else if (e.which == 2) { - //middle button - //trace("middle"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } else if (e.which == 3) { - //right button - //trace("right"); - this.dirty_canvas = true; - this.dragging_canvas = false; - } - - /* - if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) - this.draw(); - */ - - if (is_primary) - { - this.pointer_is_down = false; - this.pointer_is_double = false; - } - - this.graph.change(); - - //console.log("pointerevents: processMouseUp stopPropagation"); - e.stopPropagation(); - e.preventDefault(); - return false; - }; - - /** - * Called when a mouse wheel event has to be processed - * @method processMouseWheel - **/ - LGraphCanvas.prototype.processMouseWheel = function(e) { - if (!this.graph || !this.allow_dragcanvas) { - return; - } - - var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; - - this.adjustMouseEvent(e); - - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside) - return; - - var scale = this.ds.scale; - - if (delta > 0) { - scale *= 1.1; - } else if (delta < 0) { - scale *= 1 / 1.1; - } - - //this.setZoom( scale, [ e.clientX, e.clientY ] ); - this.ds.changeScale(scale, [e.clientX, e.clientY]); - - this.graph.change(); - - e.preventDefault(); - return false; // prevent default - }; - - /** - * returns true if a position (in graph space) is on top of a node little corner box - * @method isOverNodeBox - **/ - LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - if ( - isInsideRectangle( - canvasx, - canvasy, - node.pos[0] + 2, - node.pos[1] + 2 - title_height, - title_height - 4, - title_height - 4 - ) - ) { - return true; - } - return false; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node input slot - * @method isOverNodeInput - **/ - LGraphCanvas.prototype.isOverNodeInput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.inputs) { - for (var i = 0, l = node.inputs.length; i < l; ++i) { - var input = node.inputs[i]; - var link_pos = node.getConnectionPos(true, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * returns the INDEX if a position (in graph space) is on top of a node output slot - * @method isOverNodeOuput - **/ - LGraphCanvas.prototype.isOverNodeOutput = function( - node, - canvasx, - canvasy, - slot_pos - ) { - if (node.outputs) { - for (var i = 0, l = node.outputs.length; i < l; ++i) { - var output = node.outputs[i]; - var link_pos = node.getConnectionPos(false, i); - var is_inside = false; - if (node.horizontal) { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 5, - link_pos[1] - 10, - 10, - 20 - ); - } else { - is_inside = isInsideRectangle( - canvasx, - canvasy, - link_pos[0] - 10, - link_pos[1] - 5, - 40, - 10 - ); - } - if (is_inside) { - if (slot_pos) { - slot_pos[0] = link_pos[0]; - slot_pos[1] = link_pos[1]; - } - return i; - } - } - } - return -1; - }; - - /** - * process a key event - * @method processKey - **/ - LGraphCanvas.prototype.processKey = function(e) { - if (!this.graph) { - return; - } - - var block_default = false; - //console.log(e); //debug - - if (e.target.localName == "input") { - return; - } - - if (e.type == "keydown") { - if (e.keyCode == 32) { - //space - this.dragging_canvas = true; - block_default = true; - } - - if (e.keyCode == 27) { - //esc - if(this.node_panel) this.node_panel.close(); - if(this.options_panel) this.options_panel.close(); - block_default = true; - } - - //select all Control A - if (e.keyCode == 65 && e.ctrlKey) { - this.selectNodes(); - block_default = true; - } - - if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { - //copy - if (this.selected_nodes) { - this.copyToClipboard(); - block_default = true; - } - } - - if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { - //paste - this.pasteFromClipboard(e.shiftKey); - } - - //delete or backspace - if (e.keyCode == 46 || e.keyCode == 8) { - if ( - e.target.localName != "input" && - e.target.localName != "textarea" - ) { - this.deleteSelectedNodes(); - block_default = true; - } - } - - //collapse - //... - - //TODO - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyDown) { - this.selected_nodes[i].onKeyDown(e); - } - } - } - } else if (e.type == "keyup") { - if (e.keyCode == 32) { - // space - this.dragging_canvas = false; - } - - if (this.selected_nodes) { - for (var i in this.selected_nodes) { - if (this.selected_nodes[i].onKeyUp) { - this.selected_nodes[i].onKeyUp(e); - } - } - } - } - - this.graph.change(); - - if (block_default) { - e.preventDefault(); - e.stopImmediatePropagation(); - return false; - } - }; - - LGraphCanvas.prototype.copyToClipboard = function() { - var clipboard_info = { - nodes: [], - links: [] - }; - var index = 0; - var selected_nodes_array = []; - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - if (node.clonable === false) - continue; - node._relative_id = index; - selected_nodes_array.push(node); - index += 1; - } - - for (var i = 0; i < selected_nodes_array.length; ++i) { - var node = selected_nodes_array[i]; - if(node.clonable === false) - continue; - var cloned = node.clone(); - if(!cloned) - { - console.warn("node type not found: " + node.type ); - continue; - } - clipboard_info.nodes.push(cloned.serialize()); - if (node.inputs && node.inputs.length) { - for (var j = 0; j < node.inputs.length; ++j) { - var input = node.inputs[j]; - if (!input || input.link == null) { - continue; - } - var link_info = this.graph.links[input.link]; - if (!link_info) { - continue; - } - var target_node = this.graph.getNodeById( - link_info.origin_id - ); - if (!target_node) { - continue; - } - clipboard_info.links.push([ - target_node._relative_id, - link_info.origin_slot, //j, - node._relative_id, - link_info.target_slot, - target_node.id - ]); - } - } - } - localStorage.setItem( - "litegrapheditor_clipboard", - JSON.stringify(clipboard_info) - ); - }; - - LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { - // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior - if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - return; - } - var data = localStorage.getItem("litegrapheditor_clipboard"); - if (!data) { - return; - } - - this.graph.beforeChange(); - - //create nodes - var clipboard_info = JSON.parse(data); - // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos - var posMin = false; - var posMinIndexes = false; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - if (posMin){ - if(posMin[0]>clipboard_info.nodes[i].pos[0]){ - posMin[0] = clipboard_info.nodes[i].pos[0]; - posMinIndexes[0] = i; - } - if(posMin[1]>clipboard_info.nodes[i].pos[1]){ - posMin[1] = clipboard_info.nodes[i].pos[1]; - posMinIndexes[1] = i; - } - } - else{ - posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; - posMinIndexes = [i, i]; - } - } - var nodes = []; - for (var i = 0; i < clipboard_info.nodes.length; ++i) { - var node_data = clipboard_info.nodes[i]; - var node = LiteGraph.createNode(node_data.type); - if (node) { - node.configure(node_data); - - //paste in last known mouse position - node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; - node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; - - this.graph.add(node,{doProcessChange:false}); - - nodes.push(node); - } - } - - //create links - for (var i = 0; i < clipboard_info.links.length; ++i) { - var link_info = clipboard_info.links[i]; - var origin_node; - var origin_node_relative_id = link_info[0]; - if (origin_node_relative_id != null) { - origin_node = nodes[origin_node_relative_id]; - } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { - var origin_node_id = link_info[4]; - if (origin_node_id) { - origin_node = this.graph.getNodeById(origin_node_id); - } - } - var target_node = nodes[link_info[2]]; - if( origin_node && target_node ) - origin_node.connect(link_info[1], target_node, link_info[3]); - else - console.warn("Warning, nodes missing on pasting"); - } - - this.selectNodes(nodes); - - this.graph.afterChange(); - }; - - /** - * process a item drop event on top the canvas - * @method processDrop - **/ - LGraphCanvas.prototype.processDrop = function(e) { - e.preventDefault(); - this.adjustMouseEvent(e); - var x = e.clientX; - var y = e.clientY; - var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside){ - return; - // --- BREAK --- - } - - var pos = [e.canvasX, e.canvasY]; - - - var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; - - if (!node) { - var r = null; - if (this.onDropItem) { - r = this.onDropItem(event); - } - if (!r) { - this.checkDropItem(e); - } - return; - } - - if (node.onDropFile || node.onDropData) { - var files = e.dataTransfer.files; - if (files && files.length) { - for (var i = 0; i < files.length; i++) { - var file = e.dataTransfer.files[0]; - var filename = file.name; - var ext = LGraphCanvas.getFileExtension(filename); - //console.log(file); - - if (node.onDropFile) { - node.onDropFile(file); - } - - if (node.onDropData) { - //prepare reader - var reader = new FileReader(); - reader.onload = function(event) { - //console.log(event.target); - var data = event.target.result; - node.onDropData(data, filename, file); - }; - - //read data - var type = file.type.split("/")[0]; - if (type == "text" || type == "") { - reader.readAsText(file); - } else if (type == "image") { - reader.readAsDataURL(file); - } else { - reader.readAsArrayBuffer(file); - } - } - } - } - } - - if (node.onDropItem) { - if (node.onDropItem(event)) { - return true; - } - } - - if (this.onDropItem) { - return this.onDropItem(event); - } - - return false; - }; - - //called if the graph doesn't have a default drop item behaviour - LGraphCanvas.prototype.checkDropItem = function(e) { - if (e.dataTransfer.files.length) { - var file = e.dataTransfer.files[0]; - var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); - var nodetype = LiteGraph.node_types_by_file_extension[ext]; - if (nodetype) { - this.graph.beforeChange(); - var node = LiteGraph.createNode(nodetype.type); - node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); - if (node.onDropFile) { - node.onDropFile(file); - } - this.graph.afterChange(); - } - } - }; - - LGraphCanvas.prototype.processNodeDblClicked = function(n) { - if (this.onShowNodePanel) { - this.onShowNodePanel(n); - } - else - { - this.showShowNodePanel(n); - } - - if (this.onNodeDblClicked) { - this.onNodeDblClicked(n); - } - - this.setDirty(true); - }; - - LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); - if (this.onNodeSelected) { - this.onNodeSelected(node); - } - }; - - /** - * selects a given node (or adds it to the current selection) - * @method selectNode - **/ - LGraphCanvas.prototype.selectNode = function( - node, - add_to_current_selection - ) { - if (node == null) { - this.deselectAllNodes(); - } else { - this.selectNodes([node], add_to_current_selection); - } - }; - - /** - * selects several nodes (or adds them to the current selection) - * @method selectNodes - **/ - LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) - { - if (!add_to_current_selection) { - this.deselectAllNodes(); - } - - nodes = nodes || this.graph._nodes; - if (typeof nodes == "string") nodes = [nodes]; - for (var i in nodes) { - var node = nodes[i]; - if (node.is_selected) { - this.deselectNode(node); - continue; - } - - if (!node.is_selected && node.onSelected) { - node.onSelected(); - } - node.is_selected = true; - this.selected_nodes[node.id] = node; - - if (node.inputs) { - for (var j = 0; j < node.inputs.length; ++j) { - this.highlighted_links[node.inputs[j].link] = true; - } - } - if (node.outputs) { - for (var j = 0; j < node.outputs.length; ++j) { - var out = node.outputs[j]; - if (out.links) { - for (var k = 0; k < out.links.length; ++k) { - this.highlighted_links[out.links[k]] = true; - } - } - } - } - } - - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - - this.setDirty(true); - }; - - /** - * removes a node from the current selection - * @method deselectNode - **/ - LGraphCanvas.prototype.deselectNode = function(node) { - if (!node.is_selected) { - return; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - - //remove highlighted - if (node.inputs) { - for (var i = 0; i < node.inputs.length; ++i) { - delete this.highlighted_links[node.inputs[i].link]; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; ++i) { - var out = node.outputs[i]; - if (out.links) { - for (var j = 0; j < out.links.length; ++j) { - delete this.highlighted_links[out.links[j]]; - } - } - } - } - }; - - /** - * removes all nodes from the current selection - * @method deselectAllNodes - **/ - LGraphCanvas.prototype.deselectAllNodes = function() { - if (!this.graph) { - return; - } - var nodes = this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var node = nodes[i]; - if (!node.is_selected) { - continue; - } - if (node.onDeselected) { - node.onDeselected(); - } - node.is_selected = false; - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - if( this.onSelectionChange ) - this.onSelectionChange( this.selected_nodes ); - this.setDirty(true); - }; - - /** - * deletes all nodes in the current selection from the graph - * @method deleteSelectedNodes - **/ - LGraphCanvas.prototype.deleteSelectedNodes = function() { - - this.graph.beforeChange(); - - for (var i in this.selected_nodes) { - var node = this.selected_nodes[i]; - - if(node.block_delete) - continue; - - //autoconnect when possible (very basic, only takes into account first input-output) - if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) - { - var input_link = node.graph.links[ node.inputs[0].link ]; - var output_link = node.graph.links[ node.outputs[0].links[0] ]; - var input_node = node.getInputNode(0); - var output_node = node.getOutputNodes(0)[0]; - if(input_node && output_node) - input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); - } - this.graph.remove(node); - if (this.onNodeDeselected) { - this.onNodeDeselected(node); - } - } - this.selected_nodes = {}; - this.current_node = null; - this.highlighted_links = {}; - this.setDirty(true); - this.graph.afterChange(); - }; - - /** - * centers the camera on a given node - * @method centerOnNode - **/ - LGraphCanvas.prototype.centerOnNode = function(node) { - this.ds.offset[0] = - -node.pos[0] - - node.size[0] * 0.5 + - (this.canvas.width * 0.5) / this.ds.scale; - this.ds.offset[1] = - -node.pos[1] - - node.size[1] * 0.5 + - (this.canvas.height * 0.5) / this.ds.scale; - this.setDirty(true, true); - }; - - /** - * adds some useful properties to a mouse event, like the position in graph coordinates - * @method adjustMouseEvent - **/ - LGraphCanvas.prototype.adjustMouseEvent = function(e) { - var clientX_rel = 0; - var clientY_rel = 0; - - if (this.canvas) { - var b = this.canvas.getBoundingClientRect(); - clientX_rel = e.clientX - b.left; - clientY_rel = e.clientY - b.top; - } else { - clientX_rel = e.clientX; - clientY_rel = e.clientY; - } - - // e.deltaX = clientX_rel - this.last_mouse_position[0]; - // e.deltaY = clientY_rel- this.last_mouse_position[1]; - - this.last_mouse_position[0] = clientX_rel; - this.last_mouse_position[1] = clientY_rel; - - e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; - e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; - - //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); - }; - - /** - * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom - * @method setZoom - **/ - LGraphCanvas.prototype.setZoom = function(value, zooming_center) { - this.ds.changeScale(value, zooming_center); - /* - if(!zooming_center && this.canvas) - zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; - - var center = this.convertOffsetToCanvas( zooming_center ); - - this.ds.scale = value; - - if(this.scale > this.max_zoom) - this.scale = this.max_zoom; - else if(this.scale < this.min_zoom) - this.scale = this.min_zoom; - - var new_center = this.convertOffsetToCanvas( zooming_center ); - var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; - - this.offset[0] += delta_offset[0]; - this.offset[1] += delta_offset[1]; - */ - - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - }; - - /** - * converts a coordinate from graph coordinates to canvas2D coordinates - * @method convertOffsetToCanvas - **/ - LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { - return this.ds.convertOffsetToCanvas(pos, out); - }; - - /** - * converts a coordinate from Canvas2D coordinates to graph space - * @method convertCanvasToOffset - **/ - LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { - return this.ds.convertCanvasToOffset(pos, out); - }; - - //converts event coordinates from canvas2D to graph coordinates - LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { - var rect = this.canvas.getBoundingClientRect(); - return this.convertCanvasToOffset([ - e.clientX - rect.left, - e.clientY - rect.top - ]); - }; - - /** - * brings a node to front (above all other nodes) - * @method bringToFront - **/ - LGraphCanvas.prototype.bringToFront = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.push(node); - }; - - /** - * sends a node to the back (below all other nodes) - * @method sendToBack - **/ - LGraphCanvas.prototype.sendToBack = function(node) { - var i = this.graph._nodes.indexOf(node); - if (i == -1) { - return; - } - - this.graph._nodes.splice(i, 1); - this.graph._nodes.unshift(node); - }; - - /* Interaction */ - - /* LGraphCanvas render */ - var temp = new Float32Array(4); - - /** - * checks which nodes are visible (inside the camera area) - * @method computeVisibleNodes - **/ - LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { - var visible_nodes = out || []; - visible_nodes.length = 0; - nodes = nodes || this.graph._nodes; - for (var i = 0, l = nodes.length; i < l; ++i) { - var n = nodes[i]; - - //skip rendering nodes in live mode - if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { - continue; - } - - if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) { - continue; - } //out of the visible area - - visible_nodes.push(n); - } - return visible_nodes; - }; - - /** - * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) - * @method draw - **/ - LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { - return; - } - - //fps counting - var now = LiteGraph.getTime(); - this.render_time = (now - this.last_draw_time) * 0.001; - this.last_draw_time = now; - - if (this.graph) { - this.ds.computeVisibleArea(this.viewport); - } - - if ( - this.dirty_bgcanvas || - force_bgcanvas || - this.always_render_background || - (this.graph && - this.graph._last_trigger_time && - now - this.graph._last_trigger_time < 1000) - ) { - this.drawBackCanvas(); - } - - if (this.dirty_canvas || force_canvas) { - this.drawFrontCanvas(); - } - - this.fps = this.render_time ? 1.0 / this.render_time : 0; - this.frame += 1; - }; - - /** - * draws the front canvas (the one containing all the nodes) - * @method drawFrontCanvas - **/ - LGraphCanvas.prototype.drawFrontCanvas = function() { - this.dirty_canvas = false; - - if (!this.ctx) { - this.ctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.ctx; - if (!ctx) { - //maybe is using webgl... - return; - } - - var canvas = this.canvas; - if ( ctx.start2D && !this.viewport ) { - ctx.start2D(); - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - - //clip dirty area if there is one, otherwise work in full canvas - var area = this.viewport || this.dirty_area; - if (area) { - ctx.save(); - ctx.beginPath(); - ctx.rect( area[0],area[1],area[2],area[3] ); - ctx.clip(); - } - - //clear - //canvas.width = canvas.width; - if (this.clear_background) { - if(area) - ctx.clearRect( area[0],area[1],area[2],area[3] ); - else - ctx.clearRect(0, 0, canvas.width, canvas.height); - } - - //draw bg canvas - if (this.bgcanvas == this.canvas) { - this.drawBackCanvas(); - } else { - ctx.drawImage( this.bgcanvas, 0, 0 ); - } - - //rendering - if (this.onRender) { - this.onRender(canvas, ctx); - } - - //info widget - if (this.show_info) { - this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); - } - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //draw nodes - var drawn_nodes = 0; - var visible_nodes = this.computeVisibleNodes( - null, - this.visible_nodes - ); - - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - - //transform coords system - ctx.save(); - ctx.translate(node.pos[0], node.pos[1]); - - //Draw - this.drawNode(node, ctx); - drawn_nodes += 1; - - //Restore - ctx.restore(); - } - - //on top (debug) - if (this.render_execution_order) { - this.drawExecutionOrder(ctx); - } - - //connections ontop? - if (this.graph.config.links_ontop) { - if (!this.live_mode) { - this.drawConnections(ctx); - } - } - - //current connection (the one being dragged by the mouse) - if (this.connecting_pos != null) { - ctx.lineWidth = this.connections_width; - var link_color = null; - - var connInOrOut = this.connecting_output || this.connecting_input; - - var connType = connInOrOut.type; - var connDir = connInOrOut.dir; - if(connDir == null) - { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; - } - var connShape = connInOrOut.shape; - - switch (connType) { - case LiteGraph.EVENT: - link_color = LiteGraph.EVENT_LINK_COLOR; - break; - default: - link_color = LiteGraph.CONNECTING_LINK_COLOR; - } - - //the connection being dragged by the mouse - this.renderLink( - ctx, - this.connecting_pos, - [this.graph_mouse[0], this.graph_mouse[1]], - null, - false, - null, - link_color, - connDir, - LiteGraph.CENTER - ); - - ctx.beginPath(); - if ( - connType === LiteGraph.EVENT || - connShape === LiteGraph.BOX_SHAPE - ) { - ctx.rect( - this.connecting_pos[0] - 6 + 0.5, - this.connecting_pos[1] - 5 + 0.5, - 14, - 10 - ); - ctx.fill(); - ctx.beginPath(); - ctx.rect( - this.graph_mouse[0] - 6 + 0.5, - this.graph_mouse[1] - 5 + 0.5, - 14, - 10 - ); - } else if (connShape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); - ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); - ctx.closePath(); - } - else { - ctx.arc( - this.connecting_pos[0], - this.connecting_pos[1], - 4, - 0, - Math.PI * 2 - ); - ctx.fill(); - ctx.beginPath(); - ctx.arc( - this.graph_mouse[0], - this.graph_mouse[1], - 4, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - ctx.fillStyle = "#ffcc00"; - if (this._highlight_input) { - ctx.beginPath(); - var shape = this._highlight_input_slot.shape; - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); - ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_input[0], - this._highlight_input[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - if (this._highlight_output) { - ctx.beginPath(); - if (shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); - ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); - ctx.closePath(); - } else { - ctx.arc( - this._highlight_output[0], - this._highlight_output[1], - 6, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - } - } - - //the selection rectangle - if (this.dragging_rectangle) { - ctx.strokeStyle = "#FFF"; - ctx.strokeRect( - this.dragging_rectangle[0], - this.dragging_rectangle[1], - this.dragging_rectangle[2], - this.dragging_rectangle[3] - ); - } - - //on top of link center - if(this.over_link_center && this.render_link_tooltip) - this.drawLinkTooltip( ctx, this.over_link_center ); - else - if(this.onDrawLinkTooltip) //to remove - this.onDrawLinkTooltip(ctx,null); - - //custom info - if (this.onDrawForeground) { - this.onDrawForeground(ctx, this.visible_rect); - } - - ctx.restore(); - } - - //draws panel in the corner - if (this._graph_stack && this._graph_stack.length) { - this.drawSubgraphPanel( ctx ); - } - - - if (this.onDrawOverlay) { - this.onDrawOverlay(ctx); - } - - if (area){ - ctx.restore(); - } - - if (ctx.finish2D) { - //this is a function I use in webgl renderer - ctx.finish2D(); - } - }; - - /** - * draws the panel in the corner that shows subgraph properties - * @method drawSubgraphPanel - **/ - LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { - var subgraph = this.graph; - var subnode = subgraph._subgraph_node; - if (!subnode) { - console.warn("subgraph without subnode"); - return; - } - this.drawSubgraphPanelLeft(subgraph, subnode, ctx) - this.drawSubgraphPanelRight(subgraph, subnode, ctx) - } - - LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { - var num = subnode.inputs ? subnode.inputs.length : 0; - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - ctx.fillText("Graph Inputs", 20, 34); - // var pos = this.mouse; - - if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.inputs) - for (var i = 0; i < subnode.inputs.length; ++i) { - var input = subnode.inputs[i]; - if (input.not_subgraph_input) - continue; - - //input button clicked - if (this.drawButton(20, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.input_node_type || "graph/input"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", input.name); - newnode.setProperty("type", input.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(input.name, 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(input.type, 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialog(subnode); - } - } - LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { - var num = subnode.outputs ? subnode.outputs.length : 0; - var canvas_w = this.bgcanvas.width - var w = 200; - var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); - - ctx.fillStyle = "#111"; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); - ctx.fill(); - ctx.globalAlpha = 1; - - ctx.fillStyle = "#888"; - ctx.font = "14px Arial"; - ctx.textAlign = "left"; - var title_text = "Graph Outputs" - var tw = ctx.measureText(title_text).width - ctx.fillText(title_text, (canvas_w - tw) - 20, 34); - // var pos = this.mouse; - if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { - this.closeSubgraph(); - return; - } - - var y = 50; - ctx.font = "14px Arial"; - if (subnode.outputs) - for (var i = 0; i < subnode.outputs.length; ++i) { - var output = subnode.outputs[i]; - if (output.not_subgraph_input) - continue; - - //output button clicked - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { - var type = subnode.constructor.output_node_type || "graph/output"; - this.graph.beforeChange(); - var newnode = LiteGraph.createNode(type); - if (newnode) { - subgraph.add(newnode); - this.block_click = false; - this.last_click_position = null; - this.selectNodes([newnode]); - this.node_dragged = newnode; - this.dragging_canvas = false; - newnode.setProperty("name", output.name); - newnode.setProperty("type", output.type); - this.node_dragged.pos[0] = this.graph_mouse[0] - 5; - this.node_dragged.pos[1] = this.graph_mouse[1] - 5; - this.graph.afterChange(); - } - else - console.error("graph input node not found:", type); - } - ctx.fillStyle = "#9C9"; - ctx.beginPath(); - ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); - ctx.fill(); - ctx.fillStyle = "#AAA"; - ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); - // var tw = ctx.measureText(input.name); - ctx.fillStyle = "#777"; - ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); - y += h; - } - //add + button - if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { - this.showSubgraphPropertiesDialogRight(subnode); - } - } - //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm - LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) - { - var ctx = this.ctx; - bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; - hovercolor = hovercolor || "#555"; - textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; - var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; - if(pos) { - var rect = this.canvas.getBoundingClientRect(); - pos[0] -= rect.left; - pos[1] -= rect.top; - } - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - - ctx.fillStyle = hover ? hovercolor : bgcolor; - if(clicked) - ctx.fillStyle = "#AAA"; - ctx.beginPath(); - ctx.roundRect(x,y,w,h,[4] ); - ctx.fill(); - - if(text != null) - { - if(text.constructor == String) - { - ctx.fillStyle = textcolor; - ctx.textAlign = "center"; - ctx.font = ((h * 0.65)|0) + "px Arial"; - ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); - ctx.textAlign = "left"; - } - } - - var was_clicked = clicked && !this.block_click; - if(clicked) - this.blockClick(); - return was_clicked; - } - - LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) - { - var pos = this.mouse; - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - pos = this.last_click_position; - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); - var was_clicked = clicked && !this.block_click; - if(clicked && hold_click) - this.blockClick(); - return was_clicked; - } - - /** - * draws some useful stats in the corner of the canvas - * @method renderInfo - **/ - LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 10; - y = y || this.canvas.height - 80; - - ctx.save(); - ctx.translate(x, y); - - ctx.font = "10px Arial"; - ctx.fillStyle = "#888"; - ctx.textAlign = "left"; - if (this.graph) { - ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); - ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); - ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); - ctx.fillText("V: " + this.graph._version, 5, 13 * 4); - ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); - } else { - ctx.fillText("No graph selected", 5, 13 * 1); - } - ctx.restore(); - }; - - /** - * draws the back canvas (the one containing the background and the connections) - * @method drawBackCanvas - **/ - LGraphCanvas.prototype.drawBackCanvas = function() { - var canvas = this.bgcanvas; - if ( - canvas.width != this.canvas.width || - canvas.height != this.canvas.height - ) { - canvas.width = this.canvas.width; - canvas.height = this.canvas.height; - } - - if (!this.bgctx) { - this.bgctx = this.bgcanvas.getContext("2d"); - } - var ctx = this.bgctx; - if (ctx.start) { - ctx.start(); - } - - var viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height]; - - //clear - if (this.clear_background) { - ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] ); - } - - //show subgraph stack header - if (this._graph_stack && this._graph_stack.length) { - ctx.save(); - var parent_graph = this._graph_stack[this._graph_stack.length - 1]; - var subgraph_node = this.graph._subgraph_node; - ctx.strokeStyle = subgraph_node.bgcolor; - ctx.lineWidth = 10; - ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); - ctx.lineWidth = 1; - ctx.font = "40px Arial"; - ctx.textAlign = "center"; - ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; - var title = ""; - for (var i = 1; i < this._graph_stack.length; ++i) { - title += - this._graph_stack[i]._subgraph_node.getTitle() + " >> "; - } - ctx.fillText( - title + subgraph_node.getTitle(), - canvas.width * 0.5, - 40 - ); - ctx.restore(); - } - - var bg_already_painted = false; - if (this.onRenderBackground) { - bg_already_painted = this.onRenderBackground(canvas, ctx); - } - - //reset in case of error - if ( !this.viewport ) - { - ctx.restore(); - ctx.setTransform(1, 0, 0, 1, 0, 0); - } - this.visible_links.length = 0; - - if (this.graph) { - //apply transformations - ctx.save(); - this.ds.toCanvasContext(ctx); - - //render BG - if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color ) - { - ctx.fillStyle = this.clear_background_color; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - } - - if ( - this.background_image && - this.ds.scale > 0.5 && - !bg_already_painted - ) { - if (this.zoom_modify_alpha) { - ctx.globalAlpha = - (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; - } else { - ctx.globalAlpha = this.editor_alpha; - } - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = - if ( - !this._bg_img || - this._bg_img.name != this.background_image - ) { - this._bg_img = new Image(); - this._bg_img.name = this.background_image; - this._bg_img.src = this.background_image; - var that = this; - this._bg_img.onload = function() { - that.draw(true, true); - }; - } - - var pattern = null; - if (this._pattern == null && this._bg_img.width > 0) { - pattern = ctx.createPattern(this._bg_img, "repeat"); - this._pattern_img = this._bg_img; - this._pattern = pattern; - } else { - pattern = this._pattern; - } - if (pattern) { - ctx.fillStyle = pattern; - ctx.fillRect( - this.visible_area[0], - this.visible_area[1], - this.visible_area[2], - this.visible_area[3] - ); - ctx.fillStyle = "transparent"; - } - - ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled - } - - //groups - if (this.graph._groups.length && !this.live_mode) { - this.drawGroups(canvas, ctx); - } - - if (this.onDrawBackground) { - this.onDrawBackground(ctx, this.visible_area); - } - if (this.onBackgroundRender) { - //LEGACY - console.error( - "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " - ); - this.onBackgroundRender = null; - } - - //DEBUG: show clipping area - //ctx.fillStyle = "red"; - //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); - - //bg - if (this.render_canvas_border) { - ctx.strokeStyle = "#235"; - ctx.strokeRect(0, 0, canvas.width, canvas.height); - } - - if (this.render_connections_shadows) { - ctx.shadowColor = "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 6; - } else { - ctx.shadowColor = "rgba(0,0,0,0)"; - } - - //draw connections - if (!this.live_mode) { - this.drawConnections(ctx); - } - - ctx.shadowColor = "rgba(0,0,0,0)"; - - //restore state - ctx.restore(); - } - - if (ctx.finish) { - ctx.finish(); - } - - this.dirty_bgcanvas = false; - this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas - }; - - var temp_vec2 = new Float32Array(2); - - /** - * draws the given node inside the canvas - * @method drawNode - **/ - LGraphCanvas.prototype.drawNode = function(node, ctx) { - var glow = false; - this.current_node = node; - - var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - //shadow and glow - if (node.mouseOver) { - glow = true; - } - - var low_quality = this.ds.scale < 0.6; //zoomed out - - //only render if it forces it to do it - if (this.live_mode) { - if (!node.flags.collapsed) { - ctx.shadowColor = "transparent"; - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - } - return; - } - - var editor_alpha = this.editor_alpha; - ctx.globalAlpha = editor_alpha; - - if (this.render_shadows && !low_quality) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - ctx.shadowOffsetX = 2 * this.ds.scale; - ctx.shadowOffsetY = 2 * this.ds.scale; - ctx.shadowBlur = 3 * this.ds.scale; - } else { - ctx.shadowColor = "transparent"; - } - - //custom draw collapsed method (draw after shadows because they are affected) - if ( - node.flags.collapsed && - node.onDrawCollapsed && - node.onDrawCollapsed(ctx, this) == true - ) { - return; - } - - //clip if required (mask) - var shape = node._shape || LiteGraph.BOX_SHAPE; - var size = temp_vec2; - temp_vec2.set(node.size); - var horizontal = node.horizontal; // || node.flags.horizontal; - - if (node.flags.collapsed) { - ctx.font = this.inner_text_font; - var title = node.getTitle ? node.getTitle() : node.title; - if (title != null) { - node._collapsed_width = Math.min( - node.size[0], - ctx.measureText(title).width + - LiteGraph.NODE_TITLE_HEIGHT * 2 - ); //LiteGraph.NODE_COLLAPSED_WIDTH; - size[0] = node._collapsed_width; - size[1] = 0; - } - } - - if (node.clip_area) { - //Start clipping - ctx.save(); - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect(0, 0, size[0], size[1]); - } else if (shape == LiteGraph.ROUND_SHAPE) { - ctx.roundRect(0, 0, size[0], size[1], [10]); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.clip(); - } - - //draw shape - if (node.has_errors) { - bgcolor = "red"; - } - this.drawNodeShape( - node, - ctx, - size, - color, - bgcolor, - node.is_selected, - node.mouseOver - ); - ctx.shadowColor = "transparent"; - - //draw foreground - if (node.onDrawForeground) { - node.onDrawForeground(ctx, this, this.canvas); - } - - //connection slots - ctx.textAlign = horizontal ? "center" : "left"; - ctx.font = this.inner_text_font; - - var render_text = !low_quality; - - var out_slot = this.connecting_output; - var in_slot = this.connecting_input; - ctx.lineWidth = 1; - - var max_y = 0; - var slot_pos = new Float32Array(2); //to reuse - - //render inputs and outputs - if (!node.flags.collapsed) { - //input connection slots - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - ctx.globalAlpha = editor_alpha; - //change opacity of incompatible slots when dragging a connection - if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - ctx.fillStyle = - slot.link != null - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.input_off; - - var pos = node.getConnectionPos(true, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.beginPath(); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? - } - - var doStroke = true; - - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - ctx.fill(); - - //render name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { - ctx.fillText(text, pos[0], pos[1] - 10); - } else { - ctx.fillText(text, pos[0] + 10, pos[1] + 5); - } - } - } - } - } - - //output connection slots - - ctx.textAlign = horizontal ? "center" : "right"; - ctx.strokeStyle = "black"; - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - - var slot_type = slot.type; - var slot_shape = slot.shape; - - //change opacity of incompatible slots when dragging a connection - if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { - ctx.globalAlpha = 0.4 * editor_alpha; - } - - var pos = node.getConnectionPos(false, i, slot_pos); - pos[0] -= node.pos[0]; - pos[1] -= node.pos[1]; - if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { - max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; - } - - ctx.fillStyle = - slot.links && slot.links.length - ? slot.color_on || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_on - : slot.color_off || - this.default_connection_color_byTypeOff[slot_type] || - this.default_connection_color_byType[slot_type] || - this.default_connection_color.output_off; - ctx.beginPath(); - //ctx.rect( node.size[0] - 14,i*14,10,10); - - if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; - } - - var doStroke = true; - - if ( - slot_type === LiteGraph.EVENT || - slot_shape === LiteGraph.BOX_SHAPE - ) { - if (horizontal) { - ctx.rect( - pos[0] - 5 + 0.5, - pos[1] - 8 + 0.5, - 10, - 14 - ); - } else { - ctx.rect( - pos[0] - 6 + 0.5, - pos[1] - 5 + 0.5, - 14, - 10 - ); - } - } else if (slot_shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(pos[0] + 8, pos[1] + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); - ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); - ctx.closePath(); - } else if (slot_shape === LiteGraph.GRID_SHAPE) { - ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); - ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); - ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); - ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); - ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); - ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); - doStroke = false; - } else { - if(low_quality) - ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); - else - ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); - } - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - if(!low_quality && doStroke) - ctx.stroke(); - - //render output name - if (render_text) { - var text = slot.label != null ? slot.label : slot.name; - if (text) { - ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { - ctx.fillText(text, pos[0], pos[1] - 8); - } else { - ctx.fillText(text, pos[0] - 10, pos[1] + 5); - } - } - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1; - - if (node.widgets) { - var widgets_y = max_y; - if (horizontal || node.widgets_up) { - widgets_y = 2; - } - if( node.widgets_start_y != null ) - widgets_y = node.widgets_start_y; - this.drawNodeWidgets( - node, - widgets_y, - ctx, - this.node_widget && this.node_widget[0] == node - ? this.node_widget[1] - : null - ); - } - } else if (this.render_collapsed_slots) { - //if collapsed - var input_slot = null; - var output_slot = null; - - //get first connected slot to render - if (node.inputs) { - for (var i = 0; i < node.inputs.length; i++) { - var slot = node.inputs[i]; - if (slot.link == null) { - continue; - } - input_slot = slot; - break; - } - } - if (node.outputs) { - for (var i = 0; i < node.outputs.length; i++) { - var slot = node.outputs[i]; - if (!slot.links || !slot.links.length) { - continue; - } - output_slot = slot; - } - } - - if (input_slot) { - var x = 0; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = -LiteGraph.NODE_TITLE_HEIGHT; - } - ctx.fillStyle = "#686"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 8, y); - ctx.lineTo(x + -4, y - 4); - ctx.lineTo(x + -4, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - } - - if (output_slot) { - var x = node._collapsed_width; - var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { - x = node._collapsed_width * 0.5; - y = 0; - } - ctx.fillStyle = "#686"; - ctx.strokeStyle = "black"; - ctx.beginPath(); - if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE - ) { - ctx.rect(x - 7 + 0.5, y - 4, 14, 8); - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { - ctx.moveTo(x + 6, y); - ctx.lineTo(x - 6, y - 4); - ctx.lineTo(x - 6, y + 4); - ctx.closePath(); - } else { - ctx.arc(x, y, 4, 0, Math.PI * 2); - } - ctx.fill(); - //ctx.stroke(); - } - } - - if (node.clip_area) { - ctx.restore(); - } - - ctx.globalAlpha = 1.0; - }; - - //used by this.over_link_center - LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) - { - var pos = link._pos; - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); - ctx.fill(); - - if(link.data == null) - return; - - if(this.onDrawLinkTooltip) - if( this.onDrawLinkTooltip(ctx,link,this) == true ) - return; - - var data = link.data; - var text = null; - - if( data.constructor === Number ) - text = data.toFixed(2); - else if( data.constructor === String ) - text = "\"" + data + "\""; - else if( data.constructor === Boolean ) - text = String(data); - else if (data.toToolTip) - text = data.toToolTip(); - else - text = "[" + data.constructor.name + "]"; - - if(text == null) - return; - text = text.substr(0,30); //avoid weird - - ctx.font = "14px Courier New"; - var info = ctx.measureText(text); - var w = info.width + 20; - var h = 24; - ctx.shadowColor = "black"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 3; - ctx.fillStyle = "#454"; - ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); - ctx.moveTo( pos[0] - 10, pos[1] - 15 ); - ctx.lineTo( pos[0] + 10, pos[1] - 15 ); - ctx.lineTo( pos[0], pos[1] - 5 ); - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.textAlign = "center"; - ctx.fillStyle = "#CEC"; - ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); - } - - /** - * draws the shape of the given node in the canvas - * @method drawNodeShape - **/ - var tmp_area = new Float32Array(4); - - LGraphCanvas.prototype.drawNodeShape = function( - node, - ctx, - size, - fgcolor, - bgcolor, - selected, - mouse_over - ) { - //bg rect - ctx.strokeStyle = fgcolor; - ctx.fillStyle = bgcolor; - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - var low_quality = this.ds.scale < 0.5; - - //render node area depending on shape - var shape = - node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; - - var title_mode = node.constructor.title_mode; - - var render_title = true; - if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { - render_title = false; - } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { - render_title = true; - } - - var area = tmp_area; - area[0] = 0; //x - area[1] = render_title ? -title_height : 0; //y - area[2] = size[0] + 1; //w - area[3] = render_title ? size[1] + title_height : size[1]; //h - - var old_alpha = ctx.globalAlpha; - - //full node shape - //if(node.flags.collapsed) - { - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.fillRect(area[0], area[1], area[2], area[3]); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - ctx.roundRect( - area[0], - area[1], - area[2], - area[3], - shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5, - 0, - Math.PI * 2 - ); - } - ctx.fill(); - - //separator - if(!node.flags.collapsed && render_title) - { - ctx.shadowColor = "transparent"; - ctx.fillStyle = "rgba(0,0,0,0.2)"; - ctx.fillRect(0, -1, area[2], 2); - } - } - ctx.shadowColor = "transparent"; - - if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); - } - - //title bg (remember, it is rendered ABOVE the node) - if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { - //title bar - if (node.onDrawTitleBar) { - node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); - } else if ( - title_mode != LiteGraph.TRANSPARENT_TITLE && - (node.constructor.title_color || this.render_title_colored) - ) { - var title_color = node.constructor.title_color || fgcolor; - - if (node.flags.collapsed) { - ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; - } - - //* gradient test - if (this.use_gradients) { - var grad = LGraphCanvas.gradients[title_color]; - if (!grad) { - grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException - grad.addColorStop(1, "#000"); - } - ctx.fillStyle = grad; - } else { - ctx.fillStyle = title_color; - } - - //ctx.globalAlpha = 0.5 * old_alpha; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE || low_quality) { - ctx.rect(0, -title_height, size[0] + 1, title_height); - } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { - ctx.roundRect( - 0, - -title_height, - size[0] + 1, - title_height, - node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] - ); - } - ctx.fill(); - ctx.shadowColor = "transparent"; - } - - var colState = false; - if (LiteGraph.node_box_coloured_by_mode){ - if(LiteGraph.NODE_MODES_COLORS[node.mode]){ - colState = LiteGraph.NODE_MODES_COLORS[node.mode]; - } - } - if (LiteGraph.node_box_coloured_when_on){ - colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); - } - - //title box - var box_size = 10; - if (node.onDrawTitleBox) { - node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - shape == LiteGraph.CIRCLE_SHAPE || - shape == LiteGraph.CARD_SHAPE - ) { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5 + 1, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - if(low_quality) - ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); - else - { - ctx.beginPath(); - ctx.arc( - title_height * 0.5, - title_height * -0.5, - box_size * 0.5, - 0, - Math.PI * 2 - ); - ctx.fill(); - } - } else { - if (low_quality) { - ctx.fillStyle = "black"; - ctx.fillRect( - (title_height - box_size) * 0.5 - 1, - (title_height + box_size) * -0.5 - 1, - box_size + 2, - box_size + 2 - ); - } - ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.fillRect( - (title_height - box_size) * 0.5, - (title_height + box_size) * -0.5, - box_size, - box_size - ); - } - ctx.globalAlpha = old_alpha; - - //title text - if (node.onDrawTitleText) { - node.onDrawTitleText( - ctx, - title_height, - size, - this.ds.scale, - this.title_text_font, - selected - ); - } - if (!low_quality) { - ctx.font = this.title_text_font; - var title = String(node.getTitle()); - if (title) { - if (selected) { - ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; - } else { - ctx.fillStyle = - node.constructor.title_text_color || - this.node_title_color; - } - if (node.flags.collapsed) { - ctx.textAlign = "left"; - var measure = ctx.measureText(title); - ctx.fillText( - title.substr(0,20), //avoid urls too long - title_height,// + measure.width * 0.5, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - ctx.textAlign = "left"; - } else { - ctx.textAlign = "left"; - ctx.fillText( - title, - title_height, - LiteGraph.NODE_TITLE_TEXT_Y - title_height - ); - } - } - } - - //subgraph box - if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = node.size[0] - w; - var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); - ctx.fillStyle = over ? "#888" : "#555"; - if( shape == LiteGraph.BOX_SHAPE || low_quality) - ctx.fillRect(x+2, -w+2, w-4, w-4); - else - { - ctx.beginPath(); - ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); - ctx.fill(); - } - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - } - - //custom title render - if (node.onDrawTitle) { - node.onDrawTitle(ctx); - } - } - - //render selection marker - if (selected) { - if (node.onBounding) { - node.onBounding(area); - } - - if (title_mode == LiteGraph.TRANSPARENT_TITLE) { - area[1] -= title_height; - area[3] += title_height; - } - ctx.lineWidth = 1; - ctx.globalAlpha = 0.8; - ctx.beginPath(); - if (shape == LiteGraph.BOX_SHAPE) { - ctx.rect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3] - ); - } else if ( - shape == LiteGraph.ROUND_SHAPE || - (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) - ) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2] - ); - } else if (shape == LiteGraph.CARD_SHAPE) { - ctx.roundRect( - -6 + area[0], - -6 + area[1], - 12 + area[2], - 12 + area[3], - [this.round_radius * 2,2,this.round_radius * 2,2] - ); - } else if (shape == LiteGraph.CIRCLE_SHAPE) { - ctx.arc( - size[0] * 0.5, - size[1] * 0.5, - size[0] * 0.5 + 6, - 0, - Math.PI * 2 - ); - } - ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; - ctx.stroke(); - ctx.strokeStyle = fgcolor; - ctx.globalAlpha = 1; - } - - // these counter helps in conditioning drawing based on if the node has been executed or an action occurred - if (node.execute_triggered>0) node.execute_triggered--; - if (node.action_triggered>0) node.action_triggered--; - }; - - var margin_area = new Float32Array(4); - var link_bounding = new Float32Array(4); - var tempA = new Float32Array(2); - var tempB = new Float32Array(2); - - /** - * draws every connection visible in the canvas - * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time - * @method drawConnections - **/ - LGraphCanvas.prototype.drawConnections = function(ctx) { - var now = LiteGraph.getTime(); - var visible_area = this.visible_area; - margin_area[0] = visible_area[0] - 20; - margin_area[1] = visible_area[1] - 20; - margin_area[2] = visible_area[2] + 40; - margin_area[3] = visible_area[3] + 40; - - //draw connections - ctx.lineWidth = this.connections_width; - - ctx.fillStyle = "#AAA"; - ctx.strokeStyle = "#AAA"; - ctx.globalAlpha = this.editor_alpha; - //for every node - var nodes = this.graph._nodes; - for (var n = 0, l = nodes.length; n < l; ++n) { - var node = nodes[n]; - //for every input (we render just inputs because it is easier as every slot can only have one input) - if (!node.inputs || !node.inputs.length) { - continue; - } - - for (var i = 0; i < node.inputs.length; ++i) { - var input = node.inputs[i]; - if (!input || input.link == null) { - continue; - } - var link_id = input.link; - var link = this.graph.links[link_id]; - if (!link) { - continue; - } - - //find link info - var start_node = this.graph.getNodeById(link.origin_id); - if (start_node == null) { - continue; - } - var start_node_slot = link.origin_slot; - var start_node_slotpos = null; - if (start_node_slot == -1) { - start_node_slotpos = [ - start_node.pos[0] + 10, - start_node.pos[1] + 10 - ]; - } else { - start_node_slotpos = start_node.getConnectionPos( - false, - start_node_slot, - tempA - ); - } - var end_node_slotpos = node.getConnectionPos(true, i, tempB); - - //compute link bounding - link_bounding[0] = start_node_slotpos[0]; - link_bounding[1] = start_node_slotpos[1]; - link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; - link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; - if (link_bounding[2] < 0) { - link_bounding[0] += link_bounding[2]; - link_bounding[2] = Math.abs(link_bounding[2]); - } - if (link_bounding[3] < 0) { - link_bounding[1] += link_bounding[3]; - link_bounding[3] = Math.abs(link_bounding[3]); - } - - //skip links outside of the visible area of the canvas - if (!overlapBounding(link_bounding, margin_area)) { - continue; - } - - var start_slot = start_node.outputs[start_node_slot]; - var end_slot = node.inputs[i]; - if (!start_slot || !end_slot) { - continue; - } - var start_dir = - start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); - var end_dir = - end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); - - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - false, - 0, - null, - start_dir, - end_dir - ); - - //event triggered rendered on top - if (link && link._last_time && now - link._last_time < 1000) { - var f = 2.0 - (now - link._last_time) * 0.002; - var tmp = ctx.globalAlpha; - ctx.globalAlpha = tmp * f; - this.renderLink( - ctx, - start_node_slotpos, - end_node_slotpos, - link, - true, - f, - "white", - start_dir, - end_dir - ); - ctx.globalAlpha = tmp; - } - } - } - ctx.globalAlpha = 1; - }; - - /** - * draws a link between two points - * @method renderLink - * @param {vec2} a start pos - * @param {vec2} b end pos - * @param {Object} link the link object with all the link info - * @param {boolean} skip_border ignore the shadow of the link - * @param {boolean} flow show flow animation (for events) - * @param {string} color the color for the link - * @param {number} start_dir the direction enum - * @param {number} end_dir the direction enum - * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) - **/ - LGraphCanvas.prototype.renderLink = function( - ctx, - a, - b, - link, - skip_border, - flow, - color, - start_dir, - end_dir, - num_sublines - ) { - if (link) { - this.visible_links.push(link); - } - - //choose color - if (!color && link) { - color = link.color || LGraphCanvas.link_type_colors[link.type]; - } - if (!color) { - color = this.default_link_color; - } - if (link != null && this.highlighted_links[link.id]) { - color = "#FFF"; - } - - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - - if (this.render_connections_border && this.ds.scale > 0.6) { - ctx.lineWidth = this.connections_width + 4; - } - ctx.lineJoin = "round"; - num_sublines = num_sublines || 1; - if (num_sublines > 1) { - ctx.lineWidth = 0.5; - } - - //begin line shape - ctx.beginPath(); - for (var i = 0; i < num_sublines; i += 1) { - var offsety = (i - (num_sublines - 1) * 0.5) * 5; - - if (this.links_render_mode == LiteGraph.SPLINE_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; - } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); - } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { - ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = -1; - break; - case LiteGraph.RIGHT: - start_offset_x = 1; - break; - case LiteGraph.UP: - start_offset_y = -1; - break; - case LiteGraph.DOWN: - start_offset_y = 1; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = -1; - break; - case LiteGraph.RIGHT: - end_offset_x = 1; - break; - case LiteGraph.UP: - end_offset_y = -1; - break; - case LiteGraph.DOWN: - end_offset_y = 1; - break; - } - var l = 15; - ctx.lineTo( - a[0] + start_offset_x * l, - a[1] + start_offset_y * l + offsety - ); - ctx.lineTo( - b[0] + end_offset_x * l, - b[1] + end_offset_y * l + offsety - ); - ctx.lineTo(b[0], b[1] + offsety); - } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { - ctx.moveTo(a[0], a[1]); - var start_x = a[0]; - var start_y = a[1]; - var end_x = b[0]; - var end_y = b[1]; - if (start_dir == LiteGraph.RIGHT) { - start_x += 10; - } else { - start_y += 10; - } - if (end_dir == LiteGraph.LEFT) { - end_x -= 10; - } else { - end_y -= 10; - } - ctx.lineTo(start_x, start_y); - ctx.lineTo((start_x + end_x) * 0.5, start_y); - ctx.lineTo((start_x + end_x) * 0.5, end_y); - ctx.lineTo(end_x, end_y); - ctx.lineTo(b[0], b[1]); - } else { - return; - } //unknown - } - - //rendering the outline of the connection can be a little bit slow - if ( - this.render_connections_border && - this.ds.scale > 0.6 && - !skip_border - ) { - ctx.strokeStyle = "rgba(0,0,0,0.5)"; - ctx.stroke(); - } - - ctx.lineWidth = this.connections_width; - ctx.fillStyle = ctx.strokeStyle = color; - ctx.stroke(); - //end line shape - - var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); - if (link && link._pos) { - link._pos[0] = pos[0]; - link._pos[1] = pos[1]; - } - - //render arrow in the middle - if ( - this.ds.scale >= 0.6 && - this.highquality_render && - end_dir != LiteGraph.CENTER - ) { - //render arrow - if (this.render_connection_arrows) { - //compute two points in the connection - var posA = this.computeConnectionPoint( - a, - b, - 0.25, - start_dir, - end_dir - ); - var posB = this.computeConnectionPoint( - a, - b, - 0.26, - start_dir, - end_dir - ); - var posC = this.computeConnectionPoint( - a, - b, - 0.75, - start_dir, - end_dir - ); - var posD = this.computeConnectionPoint( - a, - b, - 0.76, - start_dir, - end_dir - ); - - //compute the angle between them so the arrow points in the right direction - var angleA = 0; - var angleB = 0; - if (this.render_curved_connections) { - angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); - angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); - } else { - angleB = angleA = b[1] > a[1] ? 0 : Math.PI; - } - - //render arrow - ctx.save(); - ctx.translate(posA[0], posA[1]); - ctx.rotate(angleA); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - ctx.save(); - ctx.translate(posC[0], posC[1]); - ctx.rotate(angleB); - ctx.beginPath(); - ctx.moveTo(-5, -3); - ctx.lineTo(0, +7); - ctx.lineTo(+5, -3); - ctx.fill(); - ctx.restore(); - } - - //circle - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); - ctx.fill(); - } - - //render flowing points - if (flow) { - ctx.fillStyle = color; - for (var i = 0; i < 5; ++i) { - var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; - var pos = this.computeConnectionPoint( - a, - b, - f, - start_dir, - end_dir - ); - ctx.beginPath(); - ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); - ctx.fill(); - } - } - }; - - //returns the link center point based on curvature - LGraphCanvas.prototype.computeConnectionPoint = function( - a, - b, - t, - start_dir, - end_dir - ) { - start_dir = start_dir || LiteGraph.RIGHT; - end_dir = end_dir || LiteGraph.LEFT; - - var dist = distance(a, b); - var p0 = a; - var p1 = [a[0], a[1]]; - var p2 = [b[0], b[1]]; - var p3 = b; - - switch (start_dir) { - case LiteGraph.LEFT: - p1[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p1[0] += dist * 0.25; - break; - case LiteGraph.UP: - p1[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p1[1] += dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - p2[0] += dist * -0.25; - break; - case LiteGraph.RIGHT: - p2[0] += dist * 0.25; - break; - case LiteGraph.UP: - p2[1] += dist * -0.25; - break; - case LiteGraph.DOWN: - p2[1] += dist * 0.25; - break; - } - - var c1 = (1 - t) * (1 - t) * (1 - t); - var c2 = 3 * ((1 - t) * (1 - t)) * t; - var c3 = 3 * (1 - t) * (t * t); - var c4 = t * t * t; - - var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; - var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; - return [x, y]; - }; - - LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { - ctx.shadowColor = "transparent"; - ctx.globalAlpha = 0.25; - - ctx.textAlign = "center"; - ctx.strokeStyle = "white"; - ctx.globalAlpha = 0.75; - - var visible_nodes = this.visible_nodes; - for (var i = 0; i < visible_nodes.length; ++i) { - var node = visible_nodes[i]; - ctx.fillStyle = "black"; - ctx.fillRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - if (node.order == 0) { - ctx.strokeRect( - node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, - LiteGraph.NODE_TITLE_HEIGHT, - LiteGraph.NODE_TITLE_HEIGHT - ); - } - ctx.fillStyle = "#FFF"; - ctx.fillText( - node.order, - node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, - node.pos[1] - 6 - ); - } - ctx.globalAlpha = 1; - }; - - /** - * draws the widgets stored inside a node - * @method drawNodeWidgets - **/ - LGraphCanvas.prototype.drawNodeWidgets = function( - node, - posY, - ctx, - active_widget - ) { - if (!node.widgets || !node.widgets.length) { - return 0; - } - var width = node.size[0]; - var widgets = node.widgets; - posY += 2; - var H = LiteGraph.NODE_WIDGET_HEIGHT; - var show_text = this.ds.scale > 0.5; - ctx.save(); - ctx.globalAlpha = this.editor_alpha; - var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; - var background_color = LiteGraph.WIDGET_BGCOLOR; - var text_color = LiteGraph.WIDGET_TEXT_COLOR; - var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; - var margin = 15; - - for (var i = 0; i < widgets.length; ++i) { - var w = widgets[i]; - var y = posY; - if (w.y) { - y = w.y; - } - w.last_y = y; - ctx.strokeStyle = outline_color; - ctx.fillStyle = "#222"; - ctx.textAlign = "left"; - //ctx.lineWidth = 2; - if(w.disabled) - ctx.globalAlpha *= 0.5; - var widget_width = w.width || width; - - switch (w.type) { - case "button": - if (w.clicked) { - ctx.fillStyle = "#AAA"; - w.clicked = false; - this.dirty_canvas = true; - } - ctx.fillRect(margin, y, widget_width - margin * 2, H); - if(show_text && !w.disabled) - ctx.strokeRect( margin, y, widget_width - margin * 2, H ); - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); - } - break; - case "toggle": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if(show_text && !w.disabled) - ctx.stroke(); - ctx.fillStyle = w.value ? "#89A" : "#333"; - ctx.beginPath(); - ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); - ctx.fill(); - if (show_text) { - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = w.value ? text_color : secondary_text_color; - ctx.textAlign = "right"; - ctx.fillText( - w.value - ? w.options.on || "true" - : w.options.off || "false", - widget_width - 40, - y + H * 0.7 - ); - } - break; - case "slider": - ctx.fillStyle = background_color; - ctx.fillRect(margin, y, widget_width - margin * 2, H); - var range = w.options.max - w.options.min; - var nvalue = (w.value - w.options.min) / range; - if(nvalue < 0.0) nvalue = 0.0; - if(nvalue > 1.0) nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); - ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); - if(show_text && !w.disabled) - ctx.strokeRect(margin, y, widget_width - margin * 2, H); - if (w.marker) { - var marker_nvalue = (w.marker - w.options.min) / range; - if(marker_nvalue < 0.0) marker_nvalue = 0.0; - if(marker_nvalue > 1.0) marker_nvalue = 1.0; - ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; - ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H ); - } - if (show_text) { - ctx.textAlign = "center"; - ctx.fillStyle = text_color; - ctx.fillText( - w.label || w.name + " " + Number(w.value).toFixed( - w.options.precision != null - ? w.options.precision - : 3 - ), - widget_width * 0.5, - y + H * 0.7 - ); - } - break; - case "number": - case "combo": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if(show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] ); - else - ctx.rect(margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.fillStyle = text_color; - if(!w.disabled) - { - ctx.beginPath(); - ctx.moveTo(margin + 16, y + 5); - ctx.lineTo(margin + 6, y + H * 0.5); - ctx.lineTo(margin + 16, y + H - 5); - ctx.fill(); - ctx.beginPath(); - ctx.moveTo(widget_width - margin - 16, y + 5); - ctx.lineTo(widget_width - margin - 6, y + H * 0.5); - ctx.lineTo(widget_width - margin - 16, y + H - 5); - ctx.fill(); - } - ctx.fillStyle = secondary_text_color; - ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - if (w.type == "number") { - ctx.fillText( - Number(w.value).toFixed( - w.options.precision !== undefined - ? w.options.precision - : 3 - ), - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } else { - var v = w.value; - if( w.options.values ) - { - var values = w.options.values; - if( values.constructor === Function ) - values = values(); - if(values && values.constructor !== Array) - v = values[ w.value ]; - } - ctx.fillText( - v, - widget_width - margin * 2 - 20, - y + H * 0.7 - ); - } - } - break; - case "string": - case "text": - ctx.textAlign = "left"; - ctx.strokeStyle = outline_color; - ctx.fillStyle = background_color; - ctx.beginPath(); - if (show_text) - ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); - else - ctx.rect( margin, y, widget_width - margin * 2, H ); - ctx.fill(); - if (show_text) { - if(!w.disabled) - ctx.stroke(); - ctx.save(); - ctx.beginPath(); - ctx.rect(margin, y, widget_width - margin * 2, H); - ctx.clip(); - - //ctx.stroke(); - ctx.fillStyle = secondary_text_color; - const label = w.label || w.name; - if (label != null) { - ctx.fillText(label, margin * 2, y + H * 0.7); - } - ctx.fillStyle = text_color; - ctx.textAlign = "right"; - ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max - ctx.restore(); - } - break; - default: - if (w.draw) { - w.draw(ctx, node, widget_width, y, H); - } - break; - } - posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; - ctx.globalAlpha = this.editor_alpha; - - } - ctx.restore(); - ctx.textAlign = "left"; - }; - - /** - * process an event on widgets - * @method processNodeWidgets - **/ - LGraphCanvas.prototype.processNodeWidgets = function( - node, - pos, - event, - active_widget - ) { - if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { - return null; - } - - var x = pos[0] - node.pos[0]; - var y = pos[1] - node.pos[1]; - var width = node.size[0]; - var deltaX = event.deltaX || event.deltax || 0; - var that = this; - var ref_window = this.getCanvasWindow(); - - for (var i = 0; i < node.widgets.length; ++i) { - var w = node.widgets[i]; - if(!w || w.disabled) - continue; - var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; - var widget_width = w.width || width; - //outside - if ( w != active_widget && - (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) - continue; - - var old_value = w.value; - - //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { - //inside widget - switch (w.type) { - case "button": - if (event.type === LiteGraph.pointerevents_method+"down") { - if (w.callback) { - setTimeout(function() { - w.callback(w, that, node, pos, event); - }, 20); - } - w.clicked = true; - this.dirty_canvas = true; - } - break; - case "slider": - var old_value = w.value; - var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); - if(w.options.read_only) break; - w.value = w.options.min + (w.options.max - w.options.min) * nvalue; - if (old_value != w.value) { - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - this.dirty_canvas = true; - break; - case "number": - case "combo": - var old_value = w.value; - if (event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") { - if(deltaX) - w.value += deltaX * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (event.type == LiteGraph.pointerevents_method+"down") { - var values = w.options.values; - if (values && values.constructor === Function) { - values = w.options.values(w, node); - } - var values_list = null; - - if( w.type != "number") - values_list = values.constructor === Array ? values : Object.keys(values); - - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (w.type == "number") { - w.value += delta * 0.1 * (w.options.step || 1); - if ( w.options.min != null && w.value < w.options.min ) { - w.value = w.options.min; - } - if ( w.options.max != null && w.value > w.options.max ) { - w.value = w.options.max; - } - } else if (delta) { //clicked in arrow, used for combos - var index = -1; - this.last_mouseclick = 0; //avoids dobl click event - if(values.constructor === Object) - index = values_list.indexOf( String( w.value ) ) + delta; - else - index = values_list.indexOf( w.value ) + delta; - if (index >= values_list.length) { - index = values_list.length - 1; - } - if (index < 0) { - index = 0; - } - if( values.constructor === Array ) - w.value = values[index]; - else - w.value = index; - } else { //combo clicked - var text_values = values != values_list ? Object.values(values) : values; - var menu = new LiteGraph.ContextMenu(text_values, { - scale: Math.max(1, this.ds.scale), - event: event, - className: "dark", - callback: inner_clicked.bind(w) - }, - ref_window); - function inner_clicked(v, option, event) { - if(values != values_list) - v = text_values.indexOf(v); - this.value = v; - inner_value_change(this, v); - that.dirty_canvas = true; - return false; - } - } - } //end mousedown - else if(event.type == LiteGraph.pointerevents_method+"up" && w.type == "number") - { - var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; - if (event.click_time < 200 && delta == 0) { - this.prompt("Value",w.value,function(v) { - // check if v is a valid equation or a number - if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { - try {//solve the equation if possible - v = eval(v); - } catch (e) { } - } - this.value = Number(v); - inner_value_change(this, this.value); - }.bind(w), - event); - } - } - - if( old_value != w.value ) - setTimeout( - function() { - inner_value_change(this, this.value); - }.bind(w), - 20 - ); - this.dirty_canvas = true; - break; - case "toggle": - if (event.type == LiteGraph.pointerevents_method+"down") { - w.value = !w.value; - setTimeout(function() { - inner_value_change(w, w.value); - }, 20); - } - break; - case "string": - case "text": - if (event.type == LiteGraph.pointerevents_method+"down") { - this.prompt("Value",w.value,function(v) { - inner_value_change(this, v); - }.bind(w), - event,w.options ? w.options.multiline : false ); - } - break; - default: - if (w.mouse) { - this.dirty_canvas = w.mouse(event, [x, y], node); - } - break; - } //end switch - - //value changed - if( old_value != w.value ) - { - if(node.onWidgetChanged) - node.onWidgetChanged( w.name,w.value,old_value,w ); - node.graph._version++; - } - - return w; - }//end for - - function inner_value_change(widget, value) { - if(widget.type == "number"){ - value = Number(value); - } - widget.value = value; - if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { - node.setProperty( widget.options.property, value ); - } - if (widget.callback) { - widget.callback(widget.value, that, node, pos, event); - } - } - - return null; - }; - - /** - * draws every group area in the background - * @method drawGroups - **/ - LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { - if (!this.graph) { - return; - } - - var groups = this.graph._groups; - - ctx.save(); - ctx.globalAlpha = 0.5 * this.editor_alpha; - - for (var i = 0; i < groups.length; ++i) { - var group = groups[i]; - - if (!overlapBounding(this.visible_area, group._bounding)) { - continue; - } //out of the visible area - - ctx.fillStyle = group.color || "#335"; - ctx.strokeStyle = group.color || "#335"; - var pos = group._pos; - var size = group._size; - ctx.globalAlpha = 0.25 * this.editor_alpha; - ctx.beginPath(); - ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); - ctx.fill(); - ctx.globalAlpha = this.editor_alpha; - ctx.stroke(); - - ctx.beginPath(); - ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); - ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); - ctx.fill(); - - var font_size = - group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; - ctx.font = font_size + "px Arial"; - ctx.textAlign = "left"; - ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); - } - - ctx.restore(); - }; - - LGraphCanvas.prototype.adjustNodesSize = function() { - var nodes = this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { - nodes[i].size = nodes[i].computeSize(); - } - this.setDirty(true, true); - }; - - /** - * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode - * @method resize - **/ - LGraphCanvas.prototype.resize = function(width, height) { - if (!width && !height) { - var parent = this.canvas.parentNode; - width = parent.offsetWidth; - height = parent.offsetHeight; - } - - if (this.canvas.width == width && this.canvas.height == height) { - return; - } - - this.canvas.width = width; - this.canvas.height = height; - this.bgcanvas.width = this.canvas.width; - this.bgcanvas.height = this.canvas.height; - this.setDirty(true, true); - }; - - /** - * switches to live mode (node shapes are not rendered, only the content) - * this feature was designed when graphs where meant to create user interfaces - * @method switchLiveMode - **/ - LGraphCanvas.prototype.switchLiveMode = function(transition) { - if (!transition) { - this.live_mode = !this.live_mode; - this.dirty_canvas = true; - this.dirty_bgcanvas = true; - return; - } - - var self = this; - var delta = this.live_mode ? 1.1 : 0.9; - if (this.live_mode) { - this.live_mode = false; - this.editor_alpha = 0.1; - } - - var t = setInterval(function() { - self.editor_alpha *= delta; - self.dirty_canvas = true; - self.dirty_bgcanvas = true; - - if (delta < 1 && self.editor_alpha < 0.01) { - clearInterval(t); - if (delta < 1) { - self.live_mode = true; - } - } - if (delta > 1 && self.editor_alpha > 0.99) { - clearInterval(t); - self.editor_alpha = 1; - } - }, 1); - }; - - LGraphCanvas.prototype.onNodeSelectionChange = function(node) { - return; //disabled - }; - - /* this is an implementation for touch not in production and not ready - */ - /*LGraphCanvas.prototype.touchHandler = function(event) { - //alert("foo"); - var touches = event.changedTouches, - first = touches[0], - type = ""; - - switch (event.type) { - case "touchstart": - type = "mousedown"; - break; - case "touchmove": - type = "mousemove"; - break; - case "touchend": - type = "mouseup"; - break; - default: - return; - } - - //initMouseEvent(type, canBubble, cancelable, view, clickCount, - // screenX, screenY, clientX, clientY, ctrlKey, - // altKey, shiftKey, metaKey, button, relatedTarget); - - // this is eventually a Dom object, get the LGraphCanvas back - if(typeof this.getCanvasWindow == "undefined"){ - var window = this.lgraphcanvas.getCanvasWindow(); - }else{ - var window = this.getCanvasWindow(); - } - - var document = window.document; - - var simulatedEvent = document.createEvent("MouseEvent"); - simulatedEvent.initMouseEvent( - type, - true, - true, - window, - 1, - first.screenX, - first.screenY, - first.clientX, - first.clientY, - false, - false, - false, - false, - 0, //left - null - ); - first.target.dispatchEvent(simulatedEvent); - event.preventDefault(); - };*/ - - /* CONTEXT MENU ********************/ - - LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var group = new LiteGraph.LGraphGroup(); - group.pos = canvas.convertEventToCanvasOffset(mouse_event); - canvas.graph.add(group); - }; - - /** - * Determines the furthest nodes in each direction - * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.getBoundaryNodes = function(nodes) { - let top = null; - let right = null; - let bottom = null; - let left = null; - for (const nID in nodes) { - const node = nodes[nID]; - const [x, y] = node.pos; - const [width, height] = node.size; - - if (top === null || y < top.pos[1]) { - top = node; - } - if (right === null || x + width > right.pos[0] + right.size[0]) { - right = node; - } - if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { - bottom = node; - } - if (left === null || x < left.pos[0]) { - left = node; - } - } - - return { - "top": top, - "right": right, - "bottom": bottom, - "left": left - }; - } - /** - * Determines the furthest nodes in each direction for the currently selected nodes - * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} - */ - LGraphCanvas.prototype.boundaryNodesForSelection = function() { - return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); - } - - /** - * - * @param {LGraphNode[]} nodes a list of nodes - * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes - * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) - */ - LGraphCanvas.alignNodes = function (nodes, direction, align_to) { - if (!nodes) { - return; - } - - const canvas = LGraphCanvas.active_canvas; - let boundaryNodes = [] - if (align_to === undefined) { - boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) - } else { - boundaryNodes = { - "top": align_to, - "right": align_to, - "bottom": align_to, - "left": align_to - } - } - - for (const [_, node] of Object.entries(canvas.selected_nodes)) { - switch (direction) { - case "right": - node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; - break; - case "left": - node.pos[0] = boundaryNodes["left"].pos[0]; - break; - case "top": - node.pos[1] = boundaryNodes["top"].pos[1]; - break; - case "bottom": - node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; - break; - } - } - - canvas.dirty_canvas = true; - canvas.dirty_bgcanvas = true; - }; - - LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); - } - } - - LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { - new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { - event: event, - callback: inner_clicked, - parentMenu: prev_menu, - }); - - function inner_clicked(value) { - LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); - } - } - - LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { - - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - var graph = canvas.graph; - if (!graph) - return; - - function inner_onMenuAdded(base_category ,prev_menu){ - - var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); - var entries = []; - - categories.map(function(category){ - - if (!category) - return; - - var base_category_regex = new RegExp('^(' + base_category + ')'); - var category_name = category.replace(base_category_regex,"").split('/')[0]; - var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; - - var name = category_name; - if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace - name = name.split("::")[1]; - - var index = entries.findIndex(function(entry){return entry.value === category_path}); - if (index === -1) { - entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){ - inner_onMenuAdded(value.value, contextMenu) - }}); - } - - }); - - var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); - nodes.map(function(node){ - - if (node.skip_list) - return; - - var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){ - - var first_event = contextMenu.getFirstEvent(); - canvas.graph.beforeChange(); - var node = LiteGraph.createNode(value.value); - if (node) { - node.pos = canvas.convertEventToCanvasOffset(first_event); - canvas.graph.add(node); - } - if(callback) - callback(node); - canvas.graph.afterChange(); - - } - } - - entries.push(entry); - - }); - - new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); - - } - - inner_onMenuAdded('',prev_menu); - return false; - - }; - - LGraphCanvas.onMenuCollapseAll = function() {}; - - LGraphCanvas.onMenuNodeEdit = function() {}; - - LGraphCanvas.showMenuNodeOptionalInputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_inputs; - if (node.onGetInputs) { - options = node.onGetInputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - entries.push(null); - continue; - } - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - - if (entry[2].label) { - label = entry[2].label; - } - - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.ACTION) { - data.className = "event"; - } - entries.push(data); - } - } - - if (node.onMenuNodeInputs) { - var retEntries = node.onMenuNodeInputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - console.log("no input entries"); - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (v.value) { - node.graph.beforeChange(); - node.addInput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeInputAdd) { // callback to the node when adding a slot - node.onNodeInputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.showMenuNodeOptionalOutputs = function( - v, - options, - e, - prev_menu, - node - ) { - if (!node) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var options = node.optional_outputs; - if (node.onGetOutputs) { - options = node.onGetOutputs(); - } - - var entries = []; - if (options) { - for (var i=0; i < options.length; i++) { - var entry = options[i]; - if (!entry) { - //separator? - entries.push(null); - continue; - } - - if ( - node.flags && - node.flags.skip_repeated_outputs && - node.findOutputSlot(entry[0]) != -1 - ) { - continue; - } //skip the ones already on - var label = entry[0]; - if(!entry[2]) - entry[2] = {}; - if (entry[2].label) { - label = entry[2].label; - } - entry[2].removable = true; - var data = { content: label, value: entry }; - if (entry[1] == LiteGraph.EVENT) { - data.className = "event"; - } - entries.push(data); - } - } - - if (this.onMenuNodeOutputs) { - entries = this.onMenuNodeOutputs(entries); - } - if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted - if (node.findOutputSlot("onExecuted") == -1){ - entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} - } - } - // add callback for modifing the menu elements onMenuNodeOutputs - if (node.onMenuNodeOutputs) { - var retEntries = node.onMenuNodeOutputs(entries); - if(retEntries) entries = retEntries; - } - - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }, - ref_window - ); - - function inner_clicked(v, e, prev) { - if (!node) { - return; - } - - if (v.callback) { - v.callback.call(that, node, v, e, prev); - } - - if (!v.value) { - return; - } - - var value = v.value[1]; - - if ( - value && - (value.constructor === Object || value.constructor === Array) - ) { - //submenu why? - var entries = []; - for (var i in value) { - entries.push({ content: i, value: value[i] }); - } - new LiteGraph.ContextMenu(entries, { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - node: node - }); - return false; - } else { - node.graph.beforeChange(); - node.addOutput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeOutputAdd) { // a callback to the node when adding a slot - node.onNodeOutputAdd(v.value); - } - node.setDirtyCanvas(true, true); - node.graph.afterChange(); - } - } - - return false; - }; - - LGraphCanvas.onShowMenuNodeProperties = function( - value, - options, - e, - prev_menu, - node - ) { - if (!node || !node.properties) { - return; - } - - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var entries = []; - for (var i in node.properties) { - var value = node.properties[i] !== undefined ? node.properties[i] : " "; - if( typeof value == "object" ) - value = JSON.stringify(value); - var info = node.getPropertyInfo(i); - if(info.type == "enum" || info.type == "combo") - value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); - - //value could contain invalid html characters, clean that - value = LGraphCanvas.decodeHTML(value); - entries.push({ - content: - "" + - (info.label ? info.label : i) + - "" + - "" + - value + - "", - value: i - }); - } - if (!entries.length) { - return; - } - - var menu = new LiteGraph.ContextMenu( - entries, - { - event: e, - callback: inner_clicked, - parentMenu: prev_menu, - allow_html: true, - node: node - }, - ref_window - ); - - function inner_clicked(v, options, e, prev) { - if (!node) { - return; - } - var rect = this.getBoundingClientRect(); - canvas.showEditPropertyValue(node, v.value, { - position: [rect.left, rect.top] - }); - } - - return false; - }; - - LGraphCanvas.decodeHTML = function(str) { - var e = document.createElement("div"); - e.innerText = str; - return e.innerHTML; - }; - - LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { - if (!node) { - return; - } - - var fApplyMultiNode = function(node){ - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.prototype.showLinkMenu = function(link, e) { - var that = this; - // console.log(link); - var node_left = that.graph.getNodeById( link.origin_id ); - var node_right = that.graph.getNodeById( link.target_id ); - var fromType = false; - if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; - var destType = false; - if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; - - var options = ["Add Node",null,"Delete",null]; - - - var menu = new LiteGraph.ContextMenu(options, { - event: e, - title: link.data != null ? link.data.constructor.name : null, - callback: inner_clicked - }); - - function inner_clicked(v,options,e) { - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - // console.debug("node autoconnect"); - if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ - return; - } - // leave the connection type checking inside connectByType - if (node_left.connectByType( link.origin_slot, node, fromType )){ - node.connectByType( link.target_slot, node_right, destType ); - node.pos[0] -= node.size[0] * 0.5; - } - }); - break; - - case "Delete": - that.graph.removeLink(link.id); - break; - default: - /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left - ,slotFrom: link.origin_slot - ,nodeTo: node - ,slotTo: link.target_slot - ,e: e - ,nodeType: "AUTO" - }); - if(nodeCreated) console.log("new node in beetween "+v+" created");*/ - } - } - - return false; - }; - - LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,position: [] // pass the event coords - ,nodeType: null // choose a nodetype to add, AUTO to set at first good - ,posAdd:[0,0] // adjust x,y - ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom!==null; - var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; - - if (!isFrom && !isTo){ - console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); - return false; - } - if (!opts.nodeType){ - console.warn("No type to createDefaultNodeForSlot"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "undefined": - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - if (slotX===false || iSlotConn===false){ - console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); - } - - // check for defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if (slotX.link !== null) { - // is connected - }else{ - // is not not connected - } - nodeNewType = false; - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ - nodeNewType = slotTypesDefault[fromSlotType][typeX]; - // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); - break; // -------- - } - } - }else{ - if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; - } - if (nodeNewType) { - var nodeNewOpts = false; - if (typeof nodeNewType == "object" && nodeNewType.node){ - nodeNewOpts = nodeNewType; - nodeNewType = nodeNewType.node; - } - - //that.graph.beforeChange(); - - var newNode = LiteGraph.createNode(nodeNewType); - if(newNode){ - // if is object pass options - if (nodeNewOpts){ - if (nodeNewOpts.properties) { - for (var i in nodeNewOpts.properties) { - newNode.addProperty( i, nodeNewOpts.properties[i] ); - } - } - if (nodeNewOpts.inputs) { - newNode.inputs = []; - for (var i in nodeNewOpts.inputs) { - newNode.addOutput( - nodeNewOpts.inputs[i][0], - nodeNewOpts.inputs[i][1] - ); - } - } - if (nodeNewOpts.outputs) { - newNode.outputs = []; - for (var i in nodeNewOpts.outputs) { - newNode.addOutput( - nodeNewOpts.outputs[i][0], - nodeNewOpts.outputs[i][1] - ); - } - } - if (nodeNewOpts.title) { - newNode.title = nodeNewOpts.title; - } - if (nodeNewOpts.json) { - newNode.configure(nodeNewOpts.json); - } - - } - - // add the node - that.graph.add(newNode); - newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) - ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ - - //that.graph.afterChange(); - - // connect the two! - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); - } - - // if connecting in between - if (isFrom && isTo){ - // TODO - } - - return true; - - }else{ - console.log("failed creating "+nodeNewType); - } - } - } - return false; - } - - LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection - var optPass = optPass || {}; - var opts = Object.assign({ nodeFrom: null // input - ,slotFrom: null // input - ,nodeTo: null // output - ,slotTo: null // output - ,e: null - } - ,optPass - ); - var that = this; - - var isFrom = opts.nodeFrom && opts.slotFrom; - var isTo = !isFrom && opts.nodeTo && opts.slotTo; - - if (!isFrom && !isTo){ - console.warn("No data passed to showConnectionMenu"); - return false; - } - - var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; - var slotX = isFrom ? opts.slotFrom : opts.slotTo; - - var iSlotConn = false; - switch (typeof slotX){ - case "string": - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - case "object": - // ok slotX - iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); - break; - case "number": - iSlotConn = slotX; - slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; - break; - default: - // bad ? - //iSlotConn = 0; - console.warn("Cant get slot information "+slotX); - return false; - } - - var options = ["Add Node",null]; - - if (that.allow_searchbox){ - options.push("Search"); - options.push(null); - } - - // get defaults nodes for this slottype - var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; - var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; - if(slotTypesDefault && slotTypesDefault[fromSlotType]){ - if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ - for(var typeX in slotTypesDefault[fromSlotType]){ - options.push(slotTypesDefault[fromSlotType][typeX]); - } - }else{ - options.push(slotTypesDefault[fromSlotType]); - } - } - - // build menu - var menu = new LiteGraph.ContextMenu(options, { - event: opts.e, - title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), - callback: inner_clicked - }); - - // callback - function inner_clicked(v,options,e) { - //console.log("Process showConnectionMenu selection"); - switch (v) { - case "Add Node": - LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - if (isFrom){ - opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); - }else{ - opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); - } - }); - break; - case "Search": - if(isFrom){ - that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); - }else{ - that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); - } - break; - default: - // check for defaults nodes for this slottype - var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] - ,nodeType: v - })); - if (nodeCreated){ - // new node created - //console.log("node "+v+" created") - }else{ - // failed or v is not in defaults - } - break; - } - } - - return false; - }; - - // TODO refactor :: this is used fot title but not for properties! - LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { - var input_html = ""; - var property = item.property || "title"; - var value = node[property]; - - // TODO refactor :: use createDialog ? - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog"; - dialog.innerHTML = - ""; - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - var title = dialog.querySelector(".name"); - title.innerText = property; - var input = dialog.querySelector(".value"); - if (input) { - input.value = value; - input.addEventListener("blur", function(e) { - this.focus(); - }); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - canvas.parentNode.appendChild(dialog); - - if(input) input.focus(); - - var dialogCloseTimer = null; - dialog.addEventListener("mouseleave", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - - function inner() { - if(input) setValue(input.value); - } - - function setValue(value) { - if (item.type == "Number") { - value = Number(value); - } else if (item.type == "Boolean") { - value = Boolean(value); - } - node[property] = value; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - node.setDirtyCanvas(true, true); - } - }; - - // refactor: there are different dialogs, some uses createDialog some dont - LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { - var that = this; - var input_html = ""; - title = title || ""; - - var dialog = document.createElement("div"); - dialog.is_modified = false; - dialog.className = "graphdialog rounded"; - if(multiline) - dialog.innerHTML = " "; - else - dialog.innerHTML = " "; - dialog.close = function() { - that.prompt_box = null; - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - canvas.parentNode.appendChild(dialog); - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - var dialogCloseTimer = null; - var prevent_timeout = false; - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout) - return; - if(LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if(LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - if (that.prompt_box) { - that.prompt_box.close(); - } - that.prompt_box = dialog; - - var first = null; - var timeout = null; - var selected = null; - - var name_element = dialog.querySelector(".name"); - name_element.innerText = title; - var value_element = dialog.querySelector(".value"); - value_element.value = value; - - var input = value_element; - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13 && e.target.localName != "textarea") { - if (callback) { - callback(this.value); - } - dialog.close(); - } else { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - - var button = dialog.querySelector("button"); - button.addEventListener("click", function(e) { - if (callback) { - callback(input.value); - } - that.setDirty(true); - dialog.close(); - }); - - var rect = canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - - setTimeout(function() { - input.focus(); - }, 10); - - return dialog; - }; - - LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event, options) { - // proposed defaults - var def_options = { slot_from: null - ,node_from: null - ,node_to: null - ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out - ,type_filter_in: false // these are default: pass to set initially set values - ,type_filter_out: false - ,show_general_if_none_on_typefilter: true - ,show_general_after_typefiltered: true - ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave - ,show_all_if_empty: true - ,show_all_on_open: LiteGraph.search_show_all_on_open - }; - options = Object.assign(def_options, options || {}); - - //console.log(options); - - var that = this; - var input_html = ""; - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var root_document = canvas.ownerDocument || document; - - var dialog = document.createElement("div"); - dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = "Search "; - if (options.do_type_filter){ - dialog.innerHTML += ""; - dialog.innerHTML += ""; - } - dialog.innerHTML += "
"; - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - // dialog element has been appended - - if (options.do_type_filter){ - var selIn = dialog.querySelector(".slot_in_type_filter"); - var selOut = dialog.querySelector(".slot_out_type_filter"); - } - - dialog.close = function() { - that.search_box = null; - this.blur(); - canvas.focus(); - root_document.body.style.overflow = ""; - - setTimeout(function() { - that.canvas.focus(); - }, 20); //important, if canvas loses focus keys wont be captured - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - if (this.ds.scale > 1) { - dialog.style.transform = "scale(" + this.ds.scale + ")"; - } - - // hide on mouse leave - if(options.hide_on_mouse_leave){ - var prevent_timeout = false; - var timeout_close = null; - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; - } - }); - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (prevent_timeout){ - return; - } - timeout_close = setTimeout(function() { - dialog.close(); - }, 500); - }); - // if filtering, check focus changed to comboboxes and prevent closing - if (options.do_type_filter){ - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - selOut.addEventListener("click", function(e) { - prevent_timeout++; - }); - selOut.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selOut.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - } - } - - if (that.search_box) { - that.search_box.close(); - } - that.search_box = dialog; - - var helper = dialog.querySelector(".helper"); - - var first = null; - var timeout = null; - var selected = null; - - var input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - if(that.search_box) - this.focus(); - }); - input.addEventListener("keydown", function(e) { - if (e.keyCode == 38) { - //UP - changeSelection(false); - } else if (e.keyCode == 40) { - //DOWN - changeSelection(true); - } else if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - refreshHelper(); - if (selected) { - select(selected.innerHTML); - } else if (first) { - select(first); - } else { - dialog.close(); - } - } else { - if (timeout) { - clearInterval(timeout); - } - timeout = setTimeout(refreshHelper, 250); - return; - } - e.preventDefault(); - e.stopPropagation(); - e.stopImmediatePropagation(); - return true; - }); - } - - // if should filter on type, load and fill selected and choose elements if passed - if (options.do_type_filter){ - if (selIn){ - var aSlots = LiteGraph.slot_types_in; - var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; - - if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) - options.type_filter_in = "_event_"; - /* this will filter on * .. but better do it manually in case - else if(options.type_filter_in === "" || options.type_filter_in === 0) - options.type_filter_in = "*";*/ - - for (var iK=0; iK (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; - - /* - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (event) { - dialog.style.left = event.clientX + offsetx + "px"; - dialog.style.top = event.clientY + offsety + "px"; - } else { - dialog.style.left = canvas.width * 0.5 + offsetx + "px"; - dialog.style.top = canvas.height * 0.5 + offsety + "px"; - } - canvas.parentNode.appendChild(dialog); - */ - - input.focus(); - if (options.show_all_on_open) refreshHelper(); - - function select(name) { - if (name) { - if (that.onSearchBoxSelection) { - that.onSearchBoxSelection(name, event, graphcanvas); - } else { - var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; - if (extra) { - name = extra.type; - } - - graphcanvas.graph.beforeChange(); - var node = LiteGraph.createNode(name); - if (node) { - node.pos = graphcanvas.convertEventToCanvasOffset( - event - ); - graphcanvas.graph.add(node, false); - } - - if (extra && extra.data) { - if (extra.data.properties) { - for (var i in extra.data.properties) { - node.addProperty( i, extra.data.properties[i] ); - } - } - if (extra.data.inputs) { - node.inputs = []; - for (var i in extra.data.inputs) { - node.addOutput( - extra.data.inputs[i][0], - extra.data.inputs[i][1] - ); - } - } - if (extra.data.outputs) { - node.outputs = []; - for (var i in extra.data.outputs) { - node.addOutput( - extra.data.outputs[i][0], - extra.data.outputs[i][1] - ); - } - } - if (extra.data.title) { - node.title = extra.data.title; - } - if (extra.data.json) { - node.configure(extra.data.json); - } - - } - - // join node after inserting - if (options.node_from){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_from.findOutputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_from.findOutputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_from.outputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); - } - }else{ - // console.warn("cant find slot " + options.slot_from); - } - } - if (options.node_to){ - var iS = false; - switch (typeof options.slot_from){ - case "string": - iS = options.node_to.findInputSlot(options.slot_from); - break; - case "object": - if (options.slot_from.name){ - iS = options.node_to.findInputSlot(options.slot_from.name); - }else{ - iS = -1; - } - if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; - break; - case "number": - iS = options.slot_from; - break; - default: - iS = 0; // try with first if no name set - } - if (typeof options.node_to.inputs[iS] !== "undefined"){ - if (iS!==false && iS>-1){ - // try connection - options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); - } - }else{ - // console.warn("cant find slot_nodeTO " + options.slot_from); - } - } - - graphcanvas.graph.afterChange(); - } - } - - dialog.close(); - } - - function changeSelection(forward) { - var prev = selected; - if (selected) { - selected.classList.remove("selected"); - } - if (!selected) { - selected = forward - ? helper.childNodes[0] - : helper.childNodes[helper.childNodes.length]; - } else { - selected = forward - ? selected.nextSibling - : selected.previousSibling; - if (!selected) { - selected = prev; - } - } - if (!selected) { - return; - } - selected.classList.add("selected"); - selected.scrollIntoView({block: "end", behavior: "smooth"}); - } - - function refreshHelper() { - timeout = null; - var str = input.value; - first = null; - helper.innerHTML = ""; - if (!str && !options.show_all_if_empty) { - return; - } - - if (that.onSearchBox) { - var list = that.onSearchBox(helper, str, graphcanvas); - if (list) { - for (var i = 0; i < list.length; ++i) { - addResult(list[i]); - } - } - } else { - var c = 0; - str = str.toLowerCase(); - var filter = graphcanvas.filter || graphcanvas.graph.filter; - - // filter by type preprocess - if(options.do_type_filter && that.search_box){ - var sIn = that.search_box.querySelector(".slot_in_type_filter"); - var sOut = that.search_box.querySelector(".slot_out_type_filter"); - }else{ - var sIn = false; - var sOut = false; - } - - //extras - for (var i in LiteGraph.searchbox_extras) { - var extra = LiteGraph.searchbox_extras[i]; - if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { - continue; - } - var ctor = LiteGraph.registered_node_types[ extra.type ]; - if( ctor && ctor.filter != filter ) - continue; - if( ! inner_test_filter(extra.type) ) - continue; - addResult( extra.desc, "searchbox_extra" ); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - var filtered = null; - if (Array.prototype.filter) { //filter supported - var keys = Object.keys( LiteGraph.registered_node_types ); //types - var filtered = keys.filter( inner_test_filter ); - } else { - filtered = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i) ) - filtered.push(i); - } - } - - for (var i = 0; i < filtered.length; i++) { - addResult(filtered[i]); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - - // add general type if filtering - if (options.show_general_after_typefiltered - && (sIn.value || sOut.value) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "generic_type"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - // check il filtering gave no results - if ((sIn.value || sOut.value) && - ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) - ){ - filtered_extra = []; - for (var i in LiteGraph.registered_node_types) { - if( inner_test_filter(i, {skipFilter: true}) ) - filtered_extra.push(i); - } - for (var i = 0; i < filtered_extra.length; i++) { - addResult(filtered_extra[i], "not_in_filter"); - if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { - break; - } - } - } - - function inner_test_filter( type, optsIn ) - { - var optsIn = optsIn || {}; - var optsDef = { skipFilter: false - ,inTypeOverride: false - ,outTypeOverride: false - }; - var opts = Object.assign(optsDef,optsIn); - var ctor = LiteGraph.registered_node_types[ type ]; - if(filter && ctor.filter != filter ) - return false; - if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) - return false; - - // filter by slot IN, OUT types - if(options.do_type_filter && !opts.skipFilter){ - var sType = type; - - var sV = sIn.value; - if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sIn && sV){ - //console.log("will check filter against "+sV); - if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_in_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - - var sV = sOut.value; - if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; - //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 - - if(sOut && sV){ - //console.log("search will check filter against "+sV); - if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored - //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); - var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); - if (doesInc!==false){ - //console.log(sType+" HAS "+sV); - }else{ - /*console.debug(LiteGraph.registered_slot_out_types[sV]); - console.log(+" DONT includes "+type);*/ - return false; - } - } - } - } - return true; - } - } - - function addResult(type, className) { - var help = document.createElement("div"); - if (!first) { - first = type; - } - help.innerText = type; - help.dataset["type"] = escape(type); - help.className = "litegraph lite-search-item"; - if (className) { - help.className += " " + className; - } - help.addEventListener("click", function(e) { - select(unescape(this.dataset["type"])); - }); - helper.appendChild(help); - } - } - - return dialog; - }; - - LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { - if (!node || node.properties[property] === undefined) { - return; - } - - options = options || {}; - var that = this; - - var info = node.getPropertyInfo(property); - var type = info.type; - - var input_html = ""; - - if (type == "string" || type == "number" || type == "array" || type == "object") { - input_html = ""; - } else if ( (type == "enum" || type == "combo") && info.values) { - input_html = ""; - } else if (type == "boolean" || type == "toggle") { - input_html = - ""; - } else { - console.warn("unknown type: " + type); - return; - } - - var dialog = this.createDialog( - "" + - (info.label ? info.label : property) + - "" + - input_html + - "", - options - ); - - var input = false; - if ((type == "enum" || type == "combo") && info.values) { - input = dialog.querySelector("select"); - input.addEventListener("change", function(e) { - dialog.modified(); - setValue(e.target.value); - //var index = e.target.value; - //setValue( e.options[e.selectedIndex].value ); - }); - } else if (type == "boolean" || type == "toggle") { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("click", function(e) { - dialog.modified(); - setValue(!!input.checked); - }); - } - } else { - input = dialog.querySelector("input"); - if (input) { - input.addEventListener("blur", function(e) { - this.focus(); - }); - - var v = node.properties[property] !== undefined ? node.properties[property] : ""; - if (type !== 'string') { - v = JSON.stringify(v); - } - - input.value = v; - input.addEventListener("keydown", function(e) { - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - // ENTER - inner(); // save - } else if (e.keyCode != 13) { - dialog.modified(); - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - } - } - if (input) input.focus(); - - var button = dialog.querySelector("button"); - button.addEventListener("click", inner); - - function inner() { - setValue(input.value); - } - - function setValue(value) { - - if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) - value = info.values[value]; - - if (typeof node.properties[property] == "number") { - value = Number(value); - } - if (type == "array" || type == "object") { - value = JSON.parse(value); - } - node.properties[property] = value; - if (node.graph) { - node.graph._version++; - } - if (node.onPropertyChanged) { - node.onPropertyChanged(property, value); - } - if(options.onclose) - options.onclose(); - dialog.close(); - node.setDirtyCanvas(true, true); - } - - return dialog; - }; - - // TODO refactor, theer are different dialog, some uses createDialog, some dont - LGraphCanvas.prototype.createDialog = function(html, options) { - var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; - options = Object.assign(def_options, options || {}); - - var dialog = document.createElement("div"); - dialog.className = "graphdialog"; - dialog.innerHTML = html; - dialog.is_modified = false; - - var rect = this.canvas.getBoundingClientRect(); - var offsetx = -20; - var offsety = -20; - if (rect) { - offsetx -= rect.left; - offsety -= rect.top; - } - - if (options.position) { - offsetx += options.position[0]; - offsety += options.position[1]; - } else if (options.event) { - offsetx += options.event.clientX; - offsety += options.event.clientY; - } //centered - else { - offsetx += this.canvas.width * 0.5; - offsety += this.canvas.height * 0.5; - } - - dialog.style.left = offsetx + "px"; - dialog.style.top = offsety + "px"; - - this.canvas.parentNode.appendChild(dialog); - - // acheck for input and use default behaviour: save on enter, close on esc - if (options.checkForInput){ - var aI = []; - var focused = false; - if (aI = dialog.querySelectorAll("input")){ - aI.forEach(function(iX) { - iX.addEventListener("keydown",function(e){ - dialog.modified(); - if (e.keyCode == 27) { - dialog.close(); - } else if (e.keyCode != 13) { - return; - } - // set value ? - e.preventDefault(); - e.stopPropagation(); - }); - if (!focused) iX.focus(); - }); - } - } - - dialog.modified = function(){ - dialog.is_modified = true; - } - dialog.close = function() { - if (dialog.parentNode) { - dialog.parentNode.removeChild(dialog); - } - }; - - var dialogCloseTimer = null; - var prevent_timeout = false; - dialog.addEventListener("mouseleave", function(e) { - if (prevent_timeout) - return; - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) - dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); - }); - dialog.addEventListener("mouseenter", function(e) { - if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) - if(dialogCloseTimer) clearTimeout(dialogCloseTimer); - }); - var selInDia = dialog.querySelectorAll("select"); - if (selInDia){ - // if filtering, check focus changed to comboboxes and prevent closing - selInDia.forEach(function(selIn) { - selIn.addEventListener("click", function(e) { - prevent_timeout++; - }); - selIn.addEventListener("blur", function(e) { - prevent_timeout = 0; - }); - selIn.addEventListener("change", function(e) { - prevent_timeout = -1; - }); - }); - } - - return dialog; - }; - - LGraphCanvas.prototype.createPanel = function(title, options) { - options = options || {}; - - var ref_window = options.window || window; - var root = document.createElement("div"); - root.className = "litegraph dialog"; - root.innerHTML = "
"; - root.header = root.querySelector(".dialog-header"); - - if(options.width) - root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); - if(options.height) - root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); - if(options.closable) - { - var close = document.createElement("span"); - close.innerHTML = "✕"; - close.classList.add("close"); - close.addEventListener("click",function(){ - root.close(); - }); - root.header.appendChild(close); - } - root.title_element = root.querySelector(".dialog-title"); - root.title_element.innerText = title; - root.content = root.querySelector(".dialog-content"); - root.alt_content = root.querySelector(".dialog-alt-content"); - root.footer = root.querySelector(".dialog-footer"); - - root.close = function() - { - if (root.onClose && typeof root.onClose == "function"){ - root.onClose(); - } - if(root.parentNode) - root.parentNode.removeChild(root); - /* XXX CHECK THIS */ - if(this.parentNode){ - this.parentNode.removeChild(this); - } - /* XXX this was not working, was fixed with an IF, check this */ - } - - // function to swap panel content - root.toggleAltContent = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - var vAlt = force ? "none" : "block"; - }else{ - var vTo = root.alt_content.style.display != "block" ? "block" : "none"; - var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; - } - root.alt_content.style.display = vTo; - root.content.style.display = vAlt; - } - - root.toggleFooterVisibility = function(force){ - if (typeof force != "undefined"){ - var vTo = force ? "block" : "none"; - }else{ - var vTo = root.footer.style.display != "block" ? "block" : "none"; - } - root.footer.style.display = vTo; - } - - root.clear = function() - { - this.content.innerHTML = ""; - } - - root.addHTML = function(code, classname, on_footer) - { - var elem = document.createElement("div"); - if(classname) - elem.className = classname; - elem.innerHTML = code; - if(on_footer) - root.footer.appendChild(elem); - else - root.content.appendChild(elem); - return elem; - } - - root.addButton = function( name, callback, options ) - { - var elem = document.createElement("button"); - elem.innerText = name; - elem.options = options; - elem.classList.add("btn"); - elem.addEventListener("click",callback); - root.footer.appendChild(elem); - return elem; - } - - root.addSeparator = function() - { - var elem = document.createElement("div"); - elem.className = "separator"; - root.content.appendChild(elem); - } - - root.addWidget = function( type, name, value, options, callback ) - { - options = options || {}; - var str_value = String(value); - type = type.toLowerCase(); - if(type == "number") - str_value = value.toFixed(3); - - var elem = document.createElement("div"); - elem.className = "property"; - elem.innerHTML = ""; - elem.querySelector(".property_name").innerText = options.label || name; - var value_element = elem.querySelector(".property_value"); - value_element.innerText = str_value; - elem.dataset["property"] = name; - elem.dataset["type"] = options.type || type; - elem.options = options; - elem.value = value; - - if( type == "code" ) - elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); - else if (type == "boolean") - { - elem.classList.add("boolean"); - if(value) - elem.classList.add("bool-on"); - elem.addEventListener("click", function(){ - //var v = node.properties[this.dataset["property"]]; - //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; - var propname = this.dataset["property"]; - this.value = !this.value; - this.classList.toggle("bool-on"); - this.querySelector(".property_value").innerText = this.value ? "true" : "false"; - innerChange(propname, this.value ); - }); - } - else if (type == "string" || type == "number") - { - value_element.setAttribute("contenteditable",true); - value_element.addEventListener("keydown", function(e){ - if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline - { - e.preventDefault(); - this.blur(); - } - }); - value_element.addEventListener("blur", function(){ - var v = this.innerText; - var propname = this.parentNode.dataset["property"]; - var proptype = this.parentNode.dataset["type"]; - if( proptype == "number") - v = Number(v); - innerChange(propname, v); - }); - } - else if (type == "enum" || type == "combo") { - var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); - value_element.innerText = str_value; - - value_element.addEventListener("click", function(event){ - var values = options.values || []; - var propname = this.parentNode.dataset["property"]; - var elem_that = this; - var menu = new LiteGraph.ContextMenu(values,{ - event: event, - className: "dark", - callback: inner_clicked - }, - ref_window); - function inner_clicked(v, option, event) { - //node.setProperty(propname,v); - //graphcanvas.dirty_canvas = true; - elem_that.innerText = v; - innerChange(propname,v); - return false; - } - }); - } - - root.content.appendChild(elem); - - function innerChange(name, value) - { - //console.log("change",name,value); - //that.dirty_canvas = true; - if(options.callback) - options.callback(name,value,options); - if(callback) - callback(name,value,options); - } - - return elem; - } - - if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); - - return root; - }; - - LGraphCanvas.getPropertyPrintableValue = function(value, values) - { - if(!values) - return String(value); - - if(values.constructor === Array) - { - return String(value); - } - - if(values.constructor === Object) - { - var desc_value = ""; - for(var k in values) - { - if(values[k] != value) - continue; - desc_value = k; - break; - } - return String(value) + " ("+desc_value+")"; - } - } - - LGraphCanvas.prototype.closePanels = function(){ - var panel = document.querySelector("#node-panel"); - if(panel) - panel.close(); - var panel = document.querySelector("#option-panel"); - if(panel) - panel.close(); - } - - LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ - if(this.constructor && this.constructor.name == "HTMLDivElement"){ - // assume coming from the menu event click - if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ - console.warn("Canvas not found"); // need a ref to canvas obj - /*console.debug(event); - console.debug(event.target);*/ - return; - } - var graphcanvas = obEv.event.target.lgraphcanvas; - }else{ - // assume called internally - var graphcanvas = this; - } - graphcanvas.closePanels(); - var ref_window = graphcanvas.getCanvasWindow(); - panel = graphcanvas.createPanel("Options",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.OPTIONPANEL_IS_OPEN = false; - graphcanvas.options_panel = null; - } - }); - graphcanvas.options_panel = panel; - panel.id = "option-panel"; - panel.classList.add("settings"); - - function inner_refresh(){ - - panel.content.innerHTML = ""; //clear - - var fUpdate = function(name, value, options){ - switch(name){ - /*case "Render mode": - // Case "".. - if (options.values && options.key){ - var kV = Object.values(options.values).indexOf(value); - if (kV>=0 && options.values[kV]){ - console.debug("update graph options: "+options.key+": "+kV); - graphcanvas[options.key] = kV; - //console.debug(graphcanvas); - break; - } - } - console.warn("unexpected options"); - console.debug(options); - break;*/ - default: - //console.debug("want to update graph options: "+name+": "+value); - if (options && options.key){ - name = options.key; - } - if (options.values){ - value = Object.values(options.values).indexOf(value); - } - //console.debug("update graph option: "+name+": "+value); - graphcanvas[name] = value; - break; - } - }; - - // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement - - var aProps = LiteGraph.availableCanvasOptions; - aProps.sort(); - for(var pI in aProps){ - var pX = aProps[pI]; - panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); - } - - var aLinks = [ graphcanvas.links_render_mode ]; - panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); - - panel.addSeparator(); - - panel.footer.innerHTML = ""; // clear - - } - inner_refresh(); - - graphcanvas.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showShowNodePanel = function( node ) - { - this.SELECTED_NODE = node; - this.closePanels(); - var ref_window = this.getCanvasWindow(); - var that = this; - var graphcanvas = this; - var panel = this.createPanel(node.title || "",{ - closable: true - ,window: ref_window - ,onOpen: function(){ - graphcanvas.NODEPANEL_IS_OPEN = true; - } - ,onClose: function(){ - graphcanvas.NODEPANEL_IS_OPEN = false; - graphcanvas.node_panel = null; - } - }); - graphcanvas.node_panel = panel; - panel.id = "node-panel"; - panel.node = node; - panel.classList.add("settings"); - - function inner_refresh() - { - panel.content.innerHTML = ""; //clear - panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); - - panel.addHTML("

Properties

"); - - var fUpdate = function(name,value){ - graphcanvas.graph.beforeChange(node); - switch(name){ - case "Title": - node.title = value; - break; - case "Mode": - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); - if (kV>=0 && LiteGraph.NODE_MODES[kV]){ - node.changeMode(kV); - }else{ - console.warn("unexpected mode: "+value); - } - break; - case "Color": - if (LGraphCanvas.node_colors[value]){ - node.color = LGraphCanvas.node_colors[value].color; - node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; - }else{ - console.warn("unexpected color: "+value); - } - break; - default: - node.setProperty(name,value); - break; - } - graphcanvas.graph.afterChange(); - graphcanvas.dirty_canvas = true; - }; - - panel.addWidget( "string", "Title", node.title, {}, fUpdate); - - panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); - - var nodeCol = ""; - if (node.color !== undefined){ - nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); - } - - panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); - - for(var pName in node.properties) - { - var value = node.properties[pName]; - var info = node.getPropertyInfo(pName); - var type = info.type || "string"; - - //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) - continue; - - panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); - } - - panel.addSeparator(); - - if(node.onShowCustomPanelInfo) - node.onShowCustomPanelInfo(panel); - - panel.footer.innerHTML = ""; // clear - panel.addButton("Delete",function(){ - if(node.block_delete) - return; - node.graph.remove(node); - panel.close(); - }).classList.add("delete"); - } - - panel.inner_showCodePad = function( propname ) - { - panel.classList.remove("settings"); - panel.classList.add("centered"); - - - /*if(window.CodeFlask) //disabled for now - { - panel.content.innerHTML = "
"; - var flask = new CodeFlask( "div.code", { language: 'js' }); - flask.updateCode(node.properties[propname]); - flask.onUpdate( function(code) { - node.setProperty(propname, code); - }); - } - else - {*/ - panel.alt_content.innerHTML = ""; - var textarea = panel.alt_content.querySelector("textarea"); - var fDoneWith = function(){ - panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); - panel.toggleFooterVisibility(true); - textarea.parentNode.removeChild(textarea); - panel.classList.add("settings"); - panel.classList.remove("centered"); - inner_refresh(); - } - textarea.value = node.properties[propname]; - textarea.addEventListener("keydown", function(e){ - if(e.code == "Enter" && e.ctrlKey ) - { - node.setProperty(propname, textarea.value); - fDoneWith(); - } - }); - panel.toggleAltContent(true); - panel.toggleFooterVisibility(false); - textarea.style.height = "calc(100% - 40px)"; - /*}*/ - var assign = panel.addButton( "Assign", function(){ - node.setProperty(propname, textarea.value); - fDoneWith(); - }); - panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); - var button = panel.addButton( "Close", fDoneWith); - button.style.float = "right"; - panel.alt_content.appendChild(button); // panel.content.appendChild(button); - } - - inner_refresh(); - - this.canvas.parentNode.appendChild( panel ); - } - - LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) - { - console.log("showing subgraph properties dialog"); - - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if(old_panel) - old_panel.close(); - - var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() - { - panel.clear(); - - //show currents - if(node.inputs) - for(var i = 0; i < node.inputs.length; ++i) - { - var input = node.inputs[i]; - if(input.not_subgraph_input) - continue; - var html = " "; - var elem = panel.addHTML(html,"subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click",function(e){ - node.removeInput( Number( this.parentNode.dataset["slot"] ) ); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html,"subgraph_property extra", true); - elem.querySelector("button").addEventListener("click", function(e){ - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if(!name || node.findInputSlot(name) != -1) - return; - node.addInput(name,type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - }); - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { - - // console.log("showing subgraph properties dialog"); - var that = this; - // old_panel if old_panel is exist close it - var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); - if (old_panel) - old_panel.close(); - // new panel - var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); - panel.node = node; - panel.classList.add("subgraph_dialog"); - - function inner_refresh() { - panel.clear(); - //show currents - if (node.outputs) - for (var i = 0; i < node.outputs.length; ++i) { - var input = node.outputs[i]; - if (input.not_subgraph_output) - continue; - var html = " "; - var elem = panel.addHTML(html, "subgraph_property"); - elem.dataset["name"] = input.name; - elem.dataset["slot"] = i; - elem.querySelector(".name").innerText = input.name; - elem.querySelector(".type").innerText = input.type; - elem.querySelector("button").addEventListener("click", function (e) { - node.removeOutput(Number(this.parentNode.dataset["slot"])); - inner_refresh(); - }); - } - } - - //add extra - var html = " + NameType"; - var elem = panel.addHTML(html, "subgraph_property extra", true); - elem.querySelector(".name").addEventListener("keydown", function (e) { - if (e.keyCode == 13) { - addOutput.apply(this) - } - }) - elem.querySelector("button").addEventListener("click", function (e) { - addOutput.apply(this) - }); - function addOutput() { - var elem = this.parentNode; - var name = elem.querySelector(".name").value; - var type = elem.querySelector(".type").value; - if (!name || node.findOutputSlot(name) != -1) - return; - node.addOutput(name, type); - elem.querySelector(".name").value = ""; - elem.querySelector(".type").value = ""; - inner_refresh(); - } - - inner_refresh(); - this.canvas.parentNode.appendChild(panel); - return panel; - } - LGraphCanvas.prototype.checkPanels = function() - { - if(!this.canvas) - return; - var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); - for(var i = 0; i < panels.length; ++i) - { - var panel = panels[i]; - if( !panel.node ) - continue; - if( !panel.node.graph || panel.graph != this.graph ) - panel.close(); - } - } - - LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.graph.beforeChange(/*?*/); - - var fApplyMultiNode = function(node){ - node.collapse(); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); - }; - - LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { - node.pin(); - }; - - LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { - new LiteGraph.ContextMenu( - LiteGraph.NODE_MODES, - { event: e, callback: inner_clicked, parentMenu: menu, node: node } - ); - - function inner_clicked(v) { - if (!node) { - return; - } - var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); - var fApplyMultiNode = function(node){ - if (kV>=0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); - else{ - console.warn("unexpected mode: "+v); - node.changeMode(LiteGraph.ALWAYS); - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - } - - return false; - }; - - LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { - if (!node) { - throw "no node for color"; - } - - var values = []; - values.push({ - value: null, - content: - "No color" - }); - - for (var i in LGraphCanvas.node_colors) { - var color = LGraphCanvas.node_colors[i]; - var value = { - value: i, - content: - "" + - i + - "" - }; - values.push(value); - } - new LiteGraph.ContextMenu(values, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - - var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - - var fApplyColor = function(node){ - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyColor(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyColor(graphcanvas.selected_nodes[i]); - } - } - node.setDirtyCanvas(true, true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { - event: e, - callback: inner_clicked, - parentMenu: menu, - node: node - }); - - function inner_clicked(v) { - if (!node) { - return; - } - node.graph.beforeChange(/*?*/); //node - - var fApplyMultiNode = function(node){ - node.shape = v; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - node.graph.afterChange(/*?*/); //node - node.setDirtyCanvas(true); - } - - return false; - }; - - LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { - if (!node) { - throw "no node passed"; - } - - var graph = node.graph; - graph.beforeChange(); - - - var fApplyMultiNode = function(node){ - if (node.removable === false) { - return; - } - graph.remove(node); - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - graph.afterChange(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { - var graph = node.graph; - var graphcanvas = LGraphCanvas.active_canvas; - if(!graphcanvas) //?? - return; - - var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); - if( !nodes_list.length ) - nodes_list = [ node ]; - - var subgraph_node = LiteGraph.createNode("graph/subgraph"); - subgraph_node.pos = node.pos.concat(); - graph.add(subgraph_node); - - subgraph_node.buildFromNodes( nodes_list ); - - graphcanvas.deselectAllNodes(); - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - - node.graph.beforeChange(); - - var newSelected = {}; - - var fApplyMultiNode = function(node){ - if (node.clonable === false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - node.graph.add(newnode); - newSelected[newnode.id] = newnode; - } - - var graphcanvas = LGraphCanvas.active_canvas; - if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ - fApplyMultiNode(node); - }else{ - for (var i in graphcanvas.selected_nodes) { - fApplyMultiNode(graphcanvas.selected_nodes[i]); - } - } - - if(Object.keys(newSelected).length){ - graphcanvas.selectNodes(newSelected); - } - - node.graph.afterChange(); - - node.setDirtyCanvas(true, true); - }; - - LGraphCanvas.node_colors = { - red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, - brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, - green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, - blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, - cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, - purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, - yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, - black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } - }; - - LGraphCanvas.prototype.getCanvasMenuOptions = function() { - var options = null; - var that = this; - if (this.getMenuOptions) { - options = this.getMenuOptions(); - } else { - options = [ - { - content: "Add Node", - has_submenu: true, - callback: LGraphCanvas.onMenuAdd - }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - //{ content: "Arrange", callback: that.graph.arrange }, - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } - ]; - /*if (LiteGraph.showCanvasOptions){ - options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); - }*/ - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align", - has_submenu: true, - callback: LGraphCanvas.onGroupAlign, - }) - } - - if (this._graph_stack && this._graph_stack.length > 0) { - options.push(null, { - content: "Close subgraph", - callback: this.closeSubgraph.bind(this) - }); - } - } - - if (this.getExtraMenuOptions) { - var extra = this.getExtraMenuOptions(this, options); - if (extra) { - options = options.concat(extra); - } - } - - return options; - }; - - //called by processContextMenu to extract the menu list - LGraphCanvas.prototype.getNodeMenuOptions = function(node) { - var options = null; - - if (node.getMenuOptions) { - options = node.getMenuOptions(this); - } else { - options = [ - { - content: "Inputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalInputs - }, - { - content: "Outputs", - has_submenu: true, - disabled: true, - callback: LGraphCanvas.showMenuNodeOptionalOutputs - }, - null, - { - content: "Properties", - has_submenu: true, - callback: LGraphCanvas.onShowMenuNodeProperties - }, - null, - { - content: "Title", - callback: LGraphCanvas.onShowPropertyEditor - }, - { - content: "Mode", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeMode - }]; - if(node.resizable !== false){ - options.push({ - content: "Resize", callback: LGraphCanvas.onMenuResizeNode - }); - } - options.push( - { - content: "Collapse", - callback: LGraphCanvas.onMenuNodeCollapse - }, - { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, - { - content: "Colors", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Shapes", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeShapes - }, - null - ); - } - - if (node.onGetInputs) { - var inputs = node.onGetInputs(); - if (inputs && inputs.length) { - options[0].disabled = false; - } - } - - if (node.onGetOutputs) { - var outputs = node.onGetOutputs(); - if (outputs && outputs.length) { - options[1].disabled = false; - } - } - - if (node.getExtraMenuOptions) { - var extra = node.getExtraMenuOptions(this, options); - if (extra) { - extra.push(null); - options = extra.concat(options); - } - } - - if (node.clonable !== false) { - options.push({ - content: "Clone", - callback: LGraphCanvas.onMenuNodeClone - }); - } - - if(0) //TODO - options.push({ - content: "To Subgraph", - callback: LGraphCanvas.onMenuNodeToSubgraph - }); - - if (Object.keys(this.selected_nodes).length > 1) { - options.push({ - content: "Align Selected To", - has_submenu: true, - callback: LGraphCanvas.onNodeAlign, - }) - } - - options.push(null, { - content: "Remove", - disabled: !(node.removable !== false && !node.block_delete ), - callback: LGraphCanvas.onMenuNodeRemove - }); - - if (node.graph && node.graph.onGetNodeMenuOptions) { - node.graph.onGetNodeMenuOptions(options, node); - } - - return options; - }; - - LGraphCanvas.prototype.getGroupMenuOptions = function(node) { - var o = [ - { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, - { - content: "Color", - has_submenu: true, - callback: LGraphCanvas.onMenuNodeColors - }, - { - content: "Font size", - property: "font_size", - type: "Number", - callback: LGraphCanvas.onShowPropertyEditor - }, - null, - { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } - ]; - - return o; - }; - - LGraphCanvas.prototype.processContextMenu = function(node, event) { - var that = this; - var canvas = LGraphCanvas.active_canvas; - var ref_window = canvas.getCanvasWindow(); - - var menu_info = null; - var options = { - event: event, - callback: inner_option_clicked, - extra: node - }; - - if(node) - options.title = node.type; - - //check if mouse is in input - var slot = null; - if (node) { - slot = node.getSlotInPosition(event.canvasX, event.canvasY); - LGraphCanvas.active_node = node; - } - - if (slot) { - //on slot - menu_info = []; - if (node.getSlotMenuOptions) { - menu_info = node.getSlotMenuOptions(slot); - } else { - if ( - slot && - slot.output && - slot.output.links && - slot.output.links.length - ) { - menu_info.push({ content: "Disconnect Links", slot: slot }); - } - var _slot = slot.input || slot.output; - if (_slot.removable){ - menu_info.push( - _slot.locked - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - } - if (!_slot.nameLocked){ - menu_info.push({ content: "Rename Slot", slot: slot }); - } - - } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { - options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { - options.title = "Event"; - } - } else { - if (node) { - //on node - menu_info = this.getNodeMenuOptions(node); - } else { - menu_info = this.getCanvasMenuOptions(); - var group = this.graph.getGroupOnPos( - event.canvasX, - event.canvasY - ); - if (group) { - //on group - menu_info.push(null, { - content: "Edit Group", - has_submenu: true, - submenu: { - title: "Group", - extra: group, - options: this.getGroupMenuOptions(group) - } - }); - } - } - } - - //show menu - if (!menu_info) { - return; - } - - var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); - - function inner_option_clicked(v, options, e) { - if (!v) { - return; - } - - if (v.content == "Remove Slot") { - var info = v.slot; - node.graph.beforeChange(); - if (info.input) { - node.removeInput(info.slot); - } else if (info.output) { - node.removeOutput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Disconnect Links") { - var info = v.slot; - node.graph.beforeChange(); - if (info.output) { - node.disconnectOutput(info.slot); - } else if (info.input) { - node.disconnectInput(info.slot); - } - node.graph.afterChange(); - return; - } else if (v.content == "Rename Slot") { - var info = v.slot; - var slot_info = info.input - ? node.getInputInfo(info.slot) - : node.getOutputInfo(info.slot); - var dialog = that.createDialog( - "Name", - options - ); - var input = dialog.querySelector("input"); - if (input && slot_info) { - input.value = slot_info.label || ""; - } - var inner = function(){ - node.graph.beforeChange(); - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); - } - dialog.close(); - node.graph.afterChange(); - } - dialog.querySelector("button").addEventListener("click", inner); - input.addEventListener("keydown", function(e) { - dialog.is_modified = true; - if (e.keyCode == 27) { - //ESC - dialog.close(); - } else if (e.keyCode == 13) { - inner(); // save - } else if (e.keyCode != 13 && e.target.localName != "textarea") { - return; - } - e.preventDefault(); - e.stopPropagation(); - }); - input.focus(); - } - - //if(v.callback) - // return v.callback.call(that, node, options, e, menu, that, event ); - } - }; - - //API ************************************************* - function compareObjects(a, b) { - for (var i in a) { - if (a[i] != b[i]) { - return false; - } - } - return true; - } - LiteGraph.compareObjects = compareObjects; - - function distance(a, b) { - return Math.sqrt( - (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) - ); - } - LiteGraph.distance = distance; - - function colorToString(c) { - return ( - "rgba(" + - Math.round(c[0] * 255).toFixed() + - "," + - Math.round(c[1] * 255).toFixed() + - "," + - Math.round(c[2] * 255).toFixed() + - "," + - (c.length == 4 ? c[3].toFixed(2) : "1.0") + - ")" - ); - } - LiteGraph.colorToString = colorToString; - - function isInsideRectangle(x, y, left, top, width, height) { - if (left < x && left + width > x && top < y && top + height > y) { - return true; - } - return false; - } - LiteGraph.isInsideRectangle = isInsideRectangle; - - //[minx,miny,maxx,maxy] - function growBounding(bounding, x, y) { - if (x < bounding[0]) { - bounding[0] = x; - } else if (x > bounding[2]) { - bounding[2] = x; - } - - if (y < bounding[1]) { - bounding[1] = y; - } else if (y > bounding[3]) { - bounding[3] = y; - } - } - LiteGraph.growBounding = growBounding; - - //point inside bounding box - function isInsideBounding(p, bb) { - if ( - p[0] < bb[0][0] || - p[1] < bb[0][1] || - p[0] > bb[1][0] || - p[1] > bb[1][1] - ) { - return false; - } - return true; - } - LiteGraph.isInsideBounding = isInsideBounding; - - //bounding overlap, format: [ startx, starty, width, height ] - function overlapBounding(a, b) { - var A_end_x = a[0] + a[2]; - var A_end_y = a[1] + a[3]; - var B_end_x = b[0] + b[2]; - var B_end_y = b[1] + b[3]; - - if ( - a[0] > B_end_x || - a[1] > B_end_y || - A_end_x < b[0] || - A_end_y < b[1] - ) { - return false; - } - return true; - } - LiteGraph.overlapBounding = overlapBounding; - - //Convert a hex value to its decimal value - the inputted hex must be in the - // format of a hex triplet - the kind we use for HTML colours. The function - // will return an array with three values. - function hex2num(hex) { - if (hex.charAt(0) == "#") { - hex = hex.slice(1); - } //Remove the '#' char - if there is one. - hex = hex.toUpperCase(); - var hex_alphabets = "0123456789ABCDEF"; - var value = new Array(3); - var k = 0; - var int1, int2; - for (var i = 0; i < 6; i += 2) { - int1 = hex_alphabets.indexOf(hex.charAt(i)); - int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); - value[k] = int1 * 16 + int2; - k++; - } - return value; - } - - LiteGraph.hex2num = hex2num; - - //Give a array with three values as the argument and the function will return - // the corresponding hex triplet. - function num2hex(triplet) { - var hex_alphabets = "0123456789ABCDEF"; - var hex = "#"; - var int1, int2; - for (var i = 0; i < 3; i++) { - int1 = triplet[i] / 16; - int2 = triplet[i] % 16; - - hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); - } - return hex; - } - - LiteGraph.num2hex = num2hex; - - /* LiteGraph GUI elements used for canvas editing *************************************/ - - /** - * ContextMenu from LiteGUI - * - * @class ContextMenu - * @constructor - * @param {Array} values (allows object { title: "Nice text", callback: function ... }) - * @param {Object} options [optional] Some options:\ - * - title: title to show on top of the menu - * - callback: function to call when an option is clicked, it receives the item information - * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback - * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position - */ - function ContextMenu(values, options) { - options = options || {}; - this.options = options; - var that = this; - - //to link a menu with its parent - if (options.parentMenu) { - if (options.parentMenu.constructor !== this.constructor) { - console.error( - "parentMenu must be of class ContextMenu, ignoring it" - ); - options.parentMenu = null; - } else { - this.parentMenu = options.parentMenu; - this.parentMenu.lock = true; - this.parentMenu.current_submenu = this; - } - } - - var eventClass = null; - if(options.event) //use strings because comparing classes between windows doesnt work - eventClass = options.event.constructor.name; - if ( eventClass !== "MouseEvent" && - eventClass !== "CustomEvent" && - eventClass !== "PointerEvent" - ) { - console.error( - "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" - ); - options.event = null; - } - - var root = document.createElement("div"); - root.className = "litegraph litecontextmenu litemenubar-panel"; - if (options.className) { - root.className += " " + options.className; - } - root.style.minWidth = 100; - root.style.minHeight = 100; - root.style.pointerEvents = "none"; - setTimeout(function() { - root.style.pointerEvents = "auto"; - }, 100); //delay so the mouse up event is not caught by this element - - //this prevents the default context browser menu to open in case this menu was created when pressing right button - LiteGraph.pointerListenerAdd(root,"up", - function(e) { - //console.log("pointerevents: ContextMenu up root prevent"); - e.preventDefault(); - return true; - }, - true - ); - root.addEventListener( - "contextmenu", - function(e) { - if (e.button != 2) { - //right button - return false; - } - e.preventDefault(); - return false; - }, - true - ); - - LiteGraph.pointerListenerAdd(root,"down", - function(e) { - //console.log("pointerevents: ContextMenu down"); - if (e.button == 2) { - that.close(); - e.preventDefault(); - return true; - } - }, - true - ); - - function on_mouse_wheel(e) { - var pos = parseInt(root.style.top); - root.style.top = - (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; - e.preventDefault(); - return true; - } - - if (!options.scroll_speed) { - options.scroll_speed = 0.1; - } - - root.addEventListener("wheel", on_mouse_wheel, true); - root.addEventListener("mousewheel", on_mouse_wheel, true); - - this.root = root; - - //title - if (options.title) { - var element = document.createElement("div"); - element.className = "litemenu-title"; - element.innerHTML = options.title; - root.appendChild(element); - } - - //entries - var num = 0; - for (var i=0; i < values.length; i++) { - var name = values.constructor == Array ? values[i] : i; - if (name != null && name.constructor !== String) { - name = name.content === undefined ? String(name) : name.content; - } - var value = values[i]; - this.addItem(name, value, options); - num++; - } - - //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that - /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { - console.log("pointerevents: ContextMenu leave"); - if (that.lock) { - return; - } - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - root.closing_timer = setTimeout(that.close.bind(that, e), 500); - //that.close(e); - });*/ - - LiteGraph.pointerListenerAdd(root,"enter", function(e) { - //console.log("pointerevents: ContextMenu enter"); - if (root.closing_timer) { - clearTimeout(root.closing_timer); - } - }); - - //insert before checking position - var root_document = document; - if (options.event) { - root_document = options.event.target.ownerDocument; - } - - if (!root_document) { - root_document = document; - } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(root); - else - root_document.body.appendChild(root); - - //compute best position - var left = options.left || 0; - var top = options.top || 0; - if (options.event) { - left = options.event.clientX - 10; - top = options.event.clientY - 10; - if (options.title) { - top -= 20; - } - - if (options.parentMenu) { - var rect = options.parentMenu.root.getBoundingClientRect(); - left = rect.left + rect.width; - } - - var body_rect = document.body.getBoundingClientRect(); - var root_rect = root.getBoundingClientRect(); - if(body_rect.height == 0) - console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); - - if (body_rect.width && left > body_rect.width - root_rect.width - 10) { - left = body_rect.width - root_rect.width - 10; - } - if (body_rect.height && top > body_rect.height - root_rect.height - 10) { - top = body_rect.height - root_rect.height - 10; - } - } - - root.style.left = left + "px"; - root.style.top = top + "px"; - - if (options.scale) { - root.style.transform = "scale(" + options.scale + ")"; - } - } - - ContextMenu.prototype.addItem = function(name, value, options) { - var that = this; - options = options || {}; - - var element = document.createElement("div"); - element.className = "litemenu-entry submenu"; - - var disabled = false; - - if (value === null) { - element.classList.add("separator"); - //element.innerHTML = "
" - //continue; - } else { - element.innerHTML = value && value.title ? value.title : name; - element.value = value; - - if (value) { - if (value.disabled) { - disabled = true; - element.classList.add("disabled"); - } - if (value.submenu || value.has_submenu) { - element.classList.add("has_submenu"); - } - } - - if (typeof value == "function") { - element.dataset["value"] = name; - element.onclick_callback = value; - } else { - element.dataset["value"] = value; - } - - if (value.className) { - element.className += " " + value.className; - } - } - - this.root.appendChild(element); - if (!disabled) { - element.addEventListener("click", inner_onclick); - } - if (!disabled && options.autoopen) { - LiteGraph.pointerListenerAdd(element,"enter",inner_over); - } - - function inner_over(e) { - var value = this.value; - if (!value || !value.has_submenu) { - return; - } - //if it is a submenu, autoopen like the item was clicked - inner_onclick.call(this, e); - } - - //menu option clicked - function inner_onclick(e) { - var value = this.value; - var close_parent = true; - - if (that.current_submenu) { - that.current_submenu.close(e); - } - - //global callback - if (options.callback) { - var r = options.callback.call( - this, - value, - options, - e, - that, - options.node - ); - if (r === true) { - close_parent = false; - } - } - - //special cases - if (value) { - if ( - value.callback && - !options.ignore_item_callbacks && - value.disabled !== true - ) { - //item callback - var r = value.callback.call( - this, - value, - options, - e, - that, - options.extra - ); - if (r === true) { - close_parent = false; - } - } - if (value.submenu) { - if (!value.submenu.options) { - throw "ContextMenu submenu needs options"; - } - var submenu = new that.constructor(value.submenu.options, { - callback: value.submenu.callback, - event: e, - parentMenu: that, - ignore_item_callbacks: - value.submenu.ignore_item_callbacks, - title: value.submenu.title, - extra: value.submenu.extra, - autoopen: options.autoopen - }); - close_parent = false; - } - } - - if (close_parent && !that.lock) { - that.close(); - } - } - - return element; - }; - - ContextMenu.prototype.close = function(e, ignore_parent_menu) { - if (this.root.parentNode) { - this.root.parentNode.removeChild(this.root); - } - if (this.parentMenu && !ignore_parent_menu) { - this.parentMenu.lock = false; - this.parentMenu.current_submenu = null; - if (e === undefined) { - this.parentMenu.close(); - } else if ( - e && - !ContextMenu.isCursorOverElement(e, this.parentMenu.root) - ) { - ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); - } - } - if (this.current_submenu) { - this.current_submenu.close(e, true); - } - - if (this.root.closing_timer) { - clearTimeout(this.root.closing_timer); - } - - // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu - // on key press, allow filtering/selecting the context menu elements - }; - - //this code is used to trigger events easily (used in the context menu mouseleave - ContextMenu.trigger = function(element, event_name, params, origin) { - var evt = document.createEvent("CustomEvent"); - evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail - evt.srcElement = origin; - if (element.dispatchEvent) { - element.dispatchEvent(evt); - } else if (element.__events) { - element.__events.dispatchEvent(evt); - } - //else nothing seems binded here so nothing to do - return evt; - }; - - //returns the top most menu - ContextMenu.prototype.getTopMenu = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getTopMenu(); - } - return this; - }; - - ContextMenu.prototype.getFirstEvent = function() { - if (this.options.parentMenu) { - return this.options.parentMenu.getFirstEvent(); - } - return this.options.event; - }; - - ContextMenu.isCursorOverElement = function(event, element) { - var left = event.clientX; - var top = event.clientY; - var rect = element.getBoundingClientRect(); - if (!rect) { - return false; - } - if ( - top > rect.top && - top < rect.top + rect.height && - left > rect.left && - left < rect.left + rect.width - ) { - return true; - } - return false; - }; - - LiteGraph.ContextMenu = ContextMenu; - - LiteGraph.closeAllContextMenus = function(ref_window) { - ref_window = ref_window || window; - - var elements = ref_window.document.querySelectorAll(".litecontextmenu"); - if (!elements.length) { - return; - } - - var result = []; - for (var i = 0; i < elements.length; i++) { - result.push(elements[i]); - } - - for (var i=0; i < result.length; i++) { - if (result[i].close) { - result[i].close(); - } else if (result[i].parentNode) { - result[i].parentNode.removeChild(result[i]); - } - } - }; - - LiteGraph.extendClass = function(target, origin) { - for (var i in origin) { - //copy class properties - if (target.hasOwnProperty(i)) { - continue; - } - target[i] = origin[i]; - } - - if (origin.prototype) { - //copy prototype properties - for (var i in origin.prototype) { - //only enumerable - if (!origin.prototype.hasOwnProperty(i)) { - continue; - } - - if (target.prototype.hasOwnProperty(i)) { - //avoid overwriting existing ones - continue; - } - - //copy getters - if (origin.prototype.__lookupGetter__(i)) { - target.prototype.__defineGetter__( - i, - origin.prototype.__lookupGetter__(i) - ); - } else { - target.prototype[i] = origin.prototype[i]; - } - - //and setters - if (origin.prototype.__lookupSetter__(i)) { - target.prototype.__defineSetter__( - i, - origin.prototype.__lookupSetter__(i) - ); - } - } - } - }; - - //used by some widgets to render a curve editor - function CurveEditor( points ) - { - this.points = points; - this.selected = -1; - this.nearest = -1; - this.size = null; //stores last size used - this.must_update = true; - this.margin = 5; - } - - CurveEditor.sampleCurve = function(f,points) - { - if(!points) - return; - for(var i = 0; i < points.length - 1; ++i) - { - var p = points[i]; - var pn = points[i+1]; - if(pn[0] < f) - continue; - var r = (pn[0] - p[0]); - if( Math.abs(r) < 0.00001 ) - return p[1]; - var local_f = (f - p[0]) / r; - return p[1] * (1.0 - local_f) + pn[1] * local_f; - } - return 0; - } - - CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) - { - var points = this.points; - if(!points) - return; - this.size = size; - var w = size[0] - this.margin * 2; - var h = size[1] - this.margin * 2; - - line_color = line_color || "#666"; - - ctx.save(); - ctx.translate(this.margin,this.margin); - - if(background_color) - { - ctx.fillStyle = "#111"; - ctx.fillRect(0,0,w,h); - ctx.fillStyle = "#222"; - ctx.fillRect(w*0.5,0,1,h); - ctx.strokeStyle = "#333"; - ctx.strokeRect(0,0,w,h); - } - ctx.strokeStyle = line_color; - if(inactive) - ctx.globalAlpha = 0.5; - ctx.beginPath(); - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); - } - ctx.stroke(); - ctx.globalAlpha = 1; - if(!inactive) - for(var i = 0; i < points.length; ++i) - { - var p = points[i]; - ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); - ctx.beginPath(); - ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); - ctx.fill(); - } - ctx.restore(); - } - - //localpos is mouse in curve editor space - CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - if( localpos[1] < 0 ) - return; - - //this.captureInput(true); - var w = this.size[0] - this.margin * 2; - var h = this.size[1] - this.margin * 2; - var x = localpos[0] - this.margin; - var y = localpos[1] - this.margin; - var pos = [x,y]; - var max_dist = 30 / graphcanvas.ds.scale; - //search closer one - this.selected = this.getCloserPoint(pos, max_dist); - //create one - if(this.selected == -1) - { - var point = [x / w, 1 - y / h]; - points.push(point); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - if(this.selected != -1) - return true; - } - - CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) - { - var points = this.points; - if(!points) - return; - var s = this.selected; - if(s < 0) - return; - var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); - var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); - var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; - var max_dist = 30 / graphcanvas.ds.scale; - this._nearest = this.getCloserPoint(curvepos, max_dist); - var point = points[s]; - if(point) - { - var is_edge_point = s == 0 || s == points.length - 1; - if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) - { - points.splice(s,1); - this.selected = -1; - return; - } - if( !is_edge_point ) //not edges - point[0] = clamp(x, 0, 1); - else - point[0] = s == 0 ? 0 : 1; - point[1] = 1.0 - clamp(y, 0, 1); - points.sort(function(a,b){ return a[0] - b[0]; }); - this.selected = points.indexOf(point); - this.must_update = true; - } - } - - CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) - { - this.selected = -1; - return false; - } - - CurveEditor.prototype.getCloserPoint = function(pos, max_dist) - { - var points = this.points; - if(!points) - return -1; - max_dist = max_dist || 30; - var w = (this.size[0] - this.margin * 2); - var h = (this.size[1] - this.margin * 2); - var num = points.length; - var p2 = [0,0]; - var min_dist = 1000000; - var closest = -1; - var last_valid = -1; - for(var i = 0; i < num; ++i) - { - var p = points[i]; - p2[0] = p[0] * w; - p2[1] = (1.0 - p[1]) * h; - if(p2[0] < pos[0]) - last_valid = i; - var dist = vec2.distance(pos,p2); - if(dist > min_dist || dist > max_dist) - continue; - closest = i; - min_dist = dist; - } - return closest; - } - - LiteGraph.CurveEditor = CurveEditor; - - //used to create nodes from wrapping functions - LiteGraph.getParameterNames = function(func) { - return (func + "") - .replace(/[/][/].*$/gm, "") // strip single-line comments - .replace(/\s+/g, "") // strip white space - .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ - .split("){", 1)[0] - .replace(/^[^(]*[(]/, "") // extract the parameters - .replace(/=[^,]+/g, "") // strip any ES6 defaults - .split(",") - .filter(Boolean); // split & filter [""] - }; - - /* helper for interaction: pointer, touch, mouse Listeners - used by LGraphCanvas DragAndScale ContextMenu*/ - LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ - //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - - var sMethod = LiteGraph.pointerevents_method; - var sEvent = sEvIn; - - // UNDER CONSTRUCTION - // convert pointerevents to touch event when not available - if (sMethod=="pointer" && !window.PointerEvent){ - console.warn("sMethod=='pointer' && !window.PointerEvent"); - console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); - switch(sEvent){ - case "down":{ - sMethod = "touch"; - sEvent = "start"; - break; - } - case "move":{ - sMethod = "touch"; - //sEvent = "move"; - break; - } - case "up":{ - sMethod = "touch"; - sEvent = "end"; - break; - } - case "cancel":{ - sMethod = "touch"; - //sEvent = "cancel"; - break; - } - case "enter":{ - console.log("debug: Should I send a move event?"); // ??? - break; - } - // case "over": case "out": not used at now - default:{ - console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); - } - } - } - - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (sMethod!="mouse"){ - return oDOM.addEventListener(sMethod+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.addEventListener(sEvent, fCall, capture); - } - } - LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ - //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); - return; // -- break -- - } - switch(sEvent){ - //both pointer and move events - case "down": case "up": case "move": case "over": case "out": case "enter": - { - if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ - oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // only pointerevents - case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": - { - if (LiteGraph.pointerevents_method=="pointer"){ - return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); - } - } - // not "pointer" || "mouse" - default: - return oDOM.removeEventListener(sEvent, fCall, capture); - } - } - - function clamp(v, a, b) { - return a > v ? a : b < v ? b : v; - }; - global.clamp = clamp; - - if (typeof window != "undefined" && !window["requestAnimationFrame"]) { - window.requestAnimationFrame = - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - function(callback) { - window.setTimeout(callback, 1000 / 60); - }; - } -})(this); - -if (typeof exports != "undefined") { - exports.LiteGraph = this.LiteGraph; - exports.LGraph = this.LGraph; - exports.LLink = this.LLink; - exports.LGraphNode = this.LGraphNode; - exports.LGraphGroup = this.LGraphGroup; - exports.DragAndScale = this.DragAndScale; - exports.LGraphCanvas = this.LGraphCanvas; - exports.ContextMenu = this.ContextMenu; -} - - -//basic nodes + (function(global) { - var LiteGraph = global.LiteGraph; + // ************************************************************* + // LiteGraph CLASS ******* + // ************************************************************* + + /** + * The Global Scope. It contains all the registered node classes. + * + * @class LiteGraph + * @constructor + */ + + var LiteGraph = (global.LiteGraph = { + VERSION: 0.4, + + CANVAS_GRID_SIZE: 10, + + NODE_TITLE_HEIGHT: 30, + NODE_TITLE_TEXT_Y: 20, + NODE_SLOT_HEIGHT: 20, + NODE_WIDGET_HEIGHT: 20, + NODE_WIDTH: 140, + NODE_MIN_WIDTH: 50, + NODE_COLLAPSED_RADIUS: 10, + NODE_COLLAPSED_WIDTH: 80, + NODE_TITLE_COLOR: "#999", + NODE_SELECTED_TITLE_COLOR: "#FFF", + NODE_TEXT_SIZE: 14, + NODE_TEXT_COLOR: "#AAA", + NODE_SUBTEXT_SIZE: 12, + NODE_DEFAULT_COLOR: "#333", + NODE_DEFAULT_BGCOLOR: "#353535", + NODE_DEFAULT_BOXCOLOR: "#666", + NODE_DEFAULT_SHAPE: "box", + NODE_BOX_OUTLINE_COLOR: "#FFF", + DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)", + DEFAULT_GROUP_FONT: 24, + + WIDGET_BGCOLOR: "#222", + WIDGET_OUTLINE_COLOR: "#666", + WIDGET_TEXT_COLOR: "#DDD", + WIDGET_SECONDARY_TEXT_COLOR: "#999", + + LINK_COLOR: "#9A9", + EVENT_LINK_COLOR: "#A86", + CONNECTING_LINK_COLOR: "#AFA", + + MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops + DEFAULT_POSITION: [100, 100], //default node position + VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" + + //shapes are used for nodes but also for slots + BOX_SHAPE: 1, + ROUND_SHAPE: 2, + CIRCLE_SHAPE: 3, + CARD_SHAPE: 4, + ARROW_SHAPE: 5, + GRID_SHAPE: 6, // intended for slot arrays + + //enums + INPUT: 1, + OUTPUT: 2, + + EVENT: -1, //for outputs + ACTION: -1, //for inputs + + NODE_MODES: ["Always", "On Event", "Never", "On Trigger"], // helper, will add "On Request" and more in the future + NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode + ALWAYS: 0, + ON_EVENT: 1, + NEVER: 2, + ON_TRIGGER: 3, + + UP: 1, + DOWN: 2, + LEFT: 3, + RIGHT: 4, + CENTER: 5, + + LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper + STRAIGHT_LINK: 0, + LINEAR_LINK: 1, + SPLINE_LINK: 2, + + NORMAL_TITLE: 0, + NO_TITLE: 1, + TRANSPARENT_TITLE: 2, + AUTOHIDE_TITLE: 3, + VERTICAL_LAYOUT: "vertical", // arrange nodes vertically + + proxy: null, //used to redirect calls + node_images_path: "", + + debug: false, + catch_exceptions: true, + throw_errors: true, + allow_scripts: false, //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration), which could lead to exploits + use_deferred_actions: true, //executes actions during the graph execution flow + registered_node_types: {}, //nodetypes by string + node_types_by_file_extension: {}, //used for dropping files in the canvas + Nodes: {}, //node types by classname + Globals: {}, //used to store vars between graphs + + searchbox_extras: {}, //used to add extra features to the search box + auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus + + node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback + node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback + + dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + dialog_close_on_mouse_leave_delay: 500, + + shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys + click_do_break_link_to: false, // [false!]prefer false, way too easy to break links + + search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] + search_show_all_on_open: true, // [true!] opens the results list when opening the search widget + + auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] + + // set these values if not using auto_load_slot_types + registered_slot_in_types: {}, // slot types for nodeclass + registered_slot_out_types: {}, // slot types for nodeclass + slot_types_in: [], // slot types IN + slot_types_out: [], // slot types OUT + slot_types_default_in: [], // specify for each IN slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + slot_types_default_out: [], // specify for each OUT slot type a(/many) default node(s), use single string, array, or object (with node, title, parameters, ..) like for search + + alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node - //Constant - function Time() { - this.addOutput("in ms", "number"); - this.addOutput("in sec", "number"); - } + do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this + + allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentially, one by one - Time.title = "Time"; - Time.desc = "Time"; + middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel) + + release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults + + pointerevents_method: "mouse", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) + // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - Time.prototype.onExecute = function() { - this.setOutputData(0, this.graph.globaltime * 1000); - this.setOutputData(1, this.graph.globaltime); - }; + ctrl_shift_v_paste_connect_unselected_outputs: false, //[true!] allows ctrl + shift + v to paste nodes with the outputs of the unselected nodes connected with the inputs of the newly pasted nodes - LiteGraph.registerNodeType("basic/time", Time); + // if true, all newly created nodes/links will use string UUIDs for their id fields instead of integers. + // use this if you must have node IDs that are unique across all graphs and subgraphs. + use_uuids: false, - //Subgraph: a node that contains a graph - function Subgraph() { - var that = this; - this.size = [140, 80]; - this.properties = { enabled: true }; - this.enabled = true; + /** + * Register a node class so it can be listed when the user wants to create a new one + * @method registerNodeType + * @param {String} type name of the node and path + * @param {Class} base_class class containing the structure of a node + */ - //create inner graph - this.subgraph = new LiteGraph.LGraph(); - this.subgraph._subgraph_node = this; - this.subgraph._is_subgraph = true; + registerNodeType: function(type, base_class) { + if (!base_class.prototype) { + throw "Cannot register a simple object, it must be a class with a prototype"; + } + base_class.type = type; - this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); + if (LiteGraph.debug) { + console.log("Node registered: " + type); + } - //nodes input node added inside - this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); - this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); - this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); - this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); + const classname = base_class.name; - this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); - this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); - this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); - this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); - } + const pos = type.lastIndexOf("/"); + base_class.category = type.substring(0, pos); - Subgraph.title = "Subgraph"; - Subgraph.desc = "Graph inside a node"; - Subgraph.title_color = "#334"; + if (!base_class.title) { + base_class.title = classname; + } - Subgraph.prototype.onGetInputs = function() { - return [["enabled", "boolean"]]; - }; + //extend class + for (var i in LGraphNode.prototype) { + if (!base_class.prototype[i]) { + base_class.prototype[i] = LGraphNode.prototype[i]; + } + } - /* - Subgraph.prototype.onDrawTitle = function(ctx) { - if (this.flags.collapsed) { - return; - } + const prev = this.registered_node_types[type]; + if(prev) { + console.log("replacing node type: " + type); + } + if( !Object.prototype.hasOwnProperty.call( base_class.prototype, "shape") ) { + Object.defineProperty(base_class.prototype, "shape", { + set: function(v) { + switch (v) { + case "default": + delete this._shape; + break; + case "box": + this._shape = LiteGraph.BOX_SHAPE; + break; + case "round": + this._shape = LiteGraph.ROUND_SHAPE; + break; + case "circle": + this._shape = LiteGraph.CIRCLE_SHAPE; + break; + case "card": + this._shape = LiteGraph.CARD_SHAPE; + break; + default: + this._shape = v; + } + }, + get: function() { + return this._shape; + }, + enumerable: true, + configurable: true + }); + + + //used to know which nodes to create when dragging files to the canvas + if (base_class.supported_extensions) { + for (let i in base_class.supported_extensions) { + const ext = base_class.supported_extensions[i]; + if(ext && ext.constructor === String) { + this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; + } + } + } + } - ctx.fillStyle = "#555"; - var w = LiteGraph.NODE_TITLE_HEIGHT; - var x = this.size[0] - w; - ctx.fillRect(x, -w, w, w); - ctx.fillStyle = "#333"; - ctx.beginPath(); - ctx.moveTo(x + w * 0.2, -w * 0.6); - ctx.lineTo(x + w * 0.8, -w * 0.6); - ctx.lineTo(x + w * 0.5, -w * 0.3); - ctx.fill(); - }; - */ + this.registered_node_types[type] = base_class; + if (base_class.constructor.name) { + this.Nodes[classname] = base_class; + } + if (LiteGraph.onNodeTypeRegistered) { + LiteGraph.onNodeTypeRegistered(type, base_class); + } + if (prev && LiteGraph.onNodeTypeReplaced) { + LiteGraph.onNodeTypeReplaced(type, base_class, prev); + } - Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - }; + //warnings + if (base_class.prototype.onPropertyChange) { + console.warn( + "LiteGraph node class " + + type + + " has onPropertyChange method, it must be called onPropertyChanged with d at the end" + ); + } + + // TODO one would want to know input and ouput :: this would allow through registerNodeAndSlotType to get all the slots types + if (this.auto_load_slot_types) { + new base_class(base_class.title || "tmpnode"); + } + }, + + /** + * removes a node type from the system + * @method unregisterNodeType + * @param {String|Object} type name of the node or the node constructor itself + */ + unregisterNodeType: function(type) { + const base_class = + type.constructor === String + ? this.registered_node_types[type] + : type; + if (!base_class) { + throw "node type not found: " + type; + } + delete this.registered_node_types[base_class.type]; + if (base_class.constructor.name) { + delete this.Nodes[base_class.constructor.name]; + } + }, + + /** + * Save a slot type and his node + * @method registerSlotType + * @param {String|Object} type name of the node or the node constructor itself + * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. + */ + registerNodeAndSlotType: function(type, slot_type, out){ + out = out || false; + const base_class = + type.constructor === String && + this.registered_node_types[type] !== "anonymous" + ? this.registered_node_types[type] + : type; + + const class_type = base_class.constructor.type; + + let allTypes = []; + if (typeof slot_type === "string") { + allTypes = slot_type.split(","); + } else if (slot_type == this.EVENT || slot_type == this.ACTION) { + allTypes = ["_event_"]; + } else { + allTypes = ["*"]; + } - /* - Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { - if ( - !this.flags.collapsed && - pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT && - pos[1] < 0 + for (let i = 0; i < allTypes.length; ++i) { + let slotType = allTypes[i]; + if (slotType === "") { + slotType = "*"; + } + const registerTo = out + ? "registered_slot_out_types" + : "registered_slot_in_types"; + if (this[registerTo][slotType] === undefined) { + this[registerTo][slotType] = { nodes: [] }; + } + if (!this[registerTo][slotType].nodes.includes(class_type)) { + this[registerTo][slotType].nodes.push(class_type); + } + + // check if is a new type + if (!out) { + if (!this.slot_types_in.includes(slotType.toLowerCase())) { + this.slot_types_in.push(slotType.toLowerCase()); + this.slot_types_in.sort(); + } + } else { + if (!this.slot_types_out.includes(slotType.toLowerCase())) { + this.slot_types_out.push(slotType.toLowerCase()); + this.slot_types_out.sort(); + } + } + } + }, + + /** + * Create a new nodetype by passing an object with some properties + * like onCreate, inputs:Array, outputs:Array, properties, onExecute + * @method buildNodeClassFromObject + * @param {String} name node name with namespace (p.e.: 'math/sum') + * @param {Object} object methods expected onCreate, inputs, outputs, properties, onExecute + */ + buildNodeClassFromObject: function( + name, + object ) { - var that = this; - setTimeout(function() { - graphcanvas.openSubgraph(that.subgraph); - }, 10); - } - }; - */ + var ctor_code = ""; + if(object.inputs) + for(var i=0; i < object.inputs.length; ++i) + { + var _name = object.inputs[i][0]; + var _type = object.inputs[i][1]; + if(_type && _type.constructor === String) + _type = '"'+_type+'"'; + ctor_code += "this.addInput('"+_name+"',"+_type+");\n"; + } + if(object.outputs) + for(var i=0; i < object.outputs.length; ++i) + { + var _name = object.outputs[i][0]; + var _type = object.outputs[i][1]; + if(_type && _type.constructor === String) + _type = '"'+_type+'"'; + ctor_code += "this.addOutput('"+_name+"',"+_type+");\n"; + } + if(object.properties) + for(var i in object.properties) + { + var prop = object.properties[i]; + if(prop && prop.constructor === String) + prop = '"'+prop+'"'; + ctor_code += "this.addProperty('"+i+"',"+prop+");\n"; + } + ctor_code += "if(this.onCreate)this.onCreate()"; + var classobj = Function(ctor_code); + for(var i in object) + if(i!="inputs" && i!="outputs" && i!="properties") + classobj.prototype[i] = object[i]; + classobj.title = object.title || name.split("/").pop(); + classobj.desc = object.desc || "Generated from object"; + this.registerNodeType(name, classobj); + return classobj; + }, + + /** + * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. + * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. + * @method wrapFunctionAsNode + * @param {String} name node name with namespace (p.e.: 'math/sum') + * @param {Function} func + * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type + * @param {String} return_type [optional] string with the return type, otherwise it will be generic + * @param {Object} properties [optional] properties to be configurable + */ + wrapFunctionAsNode: function( + name, + func, + param_types, + return_type, + properties + ) { + var params = Array(func.length); + var code = ""; + if(param_types !== null) //null means no inputs + { + var names = LiteGraph.getParameterNames(func); + for (var i = 0; i < names.length; ++i) { + var type = 0; + if(param_types) + { + //type = param_types[i] != null ? "'" + param_types[i] + "'" : "0"; + if( param_types[i] != null && param_types[i].constructor === String ) + type = "'" + param_types[i] + "'" ; + else if( param_types[i] != null ) + type = param_types[i]; + } + code += + "this.addInput('" + + names[i] + + "'," + + type + + ");\n"; + } + } + if(return_type !== null) //null means no output + code += + "this.addOutput('out'," + + (return_type != null ? (return_type.constructor === String ? "'" + return_type + "'" : return_type) : 0) + + ");\n"; + if (properties) { + code += + "this.properties = " + JSON.stringify(properties) + ";\n"; + } + var classobj = Function(code); + classobj.title = name.split("/").pop(); + classobj.desc = "Generated from " + func.name; + classobj.prototype.onExecute = function onExecute() { + for (var i = 0; i < params.length; ++i) { + params[i] = this.getInputData(i); + } + var r = func.apply(this, params); + this.setOutputData(0, r); + }; + this.registerNodeType(name, classobj); + return classobj; + }, + + /** + * Removes all previously registered node's types + */ + clearRegisteredTypes: function() { + this.registered_node_types = {}; + this.node_types_by_file_extension = {}; + this.Nodes = {}; + this.searchbox_extras = {}; + }, + + /** + * Adds this method to all nodetypes, existing and to be created + * (You can add it to LGraphNode.prototype but then existing node types wont have it) + * @method addNodeMethod + * @param {Function} func + */ + addNodeMethod: function(name, func) { + LGraphNode.prototype[name] = func; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if (type.prototype[name]) { + type.prototype["_" + name] = type.prototype[name]; + } //keep old in case of replacing + type.prototype[name] = func; + } + }, + + /** + * Create a node of a given type with a name. The node is not attached to any graph yet. + * @method createNode + * @param {String} type full name of the node class. p.e. "math/sin" + * @param {String} name a name to distinguish from other nodes + * @param {Object} options to set options + */ + + createNode: function(type, title, options) { + var base_class = this.registered_node_types[type]; + if (!base_class) { + if (LiteGraph.debug) { + console.log( + 'GraphNode type "' + type + '" not registered.' + ); + } + return null; + } - Subgraph.prototype.onAction = function(action, param) { - this.subgraph.onAction(action, param); - }; + var prototype = base_class.prototype || base_class; - Subgraph.prototype.onExecute = function() { - this.enabled = this.getInputOrProperty("enabled"); - if (!this.enabled) { - return; - } + title = title || base_class.title || type; - //send inputs to subgraph global inputs - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var value = this.getInputData(i); - this.subgraph.setInputData(input.name, value); + var node = null; + + if (LiteGraph.catch_exceptions) { + try { + node = new base_class(title); + } catch (err) { + console.error(err); + return null; + } + } else { + node = new base_class(title); } - } - //execute - this.subgraph.runStep(); + node.type = type; - //send subgraph global outputs to outputs - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - var value = this.subgraph.getOutputData(output.name); - this.setOutputData(i, value); + if (!node.title && title) { + node.title = title; + } + if (!node.properties) { + node.properties = {}; + } + if (!node.properties_info) { + node.properties_info = []; + } + if (!node.flags) { + node.flags = {}; + } + if (!node.size) { + node.size = node.computeSize(); + //call onresize? + } + if (!node.pos) { + node.pos = LiteGraph.DEFAULT_POSITION.concat(); + } + if (!node.mode) { + node.mode = LiteGraph.ALWAYS; } - } - }; - Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) { - if (this.enabled) { - this.subgraph.sendEventToAllNodes(eventname, param, mode); - } - }; + //extra options + if (options) { + for (var i in options) { + node[i] = options[i]; + } + } - Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) { - if (this.flags.collapsed) - return; - var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - // button - var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT); - let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT) - ctx.fillStyle = over ? "#555" : "#222"; - ctx.beginPath(); - if (this._shape == LiteGraph.BOX_SHAPE) { - if (overleft) { - ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); - } else { - ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + // callback + if ( node.onNodeCreated ) { + node.onNodeCreated(); } - } - else { - if (overleft) { - ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); - } else { - ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + + return node; + }, + + /** + * Returns a registered node type with a given name + * @method getNodeType + * @param {String} type full name of the node class. p.e. "math/sin" + * @return {Class} the node class + */ + getNodeType: function(type) { + return this.registered_node_types[type]; + }, + + /** + * Returns a list of node types matching one category + * @method getNodeType + * @param {String} category category name + * @return {Array} array with all the node classes + */ + + getNodeTypesInCategory: function(category, filter) { + var r = []; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if (type.filter != filter) { + continue; + } + + if (category == "") { + if (type.category == null) { + r.push(type); + } + } else if (type.category == category) { + r.push(type); + } } - } - if (over) { - ctx.fill(); - } else { - ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT); - } - // button - ctx.textAlign = "center"; - ctx.font = "24px Arial"; - ctx.fillStyle = over ? "#DDD" : "#999"; - ctx.fillText("+", this.size[0] * 0.25, y + 24); - ctx.fillText("+", this.size[0] * 0.75, y + 24); - } - // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) - // { - // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - // if(localpos[1] > y) - // { - // graphcanvas.showSubgraphPropertiesDialog(this); - // } - // } - Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) { - var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; - console.log(0) - if (localpos[1] > y) { - if (localpos[0] < this.size[0] / 2) { - console.log(1) - graphcanvas.showSubgraphPropertiesDialog(this); - } else { - console.log(2) - graphcanvas.showSubgraphPropertiesDialogRight(this); + if (this.auto_sort_node_types) { + r.sort(function(a,b){return a.title.localeCompare(b.title)}); } - } - } - Subgraph.prototype.computeSize = function() - { - var num_inputs = this.inputs ? this.inputs.length : 0; - var num_outputs = this.outputs ? this.outputs.length : 0; - return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; - } - //**** INPUTS *********************************** - Subgraph.prototype.onSubgraphTrigger = function(event, param) { - var slot = this.findOutputSlot(event); - if (slot != -1) { - this.triggerSlot(slot); - } - }; + return r; + }, + + /** + * Returns a list with all the node type categories + * @method getNodeTypesCategories + * @param {String} filter only nodes with ctor.filter equal can be shown + * @return {Array} array with all the names of the categories + */ + getNodeTypesCategories: function( filter ) { + var categories = { "": 1 }; + for (var i in this.registered_node_types) { + var type = this.registered_node_types[i]; + if ( type.category && !type.skip_list ) + { + if(type.filter != filter) + continue; + categories[type.category] = 1; + } + } + var result = []; + for (var i in categories) { + result.push(i); + } + return this.auto_sort_node_types ? result.sort() : result; + }, + + //debug purposes: reloads all the js scripts that matches a wildcard + reloadNodes: function(folder_wildcard) { + var tmp = document.getElementsByTagName("script"); + //weird, this array changes by its own, so we use a copy + var script_files = []; + for (var i=0; i < tmp.length; i++) { + script_files.push(tmp[i]); + } - Subgraph.prototype.onSubgraphNewInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - //add input to the node - this.addInput(name, type); - } - }; + var docHeadObj = document.getElementsByTagName("head")[0]; + folder_wildcard = document.location.href + folder_wildcard; - Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) { - var slot = this.findInputSlot(oldname); - if (slot == -1) { - return; + for (var i=0; i < script_files.length; i++) { + var src = script_files[i].src; + if ( + !src || + src.substr(0, folder_wildcard.length) != folder_wildcard + ) { + continue; + } + + try { + if (LiteGraph.debug) { + console.log("Reloading: " + src); + } + var dynamicScript = document.createElement("script"); + dynamicScript.type = "text/javascript"; + dynamicScript.src = src; + docHeadObj.appendChild(dynamicScript); + docHeadObj.removeChild(script_files[i]); + } catch (err) { + if (LiteGraph.throw_errors) { + throw err; + } + if (LiteGraph.debug) { + console.log("Error while reloading " + src); + } + } + } + + if (LiteGraph.debug) { + console.log("Nodes reloaded"); + } + }, + + //separated just to improve if it doesn't work + cloneObject: function(obj, target) { + if (obj == null) { + return null; + } + var r = JSON.parse(JSON.stringify(obj)); + if (!target) { + return r; + } + + for (var i in r) { + target[i] = r[i]; + } + return target; + }, + + /* + * https://gist.github.com/jed/982883?permalink_comment_id=852670#gistcomment-852670 + */ + uuidv4: function() { + return ([1e7]+-1e3+-4e3+-8e3+-1e11).replace(/[018]/g,a=>(a^Math.random()*16>>a/4).toString(16)); + }, + + /** + * Returns if the types of two slots are compatible (taking into account wildcards, etc) + * @method isValidConnection + * @param {String} type_a + * @param {String} type_b + * @return {Boolean} true if they can be connected + */ + isValidConnection: function(type_a, type_b) { + if (type_a=="" || type_a==="*") type_a = 0; + if (type_b=="" || type_b==="*") type_b = 0; + if ( + !type_a //generic output + || !type_b // generic input + || type_a == type_b //same type (is valid for triggers) + || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) + ) { + return true; + } + + // Enforce string type to handle toLowerCase call (-1 number not ok) + type_a = String(type_a); + type_b = String(type_b); + type_a = type_a.toLowerCase(); + type_b = type_b.toLowerCase(); + + // For nodes supporting multiple connection types + if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1) { + return type_a == type_b; + } + + // Check all permutations to see if one is valid + var supported_types_a = type_a.split(","); + var supported_types_b = type_b.split(","); + for (var i = 0; i < supported_types_a.length; ++i) { + for (var j = 0; j < supported_types_b.length; ++j) { + if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ + //if (supported_types_a[i] == supported_types_b[j]) { + return true; + } + } + } + + return false; + }, + + /** + * Register a string in the search box so when the user types it it will recommend this node + * @method registerSearchboxExtra + * @param {String} node_type the node recommended + * @param {String} description text to show next to it + * @param {Object} data it could contain info of how the node should be configured + * @return {Boolean} true if they can be connected + */ + registerSearchboxExtra: function(node_type, description, data) { + this.searchbox_extras[description.toLowerCase()] = { + type: node_type, + desc: description, + data: data + }; + }, + + /** + * Wrapper to load files (from url using fetch or from file using FileReader) + * @method fetchFile + * @param {String|File|Blob} url the url of the file (or the file itself) + * @param {String} type an string to know how to fetch it: "text","arraybuffer","json","blob" + * @param {Function} on_complete callback(data) + * @param {Function} on_error in case of an error + * @return {FileReader|Promise} returns the object used to + */ + fetchFile: function( url, type, on_complete, on_error ) { + var that = this; + if(!url) + return null; + + type = type || "text"; + if( url.constructor === String ) + { + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + return fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); //it will be catch below + if(type == "arraybuffer") + return response.arrayBuffer(); + else if(type == "text" || type == "string") + return response.text(); + else if(type == "json") + return response.json(); + else if(type == "blob") + return response.blob(); + }) + .then(function(data) { + if(on_complete) + on_complete(data); + }) + .catch(function(error) { + console.error("error fetching file:",url); + if(on_error) + on_error(error); + }); + } + else if( url.constructor === File || url.constructor === Blob) + { + var reader = new FileReader(); + reader.onload = function(e) + { + var v = e.target.result; + if( type == "json" ) + v = JSON.parse(v); + if(on_complete) + on_complete(v); + } + if(type == "arraybuffer") + return reader.readAsArrayBuffer(url); + else if(type == "text" || type == "json") + return reader.readAsText(url); + else if(type == "blob") + return reader.readAsBinaryString(url); + } + return null; + } + }); + + //timer that works everywhere + if (typeof performance != "undefined") { + LiteGraph.getTime = performance.now.bind(performance); + } else if (typeof Date != "undefined" && Date.now) { + LiteGraph.getTime = Date.now.bind(Date); + } else if (typeof process != "undefined") { + LiteGraph.getTime = function() { + var t = process.hrtime(); + return t[0] * 0.001 + t[1] * 1e-6; + }; + } else { + LiteGraph.getTime = function getTime() { + return new Date().getTime(); + }; + } + + //********************************************************************************* + // LGraph CLASS + //********************************************************************************* + + /** + * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. + * supported callbacks: + + onNodeAdded: when a new node is added to the graph + + onNodeRemoved: when a node inside this graph is removed + + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) + * + * @class LGraph + * @constructor + * @param {Object} o data from previous serialization [optional] + */ + + function LGraph(o) { + if (LiteGraph.debug) { + console.log("Graph created"); } - var info = this.getInputInfo(slot); - info.name = name; - }; + this.list_of_graphcanvas = null; + this.clear(); - Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; + if (o) { + this.configure(o); } - var info = this.getInputInfo(slot); - info.type = type; + } + + global.LGraph = LiteGraph.LGraph = LGraph; + + //default supported types + LGraph.supported_types = ["number", "string", "boolean"]; + + //used to know which types of connections support this graph (some graphs do not allow certain types) + LGraph.prototype.getSupportedTypes = function() { + return this.supported_types || LGraph.supported_types; }; - Subgraph.prototype.onSubgraphRemovedInput = function(name) { - var slot = this.findInputSlot(name); - if (slot == -1) { - return; + LGraph.STATUS_STOPPED = 1; + LGraph.STATUS_RUNNING = 2; + + /** + * Removes all nodes from this graph + * @method clear + */ + + LGraph.prototype.clear = function() { + this.stop(); + this.status = LGraph.STATUS_STOPPED; + + this.last_node_id = 0; + this.last_link_id = 0; + + this._version = -1; //used to detect changes + + //safe clear + if (this._nodes) { + for (var i = 0; i < this._nodes.length; ++i) { + var node = this._nodes[i]; + if (node.onRemoved) { + node.onRemoved(); + } + } } - this.removeInput(slot); + + //nodes + this._nodes = []; + this._nodes_by_id = {}; + this._nodes_in_order = []; //nodes sorted in execution order + this._nodes_executable = null; //nodes that contain onExecute sorted in execution order + + //other scene stuff + this._groups = []; + + //links + this.links = {}; //container with all the links + + //iterations + this.iteration = 0; + + //custom data + this.config = {}; + this.vars = {}; + this.extra = {}; //to store custom data + + //timing + this.globaltime = 0; + this.runningtime = 0; + this.fixedtime = 0; + this.fixedtime_lapse = 0.01; + this.elapsed_time = 0.01; + this.last_update_time = 0; + this.starttime = 0; + + this.catch_errors = true; + + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + + //subgraph_data + this.inputs = {}; + this.outputs = {}; + + //notify canvas to redraw + this.change(); + + this.sendActionToCanvas("clear"); }; - //**** OUTPUTS *********************************** - Subgraph.prototype.onSubgraphNewOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { - this.addOutput(name, type); + /** + * Attach Canvas to this graph + * @method attachCanvas + * @param {GraphCanvas} graph_canvas + */ + + LGraph.prototype.attachCanvas = function(graphcanvas) { + if (graphcanvas.constructor != LGraphCanvas) { + throw "attachCanvas expects a LGraphCanvas instance"; + } + if (graphcanvas.graph && graphcanvas.graph != this) { + graphcanvas.graph.detachCanvas(graphcanvas); } + + graphcanvas.graph = this; + + if (!this.list_of_graphcanvas) { + this.list_of_graphcanvas = []; + } + this.list_of_graphcanvas.push(graphcanvas); }; - Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) { - var slot = this.findOutputSlot(oldname); - if (slot == -1) { + /** + * Detach Canvas from this graph + * @method detachCanvas + * @param {GraphCanvas} graph_canvas + */ + LGraph.prototype.detachCanvas = function(graphcanvas) { + if (!this.list_of_graphcanvas) { return; } - var info = this.getOutputInfo(slot); - info.name = name; - }; - Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) { - var slot = this.findOutputSlot(name); - if (slot == -1) { + var pos = this.list_of_graphcanvas.indexOf(graphcanvas); + if (pos == -1) { return; } - var info = this.getOutputInfo(slot); - info.type = type; + graphcanvas.graph = null; + this.list_of_graphcanvas.splice(pos, 1); }; - Subgraph.prototype.onSubgraphRemovedOutput = function(name) { - var slot = this.findOutputSlot(name); - if (slot == -1) { + /** + * Starts running this graph every interval milliseconds. + * @method start + * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate + */ + + LGraph.prototype.start = function(interval) { + if (this.status == LGraph.STATUS_RUNNING) { return; } - this.removeOutput(slot); - }; - // ***************************************************** + this.status = LGraph.STATUS_RUNNING; - Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) { + if (this.onPlayEvent) { + this.onPlayEvent(); + } + + this.sendEventToAllNodes("onStart"); + + //launch + this.starttime = LiteGraph.getTime(); + this.last_update_time = this.starttime; + interval = interval || 0; var that = this; - return [ - { - content: "Open", - callback: function() { - graphcanvas.openSubgraph(that.subgraph); + + //execute once per frame + if ( interval == 0 && typeof window != "undefined" && window.requestAnimationFrame ) { + function on_frame() { + if (that.execution_timer_id != -1) { + return; } + window.requestAnimationFrame(on_frame); + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); } - ]; + this.execution_timer_id = -1; + on_frame(); + } else { //execute every 'interval' ms + this.execution_timer_id = setInterval(function() { + //execute + if(that.onBeforeStep) + that.onBeforeStep(); + that.runStep(1, !that.catch_errors); + if(that.onAfterStep) + that.onAfterStep(); + }, interval); + } }; - Subgraph.prototype.onResize = function(size) { - size[1] += 20; - }; + /** + * Stops the execution loop of the graph + * @method stop execution + */ - Subgraph.prototype.serialize = function() { - var data = LiteGraph.LGraphNode.prototype.serialize.call(this); - data.subgraph = this.subgraph.serialize(); - return data; - }; - //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() + LGraph.prototype.stop = function() { + if (this.status == LGraph.STATUS_STOPPED) { + return; + } - Subgraph.prototype.reassignSubgraphUUIDs = function(graph) { - const idMap = { nodeIDs: {}, linkIDs: {} } + this.status = LGraph.STATUS_STOPPED; - for (const node of graph.nodes) { - const oldID = node.id - const newID = LiteGraph.uuidv4() - node.id = newID + if (this.onStopEvent) { + this.onStopEvent(); + } - if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) { - throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`) + if (this.execution_timer_id != null) { + if (this.execution_timer_id != -1) { + clearInterval(this.execution_timer_id); } + this.execution_timer_id = null; + } - idMap.nodeIDs[oldID] = newID - idMap.nodeIDs[newID] = oldID + this.sendEventToAllNodes("onStop"); + }; + + /** + * Run N steps (cycles) of the graph + * @method runStep + * @param {number} num number of steps to run, default is 1 + * @param {Boolean} do_not_catch_errors [optional] if you want to try/catch errors + * @param {number} limit max number of nodes to execute (used to execute from start to a node) + */ + + LGraph.prototype.runStep = function(num, do_not_catch_errors, limit ) { + num = num || 1; + + var start = LiteGraph.getTime(); + this.globaltime = 0.001 * (start - this.starttime); + + //not optimal: executes possible pending actions in node, problem is it is not optimized + //it is done here as if it was done in the later loop it wont be called in the node missed the onExecute + + //from now on it will iterate only on executable nodes which is faster + var nodes = this._nodes_executable + ? this._nodes_executable + : this._nodes; + if (!nodes) { + return; } - for (const link of graph.links) { - const oldID = link[0] - const newID = LiteGraph.uuidv4(); - link[0] = newID + limit = limit || nodes.length; + + if (do_not_catch_errors) { + //iterations + for (var i = 0; i < num; i++) { + for (var j = 0; j < limit; ++j) { + var node = nodes[j]; + if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) + node.executePendingActions(); + if (node.mode == LiteGraph.ALWAYS && node.onExecute) { + //wrap node.onExecute(); + node.doExecute(); + } + } - if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) { - throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`) + this.fixedtime += this.fixedtime_lapse; + if (this.onExecuteStep) { + this.onExecuteStep(); + } } - idMap.linkIDs[oldID] = newID - idMap.linkIDs[newID] = oldID + if (this.onAfterExecute) { + this.onAfterExecute(); + } + } else { //catch errors + try { + //iterations + for (var i = 0; i < num; i++) { + for (var j = 0; j < limit; ++j) { + var node = nodes[j]; + if(LiteGraph.use_deferred_actions && node._waiting_actions && node._waiting_actions.length) + node.executePendingActions(); + if (node.mode == LiteGraph.ALWAYS && node.onExecute) { + node.onExecute(); + } + } - const nodeFrom = link[1] - const nodeTo = link[3] + this.fixedtime += this.fixedtime_lapse; + if (this.onExecuteStep) { + this.onExecuteStep(); + } + } - if (!idMap.nodeIDs[nodeFrom]) { - throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`) + if (this.onAfterExecute) { + this.onAfterExecute(); + } + this.errors_in_execution = false; + } catch (err) { + this.errors_in_execution = true; + if (LiteGraph.throw_errors) { + throw err; + } + if (LiteGraph.debug) { + console.log("Error during execution: " + err); + } + this.stop(); } + } - link[1] = idMap.nodeIDs[nodeFrom] + var now = LiteGraph.getTime(); + var elapsed = now - start; + if (elapsed == 0) { + elapsed = 1; + } + this.execution_time = 0.001 * elapsed; + this.globaltime += 0.001 * elapsed; + this.iteration += 1; + this.elapsed_time = (now - this.last_update_time) * 0.001; + this.last_update_time = now; + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + }; - if (!idMap.nodeIDs[nodeTo]) { - throw new Error(`Old node UUID not found in mapping! ${nodeTo}`) + /** + * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than + * nodes with only inputs. + * @method updateExecutionOrder + */ + LGraph.prototype.updateExecutionOrder = function() { + this._nodes_in_order = this.computeExecutionOrder(false); + this._nodes_executable = []; + for (var i = 0; i < this._nodes_in_order.length; ++i) { + if (this._nodes_in_order[i].onExecute) { + this._nodes_executable.push(this._nodes_in_order[i]); } - - link[3] = idMap.nodeIDs[nodeTo] } + }; - // Reconnect links - for (const node of graph.nodes) { + //This is more internal, it computes the executable nodes in order and returns it + LGraph.prototype.computeExecutionOrder = function( + only_onExecute, + set_level + ) { + var L = []; + var S = []; + var M = {}; + var visited_links = {}; //to avoid repeating links + var remaining_links = {}; //to a + + //search for the nodes without inputs (starting nodes) + for (var i = 0, l = this._nodes.length; i < l; ++i) { + var node = this._nodes[i]; + if (only_onExecute && !node.onExecute) { + continue; + } + + M[node.id] = node; //add to pending nodes + + var num = 0; //num of input connections if (node.inputs) { - for (const input of node.inputs) { - if (input.link) { - input.link = idMap.linkIDs[input.link] + for (var j = 0, l2 = node.inputs.length; j < l2; j++) { + if (node.inputs[j] && node.inputs[j].link != null) { + num += 1; } } } - if (node.outputs) { - for (const output of node.outputs) { - if (output.links) { - output.links = output.links.map(l => idMap.linkIDs[l]); - } + + if (num == 0) { + //is a starting node + S.push(node); + if (set_level) { + node._level = 1; } + } //num of input links + else { + if (set_level) { + node._level = 0; + } + remaining_links[node.id] = num; } } - // Recurse! - for (const node of graph.nodes) { - if (node.type === "graph/subgraph") { - const merge = reassignGraphUUIDs(node.subgraph); - idMap.nodeIDs.assign(merge.nodeIDs) - idMap.linkIDs.assign(merge.linkIDs) + while (true) { + if (S.length == 0) { + break; + } + + //get an starting node + var node = S.shift(); + L.push(node); //add to ordered list + delete M[node.id]; //remove from the pending nodes + + if (!node.outputs) { + continue; + } + + //for every output + for (var i = 0; i < node.outputs.length; i++) { + var output = node.outputs[i]; + //not connected + if ( + output == null || + output.links == null || + output.links.length == 0 + ) { + continue; + } + + //for every connection + for (var j = 0; j < output.links.length; j++) { + var link_id = output.links[j]; + var link = this.links[link_id]; + if (!link) { + continue; + } + + //already visited link (ignore it) + if (visited_links[link.id]) { + continue; + } + + var target_node = this.getNodeById(link.target_id); + if (target_node == null) { + visited_links[link.id] = true; + continue; + } + + if ( + set_level && + (!target_node._level || + target_node._level <= node._level) + ) { + target_node._level = node._level + 1; + } + + visited_links[link.id] = true; //mark as visited + remaining_links[target_node.id] -= 1; //reduce the number of links remaining + if (remaining_links[target_node.id] == 0) { + S.push(target_node); + } //if no more links, then add to starters array + } } } - }; - Subgraph.prototype.clone = function() { - var node = LiteGraph.createNode(this.type); - var data = this.serialize(); + //the remaining ones (loops) + for (var i in M) { + L.push(M[i]); + } - if (LiteGraph.use_uuids) { - // LGraph.serialize() seems to reuse objects in the original graph. But we - // need to change node IDs here, so clone it first. - const subgraph = LiteGraph.cloneObject(data.subgraph) + if (L.length != this._nodes.length && LiteGraph.debug) { + console.warn("something went wrong, nodes missing"); + } - this.reassignSubgraphUUIDs(subgraph); + var l = L.length; - data.subgraph = subgraph; + //save order number in the node + for (var i = 0; i < l; ++i) { + L[i].order = i; } - delete data["id"]; - delete data["inputs"]; - delete data["outputs"]; - node.configure(data); - return node; + //sort now by priority + L = L.sort(function(A, B) { + var Ap = A.constructor.priority || A.priority || 0; + var Bp = B.constructor.priority || B.priority || 0; + if (Ap == Bp) { + //if same priority, sort by order + return A.order - B.order; + } + return Ap - Bp; //sort by priority + }); + + //save order number in the node, again... + for (var i = 0; i < l; ++i) { + L[i].order = i; + } + + return L; }; - Subgraph.prototype.buildFromNodes = function(nodes) - { - //clear all? - //TODO + /** + * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively. + * It doesn't include the node itself + * @method getAncestors + * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution + */ + LGraph.prototype.getAncestors = function(node) { + var ancestors = []; + var pending = [node]; + var visited = {}; + + while (pending.length) { + var current = pending.shift(); + if (!current.inputs) { + continue; + } + if (!visited[current.id] && current != node) { + visited[current.id] = true; + ancestors.push(current); + } - //nodes that connect data between parent graph and subgraph - var subgraph_inputs = []; - var subgraph_outputs = []; + for (var i = 0; i < current.inputs.length; ++i) { + var input = current.getInputNode(i); + if (input && ancestors.indexOf(input) == -1) { + pending.push(input); + } + } + } - //mark inner nodes - var ids = {}; - var min_x = 0; - var max_x = 0; - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - ids[ node.id ] = node; - min_x = Math.min( node.pos[0], min_x ); - max_x = Math.max( node.pos[0], min_x ); - } - - var last_input_y = 0; - var last_output_y = 0; + ancestors.sort(function(a, b) { + return a.order - b.order; + }); + return ancestors; + }; - for(var i = 0; i < nodes.length; ++i) - { - var node = nodes[i]; - //check inputs - if( node.inputs ) - for(var j = 0; j < node.inputs.length; ++j) - { - var input = node.inputs[j]; - if( !input || !input.link ) - continue; - var link = node.graph.links[ input.link ]; - if(!link) - continue; - if( ids[ link.origin_id ] ) - continue; - //this.addInput(input.name,link.type); - this.subgraph.addInput(input.name,link.type); - /* - var input_node = LiteGraph.createNode("graph/input"); - this.subgraph.add( input_node ); - input_node.pos = [min_x - 200, last_input_y ]; - last_input_y += 100; - */ - } + /** + * Positions every node in a more readable manner + * @method arrange + */ + LGraph.prototype.arrange = function (margin, layout) { + margin = margin || 100; + + const nodes = this.computeExecutionOrder(false, true); + const columns = []; + for (let i = 0; i < nodes.length; ++i) { + const node = nodes[i]; + const col = node._level || 1; + if (!columns[col]) { + columns[col] = []; + } + columns[col].push(node); + } - //check outputs - if( node.outputs ) - for(var j = 0; j < node.outputs.length; ++j) - { - var output = node.outputs[j]; - if( !output || !output.links || !output.links.length ) - continue; - var is_external = false; - for(var k = 0; k < output.links.length; ++k) - { - var link = node.graph.links[ output.links[k] ]; - if(!link) - continue; - if( ids[ link.target_id ] ) - continue; - is_external = true; - break; - } - if(!is_external) - continue; - //this.addOutput(output.name,output.type); - /* - var output_node = LiteGraph.createNode("graph/output"); - this.subgraph.add( output_node ); - output_node.pos = [max_x + 50, last_output_y ]; - last_output_y += 100; - */ - } - } + let x = margin; - //detect inputs and outputs - //split every connection in two data_connection nodes - //keep track of internal connections - //connect external connections + for (let i = 0; i < columns.length; ++i) { + const column = columns[i]; + if (!column) { + continue; + } + let max_size = 100; + let y = margin + LiteGraph.NODE_TITLE_HEIGHT; + for (let j = 0; j < column.length; ++j) { + const node = column[j]; + node.pos[0] = (layout == LiteGraph.VERTICAL_LAYOUT) ? y : x; + node.pos[1] = (layout == LiteGraph.VERTICAL_LAYOUT) ? x : y; + const max_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 1 : 0; + if (node.size[max_size_index] > max_size) { + max_size = node.size[max_size_index]; + } + const node_size_index = (layout == LiteGraph.VERTICAL_LAYOUT) ? 0 : 1; + y += node.size[node_size_index] + margin + LiteGraph.NODE_TITLE_HEIGHT; + } + x += max_size + margin; + } - //clone nodes inside subgraph and try to reconnect them + this.setDirtyCanvas(true, true); + }; - //connect edge subgraph nodes to extarnal connections nodes - } + /** + * Returns the amount of time the graph has been running in milliseconds + * @method getTime + * @return {number} number of milliseconds the graph has been running + */ + LGraph.prototype.getTime = function() { + return this.globaltime; + }; - LiteGraph.Subgraph = Subgraph; - LiteGraph.registerNodeType("graph/subgraph", Subgraph); + /** + * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant + * @method getFixedTime + * @return {number} number of milliseconds the graph has been running + */ - //Input for a subgraph - function GraphInput() { - this.addOutput("", "number"); + LGraph.prototype.getFixedTime = function() { + return this.fixedtime; + }; - this.name_in_graph = ""; - this.properties = { - name: "", - type: "number", - value: 0 - }; + /** + * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct + * if the nodes are using graphical actions + * @method getElapsedTime + * @return {number} number of milliseconds it took the last cycle + */ - var that = this; + LGraph.prototype.getElapsedTime = function() { + return this.elapsed_time; + }; - this.name_widget = this.addWidget( - "text", - "Name", - this.properties.name, - function(v) { - if (!v) { - return; + /** + * Sends an event to all the nodes, useful to trigger stuff + * @method sendEventToAllNodes + * @param {String} eventname the name of the event (function to be called) + * @param {Array} params parameters in array format + */ + LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) { + mode = mode || LiteGraph.ALWAYS; + + var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes; + if (!nodes) { + return; + } + + for (var j = 0, l = nodes.length; j < l; ++j) { + var node = nodes[j]; + + if ( + node.constructor === LiteGraph.Subgraph && + eventname != "onExecute" + ) { + if (node.mode == mode) { + node.sendEventToAllNodes(eventname, params, mode); } - that.setProperty("name",v); + continue; } - ); - this.type_widget = this.addWidget( - "text", - "Type", - this.properties.type, - function(v) { - that.setProperty("type",v); + + if (!node[eventname] || node.mode != mode) { + continue; } - ); + if (params === undefined) { + node[eventname](); + } else if (params && params.constructor === Array) { + node[eventname].apply(node, params); + } else { + node[eventname](params); + } + } + }; - this.value_widget = this.addWidget( - "number", - "Value", - this.properties.value, - function(v) { - that.setProperty("value",v); + LGraph.prototype.sendActionToCanvas = function(action, params) { + if (!this.list_of_graphcanvas) { + return; + } + + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var c = this.list_of_graphcanvas[i]; + if (c[action]) { + c[action].apply(c, params); } - ); + } + }; - this.widgets_up = true; - this.size = [180, 90]; - } + /** + * Adds a new node instance to this graph + * @method add + * @param {LGraphNode} node the instance of the node + */ - GraphInput.title = "Input"; - GraphInput.desc = "Input of the graph"; + LGraph.prototype.add = function(node, skip_compute_order) { + if (!node) { + return; + } - GraphInput.prototype.onConfigure = function() + //groups + if (node.constructor === LGraphGroup) { + this._groups.push(node); + this.setDirtyCanvas(true); + this.change(); + node.graph = this; + this._version++; + return; + } - { - this.updateType(); - } + //nodes + if (node.id != -1 && this._nodes_by_id[node.id] != null) { + console.warn( + "LiteGraph: there is already a node with this ID, changing it" + ); + if (LiteGraph.use_uuids) { + node.id = LiteGraph.uuidv4(); + } + else { + node.id = ++this.last_node_id; + } + } - //ensures the type in the node output and the type in the associated graph input are the same - GraphInput.prototype.updateType = function() - { - var type = this.properties.type; - this.type_widget.value = type; + if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES) { + throw "LiteGraph: max number of nodes in a graph reached"; + } - //update output - if(this.outputs[0].type != type) - { - if (!LiteGraph.isValidConnection(this.outputs[0].type,type)) - this.disconnectOutput(0); - this.outputs[0].type = type; - } + //give him an id + if (LiteGraph.use_uuids) { + if (node.id == null || node.id == -1) + node.id = LiteGraph.uuidv4(); + } + else { + if (node.id == null || node.id == -1) { + node.id = ++this.last_node_id; + } else if (this.last_node_id < node.id) { + this.last_node_id = node.id; + } + } - //update widget - if(type == "number") - { - this.value_widget.type = "number"; - this.value_widget.value = 0; - } - else if(type == "boolean") - { - this.value_widget.type = "toggle"; - this.value_widget.value = true; - } - else if(type == "string") - { - this.value_widget.type = "text"; - this.value_widget.value = ""; - } - else - { - this.value_widget.type = null; - this.value_widget.value = null; - } - this.properties.value = this.value_widget.value; + node.graph = this; + this._version++; - //update graph - if (this.graph && this.name_in_graph) { - this.graph.changeInputType(this.name_in_graph, type); - } - } + this._nodes.push(node); + this._nodes_by_id[node.id] = node; - //this is executed AFTER the property has changed - GraphInput.prototype.onPropertyChanged = function(name,v) - { - if( name == "name" ) - { - if (v == "" || v == this.name_in_graph || v == "enabled") { - return false; - } - if(this.graph) - { - if (this.name_in_graph) { - //already added - this.graph.renameInput( this.name_in_graph, v ); - } else { - this.graph.addInput( v, this.properties.type ); - } - } //what if not?! - this.name_widget.value = v; - this.name_in_graph = v; - } - else if( name == "type" ) - { - this.updateType(); - } - else if( name == "value" ) - { - } - } + if (node.onAdded) { + node.onAdded(this); + } - GraphInput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; + if (this.config.align_to_grid) { + node.alignToGrid(); } - return this.title; - }; - GraphInput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.EVENT) { - this.triggerSlot(0, param); + if (!skip_compute_order) { + this.updateExecutionOrder(); } - }; - GraphInput.prototype.onExecute = function() { - var name = this.properties.name; - //read from global input - var data = this.graph.inputs[name]; - if (!data) { - this.setOutputData(0, this.properties.value ); - return; + if (this.onNodeAdded) { + this.onNodeAdded(node); } - this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value ); + this.setDirtyCanvas(true); + this.change(); + + return node; //to chain actions }; - GraphInput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeInput(this.name_in_graph); + /** + * Removes a node from the graph + * @method remove + * @param {LGraphNode} node the instance of the node + */ + + LGraph.prototype.remove = function(node) { + if (node.constructor === LiteGraph.LGraphGroup) { + var index = this._groups.indexOf(node); + if (index != -1) { + this._groups.splice(index, 1); + } + node.graph = null; + this._version++; + this.setDirtyCanvas(true, true); + this.change(); + return; } - }; - LiteGraph.GraphInput = GraphInput; - LiteGraph.registerNodeType("graph/input", GraphInput); + if (this._nodes_by_id[node.id] == null) { + return; + } //not found - //Output for a subgraph - function GraphOutput() { - this.addInput("", ""); + if (node.ignore_remove) { + return; + } //cannot be removed - this.name_in_graph = ""; - this.properties = { name: "", type: "" }; - var that = this; + this.beforeChange(); //sure? - almost sure is wrong - // Object.defineProperty(this.properties, "name", { - // get: function() { - // return that.name_in_graph; - // }, - // set: function(v) { - // if (v == "" || v == that.name_in_graph) { - // return; - // } - // if (that.name_in_graph) { - // //already added - // that.graph.renameOutput(that.name_in_graph, v); - // } else { - // that.graph.addOutput(v, that.properties.type); - // } - // that.name_widget.value = v; - // that.name_in_graph = v; - // }, - // enumerable: true - // }); + //disconnect inputs + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + if (slot.link != null) { + node.disconnectInput(i); + } + } + } - // Object.defineProperty(this.properties, "type", { - // get: function() { - // return that.inputs[0].type; - // }, - // set: function(v) { - // if (v == "action" || v == "event") { - // v = LiteGraph.ACTION; - // } - // if (!LiteGraph.isValidConnection(that.inputs[0].type,v)) - // that.disconnectInput(0); - // that.inputs[0].type = v; - // if (that.name_in_graph) { - // //already added - // that.graph.changeOutputType( - // that.name_in_graph, - // that.inputs[0].type - // ); - // } - // that.type_widget.value = v || ""; - // }, - // enumerable: true - // }); + //disconnect outputs + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + if (slot.links != null && slot.links.length) { + node.disconnectOutput(i); + } + } + } - this.name_widget = this.addWidget("text","Name",this.properties.name,"name"); - this.type_widget = this.addWidget("text","Type",this.properties.type,"type"); - this.widgets_up = true; - this.size = [180, 60]; - } + //node.id = -1; //why? - GraphOutput.title = "Output"; - GraphOutput.desc = "Output of the graph"; + //callback + if (node.onRemoved) { + node.onRemoved(); + } - GraphOutput.prototype.onPropertyChanged = function (name, v) { - if (name == "name") { - if (v == "" || v == this.name_in_graph || v == "enabled") { - return false; - } - if (this.graph) { - if (this.name_in_graph) { - //already added - this.graph.renameOutput(this.name_in_graph, v); - } else { - this.graph.addOutput(v, this.properties.type); + node.graph = null; + this._version++; + + //remove from canvas render + if (this.list_of_graphcanvas) { + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var canvas = this.list_of_graphcanvas[i]; + if (canvas.selected_nodes[node.id]) { + delete canvas.selected_nodes[node.id]; } - } //what if not?! - this.name_widget.value = v; - this.name_in_graph = v; - } - else if (name == "type") { - this.updateType(); - } - else if (name == "value") { + if (canvas.node_dragged == node) { + canvas.node_dragged = null; + } + } } - } - - GraphOutput.prototype.updateType = function () { - var type = this.properties.type; - if (this.type_widget) - this.type_widget.value = type; - - //update output - if (this.inputs[0].type != type) { - if ( type == "action" || type == "event") - type = LiteGraph.EVENT; - if (!LiteGraph.isValidConnection(this.inputs[0].type, type)) - this.disconnectInput(0); - this.inputs[0].type = type; + //remove from containers + var pos = this._nodes.indexOf(node); + if (pos != -1) { + this._nodes.splice(pos, 1); } + delete this._nodes_by_id[node.id]; - //update graph - if (this.graph && this.name_in_graph) { - this.graph.changeOutputType(this.name_in_graph, type); + if (this.onNodeRemoved) { + this.onNodeRemoved(node); } - } + //close panels + this.sendActionToCanvas("checkPanels"); + this.setDirtyCanvas(true, true); + this.afterChange(); //sure? - almost sure is wrong + this.change(); - GraphOutput.prototype.onExecute = function() { - this._value = this.getInputData(0); - this.graph.setOutputData(this.properties.name, this._value); + this.updateExecutionOrder(); }; - GraphOutput.prototype.onAction = function(action, param) { - if (this.properties.type == LiteGraph.ACTION) { - this.graph.trigger( this.properties.name, param ); + /** + * Returns a node by its id. + * @method getNodeById + * @param {Number} id + */ + + LGraph.prototype.getNodeById = function(id) { + if (id == null) { + return null; } + return this._nodes_by_id[id]; }; - GraphOutput.prototype.onRemoved = function() { - if (this.name_in_graph) { - this.graph.removeOutput(this.name_in_graph); + /** + * Returns a list of nodes that matches a class + * @method findNodesByClass + * @param {Class} classObject the class itself (not an string) + * @return {Array} a list with all the nodes of this type + */ + LGraph.prototype.findNodesByClass = function(classObject, result) { + result = result || []; + result.length = 0; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].constructor === classObject) { + result.push(this._nodes[i]); + } } + return result; }; - GraphOutput.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.name; + /** + * Returns a list of nodes that matches a type + * @method findNodesByType + * @param {String} type the name of the node type + * @return {Array} a list with all the nodes of this type + */ + LGraph.prototype.findNodesByType = function(type, result) { + var type = type.toLowerCase(); + result = result || []; + result.length = 0; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].type.toLowerCase() == type) { + result.push(this._nodes[i]); + } } - return this.title; + return result; }; - LiteGraph.GraphOutput = GraphOutput; - LiteGraph.registerNodeType("graph/output", GraphOutput); + /** + * Returns the first node that matches a name in its title + * @method findNodeByTitle + * @param {String} name the name of the node to search + * @return {Node} the node or null + */ + LGraph.prototype.findNodeByTitle = function(title) { + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].title == title) { + return this._nodes[i]; + } + } + return null; + }; - //Constant - function ConstantNumber() { - this.addOutput("value", "number"); - this.addProperty("value", 1.0); - this.widget = this.addWidget("number","value",1,"value"); - this.widgets_up = true; - this.size = [180, 30]; - } + /** + * Returns a list of nodes that matches a name + * @method findNodesByTitle + * @param {String} name the name of the node to search + * @return {Array} a list with all the nodes with this name + */ + LGraph.prototype.findNodesByTitle = function(title) { + var result = []; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + if (this._nodes[i].title == title) { + result.push(this._nodes[i]); + } + } + return result; + }; - ConstantNumber.title = "Const Number"; - ConstantNumber.desc = "Constant number"; + /** + * Returns the top-most node in this position of the canvas + * @method getNodeOnPos + * @param {number} x the x coordinate in canvas space + * @param {number} y the y coordinate in canvas space + * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph + * @return {LGraphNode} the node at this position or null + */ + LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { + nodes_list = nodes_list || this._nodes; + var nRet = null; + for (var i = nodes_list.length - 1; i >= 0; i--) { + var n = nodes_list[i]; + if (n.isPointInside(x, y, margin)) { + // check for lesser interest nodes (TODO check for overlapping, use the top) + /*if (typeof n == "LGraphGroup"){ + nRet = n; + }else{*/ + return n; + /*}*/ + } + } + return nRet; + }; - ConstantNumber.prototype.onExecute = function() { - this.setOutputData(0, parseFloat(this.properties["value"])); + /** + * Returns the top-most group in that position + * @method getGroupOnPos + * @param {number} x the x coordinate in canvas space + * @param {number} y the y coordinate in canvas space + * @return {LGraphGroup} the group or null + */ + LGraph.prototype.getGroupOnPos = function(x, y) { + for (var i = this._groups.length - 1; i >= 0; i--) { + var g = this._groups[i]; + if (g.isPointInside(x, y, 2, true)) { + return g; + } + } + return null; }; - ConstantNumber.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.value; + /** + * Checks that the node type matches the node type registered, used when replacing a nodetype by a newer version during execution + * this replaces the ones using the old version with the new version + * @method checkNodeTypes + */ + LGraph.prototype.checkNodeTypes = function() { + var changes = false; + for (var i = 0; i < this._nodes.length; i++) { + var node = this._nodes[i]; + var ctor = LiteGraph.registered_node_types[node.type]; + if (node.constructor == ctor) { + continue; + } + console.log("node being replaced by newer version: " + node.type); + var newnode = LiteGraph.createNode(node.type); + changes = true; + this._nodes[i] = newnode; + newnode.configure(node.serialize()); + newnode.graph = this; + this._nodes_by_id[newnode.id] = newnode; + if (node.inputs) { + newnode.inputs = node.inputs.concat(); + } + if (node.outputs) { + newnode.outputs = node.outputs.concat(); + } } - return this.title; + this.updateExecutionOrder(); }; - ConstantNumber.prototype.setValue = function(v) - { - this.setProperty("value",v); - } + // ********** GLOBALS ***************** - ConstantNumber.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = this.properties["value"].toFixed(3); + LGraph.prototype.onAction = function(action, param, options) { + this._input_nodes = this.findNodesByClass( + LiteGraph.GraphInput, + this._input_nodes + ); + for (var i = 0; i < this._input_nodes.length; ++i) { + var node = this._input_nodes[i]; + if (node.properties.name != action) { + continue; + } + //wrap node.onAction(action, param); + node.actionDo(action, param, options); + break; + } }; - LiteGraph.registerNodeType("basic/const", ConstantNumber); + LGraph.prototype.trigger = function(action, param) { + if (this.onTrigger) { + this.onTrigger(action, param); + } + }; - function ConstantBoolean() { - this.addOutput("bool", "boolean"); - this.addProperty("value", true); - this.widget = this.addWidget("toggle","value",true,"value"); - this.serialize_widgets = true; - this.widgets_up = true; - this.size = [140, 30]; - } + /** + * Tell this graph it has a global graph input of this type + * @method addGlobalInput + * @param {String} name + * @param {String} type + * @param {*} value [optional] + */ + LGraph.prototype.addInput = function(name, type, value) { + var input = this.inputs[name]; + if (input) { + //already exist + return; + } - ConstantBoolean.title = "Const Boolean"; - ConstantBoolean.desc = "Constant boolean"; - ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle; + this.beforeChange(); + this.inputs[name] = { name: name, type: type, value: value }; + this._version++; + this.afterChange(); - ConstantBoolean.prototype.onExecute = function() { - this.setOutputData(0, this.properties["value"]); + if (this.onInputAdded) { + this.onInputAdded(name, type); + } + + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } }; - ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Assign a data to the global graph input + * @method setGlobalInputData + * @param {String} name + * @param {*} data + */ + LGraph.prototype.setInputData = function(name, data) { + var input = this.inputs[name]; + if (!input) { + return; + } + input.value = data; + }; - ConstantBoolean.prototype.onGetInputs = function() { - return [["toggle", LiteGraph.ACTION]]; - }; + /** + * Returns the current value of a global graph input + * @method getInputData + * @param {String} name + * @return {*} the data + */ + LGraph.prototype.getInputData = function(name) { + var input = this.inputs[name]; + if (!input) { + return null; + } + return input.value; + }; - ConstantBoolean.prototype.onAction = function(action) - { - this.setValue( !this.properties.value ); - } + /** + * Changes the name of a global graph input + * @method renameInput + * @param {String} old_name + * @param {String} new_name + */ + LGraph.prototype.renameInput = function(old_name, name) { + if (name == old_name) { + return; + } - LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + if (!this.inputs[old_name]) { + return false; + } - function ConstantString() { - this.addOutput("string", "string"); - this.addProperty("value", ""); - this.widget = this.addWidget("text","value","","value"); //link to property value - this.widgets_up = true; - this.size = [180, 30]; - } + if (this.inputs[name]) { + console.error("there is already one input with that name"); + return false; + } - ConstantString.title = "Const String"; - ConstantString.desc = "Constant string"; + this.inputs[name] = this.inputs[old_name]; + delete this.inputs[old_name]; + this._version++; - ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; + if (this.onInputRenamed) { + this.onInputRenamed(old_name, name); + } - ConstantString.prototype.onExecute = function() { - this.setOutputData(0, this.properties["value"]); + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } }; - ConstantString.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Changes the type of a global graph input + * @method changeInputType + * @param {String} name + * @param {String} type + */ + LGraph.prototype.changeInputType = function(name, type) { + if (!this.inputs[name]) { + return false; + } - ConstantString.prototype.onDropFile = function(file) - { - var that = this; - var reader = new FileReader(); - reader.onload = function(e) - { - that.setProperty("value",e.target.result); - } - reader.readAsText(file); - } + if ( + this.inputs[name].type && + String(this.inputs[name].type).toLowerCase() == + String(type).toLowerCase() + ) { + return; + } - LiteGraph.registerNodeType("basic/string", ConstantString); + this.inputs[name].type = type; + this._version++; + if (this.onInputTypeChanged) { + this.onInputTypeChanged(name, type); + } + }; - function ConstantObject() { - this.addOutput("obj", "object"); - this.size = [120, 30]; - this._object = {}; - } + /** + * Removes a global graph input + * @method removeInput + * @param {String} name + * @param {String} type + */ + LGraph.prototype.removeInput = function(name) { + if (!this.inputs[name]) { + return false; + } - ConstantObject.title = "Const Object"; - ConstantObject.desc = "Constant Object"; + delete this.inputs[name]; + this._version++; - ConstantObject.prototype.onExecute = function() { - this.setOutputData(0, this._object); + if (this.onInputRemoved) { + this.onInputRemoved(name); + } + + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + return true; }; - LiteGraph.registerNodeType( "basic/object", ConstantObject ); + /** + * Creates a global graph output + * @method addOutput + * @param {String} name + * @param {String} type + * @param {*} value + */ + LGraph.prototype.addOutput = function(name, type, value) { + this.outputs[name] = { name: name, type: type, value: value }; + this._version++; + + if (this.onOutputAdded) { + this.onOutputAdded(name, type); + } - function ConstantFile() { - this.addInput("url", "string"); - this.addOutput("file", "string"); - this.addProperty("url", ""); - this.addProperty("type", "text"); - this.widget = this.addWidget("text","url","","url"); - this._data = null; - } - - ConstantFile.title = "Const File"; - ConstantFile.desc = "Fetches a file from an url"; - ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] }; + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + }; - ConstantFile.prototype.onPropertyChanged = function(name, value) { - if (name == "url") - { - if( value == null || value == "") - this._data = null; - else - { - this.fetchFile(value); - } - } - } + /** + * Assign a data to the global output + * @method setOutputData + * @param {String} name + * @param {String} value + */ + LGraph.prototype.setOutputData = function(name, value) { + var output = this.outputs[name]; + if (!output) { + return; + } + output.value = value; + }; - ConstantFile.prototype.onExecute = function() { - var url = this.getInputData(0) || this.properties.url; - if(url && (url != this._url || this._type != this.properties.type)) - this.fetchFile(url); - this.setOutputData(0, this._data ); + /** + * Returns the current value of a global graph output + * @method getOutputData + * @param {String} name + * @return {*} the data + */ + LGraph.prototype.getOutputData = function(name) { + var output = this.outputs[name]; + if (!output) { + return null; + } + return output.value; }; - ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Renames a global graph output + * @method renameOutput + * @param {String} old_name + * @param {String} new_name + */ + LGraph.prototype.renameOutput = function(old_name, name) { + if (!this.outputs[old_name]) { + return false; + } - ConstantFile.prototype.fetchFile = function(url) { - var that = this; - if(!url || url.constructor !== String) - { - that._data = null; - that.boxcolor = null; - return; - } + if (this.outputs[name]) { + console.error("there is already one output with that name"); + return false; + } - this._url = url; - this._type = this.properties.type; - if (url.substr(0, 4) == "http" && LiteGraph.proxy) { - url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + this.outputs[name] = this.outputs[old_name]; + delete this.outputs[old_name]; + this._version++; + + if (this.onOutputRenamed) { + this.onOutputRenamed(old_name, name); } - fetch(url) - .then(function(response) { - if(!response.ok) - throw new Error("File not found"); - if(that.properties.type == "arraybuffer") - return response.arrayBuffer(); - else if(that.properties.type == "text") - return response.text(); - else if(that.properties.type == "json") - return response.json(); - else if(that.properties.type == "blob") - return response.blob(); - }) - .then(function(data) { - that._data = data; - that.boxcolor = "#AEA"; - }) - .catch(function(error) { - that._data = null; - that.boxcolor = "red"; - console.error("error fetching file:",url); - }); + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } }; - ConstantFile.prototype.onDropFile = function(file) - { - var that = this; - this._url = file.name; - this._type = this.properties.type; - this.properties.url = file.name; - var reader = new FileReader(); - reader.onload = function(e) - { - that.boxcolor = "#AEA"; - var v = e.target.result; - if( that.properties.type == "json" ) - v = JSON.parse(v); - that._data = v; - } - if(that.properties.type == "arraybuffer") - reader.readAsArrayBuffer(file); - else if(that.properties.type == "text" || that.properties.type == "json") - reader.readAsText(file); - else if(that.properties.type == "blob") - return reader.readAsBinaryString(file); - } + /** + * Changes the type of a global graph output + * @method changeOutputType + * @param {String} name + * @param {String} type + */ + LGraph.prototype.changeOutputType = function(name, type) { + if (!this.outputs[name]) { + return false; + } - LiteGraph.registerNodeType("basic/file", ConstantFile); + if ( + this.outputs[name].type && + String(this.outputs[name].type).toLowerCase() == + String(type).toLowerCase() + ) { + return; + } + this.outputs[name].type = type; + this._version++; + if (this.onOutputTypeChanged) { + this.onOutputTypeChanged(name, type); + } + }; -//to store json objects -function JSONParse() { - this.addInput("parse", LiteGraph.ACTION); - this.addInput("json", "string"); - this.addOutput("done", LiteGraph.EVENT); - this.addOutput("object", "object"); - this.widget = this.addWidget("button","parse","",this.parse.bind(this)); - this._str = null; - this._obj = null; -} + /** + * Removes a global graph output + * @method removeOutput + * @param {String} name + */ + LGraph.prototype.removeOutput = function(name) { + if (!this.outputs[name]) { + return false; + } + delete this.outputs[name]; + this._version++; -JSONParse.title = "JSON Parse"; -JSONParse.desc = "Parses JSON String into object"; + if (this.onOutputRemoved) { + this.onOutputRemoved(name); + } -JSONParse.prototype.parse = function() -{ - if(!this._str) - return; + if (this.onInputsOutputsChange) { + this.onInputsOutputsChange(); + } + return true; + }; - try { - this._str = this.getInputData(1); - this._obj = JSON.parse(this._str); - this.boxcolor = "#AEA"; - this.triggerSlot(0); - } catch (err) { - this.boxcolor = "red"; - } -} + LGraph.prototype.triggerInput = function(name, value) { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].onTrigger(value); + } + }; -JSONParse.prototype.onExecute = function() { - this._str = this.getInputData(1); - this.setOutputData(1, this._obj); -}; + LGraph.prototype.setCallback = function(name, func) { + var nodes = this.findNodesByTitle(name); + for (var i = 0; i < nodes.length; ++i) { + nodes[i].setTrigger(func); + } + }; -JSONParse.prototype.onAction = function(name) { - if(name == "parse") - this.parse(); -} + //used for undo, called before any change is made to the graph + LGraph.prototype.beforeChange = function(info) { + if (this.onBeforeChange) { + this.onBeforeChange(this,info); + } + this.sendActionToCanvas("onBeforeChange", this); + }; -LiteGraph.registerNodeType("basic/jsonparse", JSONParse); + //used to resend actions, called after any change is made to the graph + LGraph.prototype.afterChange = function(info) { + if (this.onAfterChange) { + this.onAfterChange(this,info); + } + this.sendActionToCanvas("onAfterChange", this); + }; - //to store json objects - function ConstantData() { - this.addOutput("data", "object"); - this.addProperty("value", ""); - this.widget = this.addWidget("text","json","","value"); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } + LGraph.prototype.connectionChange = function(node, link_info) { + this.updateExecutionOrder(); + if (this.onConnectionChange) { + this.onConnectionChange(node); + } + this._version++; + this.sendActionToCanvas("onConnectionChange"); + }; - ConstantData.title = "Const Data"; - ConstantData.desc = "Constant Data"; + /** + * returns if the graph is in live mode + * @method isLive + */ - ConstantData.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - if (value == null || value == "") { - return; + LGraph.prototype.isLive = function() { + if (!this.list_of_graphcanvas) { + return false; } - try { - this._value = JSON.parse(value); - this.boxcolor = "#AEA"; - } catch (err) { - this.boxcolor = "red"; + for (var i = 0; i < this.list_of_graphcanvas.length; ++i) { + var c = this.list_of_graphcanvas[i]; + if (c.live_mode) { + return true; + } } + return false; }; - ConstantData.prototype.onExecute = function() { - this.setOutputData(0, this._value); + /** + * clears the triggered slot animation in all links (stop visual animation) + * @method clearTriggeredSlots + */ + LGraph.prototype.clearTriggeredSlots = function() { + for (var i in this.links) { + var link_info = this.links[i]; + if (!link_info) { + continue; + } + if (link_info._last_time) { + link_info._last_time = 0; + } + } }; - ConstantData.prototype.setValue = ConstantNumber.prototype.setValue; + /* Called when something visually changed (not the graph!) */ + LGraph.prototype.change = function() { + if (LiteGraph.debug) { + console.log("Graph changed"); + } + this.sendActionToCanvas("setDirty", [true, true]); + if (this.on_change) { + this.on_change(this); + } + }; - LiteGraph.registerNodeType("basic/data", ConstantData); + LGraph.prototype.setDirtyCanvas = function(fg, bg) { + this.sendActionToCanvas("setDirty", [fg, bg]); + }; - //to store json objects - function ConstantArray() { - this._value = []; - this.addInput("json", ""); - this.addOutput("arrayOut", "array"); - this.addOutput("length", "number"); - this.addProperty("value", "[]"); - this.widget = this.addWidget("text","array",this.properties.value,"value"); - this.widgets_up = true; - this.size = [140, 50]; - } + /** + * Destroys a link + * @method removeLink + * @param {Number} link_id + */ + LGraph.prototype.removeLink = function(link_id) { + var link = this.links[link_id]; + if (!link) { + return; + } + var node = this.getNodeById(link.target_id); + if (node) { + node.disconnectInput(link.target_slot); + } + }; - ConstantArray.title = "Const Array"; - ConstantArray.desc = "Constant Array"; + //save and recover app state *************************************** + /** + * Creates a Object containing all the info about this graph, it can be serialized + * @method serialize + * @return {Object} value of the node + */ + LGraph.prototype.serialize = function() { + var nodes_info = []; + for (var i = 0, l = this._nodes.length; i < l; ++i) { + nodes_info.push(this._nodes[i].serialize()); + } - ConstantArray.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; - if (value == null || value == "") { - return; + //pack link info into a non-verbose format + var links = []; + for (var i in this.links) { + //links is an OBJECT + var link = this.links[i]; + if (!link.serialize) { + //weird bug I havent solved yet + console.warn( + "weird LLink bug, link info is not a LLink but a regular object" + ); + var link2 = new LLink(); + for (var j in link) { + link2[j] = link[j]; + } + this.links[i] = link2; + link = link2; + } + + links.push(link.serialize()); } - try { - if(value[0] != "[") - this._value = JSON.parse("[" + value + "]"); - else - this._value = JSON.parse(value); - this.boxcolor = "#AEA"; - } catch (err) { - this.boxcolor = "red"; + var groups_info = []; + for (var i = 0; i < this._groups.length; ++i) { + groups_info.push(this._groups[i].serialize()); } - }; - ConstantArray.prototype.onExecute = function() { - var v = this.getInputData(0); - if(v && v.length) //clone - { - if(!this._value) - this._value = new Array(); - this._value.length = v.length; - for(var i = 0; i < v.length; ++i) - this._value[i] = v[i]; - } - this.setOutputData(0, this._value); - this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); + var data = { + last_node_id: this.last_node_id, + last_link_id: this.last_link_id, + nodes: nodes_info, + links: links, + groups: groups_info, + config: this.config, + extra: this.extra, + version: LiteGraph.VERSION + }; + + if(this.onSerialize) + this.onSerialize(data); + + return data; }; - ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue; + /** + * Configure a graph from a JSON string + * @method configure + * @param {String} str configure a graph from a JSON string + * @param {Boolean} returns if there was any error parsing + */ + LGraph.prototype.configure = function(data, keep_old) { + if (!data) { + return; + } - LiteGraph.registerNodeType("basic/array", ConstantArray); + if (!keep_old) { + this.clear(); + } - function SetArray() - { - this.addInput("arr", "array"); - this.addInput("value", ""); - this.addOutput("arr", "array"); - this.properties = { index: 0 }; - this.widget = this.addWidget("number","i",this.properties.index,"index",{precision: 0, step: 10, min: 0}); - } + var nodes = data.nodes; - SetArray.title = "Set Array"; - SetArray.desc = "Sets index of array"; - - SetArray.prototype.onExecute = function() { - var arr = this.getInputData(0); - if(!arr) - return; - var v = this.getInputData(1); - if(v === undefined ) - return; - if(this.properties.index) - arr[ Math.floor(this.properties.index) ] = v; - this.setOutputData(0,arr); - }; - - LiteGraph.registerNodeType("basic/set_array", SetArray ); - - function ArrayElement() { - this.addInput("array", "array,table,string"); - this.addInput("index", "number"); - this.addOutput("value", ""); - this.addProperty("index",0); - } + //decode links info (they are very verbose) + if (data.links && data.links.constructor === Array) { + var links = []; + for (var i = 0; i < data.links.length; ++i) { + var link_data = data.links[i]; + if(!link_data) //weird bug + { + console.warn("serialized graph link data contains errors, skipping."); + continue; + } + var link = new LLink(); + link.configure(link_data); + links[link.id] = link; + } + data.links = links; + } - ArrayElement.title = "Array[i]"; - ArrayElement.desc = "Returns an element from an array"; + //copy all stored fields + for (var i in data) { + if(i == "nodes" || i == "groups" ) //links must be accepted + continue; + this[i] = data[i]; + } - ArrayElement.prototype.onExecute = function() { - var array = this.getInputData(0); - var index = this.getInputData(1); - if(index == null) - index = this.properties.index; - if(array == null || index == null ) - return; - this.setOutputData(0, array[Math.floor(Number(index))] ); - }; + var error = false; + + //create nodes + this._nodes = []; + if (nodes) { + for (var i = 0, l = nodes.length; i < l; ++i) { + var n_info = nodes[i]; //stored info + var node = LiteGraph.createNode(n_info.type, n_info.title); + if (!node) { + if (LiteGraph.debug) { + console.log( + "Node not found or has errors: " + n_info.type + ); + } - LiteGraph.registerNodeType("basic/array[]", ArrayElement); + //in case of error we create a replacement node to avoid losing info + node = new LGraphNode(); + node.last_serialization = n_info; + node.has_errors = true; + error = true; + //continue; + } - function TableElement() { - this.addInput("table", "table"); - this.addInput("row", "number"); - this.addInput("col", "number"); - this.addOutput("value", ""); - this.addProperty("row",0); - this.addProperty("column",0); - } + node.id = n_info.id; //id it or it will create a new id + this.add(node, true); //add before configure, otherwise configure cannot create links + } - TableElement.title = "Table[row][col]"; - TableElement.desc = "Returns an element from a table"; + //configure nodes afterwards so they can reach each other + for (var i = 0, l = nodes.length; i < l; ++i) { + var n_info = nodes[i]; + var node = this.getNodeById(n_info.id); + if (node) { + node.configure(n_info); + } + } + } - TableElement.prototype.onExecute = function() { - var table = this.getInputData(0); - var row = this.getInputData(1); - var col = this.getInputData(2); - if(row == null) - row = this.properties.row; - if(col == null) - col = this.properties.column; - if(table == null || row == null || col == null) - return; - var row = table[Math.floor(Number(row))]; - if(row) - this.setOutputData(0, row[Math.floor(Number(col))] ); - else - this.setOutputData(0, null ); - }; + //groups + this._groups.length = 0; + if (data.groups) { + for (var i = 0; i < data.groups.length; ++i) { + var group = new LiteGraph.LGraphGroup(); + group.configure(data.groups[i]); + this.add(group); + } + } - LiteGraph.registerNodeType("basic/table[][]", TableElement); + this.updateExecutionOrder(); - function ObjectProperty() { - this.addInput("obj", "object"); - this.addOutput("property", 0); - this.addProperty("value", 0); - this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); - this.widgets_up = true; - this.size = [140, 30]; - this._value = null; - } + this.extra = data.extra || {}; - ObjectProperty.title = "Object property"; - ObjectProperty.desc = "Outputs the property of an object"; + if(this.onConfigure) + this.onConfigure(data); - ObjectProperty.prototype.setValue = function(v) { - this.properties.value = v; - this.widget.value = v; + this._version++; + this.setDirtyCanvas(true, true); + return error; }; - ObjectProperty.prototype.getTitle = function() { - if (this.flags.collapsed) { - return "in." + this.properties.value; - } - return this.title; - }; + LGraph.prototype.load = function(url, callback) { + var that = this; - ObjectProperty.prototype.onPropertyChanged = function(name, value) { - this.widget.value = value; + //from file + if(url.constructor === File || url.constructor === Blob) + { + var reader = new FileReader(); + reader.addEventListener('load', function(event) { + var data = JSON.parse(event.target.result); + that.configure(data); + if(callback) + callback(); + }); + + reader.readAsText(url); + return; + } + + //is a string, then an URL + var req = new XMLHttpRequest(); + req.open("GET", url, true); + req.send(null); + req.onload = function(oEvent) { + if (req.status !== 200) { + console.error("Error loading graph:", req.status, req.response); + return; + } + var data = JSON.parse( req.response ); + that.configure(data); + if(callback) + callback(); + }; + req.onerror = function(err) { + console.error("Error loading graph:", err); + }; }; - ObjectProperty.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, data[this.properties.value]); - } + LGraph.prototype.onNodeTrace = function(node, msg, color) { + //TODO }; - LiteGraph.registerNodeType("basic/object_property", ObjectProperty); + //this is the class in charge of storing link information + function LLink(id, type, origin_id, origin_slot, target_id, target_slot) { + this.id = id; + this.type = type; + this.origin_id = origin_id; + this.origin_slot = origin_slot; + this.target_id = target_id; + this.target_slot = target_slot; - function ObjectKeys() { - this.addInput("obj", ""); - this.addOutput("keys", "array"); - this.size = [140, 30]; + this._data = null; + this._pos = new Float32Array(2); //center } - ObjectKeys.title = "Object keys"; - ObjectKeys.desc = "Outputs an array with the keys of an object"; - - ObjectKeys.prototype.onExecute = function() { - var data = this.getInputData(0); - if (data != null) { - this.setOutputData(0, Object.keys(data) ); + LLink.prototype.configure = function(o) { + if (o.constructor === Array) { + this.id = o[0]; + this.origin_id = o[1]; + this.origin_slot = o[2]; + this.target_id = o[3]; + this.target_slot = o[4]; + this.type = o[5]; + } else { + this.id = o.id; + this.type = o.type; + this.origin_id = o.origin_id; + this.origin_slot = o.origin_slot; + this.target_id = o.target_id; + this.target_slot = o.target_slot; } }; - LiteGraph.registerNodeType("basic/object_keys", ObjectKeys); + LLink.prototype.serialize = function() { + return [ + this.id, + this.origin_id, + this.origin_slot, + this.target_id, + this.target_slot, + this.type + ]; + }; + LiteGraph.LLink = LLink; + + // ************************************************************* + // Node CLASS ******* + // ************************************************************* + + /* + title: string + pos: [x,y] + size: [x,y] + + input|output: every connection + + { name:string, type:string, pos: [x,y]=Optional, direction: "input"|"output", links: Array }); + + general properties: + + clip_area: if you render outside the node, it will be clipped + + unsafe_execution: not allowed for safe execution + + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + + resizable: if set to false it wont be resizable with the mouse + + horizontal: slots are distributed horizontally (applies to both inputs and outputs) + + inputs_horizontal: input slots are distributed horizontally (overrides horizontal for inputs) + + outputs_horizontal: output slots are distributed horizontally (overrides horizontal for outputs) + + widgets_start_y: widgets start at y distance from the top of the node + + flags object: + + collapsed: if it is collapsed + + supported callbacks: + + onAdded: when added to graph (warning: this is called BEFORE the node is configured when loading) + + onRemoved: when removed from graph + + onStart: when the graph starts playing + + onStop: when the graph stops playing + + onDrawForeground: render the inside widgets inside the node + + onDrawBackground: render the background area inside the node (only in edit mode) + + onMouseDown + + onMouseMove + + onMouseUp + + onMouseEnter + + onMouseLeave + + onExecute: execute the node + + onPropertyChanged: when a property is changed in the panel (return true to skip default behaviour) + + onGetInputs: returns an array of possible inputs + + onGetOutputs: returns an array of possible outputs + + onBounding: in case this node has a bigger bounding than the node itself (the callback receives the bounding as [x,y,w,h]) + + onDblClick: double clicked in the node + + onInputDblClick: input slot double clicked (can be used to automatically create a node connected) + + onOutputDblClick: output slot double clicked (can be used to automatically create a node connected) + + onConfigure: called after the node has been configured + + onSerialize: to add extra info when serializing (the callback receives the object that should be filled with the data) + + onSelected + + onDeselected + + onDropItem : DOM item dropped over the node + + onDropFile : file dropped over the node + + onConnectInput : if returns false the incoming connection will be canceled + + onConnectionsChange : a connection changed (new one or removed) (LiteGraph.INPUT or LiteGraph.OUTPUT, slot, true if connected, link_info, input_info ) + + onAction: action slot triggered + + getExtraMenuOptions: to add option to context menu +*/ + + /** + * Base Class for all the node type classes + * @class LGraphNode + * @param {String} name a name for the node + */ + + function LGraphNode(title) { + this._ctor(title); + } - function SetObject() - { - this.addInput("obj", ""); - this.addInput("value", ""); - this.addOutput("obj", ""); - this.properties = { property: "" }; - this.name_widget = this.addWidget("text","prop.",this.properties.property,"property"); - } + global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; - SetObject.title = "Set Object"; - SetObject.desc = "Adds propertiesrty to object"; + LGraphNode.prototype._ctor = function(title) { + this.title = title || "Unnamed"; + this.size = [LiteGraph.NODE_WIDTH, 60]; + this.graph = null; - SetObject.prototype.onExecute = function() { - var obj = this.getInputData(0); - if(!obj) - return; - var v = this.getInputData(1); - if(v === undefined ) - return; - if(this.properties.property) - obj[ this.properties.property ] = v; - this.setOutputData(0,obj); - }; + this._pos = new Float32Array(10, 10); - LiteGraph.registerNodeType("basic/set_object", SetObject ); + Object.defineProperty(this, "pos", { + set: function(v) { + if (!v || v.length < 2) { + return; + } + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() { + return this._pos; + }, + enumerable: true + }); + if (LiteGraph.use_uuids) { + this.id = LiteGraph.uuidv4(); + } + else { + this.id = -1; //not know till not added + } + this.type = null; - function MergeObjects() { - this.addInput("A", "object"); - this.addInput("B", "object"); - this.addOutput("out", "object"); - this._result = {}; - var that = this; - this.addWidget("button","clear","",function(){ - that._result = {}; - }); - this.size = this.computeSize(); - } + //inputs available: array of inputs + this.inputs = []; + this.outputs = []; + this.connections = []; - MergeObjects.title = "Merge Objects"; - MergeObjects.desc = "Creates an object copying properties from others"; + //local data + this.properties = {}; //for the values + this.properties_info = []; //for the info - MergeObjects.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this._result; - if(A) - for(var i in A) - C[i] = A[i]; - if(B) - for(var i in B) - C[i] = B[i]; - this.setOutputData(0,C); + this.flags = {}; }; - LiteGraph.registerNodeType("basic/merge_objects", MergeObjects ); - - //Store as variable - function Variable() { - this.size = [60, 30]; - this.addInput("in"); - this.addOutput("out"); - this.properties = { varname: "myname", container: Variable.LITEGRAPH }; - this.value = null; - } + /** + * configure a node from an object containing the serialized info + * @method configure + */ + LGraphNode.prototype.configure = function(info) { + if (this.graph) { + this.graph._version++; + } + for (var j in info) { + if (j == "properties") { + //i don't want to clone properties, I want to reuse the old container + for (var k in info.properties) { + this.properties[k] = info.properties[k]; + if (this.onPropertyChanged) { + this.onPropertyChanged( k, info.properties[k] ); + } + } + continue; + } - Variable.title = "Variable"; - Variable.desc = "store/read variable value"; + if (info[j] == null) { + continue; + } else if (typeof info[j] == "object") { + //object + if (this[j] && this[j].configure) { + this[j].configure(info[j]); + } else { + this[j] = LiteGraph.cloneObject(info[j], this[j]); + } + } //value + else { + this[j] = info[j]; + } + } - Variable.LITEGRAPH = 0; //between all graphs - Variable.GRAPH = 1; //only inside this graph - Variable.GLOBALSCOPE = 2; //attached to Window + if (!info.title) { + this.title = this.constructor.title; + } - Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} }; + if (this.inputs) { + for (var i = 0; i < this.inputs.length; ++i) { + var input = this.inputs[i]; + var link_info = this.graph ? this.graph.links[input.link] : null; + if (this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated - Variable.prototype.onExecute = function() { - var container = this.getContainer(); + if( this.onInputAdded ) + this.onInputAdded(input); - if(this.isInputConnected(0)) - { - this.value = this.getInputData(0); - container[ this.properties.varname ] = this.value; - this.setOutputData(0, this.value ); - return; + } } - this.setOutputData( 0, container[ this.properties.varname ] ); - }; + if (this.outputs) { + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + if (!output.links) { + continue; + } + for (var j = 0; j < output.links.length; ++j) { + var link_info = this.graph ? this.graph.links[output.links[j]] : null; + if (this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated + } - Variable.prototype.getContainer = function() - { - switch(this.properties.container) + if( this.onOutputAdded ) + this.onOutputAdded(output); + } + } + + if( this.widgets ) { - case Variable.GRAPH: - if(this.graph) - return this.graph.vars; - return {}; - break; - case Variable.GLOBALSCOPE: - return global; - break; - case Variable.LITEGRAPH: - default: - return LiteGraph.Globals; - break; + for (var i = 0; i < this.widgets.length; ++i) + { + var w = this.widgets[i]; + if(!w) + continue; + if(w.options && w.options.property && (this.properties[ w.options.property ] != undefined)) + w.value = JSON.parse( JSON.stringify( this.properties[ w.options.property ] ) ); + } + if (info.widgets_values) { + for (var i = 0; i < info.widgets_values.length; ++i) { + if (this.widgets[i]) { + this.widgets[i].value = info.widgets_values[i]; + } + } + } } - } - Variable.prototype.getTitle = function() { - return this.properties.varname; + if (this.onConfigure) { + this.onConfigure(info); + } }; - LiteGraph.registerNodeType("basic/variable", Variable); + /** + * serialize the content + * @method serialize + */ + + LGraphNode.prototype.serialize = function() { + //create serialization object + var o = { + id: this.id, + type: this.type, + pos: this.pos, + size: this.size, + flags: LiteGraph.cloneObject(this.flags), + order: this.order, + mode: this.mode + }; - function length(v) { - if(v && v.length != null) - return Number(v.length); - return 0; - } + //special case for when there were errors + if (this.constructor === LGraphNode && this.last_serialization) { + return this.last_serialization; + } - LiteGraph.wrapFunctionAsNode( - "basic/length", - length, - [""], - "number" - ); + if (this.inputs) { + o.inputs = this.inputs; + } - function length(v) { - if(v && v.length != null) - return Number(v.length); - return 0; - } + if (this.outputs) { + //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) + for (var i = 0; i < this.outputs.length; i++) { + delete this.outputs[i]._data; + } + o.outputs = this.outputs; + } - LiteGraph.wrapFunctionAsNode( - "basic/not", - function(a){ return !a; }, - [""], - "boolean" - ); + if (this.title && this.title != this.constructor.title) { + o.title = this.title; + } - function DownloadData() { - this.size = [60, 30]; - this.addInput("data", 0 ); - this.addInput("download", LiteGraph.ACTION ); - this.properties = { filename: "data.json" }; - this.value = null; - var that = this; - this.addWidget("button","Download","", function(v){ - if(!that.value) - return; - that.downloadAsFile(); - }); - } + if (this.properties) { + o.properties = LiteGraph.cloneObject(this.properties); + } - DownloadData.title = "Download"; - DownloadData.desc = "Download some data"; + if (this.widgets && this.serialize_widgets) { + o.widgets_values = []; + for (var i = 0; i < this.widgets.length; ++i) { + if(this.widgets[i]) + o.widgets_values[i] = this.widgets[i].value; + else + o.widgets_values[i] = null; + } + } - DownloadData.prototype.downloadAsFile = function() - { - if(this.value == null) - return; + if (!o.type) { + o.type = this.constructor.type; + } - var str = null; - if(this.value.constructor === String) - str = this.value; - else - str = JSON.stringify(this.value); + if (this.color) { + o.color = this.color; + } + if (this.bgcolor) { + o.bgcolor = this.bgcolor; + } + if (this.boxcolor) { + o.boxcolor = this.boxcolor; + } + if (this.shape) { + o.shape = this.shape; + } - var file = new Blob([str]); - var url = URL.createObjectURL( file ); - var element = document.createElement("a"); - element.setAttribute('href', url); - element.setAttribute('download', this.properties.filename ); - element.style.display = 'none'; - document.body.appendChild(element); - element.click(); - document.body.removeChild(element); - setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url - } + if (this.onSerialize) { + if (this.onSerialize(o)) { + console.warn( + "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter" + ); + } + } - DownloadData.prototype.onAction = function(action, param) { - var that = this; - setTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup - } + return o; + }; - DownloadData.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); + /* Creates a clone of this node */ + LGraphNode.prototype.clone = function() { + var node = LiteGraph.createNode(this.type); + if (!node) { + return null; } - }; - DownloadData.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.properties.filename; + //we clone it because serialize returns shared containers + var data = LiteGraph.cloneObject(this.serialize()); + + //remove links + if (data.inputs) { + for (var i = 0; i < data.inputs.length; ++i) { + data.inputs[i].link = null; + } } - return this.title; - }; - LiteGraph.registerNodeType("basic/download", DownloadData); + if (data.outputs) { + for (var i = 0; i < data.outputs.length; ++i) { + if (data.outputs[i].links) { + data.outputs[i].links.length = 0; + } + } + } + delete data["id"]; + if (LiteGraph.use_uuids) { + data["id"] = LiteGraph.uuidv4() + } - //Watch a value in the editor - function Watch() { - this.size = [60, 30]; - this.addInput("value", 0, { label: "" }); - this.value = 0; - } + //remove links + node.configure(data); - Watch.title = "Watch"; - Watch.desc = "Show value of input"; + return node; + }; - Watch.prototype.onExecute = function() { - if (this.inputs[0]) { - this.value = this.getInputData(0); - } + /** + * serialize and stringify + * @method toString + */ + + LGraphNode.prototype.toString = function() { + return JSON.stringify(this.serialize()); }; + //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph - Watch.prototype.getTitle = function() { - if (this.flags.collapsed) { - return this.inputs[0].label; - } - return this.title; + /** + * get the title string + * @method getTitle + */ + + LGraphNode.prototype.getTitle = function() { + return this.title || this.constructor.title; }; - Watch.toString = function(o) { - if (o == null) { - return "null"; - } else if (o.constructor === Number) { - return o.toFixed(3); - } else if (o.constructor === Array) { - var str = "["; - for (var i = 0; i < o.length; ++i) { - str += Watch.toString(o[i]) + (i + 1 != o.length ? "," : ""); - } - str += "]"; - return str; - } else { - return String(o); + /** + * sets the value of a property + * @method setProperty + * @param {String} name + * @param {*} value + */ + LGraphNode.prototype.setProperty = function(name, value) { + if (!this.properties) { + this.properties = {}; } + if( value === this.properties[name] ) + return; + var prev_value = this.properties[name]; + this.properties[name] = value; + if (this.onPropertyChanged) { + if( this.onPropertyChanged(name, value, prev_value) === false ) //abort change + this.properties[name] = prev_value; + } + if(this.widgets) //widgets could be linked to properties + for(var i = 0; i < this.widgets.length; ++i) + { + var w = this.widgets[i]; + if(!w) + continue; + if(w.options.property == name) + { + w.value = value; + break; + } + } }; - Watch.prototype.onDrawBackground = function(ctx) { - //show the current value - this.inputs[0].label = Watch.toString(this.value); - }; + // Execution ************************* + /** + * sets the output data + * @method setOutputData + * @param {number} slot + * @param {*} data + */ + LGraphNode.prototype.setOutputData = function(slot, data) { + if (!this.outputs) { + return; + } - LiteGraph.registerNodeType("basic/watch", Watch); + //this maybe slow and a niche case + //if(slot && slot.constructor === String) + // slot = this.findOutputSlot(slot); - //in case one type doesnt match other type but you want to connect them anyway - function Cast() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [40, 30]; - } + if (slot == -1 || slot >= this.outputs.length) { + return; + } - Cast.title = "Cast"; - Cast.desc = "Allows to connect different types"; + var output_info = this.outputs[slot]; + if (!output_info) { + return; + } - Cast.prototype.onExecute = function() { - this.setOutputData(0, this.getInputData(0)); + //store data in the output itself in case we want to debug + output_info._data = data; + + //if there are connections, pass the data to the connections + if (this.outputs[slot].links) { + for (var i = 0; i < this.outputs[slot].links.length; i++) { + var link_id = this.outputs[slot].links[i]; + var link = this.graph.links[link_id]; + if(link) + link.data = data; + } + } }; - LiteGraph.registerNodeType("basic/cast", Cast); + /** + * sets the output data type, useful when you want to be able to overwrite the data type + * @method setOutputDataType + * @param {number} slot + * @param {String} datatype + */ + LGraphNode.prototype.setOutputDataType = function(slot, type) { + if (!this.outputs) { + return; + } + if (slot == -1 || slot >= this.outputs.length) { + return; + } + var output_info = this.outputs[slot]; + if (!output_info) { + return; + } + //store data in the output itself in case we want to debug + output_info.type = type; + + //if there are connections, pass the data to the connections + if (this.outputs[slot].links) { + for (var i = 0; i < this.outputs[slot].links.length; i++) { + var link_id = this.outputs[slot].links[i]; + this.graph.links[link_id].type = type; + } + } + }; - //Show value inside the debug console - function Console() { - this.mode = LiteGraph.ON_EVENT; - this.size = [80, 30]; - this.addProperty("msg", ""); - this.addInput("log", LiteGraph.EVENT); - this.addInput("msg", 0); - } + /** + * Retrieves the input data (data traveling through the connection) from one slot + * @method getInputData + * @param {number} slot + * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link + * @return {*} data or if it is not connected returns undefined + */ + LGraphNode.prototype.getInputData = function(slot, force_update) { + if (!this.inputs) { + return; + } //undefined; - Console.title = "Console"; - Console.desc = "Show value inside the console"; + if (slot >= this.inputs.length || this.inputs[slot].link == null) { + return; + } - Console.prototype.onAction = function(action, param) { - // param is the action - var msg = this.getInputData(1); //getInputDataByName("msg"); - //if (msg == null || typeof msg == "undefined") return; - if (!msg) msg = this.properties.msg; - if (!msg) msg = "Event: "+param; // msg is undefined if the slot is lost? - if (action == "log") { - console.log(msg); - } else if (action == "warn") { - console.warn(msg); - } else if (action == "error") { - console.error(msg); + var link_id = this.inputs[slot].link; + var link = this.graph.links[link_id]; + if (!link) { + //bug: weird case but it happens sometimes + return null; } - }; - Console.prototype.onExecute = function() { - var msg = this.getInputData(1); //getInputDataByName("msg"); - if (!msg) msg = this.properties.msg; - if (msg != null && typeof msg != "undefined") { - this.properties.msg = msg; - console.log(msg); + if (!force_update) { + return link.data; } - }; - Console.prototype.onGetInputs = function() { - return [ - ["log", LiteGraph.ACTION], - ["warn", LiteGraph.ACTION], - ["error", LiteGraph.ACTION] - ]; - }; + //special case: used to extract data from the incoming connection before the graph has been executed + var node = this.graph.getNodeById(link.origin_id); + if (!node) { + return link.data; + } - LiteGraph.registerNodeType("basic/console", Console); + if (node.updateOutputData) { + node.updateOutputData(link.origin_slot); + } else if (node.onExecute) { + node.onExecute(); + } - //Show value inside the debug console - function Alert() { - this.mode = LiteGraph.ON_EVENT; - this.addProperty("msg", ""); - this.addInput("", LiteGraph.EVENT); - var that = this; - this.widget = this.addWidget("text", "Text", "", "msg"); - this.widgets_up = true; - this.size = [200, 30]; - } + return link.data; + }; - Alert.title = "Alert"; - Alert.desc = "Show an alert window"; - Alert.color = "#510"; + /** + * Retrieves the input data type (in case this supports multiple input types) + * @method getInputDataType + * @param {number} slot + * @return {String} datatype in string format + */ + LGraphNode.prototype.getInputDataType = function(slot) { + if (!this.inputs) { + return null; + } //undefined; - Alert.prototype.onConfigure = function(o) { - this.widget.value = o.properties.msg; + if (slot >= this.inputs.length || this.inputs[slot].link == null) { + return null; + } + var link_id = this.inputs[slot].link; + var link = this.graph.links[link_id]; + if (!link) { + //bug: weird case but it happens sometimes + return null; + } + var node = this.graph.getNodeById(link.origin_id); + if (!node) { + return link.type; + } + var output_info = node.outputs[link.origin_slot]; + if (output_info) { + return output_info.type; + } + return null; }; - Alert.prototype.onAction = function(action, param) { - var msg = this.properties.msg; - setTimeout(function() { - alert(msg); - }, 10); + /** + * Retrieves the input data from one slot using its name instead of slot number + * @method getInputDataByName + * @param {String} slot_name + * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link + * @return {*} data or if it is not connected returns null + */ + LGraphNode.prototype.getInputDataByName = function( + slot_name, + force_update + ) { + var slot = this.findInputSlot(slot_name); + if (slot == -1) { + return null; + } + return this.getInputData(slot, force_update); }; - LiteGraph.registerNodeType("basic/alert", Alert); + /** + * tells you if there is a connection in one input slot + * @method isInputConnected + * @param {number} slot + * @return {boolean} + */ + LGraphNode.prototype.isInputConnected = function(slot) { + if (!this.inputs) { + return false; + } + return slot < this.inputs.length && this.inputs[slot].link != null; + }; - //Execites simple code - function NodeScript() { - this.size = [60, 30]; - this.addProperty("onExecute", "return A;"); - this.addInput("A", 0); - this.addInput("B", 0); - this.addOutput("out", 0); + /** + * tells you info about an input connection (which node, type, etc) + * @method getInputInfo + * @param {number} slot + * @return {Object} object or null { link: id, name: string, type: string or 0 } + */ + LGraphNode.prototype.getInputInfo = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + return this.inputs[slot]; + } + return null; + }; - this._func = null; - this.data = {}; - } + /** + * Returns the link info in the connection of an input slot + * @method getInputLink + * @param {number} slot + * @return {LLink} object or null + */ + LGraphNode.prototype.getInputLink = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + var slot_info = this.inputs[slot]; + return this.graph.links[ slot_info.link ]; + } + return null; + }; - NodeScript.prototype.onConfigure = function(o) { - if (o.properties.onExecute && LiteGraph.allow_scripts) - this.compileCode(o.properties.onExecute); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + /** + * returns the node connected in the input slot + * @method getInputNode + * @param {number} slot + * @return {LGraphNode} node or null + */ + LGraphNode.prototype.getInputNode = function(slot) { + if (!this.inputs) { + return null; + } + if (slot >= this.inputs.length) { + return null; + } + var input = this.inputs[slot]; + if (!input || input.link === null) { + return null; + } + var link_info = this.graph.links[input.link]; + if (!link_info) { + return null; + } + return this.graph.getNodeById(link_info.origin_id); }; - NodeScript.title = "Script"; - NodeScript.desc = "executes a code (max 256 characters)"; + /** + * returns the value of an input with this name, otherwise checks if there is a property with that name + * @method getInputOrProperty + * @param {string} name + * @return {*} value + */ + LGraphNode.prototype.getInputOrProperty = function(name) { + if (!this.inputs || !this.inputs.length) { + return this.properties ? this.properties[name] : null; + } - NodeScript.widgets_info = { - onExecute: { type: "code" } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input_info = this.inputs[i]; + if (name == input_info.name && input_info.link != null) { + var link = this.graph.links[input_info.link]; + if (link) { + return link.data; + } + } + } + return this.properties[name]; }; - NodeScript.prototype.onPropertyChanged = function(name, value) { - if (name == "onExecute" && LiteGraph.allow_scripts) - this.compileCode(value); - else - console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + /** + * tells you the last output data that went in that slot + * @method getOutputData + * @param {number} slot + * @return {Object} object or null + */ + LGraphNode.prototype.getOutputData = function(slot) { + if (!this.outputs) { + return null; + } + if (slot >= this.outputs.length) { + return null; + } + + var info = this.outputs[slot]; + return info._data; }; - NodeScript.prototype.compileCode = function(code) { - this._func = null; - if (code.length > 256) { - console.warn("Script too long, max 256 chars"); - } else { - var code_low = code.toLowerCase(); - var forbidden_words = [ - "script", - "body", - "document", - "eval", - "nodescript", - "function" - ]; //bad security solution - for (var i = 0; i < forbidden_words.length; ++i) { - if (code_low.indexOf(forbidden_words[i]) != -1) { - console.warn("invalid script"); - return; - } - } - try { - this._func = new Function("A", "B", "C", "DATA", "node", code); - } catch (err) { - console.error("Error parsing script"); - console.error(err); - } + /** + * tells you info about an output connection (which node, type, etc) + * @method getOutputInfo + * @param {number} slot + * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] } + */ + LGraphNode.prototype.getOutputInfo = function(slot) { + if (!this.outputs) { + return null; } + if (slot < this.outputs.length) { + return this.outputs[slot]; + } + return null; }; - NodeScript.prototype.onExecute = function() { - if (!this._func) { - return; + /** + * tells you if there is a connection in one output slot + * @method isOutputConnected + * @param {number} slot + * @return {boolean} + */ + LGraphNode.prototype.isOutputConnected = function(slot) { + if (!this.outputs) { + return false; } + return ( + slot < this.outputs.length && + this.outputs[slot].links && + this.outputs[slot].links.length + ); + }; - try { - var A = this.getInputData(0); - var B = this.getInputData(1); - var C = this.getInputData(2); - this.setOutputData(0, this._func(A, B, C, this.data, this)); - } catch (err) { - console.error("Error in script"); - console.error(err); + /** + * tells you if there is any connection in the output slots + * @method isAnyOutputConnected + * @return {boolean} + */ + LGraphNode.prototype.isAnyOutputConnected = function() { + if (!this.outputs) { + return false; + } + for (var i = 0; i < this.outputs.length; ++i) { + if (this.outputs[i].links && this.outputs[i].links.length) { + return true; + } } + return false; }; - NodeScript.prototype.onGetOutputs = function() { - return [["C", ""]]; + /** + * retrieves all the nodes connected to this output slot + * @method getOutputNodes + * @param {number} slot + * @return {array} + */ + LGraphNode.prototype.getOutputNodes = function(slot) { + if (!this.outputs || this.outputs.length == 0) { + return null; + } + + if (slot >= this.outputs.length) { + return null; + } + + var output = this.outputs[slot]; + if (!output.links || output.links.length == 0) { + return null; + } + + var r = []; + for (var i = 0; i < output.links.length; i++) { + var link_id = output.links[i]; + var link = this.graph.links[link_id]; + if (link) { + var target_node = this.graph.getNodeById(link.target_id); + if (target_node) { + r.push(target_node); + } + } + } + return r; }; - LiteGraph.registerNodeType("basic/script", NodeScript); + LGraphNode.prototype.addOnTriggerInput = function(){ + var trigS = this.findInputSlot("onTrigger"); + if (trigS == -1){ //!trigS || + var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); + return this.findInputSlot("onTrigger"); + } + return trigS; + } + LGraphNode.prototype.addOnExecutedOutput = function(){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS == -1){ //!trigS || + var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); + return this.findOutputSlot("onExecuted"); + } + return trigS; + } - function GenericCompare() { - this.addInput("A", 0); - this.addInput("B", 0); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "==", "enum", { values: GenericCompare.values }); - this.addWidget("combo","Op.",this.properties.OP,{ property: "OP", values: GenericCompare.values } ); + LGraphNode.prototype.onAfterExecuteNode = function(param, options){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS != -1){ + + //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); + //console.debug(param); + //console.debug(options); + this.triggerSlot(trigS, param, null, options); + + } + } + + LGraphNode.prototype.changeMode = function(modeTo){ + switch(modeTo){ + case LiteGraph.ON_EVENT: + // this.addOnExecutedOutput(); + break; + + case LiteGraph.ON_TRIGGER: + this.addOnTriggerInput(); + this.addOnExecutedOutput(); + break; + + case LiteGraph.NEVER: + break; + + case LiteGraph.ALWAYS: + break; + + case LiteGraph.ON_REQUEST: + break; + + default: + return false; + break; + } + this.mode = modeTo; + return true; + }; - this.size = [80, 60]; + /** + * Triggers the execution of actions that were deferred when the action was triggered + * @method executePendingActions + */ + LGraphNode.prototype.executePendingActions = function() { + if(!this._waiting_actions || !this._waiting_actions.length) + return; + for(var i = 0; i < this._waiting_actions.length;++i) + { + var p = this._waiting_actions[i]; + this.onAction(p[0],p[1],p[2],p[3],p[4]); + } + this._waiting_actions.length = 0; } - GenericCompare.values = ["==", "!="]; //[">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; - GenericCompare["@OP"] = { - type: "enum", - title: "operation", - values: GenericCompare.values + + /** + * Triggers the node code execution, place a boolean/counter to mark the node as being executed + * @method doExecute + * @param {*} param + * @param {*} options + */ + LGraphNode.prototype.doExecute = function(param, options) { + options = options || {}; + if (this.onExecute){ + + // enable this to give the event an ID + if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); + + this.graph.nodes_executing[this.id] = true; //.push(this.id); + + this.onExecute(param, options); + + this.graph.nodes_executing[this.id] = false; //.pop(); + + // save execution/action ref + this.exec_version = this.graph.iteration; + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; + } + } + else { + } + this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event + if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback + }; + + /** + * Triggers an action, wrapped by logics to control execution flow + * @method actionDo + * @param {String} action name + * @param {*} param + */ + LGraphNode.prototype.actionDo = function(action, param, options, action_slot ) { + options = options || {}; + if (this.onAction){ + + // enable this to give the event an ID + if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); + + this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); + + this.onAction(action, param, options, action_slot); + + this.graph.nodes_actioning[this.id] = false; //.pop(); + + // save execution/action ref + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; + } + } + this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event + if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); }; + + /** + * Triggers an event in this node, this will trigger any output with the same name + * @method trigger + * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all + * @param {*} param + */ + LGraphNode.prototype.trigger = function(action, param, options) { + if (!this.outputs || !this.outputs.length) { + return; + } - GenericCompare.title = "Compare *"; - GenericCompare.desc = "evaluates condition between A and B"; + if (this.graph) + this.graph._last_trigger_time = LiteGraph.getTime(); - GenericCompare.prototype.getTitle = function() { - return "*A " + this.properties.OP + " *B"; + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) + continue; + this.triggerSlot(i, param, null, options); + } }; - GenericCompare.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A === undefined) { - A = this.properties.A; - } else { - this.properties.A = A; + /** + * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes + * @method triggerSlot + * @param {Number} slot the index of the output slot + * @param {*} param + * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot + */ + LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { + options = options || {}; + if (!this.outputs) { + return; } - var B = this.getInputData(1); - if (B === undefined) { - B = this.properties.B; - } else { - this.properties.B = B; + if(slot == null) + { + console.error("slot must be a number"); + return; + } + + if(slot.constructor !== Number) + console.warn("slot must be a number, use node.trigger('name') if you want to use a string"); + + var output = this.outputs[slot]; + if (!output) { + return; } - var result = false; - if (typeof A == typeof B){ - switch (this.properties.OP) { - case "==": - case "!=": - // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual() - result = true; - switch(typeof A){ - case "object": - var aProps = Object.getOwnPropertyNames(A); - var bProps = Object.getOwnPropertyNames(B); - if (aProps.length != bProps.length){ - result = false; - break; - } - for (var i = 0; i < aProps.length; i++) { - var propName = aProps[i]; - if (A[propName] !== B[propName]) { - result = false; - break; - } - } - break; - default: - result = A == B; - } - if (this.properties.OP == "!=") result = !result; - break; - /*case ">": - result = A > B; - break; - case "<": - result = A < B; - break; - case "<=": - result = A <= B; - break; - case ">=": - result = A >= B; - break; - case "||": - result = A || B; - break; - case "&&": - result = A && B; - break;*/ - } + var links = output.links; + if (!links || !links.length) { + return; } - this.setOutputData(0, result); - this.setOutputData(1, !result); - }; - LiteGraph.registerNodeType("basic/CompareValues", GenericCompare); - -})(this); - -//event related nodes -(function(global) { - var LiteGraph = global.LiteGraph; + if (this.graph) { + this.graph._last_trigger_time = LiteGraph.getTime(); + } - //Show value inside the debug console - function LogEvent() { - this.size = [60, 30]; - this.addInput("event", LiteGraph.ACTION); - } + //for every link attached here + for (var k = 0; k < links.length; ++k) { + var id = links[k]; + if (link_id != null && link_id != id) { + //to skip links + continue; + } + var link_info = this.graph.links[links[k]]; + if (!link_info) { + //not connected + continue; + } + link_info._last_time = LiteGraph.getTime(); + var node = this.graph.getNodeById(link_info.target_id); + if (!node) { + //node not found? + continue; + } - LogEvent.title = "Log Event"; - LogEvent.desc = "Log event in console"; + //used to mark events in graph + var target_connection = node.inputs[link_info.target_slot]; - LogEvent.prototype.onAction = function(action, param, options) { - console.log(action, param); + if (node.mode === LiteGraph.ON_TRIGGER) + { + // generate unique trigger ID if not present + if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); + if (node.onExecute) { + // -- wrapping node.onExecute(param); -- + node.doExecute(param, options); + } + } + else if (node.onAction) { + // generate unique action ID if not present + if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); + //pass the action name + var target_connection = node.inputs[link_info.target_slot]; + + //instead of executing them now, it will be executed in the next graph loop, to ensure data flow + if(LiteGraph.use_deferred_actions && node.onExecute) + { + if(!node._waiting_actions) + node._waiting_actions = []; + node._waiting_actions.push([target_connection.name, param, options, link_info.target_slot]); + } + else + { + // wrap node.onAction(target_connection.name, param); + node.actionDo( target_connection.name, param, options, link_info.target_slot ); + } + } + } }; - LiteGraph.registerNodeType("events/log", LogEvent); + /** + * clears the trigger slot animation + * @method clearTriggeredSlot + * @param {Number} slot the index of the output slot + * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot + */ + LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) { + if (!this.outputs) { + return; + } - //convert to Event if the value is true - function TriggerEvent() { - this.size = [60, 30]; - this.addInput("if", ""); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("change", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.properties = { only_on_change: true }; - this.prev = 0; - } + var output = this.outputs[slot]; + if (!output) { + return; + } - TriggerEvent.title = "TriggerEvent"; - TriggerEvent.desc = "Triggers event if input evaluates to true"; + var links = output.links; + if (!links || !links.length) { + return; + } - TriggerEvent.prototype.onExecute = function( param, options) { - var v = this.getInputData(0); - var changed = (v != this.prev); - if(this.prev === 0) - changed = false; - var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); - if(v && must_resend ) - this.triggerSlot(0, param, null, options); - if(!v && must_resend) - this.triggerSlot(2, param, null, options); - if(changed) - this.triggerSlot(1, param, null, options); - this.prev = v; + //for every link attached here + for (var k = 0; k < links.length; ++k) { + var id = links[k]; + if (link_id != null && link_id != id) { + //to skip links + continue; + } + var link_info = this.graph.links[links[k]]; + if (!link_info) { + //not connected + continue; + } + link_info._last_time = 0; + } }; - LiteGraph.registerNodeType("events/trigger", TriggerEvent); - - //Sequence of events - function Sequence() { - var that = this; - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addWidget("button","+",null,function(){ - that.addInput("", LiteGraph.ACTION); - that.addOutput("", LiteGraph.EVENT); - }); - this.size = [90, 70]; - this.flags = { horizontal: true, render_box: false }; - } - - Sequence.title = "Sequence"; - Sequence.desc = "Triggers a sequence of events when an event arrives"; + /** + * changes node size and triggers callback + * @method setSize + * @param {vec2} size + */ + LGraphNode.prototype.setSize = function(size) + { + this.size = size; + if(this.onResize) + this.onResize(this.size); + } - Sequence.prototype.getTitle = function() { - return ""; + /** + * add a new property to this node + * @method addProperty + * @param {string} name + * @param {*} default_value + * @param {string} type string defining the output type ("vec3","number",...) + * @param {Object} extra_info this can be used to have special properties of the property (like values, etc) + */ + LGraphNode.prototype.addProperty = function( + name, + default_value, + type, + extra_info + ) { + var o = { name: name, type: type, default_value: default_value }; + if (extra_info) { + for (var i in extra_info) { + o[i] = extra_info[i]; + } + } + if (!this.properties_info) { + this.properties_info = []; + } + this.properties_info.push(o); + if (!this.properties) { + this.properties = {}; + } + this.properties[name] = default_value; + return o; }; - Sequence.prototype.onAction = function(action, param, options) { - if (this.outputs) { - options = options || {}; - for (var i = 0; i < this.outputs.length; ++i) { - var output = this.outputs[i]; - //needs more info about this... - if( options.action_call ) // CREATE A NEW ID FOR THE ACTION - options.action_call = options.action_call + "_seq_" + i; - else - options.action_call = this.id + "_" + (action ? action : "action")+"_seq_"+i+"_"+Math.floor(Math.random()*9999); - this.triggerSlot(i, param, null, options); + //connections + + /** + * add a new output slot to use in this node + * @method addOutput + * @param {string} name + * @param {string} type string defining the output type ("vec3","number",...) + * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc) + */ + LGraphNode.prototype.addOutput = function(name, type, extra_info) { + var output = { name: name, type: type, links: null }; + if (extra_info) { + for (var i in extra_info) { + output[i] = extra_info[i]; } } + + if (!this.outputs) { + this.outputs = []; + } + this.outputs.push(output); + if (this.onOutputAdded) { + this.onOutputAdded(output); + } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); + + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); + return output; }; - LiteGraph.registerNodeType("events/sequence", Sequence); + /** + * add a new output slot to use in this node + * @method addOutputs + * @param {Array} array of triplets like [[name,type,extra_info],[...]] + */ + LGraphNode.prototype.addOutputs = function(array) { + for (var i = 0; i < array.length; ++i) { + var info = array[i]; + var o = { name: info[0], type: info[1], link: null }; + if (array[2]) { + for (var j in info[2]) { + o[j] = info[2][j]; + } + } + if (!this.outputs) { + this.outputs = []; + } + this.outputs.push(o); + if (this.onOutputAdded) { + this.onOutputAdded(o); + } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); + + } - //Sequence of events - function WaitAll() { - var that = this; - this.addInput("", LiteGraph.ACTION); - this.addInput("", LiteGraph.ACTION); - this.addOutput("", LiteGraph.EVENT); - this.addWidget("button","+",null,function(){ - that.addInput("", LiteGraph.ACTION); - that.size[0] = 90; - }); - this.size = [90, 70]; - this.ready = []; -} + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); + }; -WaitAll.title = "WaitAll"; -WaitAll.desc = "Wait until all input events arrive then triggers output"; + /** + * remove an existing output slot + * @method removeOutput + * @param {number} slot + */ + LGraphNode.prototype.removeOutput = function(slot) { + this.disconnectOutput(slot); + this.outputs.splice(slot, 1); + for (var i = slot; i < this.outputs.length; ++i) { + if (!this.outputs[i] || !this.outputs[i].links) { + continue; + } + var links = this.outputs[i].links; + for (var j = 0; j < links.length; ++j) { + var link = this.graph.links[links[j]]; + if (!link) { + continue; + } + link.origin_slot -= 1; + } + } -WaitAll.prototype.getTitle = function() { - return ""; -}; + this.setSize( this.computeSize() ); + if (this.onOutputRemoved) { + this.onOutputRemoved(slot); + } + this.setDirtyCanvas(true, true); + }; -WaitAll.prototype.onDrawBackground = function(ctx) -{ - if (this.flags.collapsed) { - return; - } - for(var i = 0; i < this.inputs.length; ++i) - { - var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10; - ctx.fillStyle = this.ready[i] ? "#AFB" : "#000"; - ctx.fillRect(20, y, 10, 10); - } -} + /** + * add a new input slot to use in this node + * @method addInput + * @param {string} name + * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0 + * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc) + */ + LGraphNode.prototype.addInput = function(name, type, extra_info) { + type = type || 0; + var input = { name: name, type: type, link: null }; + if (extra_info) { + for (var i in extra_info) { + input[i] = extra_info[i]; + } + } -WaitAll.prototype.onAction = function(action, param, options, slot_index) { - if(slot_index == null) - return; + if (!this.inputs) { + this.inputs = []; + } - //check all - this.ready.length = this.outputs.length; - this.ready[slot_index] = true; - for(var i = 0; i < this.ready.length;++i) - if(!this.ready[i]) - return; - //pass - this.reset(); - this.triggerSlot(0); -}; + this.inputs.push(input); + this.setSize( this.computeSize() ); -WaitAll.prototype.reset = function() -{ - this.ready.length = 0; -} + if (this.onInputAdded) { + this.onInputAdded(input); + } + + LiteGraph.registerNodeAndSlotType(this,type); -LiteGraph.registerNodeType("events/waitAll", WaitAll); + this.setDirtyCanvas(true, true); + return input; + }; + /** + * add several new input slots in this node + * @method addInputs + * @param {Array} array of triplets like [[name,type,extra_info],[...]] + */ + LGraphNode.prototype.addInputs = function(array) { + for (var i = 0; i < array.length; ++i) { + var info = array[i]; + var o = { name: info[0], type: info[1], link: null }; + if (array[2]) { + for (var j in info[2]) { + o[j] = info[2][j]; + } + } - //Sequencer for events - function Stepper() { - var that = this; - this.properties = { index: 0 }; - this.addInput("index", "number"); - this.addInput("step", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("index", "number"); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT); - this.addOutput("", LiteGraph.EVENT,{removable:true}); - this.addWidget("button","+",null,function(){ - that.addOutput("", LiteGraph.EVENT, {removable:true}); - }); - this.size = [120, 120]; - this.flags = { render_box: false }; - } + if (!this.inputs) { + this.inputs = []; + } + this.inputs.push(o); + if (this.onInputAdded) { + this.onInputAdded(o); + } + + LiteGraph.registerNodeAndSlotType(this,info[1]); + } - Stepper.title = "Stepper"; - Stepper.desc = "Trigger events sequentially when an tick arrives"; + this.setSize( this.computeSize() ); + this.setDirtyCanvas(true, true); + }; - Stepper.prototype.onDrawBackground = function(ctx) - { - if (this.flags.collapsed) { - return; + /** + * remove an existing input slot + * @method removeInput + * @param {number} slot + */ + LGraphNode.prototype.removeInput = function(slot) { + this.disconnectInput(slot); + var slot_info = this.inputs.splice(slot, 1); + for (var i = slot; i < this.inputs.length; ++i) { + if (!this.inputs[i]) { + continue; + } + var link = this.graph.links[this.inputs[i].link]; + if (!link) { + continue; + } + link.target_slot -= 1; } - var index = this.properties.index || 0; - ctx.fillStyle = "#AFB"; - var w = this.size[0]; - var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4; - ctx.beginPath(); - ctx.moveTo(w - 30, y); - ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); - } - - Stepper.prototype.onExecute = function() - { - var index = this.getInputData(0); - if(index != null) - { - index = Math.floor(index); - index = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 ); - if( index != this.properties.index ) - { - this.properties.index = index; - this.triggerSlot( index+1 ); - } - } + this.setSize( this.computeSize() ); + if (this.onInputRemoved) { + this.onInputRemoved(slot, slot_info[0] ); + } + this.setDirtyCanvas(true, true); + }; - this.setOutputData(0, this.properties.index ); - } + /** + * add an special connection to this node (used for special kinds of graphs) + * @method addConnection + * @param {string} name + * @param {string} type string defining the input type ("vec3","number",...) + * @param {[x,y]} pos position of the connection inside the node + * @param {string} direction if is input or output + */ + LGraphNode.prototype.addConnection = function(name, type, pos, direction) { + var o = { + name: name, + type: type, + pos: pos, + direction: direction, + links: null + }; + this.connections.push(o); + return o; + }; - Stepper.prototype.onAction = function(action, param) { - if(action == "reset") - this.properties.index = 0; - else if(action == "step") - { - this.triggerSlot(this.properties.index+1, param); - var n = this.outputs ? this.outputs.length - 1 : 0; - this.properties.index = (this.properties.index + 1) % n; + /** + * computes the minimum size of a node according to its inputs and output slots + * @method computeSize + * @param {vec2} minHeight + * @return {vec2} the total size + */ + LGraphNode.prototype.computeSize = function(out) { + if (this.constructor.size) { + return this.constructor.size.concat(); } - }; - LiteGraph.registerNodeType("events/stepper", Stepper); + var rows = Math.max( + this.inputs ? this.inputs.length : 1, + this.outputs ? this.outputs.length : 1 + ); + var size = out || new Float32Array([0, 0]); + rows = Math.max(rows, 1); + var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - //Filter events - function FilterEvent() { - this.size = [60, 30]; - this.addInput("event", LiteGraph.ACTION); - this.addOutput("event", LiteGraph.EVENT); - this.properties = { - equal_to: "", - has_property: "", - property_equal_to: "" - }; - } + var title_width = compute_text_size(this.title); + var input_width = 0; + var output_width = 0; - FilterEvent.title = "Filter Event"; - FilterEvent.desc = "Blocks events that do not match the filter"; + if (this.inputs) { + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input = this.inputs[i]; + var text = input.label || input.name || ""; + var text_width = compute_text_size(text); + if (input_width < text_width) { + input_width = text_width; + } + } + } - FilterEvent.prototype.onAction = function(action, param, options) { - if (param == null) { - return; + if (this.outputs) { + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + var text = output.label || output.name || ""; + var text_width = compute_text_size(text); + if (output_width < text_width) { + output_width = text_width; + } + } } - if (this.properties.equal_to && this.properties.equal_to != param) { - return; + size[0] = Math.max(input_width + output_width + 10, title_width); + size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH); + if (this.widgets && this.widgets.length) { + size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5); } - if (this.properties.has_property) { - var prop = param[this.properties.has_property]; - if (prop == null) { - return; - } + size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT; - if ( - this.properties.property_equal_to && - this.properties.property_equal_to != prop - ) { - return; + var widgets_height = 0; + if (this.widgets && this.widgets.length) { + for (var i = 0, l = this.widgets.length; i < l; ++i) { + if (this.widgets[i].computeSize) + widgets_height += this.widgets[i].computeSize(size[0])[1] + 4; + else + widgets_height += LiteGraph.NODE_WIDGET_HEIGHT + 4; } + widgets_height += 8; } - this.triggerSlot(0, param, null, options); + //compute height using widgets height + if( this.widgets_up ) + size[1] = Math.max( size[1], widgets_height ); + else if( this.widgets_start_y != null ) + size[1] = Math.max( size[1], widgets_height + this.widgets_start_y ); + else + size[1] += widgets_height; + + function compute_text_size(text) { + if (!text) { + return 0; + } + return font_size * text.length * 0.6; + } + + if ( + this.constructor.min_height && + size[1] < this.constructor.min_height + ) { + size[1] = this.constructor.min_height; + } + + size[1] += 6; //margin + + return size; }; - LiteGraph.registerNodeType("events/filter", FilterEvent); + /** + * returns all the info available about a property of this node. + * + * @method getPropertyInfo + * @param {String} property name of the property + * @return {Object} the object with all the available info + */ + LGraphNode.prototype.getPropertyInfo = function( property ) + { + var info = null; + + //there are several ways to define info about a property + //legacy mode + if (this.properties_info) { + for (var i = 0; i < this.properties_info.length; ++i) { + if (this.properties_info[i].name == property) { + info = this.properties_info[i]; + break; + } + } + } + //litescene mode using the constructor + if(this.constructor["@" + property]) + info = this.constructor["@" + property]; + if(this.constructor.widgets_info && this.constructor.widgets_info[property]) + info = this.constructor.widgets_info[property]; - function EventBranch() { - this.addInput("in", LiteGraph.ACTION); - this.addInput("cond", "boolean"); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.size = [120, 60]; - this._value = false; - } + //litescene mode using the constructor + if (!info && this.onGetPropertyInfo) { + info = this.onGetPropertyInfo(property); + } - EventBranch.title = "Branch"; - EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + if (!info) + info = {}; + if(!info.type) + info.type = typeof this.properties[property]; + if(info.widget == "combo") + info.type = "enum"; - EventBranch.prototype.onExecute = function() { - this._value = this.getInputData(1); + return info; } - EventBranch.prototype.onAction = function(action, param, options) { - this._value = this.getInputData(1); - this.triggerSlot(this._value ? 0 : 1, param, null, options); - } + /** + * Defines a widget inside the node, it will be rendered on top of the node, you can control lots of properties + * + * @method addWidget + * @param {String} type the widget type (could be "number","string","combo" + * @param {String} name the text to show on the widget + * @param {String} value the default value + * @param {Function|String} callback function to call when it changes (optionally, it can be the name of the property to modify) + * @param {Object} options the object that contains special properties of this widget + * @return {Object} the created widget object + */ + LGraphNode.prototype.addWidget = function( type, name, value, callback, options ) + { + if (!this.widgets) { + this.widgets = []; + } - LiteGraph.registerNodeType("events/branch", EventBranch); + if(!options && callback && callback.constructor === Object) + { + options = callback; + callback = null; + } - //Show value inside the debug console - function EventCounter() { - this.addInput("inc", LiteGraph.ACTION); - this.addInput("dec", LiteGraph.ACTION); - this.addInput("reset", LiteGraph.ACTION); - this.addOutput("change", LiteGraph.EVENT); - this.addOutput("num", "number"); - this.addProperty("doCountExecution", false, "boolean", {name: "Count Executions"}); - this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"); - this.num = 0; - } + if(options && options.constructor === String) //options can be the property name + options = { property: options }; - EventCounter.title = "Counter"; - EventCounter.desc = "Counts events"; + if(callback && callback.constructor === String) //callback can be the property name + { + if(!options) + options = {}; + options.property = callback; + callback = null; + } - EventCounter.prototype.getTitle = function() { - if (this.flags.collapsed) { - return String(this.num); + if(callback && callback.constructor !== Function) + { + console.warn("addWidget: callback must be a function"); + callback = null; + } + + var w = { + type: type.toLowerCase(), + name: name, + value: value, + callback: callback, + options: options || {} + }; + + if (w.options.y !== undefined) { + w.y = w.options.y; } - return this.title; - }; - EventCounter.prototype.onAction = function(action, param, options) { - var v = this.num; - if (action == "inc") { - this.num += 1; - } else if (action == "dec") { - this.num -= 1; - } else if (action == "reset") { - this.num = 0; + if (!callback && !w.options.callback && !w.options.property) { + console.warn("LiteGraph addWidget(...) without a callback or property assigned"); } - if (this.num != v) { - this.trigger("change", this.num); + if (type == "combo" && !w.options.values) { + throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }"; } + this.widgets.push(w); + this.setSize( this.computeSize() ); + return w; }; - EventCounter.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; + LGraphNode.prototype.addCustomWidget = function(custom_widget) { + if (!this.widgets) { + this.widgets = []; } - ctx.fillStyle = "#AAA"; - ctx.font = "20px Arial"; - ctx.textAlign = "center"; - ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); + this.widgets.push(custom_widget); + return custom_widget; }; - EventCounter.prototype.onExecute = function() { - if(this.properties.doCountExecution){ - this.num += 1; + /** + * returns the bounding of the object, used for rendering purposes + * @method getBounding + * @param out {Float32Array[4]?} [optional] a place to store the output, to free garbage + * @param compute_outer {boolean?} [optional] set to true to include the shadow and connection points in the bounding calculation + * @return {Float32Array[4]} the bounding box in format of [topleft_cornerx, topleft_cornery, width, height] + */ + LGraphNode.prototype.getBounding = function(out, compute_outer) { + out = out || new Float32Array(4); + const nodePos = this.pos; + const isCollapsed = this.flags.collapsed; + const nodeSize = this.size; + + let left_offset = 0; + // 1 offset due to how nodes are rendered + let right_offset = 1 ; + let top_offset = 0; + let bottom_offset = 0; + + if (compute_outer) { + // 4 offset for collapsed node connection points + left_offset = 4; + // 6 offset for right shadow and collapsed node connection points + right_offset = 6 + left_offset; + // 4 offset for collapsed nodes top connection points + top_offset = 4; + // 5 offset for bottom shadow and collapsed node connection points + bottom_offset = 5 + top_offset; } - this.setOutputData(1, this.num); + + out[0] = nodePos[0] - left_offset; + out[1] = nodePos[1] - LiteGraph.NODE_TITLE_HEIGHT - top_offset; + out[2] = isCollapsed ? + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + right_offset : + nodeSize[0] + right_offset; + out[3] = isCollapsed ? + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset : + nodeSize[1] + LiteGraph.NODE_TITLE_HEIGHT + bottom_offset; + + if (this.onBounding) { + this.onBounding(out); + } + return out; }; - LiteGraph.registerNodeType("events/counter", EventCounter); + /** + * checks if a point is inside the shape of a node + * @method isPointInside + * @param {number} x + * @param {number} y + * @return {boolean} + */ + LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) { + margin = margin || 0; + + var margin_top = this.graph && this.graph.isLive() ? 0 : LiteGraph.NODE_TITLE_HEIGHT; + if (skip_title) { + margin_top = 0; + } + if (this.flags && this.flags.collapsed) { + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if ( + isInsideRectangle( + x, + y, + this.pos[0] - margin, + this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, + (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) + + 2 * margin, + LiteGraph.NODE_TITLE_HEIGHT + 2 * margin + ) + ) { + return true; + } + } else if ( + this.pos[0] - 4 - margin < x && + this.pos[0] + this.size[0] + 4 + margin > x && + this.pos[1] - margin_top - margin < y && + this.pos[1] + this.size[1] + margin > y + ) { + return true; + } + return false; + }; - //Show value inside the debug console - function DelayEvent() { - this.size = [60, 30]; - this.addProperty("time_in_ms", 1000); - this.addInput("event", LiteGraph.ACTION); - this.addOutput("on_time", LiteGraph.EVENT); + /** + * checks if a point is inside a node slot, and returns info about which slot + * @method getSlotInPosition + * @param {number} x + * @param {number} y + * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] } + */ + LGraphNode.prototype.getSlotInPosition = function(x, y) { + //search for inputs + var link_pos = new Float32Array(2); + if (this.inputs) { + for (var i = 0, l = this.inputs.length; i < l; ++i) { + var input = this.inputs[i]; + this.getConnectionPos(true, i, link_pos); + if ( + isInsideRectangle( + x, + y, + link_pos[0] - 10, + link_pos[1] - 5, + 20, + 10 + ) + ) { + return { input: input, slot: i, link_pos: link_pos }; + } + } + } - this._pending = []; - } + if (this.outputs) { + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + this.getConnectionPos(false, i, link_pos); + if ( + isInsideRectangle( + x, + y, + link_pos[0] - 10, + link_pos[1] - 5, + 20, + 10 + ) + ) { + return { output: output, slot: i, link_pos: link_pos }; + } + } + } - DelayEvent.title = "Delay"; - DelayEvent.desc = "Delays one event"; + return null; + }; - DelayEvent.prototype.onAction = function(action, param, options) { - var time = this.properties.time_in_ms; - if (time <= 0) { - this.trigger(null, param, options); - } else { - this._pending.push([time, param]); + /** + * returns the input slot with a given name (used for dynamic slots), -1 if not found + * @method findInputSlot + * @param {string} name the name of the slot + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findInputSlot = function(name, returnObj) { + if (!this.inputs) { + return -1; } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + if (name == this.inputs[i].name) { + return !returnObj ? i : this.inputs[i]; + } + } + return -1; }; - DelayEvent.prototype.onExecute = function(param, options) { - var dt = this.graph.elapsed_time * 1000; //in ms - - if (this.isInputConnected(1)) { - this.properties.time_in_ms = this.getInputData(1); + /** + * returns the output slot with a given name (used for dynamic slots), -1 if not found + * @method findOutputSlot + * @param {string} name the name of the slot + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findOutputSlot = function(name, returnObj) { + returnObj = returnObj || false; + if (!this.outputs) { + return -1; } - - for (var i = 0; i < this._pending.length; ++i) { - var actionPass = this._pending[i]; - actionPass[0] -= dt; - if (actionPass[0] > 0) { + for (var i = 0, l = this.outputs.length; i < l; ++i) { + if (name == this.outputs[i].name) { + return !returnObj ? i : this.outputs[i]; + } + } + return -1; + }; + + // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options + + /** + * returns the first free input slot + * @method findInputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findInputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = {returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.inputs) { + return -1; + } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + if (this.inputs[i].link && this.inputs[i].link != null) { continue; } - - //remove - this._pending.splice(i, 1); - --i; - - //trigger - this.trigger(null, actionPass[1], options); + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.inputs[i]; } + return -1; }; - DelayEvent.prototype.onGetInputs = function() { - return [["event", LiteGraph.ACTION], ["time_in_ms", "number"]]; + /** + * returns the first output slot free + * @method findOutputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findOutputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.outputs) { + return -1; + } + for (var i = 0, l = this.outputs.length; i < l; ++i) { + if (this.outputs[i].links && this.outputs[i].links != null) { + continue; + } + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.outputs[i]; + } + return -1; + }; + + /** + * findSlotByType for INPUTS + */ + LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); }; - LiteGraph.registerNodeType("events/delay", DelayEvent); + /** + * findSlotByType for OUTPUTS + */ + LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); + }; + + /** + * returns the output (or input) slot with a given type, -1 if not found + * @method findSlotByType + * @param {boolean} input uise inputs instead of outputs + * @param {string} type the type of the slot + * @param {boolean} returnObj if the obj itself wanted + * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { + input = input || false; + returnObj = returnObj || false; + preferFreeSlot = preferFreeSlot || false; + doNotUseOccupied = doNotUseOccupied || false; + var aSlots = input ? this.inputs : this.outputs; + if (!aSlots) { + return -1; + } + // !! empty string type is considered 0, * !! + if (type == "" || type == "*") type = 0; + for (var i = 0, l = aSlots.length; i < l; ++i) { + var tFound = false; + var aSource = (type+"").toLowerCase().split(","); + var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; + aDest = (aDest+"").toLowerCase().split(","); + for(var sI=0;sI= 0 && target_slot !== null){ + //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) + return this.connect(slot, target_node, target_slot); + }else{ + //console.log("type "+target_slotType+" not found or not free?") + if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onTrigger IN SLOT + //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); + return this.connect(slot, target_node, -1); + } + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var target_slot = target_node.findInputSlotByType(0, false, true, true); + //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); + if (target_slot >= 0){ + return this.connect(slot, target_node, target_slot); + } + } + // connect to the first free input slot if not found a specific type and this output is general + if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ + var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); + //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); + if (target_slot >= 0){ + return this.connect(slot, target_node, target_slot); + } + } + + console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); + //TODO filter + + return null; + } + } + + /** + * connect this node input to the output of another node BY TYPE + * @method connectByType + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {string} target_type the output slot type of the target node + * @return {Object} the link_info is created, otherwise null + */ + LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { createEventInCase: true + ,firstFreeIfInputGeneralInCase: true + ,generalTypeInCase: true + }; + var opts = Object.assign(optsDef,optsIn); + if (source_node && source_node.constructor === Number) { + source_node = this.graph.getNodeById(source_node); + } + var source_slot = source_node.findOutputSlotByType(source_slotType, false, true); + if (source_slot >= 0 && source_slot !== null){ + //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) + return source_node.connect(source_slot, this, slot); + }else{ + + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var source_slot = source_node.findOutputSlotByType(0, false, true, true); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onExecuted OUT SLOT + if (LiteGraph.do_add_triggers_slots){ + var source_slot = source_node.addOnExecutedOutput(); + return source_node.connect(source_slot, this, slot); + } + } + // connect to the first free output slot if not found a specific type and this input is general + if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ + var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); + //TODO filter + + //console.log("type OUT! "+source_slotType+" not found or not free?") + return null; + } } + + /** + * connect this node output to the input of another node + * @method connect + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger) + * @return {Object} the link_info is created, otherwise null + */ + LGraphNode.prototype.connect = function(slot, target_node, target_slot) { + target_slot = target_slot || 0; + + if (!this.graph) { + //could be connected before adding it to a graph + console.log( + "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them." + ); //due to link ids being associated with graphs + return null; + } - TimerEvent.title = "Timer"; - TimerEvent.desc = "Sends an event every N milliseconds"; + //seek for the output slot + if (slot.constructor === String) { + slot = this.findOutputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return null; + } + } else if (!this.outputs || slot >= this.outputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return null; + } - TimerEvent.prototype.onStart = function() { - this.time = 0; - }; + if (target_node && target_node.constructor === Number) { + target_node = this.graph.getNodeById(target_node); + } + if (!target_node) { + throw "target node is null"; + } - TimerEvent.prototype.getTitle = function() { - return "Timer: " + this.last_interval.toString() + "ms"; - }; + //avoid loopback + if (target_node == this) { + return null; + } - TimerEvent.on_color = "#AAA"; - TimerEvent.off_color = "#222"; + //you can specify the slot by name + if (target_slot.constructor === String) { + target_slot = target_node.findInputSlot(target_slot); + if (target_slot == -1) { + if (LiteGraph.debug) { + console.log( + "Connect: Error, no slot of name " + target_slot + ); + } + return null; + } + } else if (target_slot === LiteGraph.EVENT) { + + if (LiteGraph.do_add_triggers_slots){ + //search for first slot with event? :: NO this is done outside + //console.log("Connect: Creating triggerEvent"); + // force mode + target_node.changeMode(LiteGraph.ON_TRIGGER); + target_slot = target_node.findInputSlot("onTrigger"); + }else{ + return null; // -- break -- + } + } else if ( + !target_node.inputs || + target_slot >= target_node.inputs.length + ) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return null; + } - TimerEvent.prototype.onDrawBackground = function() { - this.boxcolor = this.triggered - ? TimerEvent.on_color - : TimerEvent.off_color; - this.triggered = false; - }; + var changed = false; - TimerEvent.prototype.onExecute = function() { - var dt = this.graph.elapsed_time * 1000; //in ms + var input = target_node.inputs[target_slot]; + var link_info = null; + var output = this.outputs[slot]; + + if (!this.outputs[slot]){ + /*console.debug("Invalid slot passed: "+slot); + console.debug(this.outputs);*/ + return null; + } - var trigger = this.time == 0; + // allow target node to change slot + if (target_node.onBeforeConnectInput) { + // This way node can choose another slot (or make a new one?) + target_slot = target_node.onBeforeConnectInput(target_slot); //callback + } - this.time += dt; - this.last_interval = Math.max( - 1, - this.getInputOrProperty("interval") | 0 - ); + //check target_slot and check connection types + if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) + { + this.setDirtyCanvas(false, true); + if(changed) + this.graph.connectionChange(this, link_info); + return null; + }else{ + //console.debug("valid connection",output.type, input.type); + } - if ( - !trigger && - (this.time < this.last_interval || isNaN(this.last_interval)) - ) { - if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { - this.setOutputData(1, false); + //allows nodes to block connection, callback + if (target_node.onConnectInput) { + if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { + return null; + } + } + if (this.onConnectOutput) { // callback + if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { + return null; } - return; } - this.triggered = true; - this.time = this.time % this.last_interval; - this.trigger("on_tick", this.properties.event); - if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { - this.setOutputData(1, true); + //if there is something already plugged there, disconnect + if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { + this.graph.beforeChange(); + target_node.disconnectInput(target_slot, {doProcessChange: false}); + changed = true; + } + if (output.links !== null && output.links.length){ + switch(output.type){ + case LiteGraph.EVENT: + if (!LiteGraph.allow_multi_output_for_events){ + this.graph.beforeChange(); + this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); + changed = true; + } + break; + default: + break; + } } + + var nextId + if (LiteGraph.use_uuids) + nextId = LiteGraph.uuidv4(); + else + nextId = ++this.graph.last_link_id; + + //create link class + link_info = new LLink( + nextId, + input.type || output.type, + this.id, + slot, + target_node.id, + target_slot + ); + + //add to graph links list + this.graph.links[link_info.id] = link_info; + + //connect in output + if (output.links == null) { + output.links = []; + } + output.links.push(link_info.id); + //connect in input + target_node.inputs[target_slot].link = link_info.id; + if (this.graph) { + this.graph._version++; + } + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + true, + link_info, + output + ); + } //link_info has been created now, so its updated + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + target_slot, + true, + link_info, + input + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + target_slot, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot, + target_node, + target_slot + ); + } + + this.setDirtyCanvas(false, true); + this.graph.afterChange(); + this.graph.connectionChange(this, link_info); + + return link_info; }; - TimerEvent.prototype.onGetInputs = function() { - return [["interval", "number"]]; + /** + * disconnect one output to an specific node + * @method disconnectOutput + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] + * @return {boolean} if it was disconnected successfully + */ + LGraphNode.prototype.disconnectOutput = function(slot, target_node) { + if (slot.constructor === String) { + slot = this.findOutputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return false; + } + } else if (!this.outputs || slot >= this.outputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return false; + } + + //get output slot + var output = this.outputs[slot]; + if (!output || !output.links || output.links.length == 0) { + return false; + } + + //one of the output links in this slot + if (target_node) { + if (target_node.constructor === Number) { + target_node = this.graph.getNodeById(target_node); + } + if (!target_node) { + throw "Target Node not found"; + } + + for (var i = 0, l = output.links.length; i < l; i++) { + var link_id = output.links[i]; + var link_info = this.graph.links[link_id]; + + //is the link we are searching for... + if (link_info.target_id == target_node.id) { + output.links.splice(i, 1); //remove here + var input = target_node.inputs[link_info.target_slot]; + input.link = null; //remove there + delete this.graph.links[link_id]; //remove the link from the links pool + if (this.graph) { + this.graph._version++; + } + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + link_info.target_slot, + false, + link_info, + input + ); + } //link_info hasn't been modified so its ok + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + break; + } + } + } //all the links in this output slot + else { + for (var i = 0, l = output.links.length; i < l; i++) { + var link_id = output.links[i]; + var link_info = this.graph.links[link_id]; + if (!link_info) { + //bug: it happens sometimes + continue; + } + + var target_node = this.graph.getNodeById(link_info.target_id); + var input = null; + if (this.graph) { + this.graph._version++; + } + if (target_node) { + input = target_node.inputs[link_info.target_slot]; + input.link = null; //remove other side link + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.INPUT, + link_info.target_slot, + false, + link_info, + input + ); + } //link_info hasn't been modified so its ok + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + } + delete this.graph.links[link_id]; //remove the link from the links pool + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.OUTPUT, + slot, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + this, + slot + ); + this.graph.onNodeConnectionChange( + LiteGraph.INPUT, + target_node, + link_info.target_slot + ); + } + } + output.links = null; + } + + this.setDirtyCanvas(false, true); + this.graph.connectionChange(this); + return true; }; - TimerEvent.prototype.onGetOutputs = function() { - return [["tick", "boolean"]]; + /** + * disconnect one input + * @method disconnectInput + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @return {boolean} if it was disconnected successfully + */ + LGraphNode.prototype.disconnectInput = function(slot) { + //seek for the output slot + if (slot.constructor === String) { + slot = this.findInputSlot(slot); + if (slot == -1) { + if (LiteGraph.debug) { + console.log("Connect: Error, no slot of name " + slot); + } + return false; + } + } else if (!this.inputs || slot >= this.inputs.length) { + if (LiteGraph.debug) { + console.log("Connect: Error, slot number not found"); + } + return false; + } + + var input = this.inputs[slot]; + if (!input) { + return false; + } + + var link_id = this.inputs[slot].link; + if(link_id != null) + { + this.inputs[slot].link = null; + + //remove other side + var link_info = this.graph.links[link_id]; + if (link_info) { + var target_node = this.graph.getNodeById(link_info.origin_id); + if (!target_node) { + return false; + } + + var output = target_node.outputs[link_info.origin_slot]; + if (!output || !output.links || output.links.length == 0) { + return false; + } + + //search in the inputs list for this link + for (var i = 0, l = output.links.length; i < l; i++) { + if (output.links[i] == link_id) { + output.links.splice(i, 1); + break; + } + } + + delete this.graph.links[link_id]; //remove from the pool + if (this.graph) { + this.graph._version++; + } + if (this.onConnectionsChange) { + this.onConnectionsChange( + LiteGraph.INPUT, + slot, + false, + link_info, + input + ); + } + if (target_node.onConnectionsChange) { + target_node.onConnectionsChange( + LiteGraph.OUTPUT, + i, + false, + link_info, + output + ); + } + if (this.graph && this.graph.onNodeConnectionChange) { + this.graph.onNodeConnectionChange( + LiteGraph.OUTPUT, + target_node, + i + ); + this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot); + } + } + } //link != null + + this.setDirtyCanvas(false, true); + if(this.graph) + this.graph.connectionChange(this); + return true; }; - LiteGraph.registerNodeType("events/timer", TimerEvent); + /** + * returns the center of a connection point in canvas coords + * @method getConnectionPos + * @param {boolean} is_input true if if a input slot, false if it is an output + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {vec2} out [optional] a place to store the output, to free garbage + * @return {[x,y]} the position + **/ + LGraphNode.prototype.getConnectionPos = function( + is_input, + slot_number, + out + ) { + out = out || new Float32Array(2); + var num_slots = 0; + if (is_input && this.inputs) { + num_slots = this.inputs.length; + } + if (!is_input && this.outputs) { + num_slots = this.outputs.length; + } + var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; + // Check for separate input/output orientation, fallback to general horizontal property + var is_horizontal = false; + if (is_input) { + is_horizontal = this.inputs_horizontal !== undefined && this.inputs_horizontal !== null ? this.inputs_horizontal : this.horizontal; + } else { + is_horizontal = this.outputs_horizontal !== undefined && this.outputs_horizontal !== null ? this.outputs_horizontal : this.horizontal; + } - function SemaphoreEvent() { - this.addInput("go", LiteGraph.ACTION ); - this.addInput("green", LiteGraph.ACTION ); - this.addInput("red", LiteGraph.ACTION ); - this.addOutput("continue", LiteGraph.EVENT ); - this.addOutput("blocked", LiteGraph.EVENT ); - this.addOutput("is_green", "boolean" ); - this._ready = false; - this.properties = {}; - var that = this; - this.addWidget("button","reset","",function(){ - that._ready = false; - }); - } + if (this.flags.collapsed) { + var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; + if (is_horizontal) { + out[0] = this.pos[0] + w * 0.5; + if (is_input) { + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; + } else { + out[1] = this.pos[1]; + } + } else { + if (is_input) { + out[0] = this.pos[0]; + } else { + out[0] = this.pos[0] + w; + } + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5; + } + return out; + } - SemaphoreEvent.title = "Semaphore Event"; - SemaphoreEvent.desc = "Until both events are not triggered, it doesnt continue."; + //weird feature that never got finished + if (is_input && slot_number == -1) { + out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; + out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5; + return out; + } - SemaphoreEvent.prototype.onExecute = function() - { - this.setOutputData(1,this._ready); - this.boxcolor = this._ready ? "#9F9" : "#FA5"; - } + //hard-coded pos + if ( + is_input && + num_slots > slot_number && + this.inputs[slot_number].pos + ) { + out[0] = this.pos[0] + this.inputs[slot_number].pos[0]; + out[1] = this.pos[1] + this.inputs[slot_number].pos[1]; + return out; + } else if ( + !is_input && + num_slots > slot_number && + this.outputs[slot_number].pos + ) { + out[0] = this.pos[0] + this.outputs[slot_number].pos[0]; + out[1] = this.pos[1] + this.outputs[slot_number].pos[1]; + return out; + } - SemaphoreEvent.prototype.onAction = function(action, param) { - if( action == "go" ) - this.triggerSlot( this._ready ? 0 : 1 ); - else if( action == "green" ) - this._ready = true; - else if( action == "red" ) - this._ready = false; + //horizontal distributed slots + if (is_horizontal) { + out[0] = + this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); + if (is_input) { + out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; + } else { + out[1] = this.pos[1] + this.size[1]; + } + return out; + } + + //default vertical slots + if (is_input) { + out[0] = this.pos[0] + offset; + } else { + out[0] = this.pos[0] + this.size[0] + 1 - offset; + } + out[1] = + this.pos[1] + + (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT + + (this.constructor.slot_start_y || 0); + return out; }; - LiteGraph.registerNodeType("events/semaphore", SemaphoreEvent); + /* Force align to grid */ + LGraphNode.prototype.alignToGrid = function() { + this.pos[0] = + LiteGraph.CANVAS_GRID_SIZE * + Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); + this.pos[1] = + LiteGraph.CANVAS_GRID_SIZE * + Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); + }; - function OnceEvent() { - this.addInput("in", LiteGraph.ACTION ); - this.addInput("reset", LiteGraph.ACTION ); - this.addOutput("out", LiteGraph.EVENT ); - this._once = false; - this.properties = {}; - var that = this; - this.addWidget("button","reset","",function(){ - that._once = false; - }); - } + /* Console output */ + LGraphNode.prototype.trace = function(msg) { + if (!this.console) { + this.console = []; + } - OnceEvent.title = "Once"; - OnceEvent.desc = "Only passes an event once, then gets locked"; + this.console.push(msg); + if (this.console.length > LGraphNode.MAX_CONSOLE) { + this.console.shift(); + } - OnceEvent.prototype.onAction = function(action, param) { - if( action == "in" && !this._once ) - { - this._once = true; - this.triggerSlot( 0, param ); - } - else if( action == "reset" ) - this._once = false; + if(this.graph.onNodeTrace) + this.graph.onNodeTrace(this, msg); }; - LiteGraph.registerNodeType("events/once", OnceEvent); + /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ + LGraphNode.prototype.setDirtyCanvas = function( + dirty_foreground, + dirty_background + ) { + if (!this.graph) { + return; + } + this.graph.sendActionToCanvas("setDirty", [ + dirty_foreground, + dirty_background + ]); + }; - function DataStore() { - this.addInput("data", 0); - this.addInput("assign", LiteGraph.ACTION); - this.addOutput("data", 0); - this._last_value = null; - this.properties = { data: null, serialize: true }; - var that = this; - this.addWidget("button","store","",function(){ - that.properties.data = that._last_value; - }); - } + LGraphNode.prototype.loadImage = function(url) { + var img = new Image(); + img.src = LiteGraph.node_images_path + url; + img.ready = false; - DataStore.title = "Data Store"; - DataStore.desc = "Stores data and only changes when event is received"; + var that = this; + img.onload = function() { + this.ready = true; + that.setDirtyCanvas(true); + }; + return img; + }; - DataStore.prototype.onExecute = function() + //safe LGraphNode action execution (not sure if safe) + /* +LGraphNode.prototype.executeAction = function(action) +{ + if(action == "") return false; + + if( action.indexOf(";") != -1 || action.indexOf("}") != -1) { - this._last_value = this.getInputData(0); - this.setOutputData(0, this.properties.data ); + this.trace("Error: Action contains unsafe characters"); + return false; } - DataStore.prototype.onAction = function(action, param, options) { - this.properties.data = this._last_value; - }; + var tokens = action.split("("); + var func_name = tokens[0]; + if( typeof(this[func_name]) != "function") + { + this.trace("Error: Action not found on node: " + func_name); + return false; + } - DataStore.prototype.onSerialize = function(o) + var code = action; + + try { - if(o.data == null) - return; - if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) - o.data = null; + var _foo = eval; + eval = null; + (new Function("with(this) { " + code + "}")).call(this); + eval = _foo; + } + catch (err) + { + this.trace("Error executing action {" + action + "} :" + err); + return false; } - LiteGraph.registerNodeType("basic/data_store", DataStore); + return true; +} +*/ + /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ + LGraphNode.prototype.captureInput = function(v) { + if (!this.graph || !this.graph.list_of_graphcanvas) { + return; + } + var list = this.graph.list_of_graphcanvas; -})(this); - -(function(global) { - var LiteGraph = global.LiteGraph; + for (var i = 0; i < list.length; ++i) { + var c = list[i]; + //releasing somebody elses capture?! + if (!v && c.node_capturing_input != this) { + continue; + } - function GamepadInput() { - this.addOutput("left_x_axis", "number"); - this.addOutput("left_y_axis", "number"); - this.addOutput("button_pressed", LiteGraph.EVENT); - this.properties = { gamepad_index: 0, threshold: 0.1 }; + //change + c.node_capturing_input = v ? this : null; + } + }; - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); - } + /** + * Collapse the node to make it smaller on the canvas + * @method collapse + **/ + LGraphNode.prototype.collapse = function(force) { + this.graph._version++; + if (this.constructor.collapsable === false && !force) { + return; + } + if (!this.flags.collapsed) { + this.flags.collapsed = true; + } else { + this.flags.collapsed = false; + } + this.setDirtyCanvas(true, true); + }; - GamepadInput.title = "Gamepad"; - GamepadInput.desc = "gets the input of the gamepad"; + /** + * Forces the node to do not move or realign on Z + * @method pin + **/ - GamepadInput.CENTER = 0; - GamepadInput.LEFT = 1; - GamepadInput.RIGHT = 2; - GamepadInput.UP = 4; - GamepadInput.DOWN = 8; + LGraphNode.prototype.pin = function(v) { + this.graph._version++; + if (v === undefined) { + this.flags.pinned = !this.flags.pinned; + } else { + this.flags.pinned = v; + } + }; - GamepadInput.zero = new Float32Array(2); - GamepadInput.buttons = [ - "a", - "b", - "x", - "y", - "lb", - "rb", - "lt", - "rt", - "back", - "start", - "ls", - "rs", - "home" - ]; + LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) { + return [ + (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0], + (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1] + ]; + }; - GamepadInput.prototype.onExecute = function() { - //get gamepad - var gamepad = this.getGamepad(); - var threshold = this.properties.threshold || 0.0; + function LGraphGroup(title) { + this._ctor(title); + } - if (gamepad) { - this._left_axis[0] = - Math.abs(gamepad.xbox.axes["lx"]) > threshold - ? gamepad.xbox.axes["lx"] - : 0; - this._left_axis[1] = - Math.abs(gamepad.xbox.axes["ly"]) > threshold - ? gamepad.xbox.axes["ly"] - : 0; - this._right_axis[0] = - Math.abs(gamepad.xbox.axes["rx"]) > threshold - ? gamepad.xbox.axes["rx"] - : 0; - this._right_axis[1] = - Math.abs(gamepad.xbox.axes["ry"]) > threshold - ? gamepad.xbox.axes["ry"] - : 0; - this._triggers[0] = - Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold - ? gamepad.xbox.axes["ltrigger"] - : 0; - this._triggers[1] = - Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold - ? gamepad.xbox.axes["rtrigger"] - : 0; - } + global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup; + + LGraphGroup.prototype._ctor = function(title) { + this.title = title || "Group"; + this.font_size = 24; + this.color = LGraphCanvas.node_colors.pale_blue + ? LGraphCanvas.node_colors.pale_blue.groupcolor + : "#AAA"; + this._bounding = new Float32Array([10, 10, 140, 80]); + this._pos = this._bounding.subarray(0, 2); + this._size = this._bounding.subarray(2, 4); + this._nodes = []; + this.graph = null; + + Object.defineProperty(this, "pos", { + set: function(v) { + if (!v || v.length < 2) { + return; + } + this._pos[0] = v[0]; + this._pos[1] = v[1]; + }, + get: function() { + return this._pos; + }, + enumerable: true + }); - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; + Object.defineProperty(this, "size", { + set: function(v) { + if (!v || v.length < 2) { + return; } - var v = null; + this._size[0] = Math.max(140, v[0]); + this._size[1] = Math.max(80, v[1]); + }, + get: function() { + return this._size; + }, + enumerable: true + }); + }; - if (gamepad) { - switch (output.name) { - case "left_axis": - v = this._left_axis; - break; - case "right_axis": - v = this._right_axis; - break; - case "left_x_axis": - v = this._left_axis[0]; - break; - case "left_y_axis": - v = this._left_axis[1]; - break; - case "right_x_axis": - v = this._right_axis[0]; - break; - case "right_y_axis": - v = this._right_axis[1]; - break; - case "trigger_left": - v = this._triggers[0]; - break; - case "trigger_right": - v = this._triggers[1]; - break; - case "a_button": - v = gamepad.xbox.buttons["a"] ? 1 : 0; - break; - case "b_button": - v = gamepad.xbox.buttons["b"] ? 1 : 0; - break; - case "x_button": - v = gamepad.xbox.buttons["x"] ? 1 : 0; - break; - case "y_button": - v = gamepad.xbox.buttons["y"] ? 1 : 0; - break; - case "lb_button": - v = gamepad.xbox.buttons["lb"] ? 1 : 0; - break; - case "rb_button": - v = gamepad.xbox.buttons["rb"] ? 1 : 0; - break; - case "ls_button": - v = gamepad.xbox.buttons["ls"] ? 1 : 0; - break; - case "rs_button": - v = gamepad.xbox.buttons["rs"] ? 1 : 0; - break; - case "hat_left": - v = gamepad.xbox.hatmap & GamepadInput.LEFT; - break; - case "hat_right": - v = gamepad.xbox.hatmap & GamepadInput.RIGHT; - break; - case "hat_up": - v = gamepad.xbox.hatmap & GamepadInput.UP; - break; - case "hat_down": - v = gamepad.xbox.hatmap & GamepadInput.DOWN; - break; - case "hat": - v = gamepad.xbox.hatmap; - break; - case "start_button": - v = gamepad.xbox.buttons["start"] ? 1 : 0; - break; - case "back_button": - v = gamepad.xbox.buttons["back"] ? 1 : 0; - break; - case "button_pressed": - for ( - var j = 0; - j < this._current_buttons.length; - ++j - ) { - if ( - this._current_buttons[j] && - !this._previous_buttons[j] - ) { - this.triggerSlot( - i, - GamepadInput.buttons[j] - ); - } - } - break; - default: - break; - } - } else { - //if no gamepad is connected, output 0 - switch (output.name) { - case "button_pressed": - break; - case "left_axis": - case "right_axis": - v = GamepadInput.zero; - break; - default: - v = 0; - } + LGraphGroup.prototype.configure = function(o) { + this.title = o.title; + this._bounding.set(o.bounding); + this.color = o.color; + this.font_size = o.font_size; + }; + + LGraphGroup.prototype.serialize = function() { + var b = this._bounding; + return { + title: this.title, + bounding: [ + Math.round(b[0]), + Math.round(b[1]), + Math.round(b[2]), + Math.round(b[3]) + ], + color: this.color, + font_size: this.font_size + }; + }; + + LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) { + this._pos[0] += deltax; + this._pos[1] += deltay; + if (ignore_nodes) { + return; + } + for (var i = 0; i < this._nodes.length; ++i) { + var node = this._nodes[i]; + node.pos[0] += deltax; + node.pos[1] += deltay; + } + }; + + LGraphGroup.prototype.recomputeInsideNodes = function() { + this._nodes.length = 0; + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + + for (var i = 0; i < nodes.length; ++i) { + var node = nodes[i]; + node.getBounding(node_bounding); + if (!overlapBounding(this._bounding, node_bounding)) { + continue; + } //out of the visible area + this._nodes.push(node); + } + }; + + LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside; + LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas; + + //**************************************** + + //Scale and Offset + function DragAndScale(element, skip_events) { + this.offset = new Float32Array([0, 0]); + this.scale = 1; + this.max_scale = 10; + this.min_scale = 0.1; + this.onredraw = null; + this.enabled = true; + this.last_mouse = [0, 0]; + this.element = null; + this.visible_area = new Float32Array(4); + + if (element) { + this.element = element; + if (!skip_events) { + this.bindEvents(element); + } + } + } + + LiteGraph.DragAndScale = DragAndScale; + + DragAndScale.prototype.bindEvents = function(element) { + this.last_mouse = new Float32Array(2); + + this._binded_mouse_callback = this.onMouse.bind(this); + + LiteGraph.pointerListenerAdd(element,"down", this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(element,"move", this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(element,"up", this._binded_mouse_callback); + + element.addEventListener( + "mousewheel", + this._binded_mouse_callback, + false + ); + element.addEventListener("wheel", this._binded_mouse_callback, false); + }; + + DragAndScale.prototype.computeVisibleArea = function( viewport ) { + if (!this.element) { + this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0; + return; + } + var width = this.element.width; + var height = this.element.height; + var startx = -this.offset[0]; + var starty = -this.offset[1]; + if( viewport ) + { + startx += viewport[0] / this.scale; + starty += viewport[1] / this.scale; + width = viewport[2]; + height = viewport[3]; + } + var endx = startx + width / this.scale; + var endy = starty + height / this.scale; + this.visible_area[0] = startx; + this.visible_area[1] = starty; + this.visible_area[2] = endx - startx; + this.visible_area[3] = endy - starty; + }; + + DragAndScale.prototype.onMouse = function(e) { + if (!this.enabled) { + return; + } + + var canvas = this.element; + var rect = canvas.getBoundingClientRect(); + var x = e.clientX - rect.left; + var y = e.clientY - rect.top; + e.canvasx = x; + e.canvasy = y; + e.dragging = this.dragging; + + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + + //console.log("pointerevents: DragAndScale onMouse "+e.type+" "+is_inside); + + var ignore = false; + if (this.onmouse) { + ignore = this.onmouse(e); + } + + if (e.type == LiteGraph.pointerevents_method+"down" && is_inside) { + this.dragging = true; + LiteGraph.pointerListenerRemove(canvas,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(document,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(document,"up",this._binded_mouse_callback); + } else if (e.type == LiteGraph.pointerevents_method+"move") { + if (!ignore) { + var deltax = x - this.last_mouse[0]; + var deltay = y - this.last_mouse[1]; + if (this.dragging) { + this.mouseDrag(deltax, deltay); } - this.setOutputData(i, v); } + } else if (e.type == LiteGraph.pointerevents_method+"up") { + this.dragging = false; + LiteGraph.pointerListenerRemove(document,"move",this._binded_mouse_callback); + LiteGraph.pointerListenerRemove(document,"up",this._binded_mouse_callback); + LiteGraph.pointerListenerAdd(canvas,"move",this._binded_mouse_callback); + } else if ( is_inside && + (e.type == "mousewheel" || + e.type == "wheel" || + e.type == "DOMMouseScroll") + ) { + e.eventType = "mousewheel"; + if (e.type == "wheel") { + e.wheel = -e.deltaY; + } else { + e.wheel = + e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; + } + + //from stack overflow + e.delta = e.wheelDelta + ? e.wheelDelta / 40 + : e.deltaY + ? -e.deltaY / 3 + : 0; + this.changeDeltaScale(1.0 + e.delta * 0.05); + } + + this.last_mouse[0] = x; + this.last_mouse[1] = y; + + if(is_inside) + { + e.preventDefault(); + e.stopPropagation(); + return false; + } + }; + + DragAndScale.prototype.toCanvasContext = function(ctx) { + ctx.scale(this.scale, this.scale); + ctx.translate(this.offset[0], this.offset[1]); + }; + + DragAndScale.prototype.convertOffsetToCanvas = function(pos) { + //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]]; + return [ + (pos[0] + this.offset[0]) * this.scale, + (pos[1] + this.offset[1]) * this.scale + ]; + }; + + DragAndScale.prototype.convertCanvasToOffset = function(pos, out) { + out = out || [0, 0]; + out[0] = pos[0] / this.scale - this.offset[0]; + out[1] = pos[1] / this.scale - this.offset[1]; + return out; + }; + + DragAndScale.prototype.mouseDrag = function(x, y) { + this.offset[0] += x / this.scale; + this.offset[1] += y / this.scale; + + if (this.onredraw) { + this.onredraw(this); + } + }; + + DragAndScale.prototype.changeScale = function(value, zooming_center) { + if (value < this.min_scale) { + value = this.min_scale; + } else if (value > this.max_scale) { + value = this.max_scale; + } + + if (value == this.scale) { + return; + } + + if (!this.element) { + return; + } + + var rect = this.element.getBoundingClientRect(); + if (!rect) { + return; + } + + zooming_center = zooming_center || [ + rect.width * 0.5, + rect.height * 0.5 + ]; + var center = this.convertCanvasToOffset(zooming_center); + this.scale = value; + if (Math.abs(this.scale - 1) < 0.01) { + this.scale = 1; + } + + var new_center = this.convertCanvasToOffset(zooming_center); + var delta_offset = [ + new_center[0] - center[0], + new_center[1] - center[1] + ]; + + this.offset[0] += delta_offset[0]; + this.offset[1] += delta_offset[1]; + + if (this.onredraw) { + this.onredraw(this); + } + }; + + DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) { + this.changeScale(this.scale * value, zooming_center); + }; + + DragAndScale.prototype.reset = function() { + this.scale = 1; + this.offset[0] = 0; + this.offset[1] = 0; + }; + + //********************************************************************************* + // LGraphCanvas: LGraph renderer CLASS + //********************************************************************************* + + /** + * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required. + * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked + * + * @class LGraphCanvas + * @constructor + * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself) + * @param {LGraph} graph [optional] + * @param {Object} options [optional] { skip_rendering, autoresize, viewport } + */ + function LGraphCanvas(canvas, graph, options) { + this.options = options = options || {}; + + //if(graph === undefined) + // throw ("No graph assigned"); + this.background_image = LGraphCanvas.DEFAULT_BACKGROUND_IMAGE; + + if (canvas && canvas.constructor === String) { + canvas = document.querySelector(canvas); + } + + this.ds = new DragAndScale(); + this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much + + this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial"; + this.inner_text_font = + "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial"; + this.node_title_color = LiteGraph.NODE_TITLE_COLOR; + this.default_link_color = LiteGraph.LINK_COLOR; + this.default_connection_color = { + input_off: "#778", + input_on: "#7F7", //"#BBD" + output_off: "#778", + output_on: "#7F7" //"#BBD" + }; + this.default_connection_color_byType = { + /*number: "#7F7", + string: "#77F", + boolean: "#F77",*/ + } + this.default_connection_color_byTypeOff = { + /*number: "#474", + string: "#447", + boolean: "#744",*/ + }; + + this.highquality_render = true; + this.use_gradients = false; //set to true to render titlebar with gradients + this.editor_alpha = 1; //used for transition + this.pause_rendering = false; + this.clear_background = true; + this.clear_background_color = "#222"; + + this.read_only = false; //if set to true users cannot modify the graph + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc + this.multi_select = false; //allow selecting multi nodes without pressing extra keys + this.allow_searchbox = true; + this.allow_reconnect_links = true; //allows to change a connection with having to redo it again + this.align_to_grid = false; //snap to grid + + this.drag_mode = false; + this.dragging_rectangle = null; + + this.filter = null; //allows to filter to only accept some type of nodes in a graph + + this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything + this.always_render_background = false; + this.render_shadows = true; + this.render_canvas_border = true; + this.render_connections_shadows = false; //too much cpu + this.render_connections_border = true; + this.render_curved_connections = false; + this.render_connection_arrows = false; + this.render_collapsed_slots = true; + this.render_execution_order = false; + this.render_title_colored = true; + this.render_link_tooltip = true; + + this.links_render_mode = LiteGraph.SPLINE_LINK; + + this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD + + //to personalize the search box + this.onSearchBox = null; + this.onSearchBoxSelection = null; + + //callbacks + this.onMouse = null; + this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform + this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform + this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs) + this.onDrawLinkTooltip = null; //called when rendering a tooltip + this.onNodeMoved = null; //called after moving a node + this.onSelectionChange = null; //called if the selection changes + this.onConnectingChange = null; //called before any link changes + this.onBeforeChange = null; //called before modifying the graph + this.onAfterChange = null; //called after modifying the graph + + this.connections_width = 3; + this.round_radius = 8; + + this.current_node = null; + this.node_widget = null; //used for widgets + this.over_link_center = null; + this.last_mouse_position = [0, 0]; + this.visible_area = this.ds.visible_area; + this.visible_links = []; + + this.viewport = options.viewport || null; //to constraint render area to a portion of the canvas + + //link canvas and graph + if (graph) { + graph.attachCanvas(this); + } + + this.setCanvas(canvas,options.skip_events); + this.clear(); + + if (!options.skip_render) { + this.startRendering(); + } + + this.autoresize = options.autoresize; + } + + global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; + + LGraphCanvas.DEFAULT_BACKGROUND_IMAGE = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; + + LGraphCanvas.link_type_colors = { + "-1": LiteGraph.EVENT_LINK_COLOR, + number: "#AAA", + node: "#DCA" + }; + LGraphCanvas.gradients = {}; //cache of gradients + + /** + * clears all the data inside + * + * @method clear + */ + LGraphCanvas.prototype.clear = function() { + this.frame = 0; + this.last_draw_time = 0; + this.render_time = 0; + this.fps = 0; + + //this.scale = 1; + //this.offset = [0,0]; + + this.dragging_rectangle = null; + + this.selected_nodes = {}; + this.selected_group = null; + + this.visible_nodes = []; + this.node_dragged = null; + this.node_over = null; + this.node_capturing_input = null; + this.connecting_node = null; + this.highlighted_links = {}; + + this.dragging_canvas = false; + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.dirty_area = null; + + this.node_in_panel = null; + this.node_widget = null; + + this.last_mouse = [0, 0]; + this.last_mouseclick = 0; + this.pointer_is_down = false; + this.pointer_is_double = false; + this.visible_area.set([0, 0, 0, 0]); + + if (this.onClear) { + this.onClear(); + } + }; + + /** + * assigns a graph, you can reassign graphs to the same canvas + * + * @method setGraph + * @param {LGraph} graph + */ + LGraphCanvas.prototype.setGraph = function(graph, skip_clear) { + if (this.graph == graph) { + return; + } + + if (!skip_clear) { + this.clear(); + } + + if (!graph && this.graph) { + this.graph.detachCanvas(this); + return; + } + + graph.attachCanvas(this); + + //remove the graph stack in case a subgraph was open + if (this._graph_stack) + this._graph_stack = null; + + this.setDirty(true, true); + }; + + /** + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @method getTopGraph + * @return {LGraph} graph + */ + LGraphCanvas.prototype.getTopGraph = function() + { + if(this._graph_stack.length) + return this._graph_stack[0]; + return this.graph; + } + + /** + * opens a graph contained inside a node in the current graph + * + * @method openSubgraph + * @param {LGraph} graph + */ + LGraphCanvas.prototype.openSubgraph = function(graph) { + if (!graph) { + throw "graph cannot be null"; + } + + if (this.graph == graph) { + throw "graph cannot be the same"; + } + + this.clear(); + + if (this.graph) { + if (!this._graph_stack) { + this._graph_stack = []; + } + this._graph_stack.push(this.graph); + } + + graph.attachCanvas(this); + this.checkPanels(); + this.setDirty(true, true); + }; + + /** + * closes a subgraph contained inside a node + * + * @method closeSubgraph + * @param {LGraph} assigns a graph + */ + LGraphCanvas.prototype.closeSubgraph = function() { + if (!this._graph_stack || this._graph_stack.length == 0) { + return; + } + var subgraph_node = this.graph._subgraph_node; + var graph = this._graph_stack.pop(); + this.selected_nodes = {}; + this.highlighted_links = {}; + graph.attachCanvas(this); + this.setDirty(true, true); + if (subgraph_node) { + this.centerOnNode(subgraph_node); + this.selectNodes([subgraph_node]); + } + // when close sub graph back to offset [0, 0] scale 1 + this.ds.offset = [0, 0] + this.ds.scale = 1 + }; + + /** + * returns the visually active graph (in case there are more in the stack) + * @method getCurrentGraph + * @return {LGraph} the active graph + */ + LGraphCanvas.prototype.getCurrentGraph = function() { + return this.graph; + }; + + /** + * assigns a canvas + * + * @method setCanvas + * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector) + */ + LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) { + var that = this; + + if (canvas) { + if (canvas.constructor === String) { + canvas = document.getElementById(canvas); + if (!canvas) { + throw "Error creating LiteGraph canvas: Canvas not found"; + } + } + } + + if (canvas === this.canvas) { + return; + } + + if (!canvas && this.canvas) { + //maybe detach events from old_canvas + if (!skip_events) { + this.unbindEvents(); + } + } + + this.canvas = canvas; + this.ds.element = canvas; + + if (!canvas) { + return; + } + + //this.canvas.tabindex = "1000"; + canvas.className += " lgraphcanvas"; + canvas.data = this; + canvas.tabindex = "1"; //to allow key events + + //bg canvas: used for non changing stuff + this.bgcanvas = null; + if (!this.bgcanvas) { + this.bgcanvas = document.createElement("canvas"); + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + } + + if (canvas.getContext == null) { + if (canvas.localName != "canvas") { + throw "Element supplied for LGraphCanvas must be a element, you passed a " + + canvas.localName; + } + throw "This browser doesn't support Canvas"; + } + + var ctx = (this.ctx = canvas.getContext("2d")); + if (ctx == null) { + if (!canvas.webgl_enabled) { + console.warn( + "This canvas seems to be WebGL, enabling WebGL renderer" + ); + } + this.enableWebGL(); + } + + //input: (move and up could be unbinded) + // why here? this._mousemove_callback = this.processMouseMove.bind(this); + // why here? this._mouseup_callback = this.processMouseUp.bind(this); + + if (!skip_events) { + this.bindEvents(); + } + }; + + //used in some events to capture them + LGraphCanvas.prototype._doNothing = function doNothing(e) { + //console.log("pointerevents: _doNothing "+e.type); + e.preventDefault(); + return false; + }; + LGraphCanvas.prototype._doReturnTrue = function doNothing(e) { + e.preventDefault(); + return true; + }; + + /** + * binds mouse, keyboard, touch and drag events to the canvas + * @method bindEvents + **/ + LGraphCanvas.prototype.bindEvents = function() { + if (this._events_binded) { + console.warn("LGraphCanvas: events already binded"); + return; + } + + //console.log("pointerevents: bindEvents"); + + var canvas = this.canvas; + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; //hack used when moving canvas between windows + + this._mousedown_callback = this.processMouseDown.bind(this); + this._mousewheel_callback = this.processMouseWheel.bind(this); + // why mousemove and mouseup were not binded here? + this._mousemove_callback = this.processMouseMove.bind(this); + this._mouseup_callback = this.processMouseUp.bind(this); + + //touch events -- TODO IMPLEMENT + //this._touch_callback = this.touchHandler.bind(this); + + LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded + canvas.addEventListener("mousewheel", this._mousewheel_callback, false); + + LiteGraph.pointerListenerAdd(canvas,"up", this._mouseup_callback, true); // CHECK: ??? binded or not + LiteGraph.pointerListenerAdd(canvas,"move", this._mousemove_callback); + + canvas.addEventListener("contextmenu", this._doNothing); + canvas.addEventListener( + "DOMMouseScroll", + this._mousewheel_callback, + false + ); + + //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents + /*if( 'touchstart' in document.documentElement ) + { + canvas.addEventListener("touchstart", this._touch_callback, true); + canvas.addEventListener("touchmove", this._touch_callback, true); + canvas.addEventListener("touchend", this._touch_callback, true); + canvas.addEventListener("touchcancel", this._touch_callback, true); + }*/ + + //Keyboard ****************** + this._key_callback = this.processKey.bind(this); + canvas.setAttribute("tabindex",1); //otherwise key events are ignored + canvas.addEventListener("keydown", this._key_callback, true); + document.addEventListener("keyup", this._key_callback, true); //in document, otherwise it doesn't fire keyup + + //Dropping Stuff over nodes ************************************ + this._ondrop_callback = this.processDrop.bind(this); + + canvas.addEventListener("dragover", this._doNothing, false); + canvas.addEventListener("dragend", this._doNothing, false); + canvas.addEventListener("drop", this._ondrop_callback, false); + canvas.addEventListener("dragenter", this._doReturnTrue, false); + + this._events_binded = true; + }; + + /** + * unbinds mouse events from the canvas + * @method unbindEvents + **/ + LGraphCanvas.prototype.unbindEvents = function() { + if (!this._events_binded) { + console.warn("LGraphCanvas: no events binded"); + return; + } + + //console.log("pointerevents: unbindEvents"); + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + + LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousedown_callback); + LiteGraph.pointerListenerRemove(this.canvas,"up", this._mousedown_callback); + LiteGraph.pointerListenerRemove(this.canvas,"down", this._mousedown_callback); + this.canvas.removeEventListener( + "mousewheel", + this._mousewheel_callback + ); + this.canvas.removeEventListener( + "DOMMouseScroll", + this._mousewheel_callback + ); + this.canvas.removeEventListener("keydown", this._key_callback); + document.removeEventListener("keyup", this._key_callback); + this.canvas.removeEventListener("contextmenu", this._doNothing); + this.canvas.removeEventListener("drop", this._ondrop_callback); + this.canvas.removeEventListener("dragenter", this._doReturnTrue); + + //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents + /*this.canvas.removeEventListener("touchstart", this._touch_callback ); + this.canvas.removeEventListener("touchmove", this._touch_callback ); + this.canvas.removeEventListener("touchend", this._touch_callback ); + this.canvas.removeEventListener("touchcancel", this._touch_callback );*/ + + this._mousedown_callback = null; + this._mousewheel_callback = null; + this._key_callback = null; + this._ondrop_callback = null; + + this._events_binded = false; + }; + + LGraphCanvas.getFileExtension = function(url) { + var question = url.indexOf("?"); + if (question != -1) { + url = url.substr(0, question); + } + var point = url.lastIndexOf("."); + if (point == -1) { + return ""; + } + return url.substr(point + 1).toLowerCase(); + }; + + /** + * this function allows to render the canvas using WebGL instead of Canvas2D + * this is useful if you plant to render 3D objects inside your nodes, it uses litegl.js for webgl and canvas2DtoWebGL to emulate the Canvas2D calls in webGL + * @method enableWebGL + **/ + LGraphCanvas.prototype.enableWebGL = function() { + if (typeof GL === "undefined") { + throw "litegl.js must be included to use a WebGL canvas"; + } + if (typeof enableWebGLCanvas === "undefined") { + throw "webglCanvas.js must be included to use this feature"; + } + + this.gl = this.ctx = enableWebGLCanvas(this.canvas); + this.ctx.webgl = true; + this.bgcanvas = this.canvas; + this.bgctx = this.gl; + this.canvas.webgl_enabled = true; + + /* + GL.create({ canvas: this.bgcanvas }); + this.bgctx = enableWebGLCanvas( this.bgcanvas ); + window.gl = this.gl; + */ + }; + + /** + * marks as dirty the canvas, this way it will be rendered again + * + * @class LGraphCanvas + * @method setDirty + * @param {bool} fgcanvas if the foreground canvas is dirty (the one containing the nodes) + * @param {bool} bgcanvas if the background canvas is dirty (the one containing the wires) + */ + LGraphCanvas.prototype.setDirty = function(fgcanvas, bgcanvas) { + if (fgcanvas) { + this.dirty_canvas = true; + } + if (bgcanvas) { + this.dirty_bgcanvas = true; + } + }; + + /** + * Used to attach the canvas in a popup + * + * @method getCanvasWindow + * @return {window} returns the window where the canvas is attached (the DOM root node) + */ + LGraphCanvas.prototype.getCanvasWindow = function() { + if (!this.canvas) { + return window; + } + var doc = this.canvas.ownerDocument; + return doc.defaultView || doc.parentWindow; + }; + + /** + * starts rendering the content of the canvas when needed + * + * @method startRendering + */ + LGraphCanvas.prototype.startRendering = function() { + if (this.is_rendering) { + return; + } //already rendering + + this.is_rendering = true; + renderFrame.call(this); + + function renderFrame() { + if (!this.pause_rendering) { + this.draw(); + } + + var window = this.getCanvasWindow(); + if (this.is_rendering) { + window.requestAnimationFrame(renderFrame.bind(this)); + } + } + }; + + /** + * stops rendering the content of the canvas (to save resources) + * + * @method stopRendering + */ + LGraphCanvas.prototype.stopRendering = function() { + this.is_rendering = false; + /* + if(this.rendering_timer_id) + { + clearInterval(this.rendering_timer_id); + this.rendering_timer_id = null; + } + */ + }; + + /* LiteGraphCanvas input */ + + //used to block future mouse events (because of im gui) + LGraphCanvas.prototype.blockClick = function() + { + this.block_click = true; + this.last_mouseclick = 0; + } + + LGraphCanvas.prototype.processMouseDown = function(e) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { + return; + } + + this.adjustMouseEvent(e); + + var ref_window = this.getCanvasWindow(); + var document = ref_window.document; + LGraphCanvas.active_canvas = this; + var that = this; + + var x = e.clientX; + var y = e.clientY; + //console.log(y,this.viewport); + //console.log("pointerevents: processMouseDown pointerId:"+e.pointerId+" which:"+e.which+" isPrimary:"+e.isPrimary+" :: x y "+x+" "+y); + + this.ds.viewport = this.viewport; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + + //move mouse move event to the window in case it drags outside of the canvas + if(!this.options.skip_events) + { + LiteGraph.pointerListenerRemove(this.canvas,"move", this._mousemove_callback); + LiteGraph.pointerListenerAdd(ref_window.document,"move", this._mousemove_callback,true); //catch for the entire window + LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); + } + + if(!is_inside){ + return; + } + + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); + var skip_dragging = false; + var skip_action = false; + var now = LiteGraph.getTime(); + var is_primary = (e.isPrimary === undefined || !e.isPrimary); + var is_double_click = (now - this.last_mouseclick < 300) && is_primary; + this.mouse[0] = e.clientX; + this.mouse[1] = e.clientY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + this.last_click_position = [this.mouse[0],this.mouse[1]]; + + if (this.pointer_is_down && is_primary ){ + this.pointer_is_double = true; + //console.log("pointerevents: pointer_is_double start"); + }else{ + this.pointer_is_double = false; + } + this.pointer_is_down = true; + + + this.canvas.focus(); + + LiteGraph.closeAllContextMenus(ref_window); + + if (this.onMouse) + { + if (this.onMouse(e) == true) + return; + } + + //left button mouse / single finger + if (e.which == 1 && !this.pointer_is_double) + { + if (e.ctrlKey) + { + this.dragging_rectangle = new Float32Array(4); + this.dragging_rectangle[0] = e.canvasX; + this.dragging_rectangle[1] = e.canvasY; + this.dragging_rectangle[2] = 1; + this.dragging_rectangle[3] = 1; + skip_action = true; + } + + // clone node ALT dragging + if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) + { + if (cloned = node.clone()){ + cloned.pos[0] += 5; + cloned.pos[1] += 5; + this.graph.add(cloned,false,{doCalcSize: false}); + node = cloned; + skip_action = true; + if (!block_drag_node) { + if (this.allow_dragnodes) { + this.graph.beforeChange(); + this.node_dragged = node; + } + if (!this.selected_nodes[node.id]) { + this.processNodeSelected(node, e); + } + } + } + } + + var clicking_canvas_bg = false; + + //when clicked on top of a node + //and it is not interactive + if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { + if (!this.live_mode && !node.flags.pinned) { + this.bringToFront(node); + } //if it wasn't selected? + + //not dragging mouse to connect two slots + if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { + //Search for corner for resize + if ( !skip_action && + node.resizable !== false && + isInsideRectangle( e.canvasX, + e.canvasY, + node.pos[0] + node.size[0] - 5, + node.pos[1] + node.size[1] - 5, + 10, + 10 + ) + ) { + this.graph.beforeChange(); + this.resizing_node = node; + this.canvas.style.cursor = "se-resize"; + skip_action = true; + } else { + //search for outputs + if (node.outputs) { + for ( var i = 0, l = node.outputs.length; i < l; ++i ) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + link_pos[0] - 15, + link_pos[1] - 10, + 30, + 20 + ) + ) { + this.connecting_node = node; + this.connecting_output = output; + this.connecting_output.slot_index = i; + this.connecting_pos = node.getConnectionPos( false, i ); + this.connecting_slot = i; + + if (LiteGraph.shift_click_do_break_link_from){ + if (e.shiftKey) { + node.disconnectOutput(i); + } + } + + if (is_double_click) { + if (node.onOutputDblClick) { + node.onOutputDblClick(i, e); + } + } else { + if (node.onOutputClick) { + node.onOutputClick(i, e); + } + } + + skip_action = true; + break; + } + } + } + + //search for inputs + if (node.inputs) { + for ( var i = 0, l = node.inputs.length; i < l; ++i ) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + link_pos[0] - 15, + link_pos[1] - 10, + 30, + 20 + ) + ) { + if (is_double_click) { + if (node.onInputDblClick) { + node.onInputDblClick(i, e); + } + } else { + if (node.onInputClick) { + node.onInputClick(i, e); + } + } + + if (input.link !== null) { + var link_info = this.graph.links[ + input.link + ]; //before disconnecting + if (LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + this.dirty_bgcanvas = true; + skip_action = true; + }else{ + // do same action as has not node ? + } + + if ( + this.allow_reconnect_links || + //this.move_destination_link_without_shift || + e.shiftKey + ) { + if (!LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + } + this.connecting_node = this.graph._nodes_by_id[ + link_info.origin_id + ]; + this.connecting_slot = + link_info.origin_slot; + this.connecting_output = this.connecting_node.outputs[ + this.connecting_slot + ]; + this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); + + this.dirty_bgcanvas = true; + skip_action = true; + } + + + }else{ + // has not node + } + + if (!skip_action){ + // connect from in to out, from to to from + this.connecting_node = node; + this.connecting_input = input; + this.connecting_input.slot_index = i; + this.connecting_pos = node.getConnectionPos( true, i ); + this.connecting_slot = i; + + this.dirty_bgcanvas = true; + skip_action = true; + } + } + } + } + } //not resizing + } + + //it wasn't clicked on the links boxes + if (!skip_action) { + var block_drag_node = false; + var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; + + //widgets + var widget = this.processNodeWidgets( node, this.graph_mouse, e ); + if (widget) { + block_drag_node = true; + this.node_widget = [node, widget]; + } + + //double clicking + if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { + //double click node + if (node.onDblClick) { + node.onDblClick( e, pos, this ); + } + this.processNodeDblClicked(node); + block_drag_node = true; + } + + //if do not capture mouse + if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { + block_drag_node = true; + } else { + //open subgraph button + if(node.subgraph && !node.skip_subgraph_button) + { + if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { + var that = this; + setTimeout(function() { + that.openSubgraph(node.subgraph); + }, 10); + } + } + + if (this.live_mode) { + clicking_canvas_bg = true; + block_drag_node = true; + } + } + + if (!block_drag_node) { + if (this.allow_dragnodes) { + this.graph.beforeChange(); + this.node_dragged = node; + } + this.processNodeSelected(node, e); + } else { // double-click + /** + * Don't call the function if the block is already selected. + * Otherwise, it could cause the block to be unselected while its panel is open. + */ + if (!node.is_selected) this.processNodeSelected(node, e); + } + + this.dirty_canvas = true; + } + } //clicked outside of nodes + else { + if (!skip_action){ + //search for link connector + if(!this.read_only) { + for (var i = 0; i < this.visible_links.length; ++i) { + var link = this.visible_links[i]; + var center = link._pos; + if ( + !center || + e.canvasX < center[0] - 4 || + e.canvasX > center[0] + 4 || + e.canvasY < center[1] - 4 || + e.canvasY > center[1] + 4 + ) { + continue; + } + //link clicked + this.showLinkMenu(link, e); + this.over_link_center = null; //clear tooltip + break; + } + } + + this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); + this.selected_group_resizing = false; + if (this.selected_group && !this.read_only ) { + if (e.ctrlKey) { + this.dragging_rectangle = null; + } + + var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); + if (dist * this.ds.scale < 10) { + this.selected_group_resizing = true; + } else { + this.selected_group.recomputeInsideNodes(); + } + } + + if (is_double_click && !this.read_only && this.allow_searchbox) { + this.showSearchBox(e); + e.preventDefault(); + e.stopPropagation(); + } + + clicking_canvas_bg = true; + } + } + + if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { + //console.log("pointerevents: dragging_canvas start"); + this.dragging_canvas = true; + } + + } else if (e.which == 2) { + //middle button + + if (LiteGraph.middle_click_slot_add_default_node){ + if (node && this.allow_interaction && !skip_action && !this.read_only){ + //not dragging mouse to connect two slots + if ( + !this.connecting_node && + !node.flags.collapsed && + !this.live_mode + ) { + var mClikSlot = false; + var mClikSlot_index = false; + var mClikSlot_isOut = false; + //search for outputs + if (node.outputs) { + for ( var i = 0, l = node.outputs.length; i < l; ++i ) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = output; + mClikSlot_index = i; + mClikSlot_isOut = true; + break; + } + } + } + + //search for inputs + if (node.inputs) { + for ( var i = 0, l = node.inputs.length; i < l; ++i ) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = input; + mClikSlot_index = i; + mClikSlot_isOut = false; + break; + } + } + } + //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); + if (mClikSlot && mClikSlot_index!==false){ + + var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); + var node_bounding = node.getBounding(); + // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes + var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 + ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" + ]; + var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node + ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index + ,nodeTo: !mClikSlot_isOut?node:null + ,slotTo: !mClikSlot_isOut?mClikSlot_index:null + ,position: posRef //,e: e + ,nodeType: "AUTO" //nodeNewType + ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] + ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ + }); + + } + } + } + } else if (!skip_action && this.allow_dragcanvas) { + //console.log("pointerevents: dragging_canvas start from middle button"); + this.dragging_canvas = true; + } + + + } else if (e.which == 3 || this.pointer_is_double) { + + //right button + if (this.allow_interaction && !skip_action && !this.read_only){ + + // is it hover a node ? + if (node){ + if(Object.keys(this.selected_nodes).length + && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) + ){ + // is multiselected or using shift to include the now node + if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present + }else{ + // update selection + this.selectNodes([node]); + } + } + + // show menu on this node + this.processContextMenu(node, e); + } + + } + + //TODO + //if(this.node_selected != prev_selected) + // this.onNodeSelectionChange(this.node_selected); + + this.last_mouse[0] = e.clientX; + this.last_mouse[1] = e.clientY; + this.last_mouseclick = LiteGraph.getTime(); + this.last_mouse_dragging = true; + + /* + if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + //this is to ensure to defocus(blur) if a text input element is on focus + if ( + !ref_window.document.activeElement || + (ref_window.document.activeElement.nodeName.toLowerCase() != + "input" && + ref_window.document.activeElement.nodeName.toLowerCase() != + "textarea") + ) { + e.preventDefault(); + } + e.stopPropagation(); + + if (this.onMouseDown) { + this.onMouseDown(e); + } + + return false; + }; + + /** + * Called when a mouse move event has to be processed + * @method processMouseMove + **/ + LGraphCanvas.prototype.processMouseMove = function(e) { + if (this.autoresize) { + this.resize(); + } + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { + return; + } + + LGraphCanvas.active_canvas = this; + this.adjustMouseEvent(e); + var mouse = [e.clientX, e.clientY]; + this.mouse[0] = mouse[0]; + this.mouse[1] = mouse[1]; + var delta = [ + mouse[0] - this.last_mouse[0], + mouse[1] - this.last_mouse[1] + ]; + this.last_mouse = mouse; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + + //console.log("pointerevents: processMouseMove "+e.pointerId+" "+e.isPrimary); + + if(this.block_click) + { + //console.log("pointerevents: processMouseMove block_click"); + e.preventDefault(); + return false; + } + + e.dragging = this.last_mouse_dragging; + + if (this.node_widget) { + this.processNodeWidgets( + this.node_widget[0], + this.graph_mouse, + e, + this.node_widget[1] + ); + this.dirty_canvas = true; + } + + //get node over + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); + + if (this.dragging_rectangle) + { + this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; + this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; + this.dirty_canvas = true; + } + else if (this.selected_group && !this.read_only) + { + //moving/resizing a group + if (this.selected_group_resizing) { + this.selected_group.size = [ + e.canvasX - this.selected_group.pos[0], + e.canvasY - this.selected_group.pos[1] + ]; + } else { + var deltax = delta[0] / this.ds.scale; + var deltay = delta[1] / this.ds.scale; + this.selected_group.move(deltax, deltay, e.ctrlKey); + if (this.selected_group._nodes.length) { + this.dirty_canvas = true; + } + } + this.dirty_bgcanvas = true; + } else if (this.dragging_canvas) { + ////console.log("pointerevents: processMouseMove is dragging_canvas"); + this.ds.offset[0] += delta[0] / this.ds.scale; + this.ds.offset[1] += delta[1] / this.ds.scale; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { + if (this.connecting_node) { + this.dirty_canvas = true; + } + + //remove mouseover flag + for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { + if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { + //mouse leave + this.graph._nodes[i].mouseOver = false; + if (this.node_over && this.node_over.onMouseLeave) { + this.node_over.onMouseLeave(e); + } + this.node_over = null; + this.dirty_canvas = true; + } + } + + //mouse over a node + if (node) { + + if(node.redraw_on_mouse) + this.dirty_canvas = true; + + //this.canvas.style.cursor = "move"; + if (!node.mouseOver) { + //mouse enter + node.mouseOver = true; + this.node_over = node; + this.dirty_canvas = true; + + if (node.onMouseEnter) { + node.onMouseEnter(e); + } + } + + //in case the node wants to do something + if (node.onMouseMove) { + node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); + } + + //if dragging a link + if (this.connecting_node) { + + if (this.connecting_output){ + + var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput + + //on top of input + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do + } else { + //check if I have a slot below de mouse + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.inputs[slot]) { + var slot_type = node.inputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { + this._highlight_input = pos; + this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS + } + } else { + this._highlight_input = null; + this._highlight_input_slot = null; // XXX CHECK THIS + } + } + + }else if(this.connecting_input){ + + var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput + + //on top of output + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do + } else { + //check if I have a slot below de mouse + var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.outputs[slot]) { + var slot_type = node.outputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { + this._highlight_output = pos; + } + } else { + this._highlight_output = null; + } + } + } + } + + //Search for corner + if (this.canvas) { + if ( + isInsideRectangle( + e.canvasX, + e.canvasY, + node.pos[0] + node.size[0] - 5, + node.pos[1] + node.size[1] - 5, + 5, + 5 + ) + ) { + this.canvas.style.cursor = "se-resize"; + } else { + this.canvas.style.cursor = "crosshair"; + } + } + } else { //not over a node + + //search for link connector + var over_link = null; + for (var i = 0; i < this.visible_links.length; ++i) { + var link = this.visible_links[i]; + var center = link._pos; + if ( + !center || + e.canvasX < center[0] - 4 || + e.canvasX > center[0] + 4 || + e.canvasY < center[1] - 4 || + e.canvasY > center[1] + 4 + ) { + continue; + } + over_link = link; + break; + } + if( over_link != this.over_link_center ) + { + this.over_link_center = over_link; + this.dirty_canvas = true; + } + + if (this.canvas) { + this.canvas.style.cursor = ""; + } + } //end + + //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) + if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { + this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); + } + + //node being dragged + if (this.node_dragged && !this.live_mode) { + //console.log("draggin!",this.selected_nodes); + for (var i in this.selected_nodes) { + var n = this.selected_nodes[i]; + n.pos[0] += delta[0] / this.ds.scale; + n.pos[1] += delta[1] / this.ds.scale; + if (!n.is_selected) this.processNodeSelected(n, e); /* + * Don't call the function if the block is already selected. + * Otherwise, it could cause the block to be unselected while dragging. + */ + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + + if (this.resizing_node && !this.live_mode) { + //convert mouse to node space + var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; + var min_size = this.resizing_node.computeSize(); + desired_size[0] = Math.max( min_size[0], desired_size[0] ); + desired_size[1] = Math.max( min_size[1], desired_size[1] ); + this.resizing_node.setSize( desired_size ); + + this.canvas.style.cursor = "se-resize"; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + } + + e.preventDefault(); + return false; + }; + + /** + * Called when a mouse up event has to be processed + * @method processMouseUp + **/ + LGraphCanvas.prototype.processMouseUp = function(e) { + + var is_primary = ( e.isPrimary === undefined || e.isPrimary ); + + //early exit for extra pointer + if(!is_primary){ + /*e.stopPropagation(); + e.preventDefault();*/ + //console.log("pointerevents: processMouseUp pointerN_stop "+e.pointerId+" "+e.isPrimary); + return false; + } + + //console.log("pointerevents: processMouseUp "+e.pointerId+" "+e.isPrimary+" :: "+e.clientX+" "+e.clientY); + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) + return; + + var window = this.getCanvasWindow(); + var document = window.document; + LGraphCanvas.active_canvas = this; + + //restore the mousemove event back to the canvas + if(!this.options.skip_events) + { + //console.log("pointerevents: processMouseUp adjustEventListener"); + LiteGraph.pointerListenerRemove(document,"move", this._mousemove_callback,true); + LiteGraph.pointerListenerAdd(this.canvas,"move", this._mousemove_callback,true); + LiteGraph.pointerListenerRemove(document,"up", this._mouseup_callback,true); + } + + this.adjustMouseEvent(e); + var now = LiteGraph.getTime(); + e.click_time = now - this.last_mouseclick; + this.last_mouse_dragging = false; + this.last_click_position = null; + + if(this.block_click) + { + //console.log("pointerevents: processMouseUp block_clicks"); + this.block_click = false; //used to avoid sending twice a click in a immediate button + } + + //console.log("pointerevents: processMouseUp which: "+e.which); + + if (e.which == 1) { + + if( this.node_widget ) + { + this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); + } + + //left button + this.node_widget = null; + + if (this.selected_group) { + var diffx = + this.selected_group.pos[0] - + Math.round(this.selected_group.pos[0]); + var diffy = + this.selected_group.pos[1] - + Math.round(this.selected_group.pos[1]); + this.selected_group.move(diffx, diffy, e.ctrlKey); + this.selected_group.pos[0] = Math.round( + this.selected_group.pos[0] + ); + this.selected_group.pos[1] = Math.round( + this.selected_group.pos[1] + ); + if (this.selected_group._nodes.length) { + this.dirty_canvas = true; + } + this.selected_group = null; + } + this.selected_group_resizing = false; + + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + + if (this.dragging_rectangle) { + if (this.graph) { + var nodes = this.graph._nodes; + var node_bounding = new Float32Array(4); + + //compute bounding and flip if left to right + var w = Math.abs(this.dragging_rectangle[2]); + var h = Math.abs(this.dragging_rectangle[3]); + var startx = + this.dragging_rectangle[2] < 0 + ? this.dragging_rectangle[0] - w + : this.dragging_rectangle[0]; + var starty = + this.dragging_rectangle[3] < 0 + ? this.dragging_rectangle[1] - h + : this.dragging_rectangle[1]; + this.dragging_rectangle[0] = startx; + this.dragging_rectangle[1] = starty; + this.dragging_rectangle[2] = w; + this.dragging_rectangle[3] = h; + + // test dragging rect size, if minimun simulate a click + if (!node || (w > 10 && h > 10 )){ + //test against all nodes (not visible because the rectangle maybe start outside + var to_select = []; + for (var i = 0; i < nodes.length; ++i) { + var nodeX = nodes[i]; + nodeX.getBounding(node_bounding); + if ( + !overlapBounding( + this.dragging_rectangle, + node_bounding + ) + ) { + continue; + } //out of the visible area + to_select.push(nodeX); + } + if (to_select.length) { + this.selectNodes(to_select,e.shiftKey); // add to selection with shift + } + }else{ + // will select of update selection + this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey + } + + } + this.dragging_rectangle = null; + } else if (this.connecting_node) { + //dragging a connection + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + + var connInOrOut = this.connecting_output || this.connecting_input; + var connType = connInOrOut.type; + + //node below mouse + if (node) { + + /* no need to condition on event type.. just another type + if ( + connType == LiteGraph.EVENT && + this.isOverNodeBox(node, e.canvasX, e.canvasY) + ) { + + this.connecting_node.connect( + this.connecting_slot, + node, + LiteGraph.EVENT + ); + + } else {*/ + + //slot below mouse? connect + + if (this.connecting_output){ + + var slot = this.isOverNodeInput( + node, + e.canvasX, + e.canvasY + ); + if (slot != -1) { + this.connecting_node.connect(this.connecting_slot, node, slot); + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByType(this.connecting_slot,node,connType); + } + + }else if (this.connecting_input){ + + var slot = this.isOverNodeOutput( + node, + e.canvasX, + e.canvasY + ); + + if (slot != -1) { + node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); + } + + } + + + //} + + }else{ + + // add menu when releasing link in empty space + if (LiteGraph.release_link_on_empty_shows_menu){ + if (e.shiftKey && this.allow_searchbox){ + if(this.connecting_output){ + this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); + }else if(this.connecting_input){ + this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); + } + }else{ + if(this.connecting_output){ + this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); + }else if(this.connecting_input){ + this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); + } + } + } + } + + this.connecting_output = null; + this.connecting_input = null; + this.connecting_pos = null; + this.connecting_node = null; + this.connecting_slot = -1; + } //not dragging connection + else if (this.resizing_node) { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.graph.afterChange(this.resizing_node); + this.resizing_node = null; + } else if (this.node_dragged) { + //node being dragged? + var node = this.node_dragged; + if ( + node && + e.click_time < 300 && + isInsideRectangle( e.canvasX, e.canvasY, node.pos[0], node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_TITLE_HEIGHT ) + ) { + node.collapse(); + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + if (this.graph.config.align_to_grid || this.align_to_grid ) { + this.node_dragged.alignToGrid(); + } + if( this.onNodeMoved ) + this.onNodeMoved( this.node_dragged ); + this.graph.afterChange(this.node_dragged); + this.node_dragged = null; + } //no node being dragged + else { + //get node over + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + + if (!node && e.click_time < 300) { + this.deselectAllNodes(); + } + + this.dirty_canvas = true; + this.dragging_canvas = false; + + if (this.node_over && this.node_over.onMouseUp) { + this.node_over.onMouseUp( e, [ e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1] ], this ); + } + if ( + this.node_capturing_input && + this.node_capturing_input.onMouseUp + ) { + this.node_capturing_input.onMouseUp(e, [ + e.canvasX - this.node_capturing_input.pos[0], + e.canvasY - this.node_capturing_input.pos[1] + ]); + } + } + } else if (e.which == 2) { + //middle button + //trace("middle"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } else if (e.which == 3) { + //right button + //trace("right"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + if (is_primary) + { + this.pointer_is_down = false; + this.pointer_is_double = false; + } + + this.graph.change(); + + //console.log("pointerevents: processMouseUp stopPropagation"); + e.stopPropagation(); + e.preventDefault(); + return false; + }; + + /** + * Called when a mouse wheel event has to be processed + * @method processMouseWheel + **/ + LGraphCanvas.prototype.processMouseWheel = function(e) { + if (!this.graph || !this.allow_dragcanvas) { + return; + } + + var delta = e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60; + + this.adjustMouseEvent(e); + + var x = e.clientX; + var y = e.clientY; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + if(!is_inside) + return; + + var scale = this.ds.scale; + + if (delta > 0) { + scale *= 1.1; + } else if (delta < 0) { + scale *= 1 / 1.1; + } + + //this.setZoom( scale, [ e.clientX, e.clientY ] ); + this.ds.changeScale(scale, [e.clientX, e.clientY]); + + this.graph.change(); + + e.preventDefault(); + return false; // prevent default + }; + + /** + * returns true if a position (in graph space) is on top of a node little corner box + * @method isOverNodeBox + **/ + LGraphCanvas.prototype.isOverNodeBox = function(node, canvasx, canvasy) { + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + if ( + isInsideRectangle( + canvasx, + canvasy, + node.pos[0] + 2, + node.pos[1] + 2 - title_height, + title_height - 4, + title_height - 4 + ) + ) { + return true; + } + return false; + }; + + /** + * returns the INDEX if a position (in graph space) is on top of a node input slot + * @method isOverNodeInput + **/ + LGraphCanvas.prototype.isOverNodeInput = function( + node, + canvasx, + canvasy, + slot_pos + ) { + if (node.inputs) { + for (var i = 0, l = node.inputs.length; i < l; ++i) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + var is_inside = false; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : node.horizontal; + if (inputs_horizontal) { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 5, + link_pos[1] - 10, + 10, + 20 + ); + } else { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 10, + link_pos[1] - 5, + 40, + 10 + ); + } + if (is_inside) { + if (slot_pos) { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + } + return -1; + }; + + /** + * returns the INDEX if a position (in graph space) is on top of a node output slot + * @method isOverNodeOuput + **/ + LGraphCanvas.prototype.isOverNodeOutput = function( + node, + canvasx, + canvasy, + slot_pos + ) { + if (node.outputs) { + for (var i = 0, l = node.outputs.length; i < l; ++i) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + var is_inside = false; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : node.horizontal; + if (outputs_horizontal) { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 5, + link_pos[1] - 10, + 10, + 20 + ); + } else { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 10, + link_pos[1] - 5, + 40, + 10 + ); + } + if (is_inside) { + if (slot_pos) { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + } + return -1; + }; + + /** + * process a key event + * @method processKey + **/ + LGraphCanvas.prototype.processKey = function(e) { + if (!this.graph) { + return; + } + + var block_default = false; + //console.log(e); //debug + + if (e.target.localName == "input") { + return; + } + + if (e.type == "keydown") { + if (e.keyCode == 32) { + //space + this.dragging_canvas = true; + block_default = true; + } + + if (e.keyCode == 27) { + //esc + if(this.node_panel) this.node_panel.close(); + if(this.options_panel) this.options_panel.close(); + block_default = true; + } + + //select all Control A + if (e.keyCode == 65 && e.ctrlKey) { + this.selectNodes(); + block_default = true; + } + + if ((e.keyCode === 67) && (e.metaKey || e.ctrlKey) && !e.shiftKey) { + //copy + if (this.selected_nodes) { + this.copyToClipboard(); + block_default = true; + } + } + + if ((e.keyCode === 86) && (e.metaKey || e.ctrlKey)) { + //paste + this.pasteFromClipboard(e.shiftKey); + } + + //delete or backspace + if (e.keyCode == 46 || e.keyCode == 8) { + if ( + e.target.localName != "input" && + e.target.localName != "textarea" + ) { + this.deleteSelectedNodes(); + block_default = true; + } + } + + //collapse + //... + + //TODO + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].onKeyDown) { + this.selected_nodes[i].onKeyDown(e); + } + } + } + } else if (e.type == "keyup") { + if (e.keyCode == 32) { + // space + this.dragging_canvas = false; + } + + if (this.selected_nodes) { + for (var i in this.selected_nodes) { + if (this.selected_nodes[i].onKeyUp) { + this.selected_nodes[i].onKeyUp(e); + } + } + } + } + + this.graph.change(); + + if (block_default) { + e.preventDefault(); + e.stopImmediatePropagation(); + return false; + } + }; + + LGraphCanvas.prototype.copyToClipboard = function() { + var clipboard_info = { + nodes: [], + links: [] + }; + var index = 0; + var selected_nodes_array = []; + for (var i in this.selected_nodes) { + var node = this.selected_nodes[i]; + if (node.clonable === false) + continue; + node._relative_id = index; + selected_nodes_array.push(node); + index += 1; + } + + for (var i = 0; i < selected_nodes_array.length; ++i) { + var node = selected_nodes_array[i]; + if(node.clonable === false) + continue; + var cloned = node.clone(); + if(!cloned) + { + console.warn("node type not found: " + node.type ); + continue; + } + clipboard_info.nodes.push(cloned.serialize()); + if (node.inputs && node.inputs.length) { + for (var j = 0; j < node.inputs.length; ++j) { + var input = node.inputs[j]; + if (!input || input.link == null) { + continue; + } + var link_info = this.graph.links[input.link]; + if (!link_info) { + continue; + } + var target_node = this.graph.getNodeById( + link_info.origin_id + ); + if (!target_node) { + continue; + } + clipboard_info.links.push([ + target_node._relative_id, + link_info.origin_slot, //j, + node._relative_id, + link_info.target_slot, + target_node.id + ]); + } + } + } + localStorage.setItem( + "litegrapheditor_clipboard", + JSON.stringify(clipboard_info) + ); + }; + + LGraphCanvas.prototype.pasteFromClipboard = function(isConnectUnselected = false) { + // if ctrl + shift + v is off, return when isConnectUnselected is true (shift is pressed) to maintain old behavior + if (!LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { + return; + } + var data = localStorage.getItem("litegrapheditor_clipboard"); + if (!data) { + return; + } + + this.graph.beforeChange(); + + //create nodes + var clipboard_info = JSON.parse(data); + // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos + var posMin = false; + var posMinIndexes = false; + for (var i = 0; i < clipboard_info.nodes.length; ++i) { + if (posMin){ + if(posMin[0]>clipboard_info.nodes[i].pos[0]){ + posMin[0] = clipboard_info.nodes[i].pos[0]; + posMinIndexes[0] = i; + } + if(posMin[1]>clipboard_info.nodes[i].pos[1]){ + posMin[1] = clipboard_info.nodes[i].pos[1]; + posMinIndexes[1] = i; + } + } + else{ + posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; + posMinIndexes = [i, i]; + } + } + var nodes = []; + for (var i = 0; i < clipboard_info.nodes.length; ++i) { + var node_data = clipboard_info.nodes[i]; + var node = LiteGraph.createNode(node_data.type); + if (node) { + node.configure(node_data); + + //paste in last known mouse position + node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; + node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; + + this.graph.add(node,{doProcessChange:false}); + + nodes.push(node); + } + } + + //create links + for (var i = 0; i < clipboard_info.links.length; ++i) { + var link_info = clipboard_info.links[i]; + var origin_node; + var origin_node_relative_id = link_info[0]; + if (origin_node_relative_id != null) { + origin_node = nodes[origin_node_relative_id]; + } else if (LiteGraph.ctrl_shift_v_paste_connect_unselected_outputs && isConnectUnselected) { + var origin_node_id = link_info[4]; + if (origin_node_id) { + origin_node = this.graph.getNodeById(origin_node_id); + } + } + var target_node = nodes[link_info[2]]; + if( origin_node && target_node ) + origin_node.connect(link_info[1], target_node, link_info[3]); + else + console.warn("Warning, nodes missing on pasting"); + } + + this.selectNodes(nodes); + + this.graph.afterChange(); + }; + + /** + * process a item drop event on top the canvas + * @method processDrop + **/ + LGraphCanvas.prototype.processDrop = function(e) { + e.preventDefault(); + this.adjustMouseEvent(e); + var x = e.clientX; + var y = e.clientY; + var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); + if(!is_inside){ + return; + // --- BREAK --- + } + + var pos = [e.canvasX, e.canvasY]; + + + var node = this.graph ? this.graph.getNodeOnPos(pos[0], pos[1]) : null; + + if (!node) { + var r = null; + if (this.onDropItem) { + r = this.onDropItem(event); + } + if (!r) { + this.checkDropItem(e); + } + return; + } + + if (node.onDropFile || node.onDropData) { + var files = e.dataTransfer.files; + if (files && files.length) { + for (var i = 0; i < files.length; i++) { + var file = e.dataTransfer.files[0]; + var filename = file.name; + var ext = LGraphCanvas.getFileExtension(filename); + //console.log(file); + + if (node.onDropFile) { + node.onDropFile(file); + } + + if (node.onDropData) { + //prepare reader + var reader = new FileReader(); + reader.onload = function(event) { + //console.log(event.target); + var data = event.target.result; + node.onDropData(data, filename, file); + }; + + //read data + var type = file.type.split("/")[0]; + if (type == "text" || type == "") { + reader.readAsText(file); + } else if (type == "image") { + reader.readAsDataURL(file); + } else { + reader.readAsArrayBuffer(file); + } + } + } + } + } + + if (node.onDropItem) { + if (node.onDropItem(event)) { + return true; + } + } + + if (this.onDropItem) { + return this.onDropItem(event); + } + + return false; + }; + + //called if the graph doesn't have a default drop item behaviour + LGraphCanvas.prototype.checkDropItem = function(e) { + if (e.dataTransfer.files.length) { + var file = e.dataTransfer.files[0]; + var ext = LGraphCanvas.getFileExtension(file.name).toLowerCase(); + var nodetype = LiteGraph.node_types_by_file_extension[ext]; + if (nodetype) { + this.graph.beforeChange(); + var node = LiteGraph.createNode(nodetype.type); + node.pos = [e.canvasX, e.canvasY]; + this.graph.add(node); + if (node.onDropFile) { + node.onDropFile(file); + } + this.graph.afterChange(); + } + } + }; + + LGraphCanvas.prototype.processNodeDblClicked = function(n) { + if (this.onShowNodePanel) { + this.onShowNodePanel(n); + } + else + { + this.showShowNodePanel(n); + } + + if (this.onNodeDblClicked) { + this.onNodeDblClicked(n); + } + + this.setDirty(true); + }; + + LGraphCanvas.prototype.processNodeSelected = function(node, e) { + this.selectNode(node, e && (e.shiftKey || e.ctrlKey || this.multi_select)); + if (this.onNodeSelected) { + this.onNodeSelected(node); + } + }; + + /** + * selects a given node (or adds it to the current selection) + * @method selectNode + **/ + LGraphCanvas.prototype.selectNode = function( + node, + add_to_current_selection + ) { + if (node == null) { + this.deselectAllNodes(); + } else { + this.selectNodes([node], add_to_current_selection); + } + }; + + /** + * selects several nodes (or adds them to the current selection) + * @method selectNodes + **/ + LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) + { + if (!add_to_current_selection) { + this.deselectAllNodes(); + } + + nodes = nodes || this.graph._nodes; + if (typeof nodes == "string") nodes = [nodes]; + for (var i in nodes) { + var node = nodes[i]; + if (node.is_selected) { + this.deselectNode(node); + continue; + } + + if (!node.is_selected && node.onSelected) { + node.onSelected(); + } + node.is_selected = true; + this.selected_nodes[node.id] = node; + + if (node.inputs) { + for (var j = 0; j < node.inputs.length; ++j) { + this.highlighted_links[node.inputs[j].link] = true; + } + } + if (node.outputs) { + for (var j = 0; j < node.outputs.length; ++j) { + var out = node.outputs[j]; + if (out.links) { + for (var k = 0; k < out.links.length; ++k) { + this.highlighted_links[out.links[k]] = true; + } + } + } + } + } + + if( this.onSelectionChange ) + this.onSelectionChange( this.selected_nodes ); + + this.setDirty(true); + }; + + /** + * removes a node from the current selection + * @method deselectNode + **/ + LGraphCanvas.prototype.deselectNode = function(node) { + if (!node.is_selected) { + return; + } + if (node.onDeselected) { + node.onDeselected(); + } + node.is_selected = false; + + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + + //remove highlighted + if (node.inputs) { + for (var i = 0; i < node.inputs.length; ++i) { + delete this.highlighted_links[node.inputs[i].link]; + } + } + if (node.outputs) { + for (var i = 0; i < node.outputs.length; ++i) { + var out = node.outputs[i]; + if (out.links) { + for (var j = 0; j < out.links.length; ++j) { + delete this.highlighted_links[out.links[j]]; + } + } + } + } + }; + + /** + * removes all nodes from the current selection + * @method deselectAllNodes + **/ + LGraphCanvas.prototype.deselectAllNodes = function() { + if (!this.graph) { + return; + } + var nodes = this.graph._nodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var node = nodes[i]; + if (!node.is_selected) { + continue; + } + if (node.onDeselected) { + node.onDeselected(); + } + node.is_selected = false; + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + } + this.selected_nodes = {}; + this.current_node = null; + this.highlighted_links = {}; + if( this.onSelectionChange ) + this.onSelectionChange( this.selected_nodes ); + this.setDirty(true); + }; + + /** + * deletes all nodes in the current selection from the graph + * @method deleteSelectedNodes + **/ + LGraphCanvas.prototype.deleteSelectedNodes = function() { + + this.graph.beforeChange(); + + for (var i in this.selected_nodes) { + var node = this.selected_nodes[i]; + + if(node.block_delete) + continue; + + //autoconnect when possible (very basic, only takes into account first input-output) + if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) + { + var input_link = node.graph.links[ node.inputs[0].link ]; + var output_link = node.graph.links[ node.outputs[0].links[0] ]; + var input_node = node.getInputNode(0); + var output_node = node.getOutputNodes(0)[0]; + if(input_node && output_node) + input_node.connect( input_link.origin_slot, output_node, output_link.target_slot ); + } + this.graph.remove(node); + if (this.onNodeDeselected) { + this.onNodeDeselected(node); + } + } + this.selected_nodes = {}; + this.current_node = null; + this.highlighted_links = {}; + this.setDirty(true); + this.graph.afterChange(); + }; + + /** + * centers the camera on a given node + * @method centerOnNode + **/ + LGraphCanvas.prototype.centerOnNode = function(node) { + this.ds.offset[0] = + -node.pos[0] - + node.size[0] * 0.5 + + (this.canvas.width * 0.5) / this.ds.scale; + this.ds.offset[1] = + -node.pos[1] - + node.size[1] * 0.5 + + (this.canvas.height * 0.5) / this.ds.scale; + this.setDirty(true, true); + }; + + /** + * adds some useful properties to a mouse event, like the position in graph coordinates + * @method adjustMouseEvent + **/ + LGraphCanvas.prototype.adjustMouseEvent = function(e) { + var clientX_rel = 0; + var clientY_rel = 0; + + if (this.canvas) { + var b = this.canvas.getBoundingClientRect(); + clientX_rel = e.clientX - b.left; + clientY_rel = e.clientY - b.top; + } else { + clientX_rel = e.clientX; + clientY_rel = e.clientY; + } + + // e.deltaX = clientX_rel - this.last_mouse_position[0]; + // e.deltaY = clientY_rel- this.last_mouse_position[1]; + + this.last_mouse_position[0] = clientX_rel; + this.last_mouse_position[1] = clientY_rel; + + e.canvasX = clientX_rel / this.ds.scale - this.ds.offset[0]; + e.canvasY = clientY_rel / this.ds.scale - this.ds.offset[1]; + + //console.log("pointerevents: adjustMouseEvent "+e.clientX+":"+e.clientY+" "+clientX_rel+":"+clientY_rel+" "+e.canvasX+":"+e.canvasY); + }; + + /** + * changes the zoom level of the graph (default is 1), you can pass also a place used to pivot the zoom + * @method setZoom + **/ + LGraphCanvas.prototype.setZoom = function(value, zooming_center) { + this.ds.changeScale(value, zooming_center); + /* + if(!zooming_center && this.canvas) + zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; + + var center = this.convertOffsetToCanvas( zooming_center ); + + this.ds.scale = value; + + if(this.scale > this.max_zoom) + this.scale = this.max_zoom; + else if(this.scale < this.min_zoom) + this.scale = this.min_zoom; + + var new_center = this.convertOffsetToCanvas( zooming_center ); + var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; + + this.offset[0] += delta_offset[0]; + this.offset[1] += delta_offset[1]; + */ + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + }; + + /** + * converts a coordinate from graph coordinates to canvas2D coordinates + * @method convertOffsetToCanvas + **/ + LGraphCanvas.prototype.convertOffsetToCanvas = function(pos, out) { + return this.ds.convertOffsetToCanvas(pos, out); + }; + + /** + * converts a coordinate from Canvas2D coordinates to graph space + * @method convertCanvasToOffset + **/ + LGraphCanvas.prototype.convertCanvasToOffset = function(pos, out) { + return this.ds.convertCanvasToOffset(pos, out); + }; + + //converts event coordinates from canvas2D to graph coordinates + LGraphCanvas.prototype.convertEventToCanvasOffset = function(e) { + var rect = this.canvas.getBoundingClientRect(); + return this.convertCanvasToOffset([ + e.clientX - rect.left, + e.clientY - rect.top + ]); + }; + + /** + * brings a node to front (above all other nodes) + * @method bringToFront + **/ + LGraphCanvas.prototype.bringToFront = function(node) { + var i = this.graph._nodes.indexOf(node); + if (i == -1) { + return; + } + + this.graph._nodes.splice(i, 1); + this.graph._nodes.push(node); + }; + + /** + * sends a node to the back (below all other nodes) + * @method sendToBack + **/ + LGraphCanvas.prototype.sendToBack = function(node) { + var i = this.graph._nodes.indexOf(node); + if (i == -1) { + return; + } + + this.graph._nodes.splice(i, 1); + this.graph._nodes.unshift(node); + }; + + /* Interaction */ + + /* LGraphCanvas render */ + var temp = new Float32Array(4); + + /** + * checks which nodes are visible (inside the camera area) + * @method computeVisibleNodes + **/ + LGraphCanvas.prototype.computeVisibleNodes = function(nodes, out) { + var visible_nodes = out || []; + visible_nodes.length = 0; + nodes = nodes || this.graph._nodes; + for (var i = 0, l = nodes.length; i < l; ++i) { + var n = nodes[i]; + + //skip rendering nodes in live mode + if (this.live_mode && !n.onDrawBackground && !n.onDrawForeground) { + continue; + } + + if (!overlapBounding(this.visible_area, n.getBounding(temp, true))) { + continue; + } //out of the visible area + + visible_nodes.push(n); + } + return visible_nodes; + }; + + /** + * renders the whole canvas content, by rendering in two separated canvas, one containing the background grid and the connections, and one containing the nodes) + * @method draw + **/ + LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { + return; + } + + //fps counting + var now = LiteGraph.getTime(); + this.render_time = (now - this.last_draw_time) * 0.001; + this.last_draw_time = now; + + if (this.graph) { + this.ds.computeVisibleArea(this.viewport); + } + + if ( + this.dirty_bgcanvas || + force_bgcanvas || + this.always_render_background || + (this.graph && + this.graph._last_trigger_time && + now - this.graph._last_trigger_time < 1000) + ) { + this.drawBackCanvas(); + } + + if (this.dirty_canvas || force_canvas) { + this.drawFrontCanvas(); + } + + this.fps = this.render_time ? 1.0 / this.render_time : 0; + this.frame += 1; + }; + + /** + * draws the front canvas (the one containing all the nodes) + * @method drawFrontCanvas + **/ + LGraphCanvas.prototype.drawFrontCanvas = function() { + this.dirty_canvas = false; + + if (!this.ctx) { + this.ctx = this.bgcanvas.getContext("2d"); + } + var ctx = this.ctx; + if (!ctx) { + //maybe is using webgl... + return; + } + + var canvas = this.canvas; + if ( ctx.start2D && !this.viewport ) { + ctx.start2D(); + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + + //clip dirty area if there is one, otherwise work in full canvas + var area = this.viewport || this.dirty_area; + if (area) { + ctx.save(); + ctx.beginPath(); + ctx.rect( area[0],area[1],area[2],area[3] ); + ctx.clip(); + } + + //clear + //canvas.width = canvas.width; + if (this.clear_background) { + if(area) + ctx.clearRect( area[0],area[1],area[2],area[3] ); + else + ctx.clearRect(0, 0, canvas.width, canvas.height); + } + + //draw bg canvas + if (this.bgcanvas == this.canvas) { + this.drawBackCanvas(); + } else { + ctx.drawImage( this.bgcanvas, 0, 0 ); + } + + //rendering + if (this.onRender) { + this.onRender(canvas, ctx); + } + + //info widget + if (this.show_info) { + this.renderInfo(ctx, area ? area[0] : 0, area ? area[1] : 0 ); + } + + if (this.graph) { + //apply transformations + ctx.save(); + this.ds.toCanvasContext(ctx); + + //draw nodes + var drawn_nodes = 0; + var visible_nodes = this.computeVisibleNodes( + null, + this.visible_nodes + ); + + for (var i = 0; i < visible_nodes.length; ++i) { + var node = visible_nodes[i]; + + //transform coords system + ctx.save(); + ctx.translate(node.pos[0], node.pos[1]); + + //Draw + this.drawNode(node, ctx); + drawn_nodes += 1; + + //Restore + ctx.restore(); + } + + //on top (debug) + if (this.render_execution_order) { + this.drawExecutionOrder(ctx); + } + + //connections ontop? + if (this.graph.config.links_ontop) { + if (!this.live_mode) { + this.drawConnections(ctx); + } + } + + //current connection (the one being dragged by the mouse) + if (this.connecting_pos != null) { + ctx.lineWidth = this.connections_width; + var link_color = null; + + var connInOrOut = this.connecting_output || this.connecting_input; + + var connType = connInOrOut.type; + var connDir = connInOrOut.dir; + if(connDir == null) + { + if (this.connecting_output) { + var outputs_horizontal = this.connecting_node.outputs_horizontal !== undefined && this.connecting_node.outputs_horizontal !== null ? + this.connecting_node.outputs_horizontal : this.connecting_node.horizontal; + connDir = outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; + } else { + var inputs_horizontal = this.connecting_node.inputs_horizontal !== undefined && this.connecting_node.inputs_horizontal !== null ? + this.connecting_node.inputs_horizontal : this.connecting_node.horizontal; + connDir = inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT; + } + } + var connShape = connInOrOut.shape; + + switch (connType) { + case LiteGraph.EVENT: + link_color = LiteGraph.EVENT_LINK_COLOR; + break; + default: + link_color = LiteGraph.CONNECTING_LINK_COLOR; + } + + //the connection being dragged by the mouse + this.renderLink( + ctx, + this.connecting_pos, + [this.graph_mouse[0], this.graph_mouse[1]], + null, + false, + null, + link_color, + connDir, + LiteGraph.CENTER + ); + + ctx.beginPath(); + if ( + connType === LiteGraph.EVENT || + connShape === LiteGraph.BOX_SHAPE + ) { + ctx.rect( + this.connecting_pos[0] - 6 + 0.5, + this.connecting_pos[1] - 5 + 0.5, + 14, + 10 + ); + ctx.fill(); + ctx.beginPath(); + ctx.rect( + this.graph_mouse[0] - 6 + 0.5, + this.graph_mouse[1] - 5 + 0.5, + 14, + 10 + ); + } else if (connShape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); + ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); + ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); + ctx.closePath(); + } + else { + ctx.arc( + this.connecting_pos[0], + this.connecting_pos[1], + 4, + 0, + Math.PI * 2 + ); + ctx.fill(); + ctx.beginPath(); + ctx.arc( + this.graph_mouse[0], + this.graph_mouse[1], + 4, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + + ctx.fillStyle = "#ffcc00"; + if (this._highlight_input) { + ctx.beginPath(); + var shape = this._highlight_input_slot.shape; + if (shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_input[0] + 8, this._highlight_input[1] + 0.5); + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] + 6 + 0.5); + ctx.lineTo(this._highlight_input[0] - 4, this._highlight_input[1] - 6 + 0.5); + ctx.closePath(); + } else { + ctx.arc( + this._highlight_input[0], + this._highlight_input[1], + 6, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + } + if (this._highlight_output) { + ctx.beginPath(); + if (shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); + ctx.closePath(); + } else { + ctx.arc( + this._highlight_output[0], + this._highlight_output[1], + 6, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + } + } + + //the selection rectangle + if (this.dragging_rectangle) { + ctx.strokeStyle = "#FFF"; + ctx.strokeRect( + this.dragging_rectangle[0], + this.dragging_rectangle[1], + this.dragging_rectangle[2], + this.dragging_rectangle[3] + ); + } + + //on top of link center + if(this.over_link_center && this.render_link_tooltip) + this.drawLinkTooltip( ctx, this.over_link_center ); + else + if(this.onDrawLinkTooltip) //to remove + this.onDrawLinkTooltip(ctx,null); + + //custom info + if (this.onDrawForeground) { + this.onDrawForeground(ctx, this.visible_rect); + } + + ctx.restore(); + } + + //draws panel in the corner + if (this._graph_stack && this._graph_stack.length) { + this.drawSubgraphPanel( ctx ); + } + + + if (this.onDrawOverlay) { + this.onDrawOverlay(ctx); + } + + if (area){ + ctx.restore(); + } + + if (ctx.finish2D) { + //this is a function I use in webgl renderer + ctx.finish2D(); + } + }; + + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + LGraphCanvas.prototype.drawSubgraphPanel = function (ctx) { + var subgraph = this.graph; + var subnode = subgraph._subgraph_node; + if (!subnode) { + console.warn("subgraph without subnode"); + return; + } + this.drawSubgraphPanelLeft(subgraph, subnode, ctx) + this.drawSubgraphPanelRight(subgraph, subnode, ctx) + } + + LGraphCanvas.prototype.drawSubgraphPanelLeft = function (subgraph, subnode, ctx) { + var num = subnode.inputs ? subnode.inputs.length : 0; + var w = 200; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + ctx.fillText("Graph Inputs", 20, 34); + // var pos = this.mouse; + + if (this.drawButton(w - 20, 20, 20, 20, "X", "#151515")) { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "14px Arial"; + if (subnode.inputs) + for (var i = 0; i < subnode.inputs.length; ++i) { + var input = subnode.inputs[i]; + if (input.not_subgraph_input) + continue; + + //input button clicked + if (this.drawButton(20, y + 2, w - 20, h - 2)) { + var type = subnode.constructor.input_node_type || "graph/input"; + this.graph.beforeChange(); + var newnode = LiteGraph.createNode(type); + if (newnode) { + subgraph.add(newnode); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name", input.name); + newnode.setProperty("type", input.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + this.graph.afterChange(); + } + else + console.error("graph input node not found:", type); + } + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(w - 16, y + h * 0.5, 5, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = "#AAA"; + ctx.fillText(input.name, 30, y + h * 0.75); + // var tw = ctx.measureText(input.name); + ctx.fillStyle = "#777"; + ctx.fillText(input.type, 130, y + h * 0.75); + y += h; + } + //add + button + if (this.drawButton(20, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { + this.showSubgraphPropertiesDialog(subnode); + } + } + LGraphCanvas.prototype.drawSubgraphPanelRight = function (subgraph, subnode, ctx) { + var num = subnode.outputs ? subnode.outputs.length : 0; + var canvas_w = this.bgcanvas.width + var w = 200; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + var title_text = "Graph Outputs" + var tw = ctx.measureText(title_text).width + ctx.fillText(title_text, (canvas_w - tw) - 20, 34); + // var pos = this.mouse; + if (this.drawButton(canvas_w - w, 20, 20, 20, "X", "#151515")) { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "14px Arial"; + if (subnode.outputs) + for (var i = 0; i < subnode.outputs.length; ++i) { + var output = subnode.outputs[i]; + if (output.not_subgraph_input) + continue; + + //output button clicked + if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2)) { + var type = subnode.constructor.output_node_type || "graph/output"; + this.graph.beforeChange(); + var newnode = LiteGraph.createNode(type); + if (newnode) { + subgraph.add(newnode); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name", output.name); + newnode.setProperty("type", output.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + this.graph.afterChange(); + } + else + console.error("graph input node not found:", type); + } + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(canvas_w - w + 16, y + h * 0.5, 5, 0, 2 * Math.PI); + ctx.fill(); + ctx.fillStyle = "#AAA"; + ctx.fillText(output.name, canvas_w - w + 30, y + h * 0.75); + // var tw = ctx.measureText(input.name); + ctx.fillStyle = "#777"; + ctx.fillText(output.type, canvas_w - w + 130, y + h * 0.75); + y += h; + } + //add + button + if (this.drawButton(canvas_w - w, y + 2, w - 20, h - 2, "+", "#151515", "#222")) { + this.showSubgraphPropertiesDialogRight(subnode); + } + } + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) + { + var ctx = this.ctx; + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; + hovercolor = hovercolor || "#555"; + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; + var pos = this.ds.convertOffsetToCanvas(this.graph_mouse); + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position ? [this.last_click_position[0], this.last_click_position[1]] : null; + if(pos) { + var rect = this.canvas.getBoundingClientRect(); + pos[0] -= rect.left; + pos[1] -= rect.top; + } + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + + ctx.fillStyle = hover ? hovercolor : bgcolor; + if(clicked) + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.roundRect(x,y,w,h,[4] ); + ctx.fill(); + + if(text != null) + { + if(text.constructor == String) + { + ctx.fillStyle = textcolor; + ctx.textAlign = "center"; + ctx.font = ((h * 0.65)|0) + "px Arial"; + ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); + ctx.textAlign = "left"; + } + } + + var was_clicked = clicked && !this.block_click; + if(clicked) + this.blockClick(); + return was_clicked; + } + + LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) + { + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var was_clicked = clicked && !this.block_click; + if(clicked && hold_click) + this.blockClick(); + return was_clicked; + } + + /** + * draws some useful stats in the corner of the canvas + * @method renderInfo + **/ + LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { + x = x || 10; + y = y || this.canvas.height - 80; + + ctx.save(); + ctx.translate(x, y); + + ctx.font = "10px Arial"; + ctx.fillStyle = "#888"; + ctx.textAlign = "left"; + if (this.graph) { + ctx.fillText( "T: " + this.graph.globaltime.toFixed(2) + "s", 5, 13 * 1 ); + ctx.fillText("I: " + this.graph.iteration, 5, 13 * 2 ); + ctx.fillText("N: " + this.graph._nodes.length + " [" + this.visible_nodes.length + "]", 5, 13 * 3 ); + ctx.fillText("V: " + this.graph._version, 5, 13 * 4); + ctx.fillText("FPS:" + this.fps.toFixed(2), 5, 13 * 5); + } else { + ctx.fillText("No graph selected", 5, 13 * 1); + } + ctx.restore(); + }; + + /** + * draws the back canvas (the one containing the background and the connections) + * @method drawBackCanvas + **/ + LGraphCanvas.prototype.drawBackCanvas = function() { + var canvas = this.bgcanvas; + if ( + canvas.width != this.canvas.width || + canvas.height != this.canvas.height + ) { + canvas.width = this.canvas.width; + canvas.height = this.canvas.height; + } + + if (!this.bgctx) { + this.bgctx = this.bgcanvas.getContext("2d"); + } + var ctx = this.bgctx; + if (ctx.start) { + ctx.start(); + } + + var viewport = this.viewport || [0,0,ctx.canvas.width,ctx.canvas.height]; + + //clear + if (this.clear_background) { + ctx.clearRect( viewport[0], viewport[1], viewport[2], viewport[3] ); + } + + //show subgraph stack header + if (this._graph_stack && this._graph_stack.length) { + ctx.save(); + var parent_graph = this._graph_stack[this._graph_stack.length - 1]; + var subgraph_node = this.graph._subgraph_node; + ctx.strokeStyle = subgraph_node.bgcolor; + ctx.lineWidth = 10; + ctx.strokeRect(1, 1, canvas.width - 2, canvas.height - 2); + ctx.lineWidth = 1; + ctx.font = "40px Arial"; + ctx.textAlign = "center"; + ctx.fillStyle = subgraph_node.bgcolor || "#AAA"; + var title = ""; + for (var i = 1; i < this._graph_stack.length; ++i) { + title += + this._graph_stack[i]._subgraph_node.getTitle() + " >> "; + } + ctx.fillText( + title + subgraph_node.getTitle(), + canvas.width * 0.5, + 40 + ); + ctx.restore(); + } + + var bg_already_painted = false; + if (this.onRenderBackground) { + bg_already_painted = this.onRenderBackground(canvas, ctx); + } + + //reset in case of error + if ( !this.viewport ) + { + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + } + this.visible_links.length = 0; + + if (this.graph) { + //apply transformations + ctx.save(); + this.ds.toCanvasContext(ctx); + + //render BG + if ( this.ds.scale < 1.5 && !bg_already_painted && this.clear_background_color ) + { + ctx.fillStyle = this.clear_background_color; + ctx.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ); + } + + if ( + this.background_image && + this.ds.scale > 0.5 && + !bg_already_painted + ) { + if (this.zoom_modify_alpha) { + ctx.globalAlpha = + (1.0 - 0.5 / this.ds.scale) * this.editor_alpha; + } else { + ctx.globalAlpha = this.editor_alpha; + } + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = + if ( + !this._bg_img || + this._bg_img.name != this.background_image + ) { + this._bg_img = new Image(); + this._bg_img.name = this.background_image; + this._bg_img.src = this.background_image; + var that = this; + this._bg_img.onload = function() { + that.draw(true, true); + }; + } + + var pattern = null; + if (this._pattern == null && this._bg_img.width > 0) { + pattern = ctx.createPattern(this._bg_img, "repeat"); + this._pattern_img = this._bg_img; + this._pattern = pattern; + } else { + pattern = this._pattern; + } + if (pattern) { + ctx.fillStyle = pattern; + ctx.fillRect( + this.visible_area[0], + this.visible_area[1], + this.visible_area[2], + this.visible_area[3] + ); + ctx.fillStyle = "transparent"; + } + + ctx.globalAlpha = 1.0; + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled + } + + //groups + if (this.graph._groups.length && !this.live_mode) { + this.drawGroups(canvas, ctx); + } + + if (this.onDrawBackground) { + this.onDrawBackground(ctx, this.visible_area); + } + if (this.onBackgroundRender) { + //LEGACY + console.error( + "WARNING! onBackgroundRender deprecated, now is named onDrawBackground " + ); + this.onBackgroundRender = null; + } + + //DEBUG: show clipping area + //ctx.fillStyle = "red"; + //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - 20, this.visible_area[3] - 20); + + //bg + if (this.render_canvas_border) { + ctx.strokeStyle = "#235"; + ctx.strokeRect(0, 0, canvas.width, canvas.height); + } + + if (this.render_connections_shadows) { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 6; + } else { + ctx.shadowColor = "rgba(0,0,0,0)"; + } + + //draw connections + if (!this.live_mode) { + this.drawConnections(ctx); + } + + ctx.shadowColor = "rgba(0,0,0,0)"; + + //restore state + ctx.restore(); + } + + if (ctx.finish) { + ctx.finish(); + } + + this.dirty_bgcanvas = false; + this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas + }; + + var temp_vec2 = new Float32Array(2); + + /** + * draws the given node inside the canvas + * @method drawNode + **/ + LGraphCanvas.prototype.drawNode = function(node, ctx) { + var glow = false; + this.current_node = node; + + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + //shadow and glow + if (node.mouseOver) { + glow = true; + } + + var low_quality = this.ds.scale < 0.6; //zoomed out + + //only render if it forces it to do it + if (this.live_mode) { + if (!node.flags.collapsed) { + ctx.shadowColor = "transparent"; + if (node.onDrawForeground) { + node.onDrawForeground(ctx, this, this.canvas); + } + } + return; + } + + var editor_alpha = this.editor_alpha; + ctx.globalAlpha = editor_alpha; + + if (this.render_shadows && !low_quality) { + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; + ctx.shadowOffsetX = 2 * this.ds.scale; + ctx.shadowOffsetY = 2 * this.ds.scale; + ctx.shadowBlur = 3 * this.ds.scale; + } else { + ctx.shadowColor = "transparent"; + } + + //custom draw collapsed method (draw after shadows because they are affected) + if ( + node.flags.collapsed && + node.onDrawCollapsed && + node.onDrawCollapsed(ctx, this) == true + ) { + return; + } + + //clip if required (mask) + var shape = node._shape || LiteGraph.BOX_SHAPE; + var size = temp_vec2; + temp_vec2.set(node.size); + var horizontal = node.horizontal; // || node.flags.horizontal; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + + if (node.flags.collapsed) { + ctx.font = this.inner_text_font; + var title = node.getTitle ? node.getTitle() : node.title; + if (title != null) { + node._collapsed_width = Math.min( + node.size[0], + ctx.measureText(title).width + + LiteGraph.NODE_TITLE_HEIGHT * 2 + ); //LiteGraph.NODE_COLLAPSED_WIDTH; + size[0] = node._collapsed_width; + size[1] = 0; + } + } + + if (node.clip_area) { + //Start clipping + ctx.save(); + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) { + ctx.rect(0, 0, size[0], size[1]); + } else if (shape == LiteGraph.ROUND_SHAPE) { + ctx.roundRect(0, 0, size[0], size[1], [10]); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5, + 0, + Math.PI * 2 + ); + } + ctx.clip(); + } + + //draw shape + if (node.has_errors) { + bgcolor = "red"; + } + this.drawNodeShape( + node, + ctx, + size, + color, + bgcolor, + node.is_selected, + node.mouseOver + ); + ctx.shadowColor = "transparent"; + + //draw foreground + if (node.onDrawForeground) { + node.onDrawForeground(ctx, this, this.canvas); + } + + //connection slots + ctx.textAlign = inputs_horizontal ? "center" : "left"; + ctx.font = this.inner_text_font; + + var render_text = !low_quality; + + var out_slot = this.connecting_output; + var in_slot = this.connecting_input; + ctx.lineWidth = 1; + + var max_y = 0; + var slot_pos = new Float32Array(2); //to reuse + + //render inputs and outputs + if (!node.flags.collapsed) { + //input connection slots + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + + var slot_type = slot.type; + var slot_shape = slot.shape; + + ctx.globalAlpha = editor_alpha; + //change opacity of incompatible slots when dragging a connection + if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { + ctx.globalAlpha = 0.4 * editor_alpha; + } + + ctx.fillStyle = + slot.link != null + ? slot.color_on || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.input_on + : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.input_off; + + var pos = node.getConnectionPos(true, i, slot_pos); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + } + + ctx.beginPath(); + + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? + } + + var doStroke = true; + + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + if (inputs_horizontal) { + ctx.rect( + pos[0] - 5 + 0.5, + pos[1] - 8 + 0.5, + 10, + 14 + ); + } else { + ctx.rect( + pos[0] - 6 + 0.5, + pos[1] - 5 + 0.5, + 14, + 10 + ); + } + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); + ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; + } else { + if(low_quality) + ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster + else + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + ctx.fill(); + + //render name + if (render_text) { + var text = slot.label != null ? slot.label : slot.name; + if (text) { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if (inputs_horizontal || slot.dir == LiteGraph.UP) { + ctx.fillText(text, pos[0], pos[1] - 10); + } else { + ctx.fillText(text, pos[0] + 10, pos[1] + 5); + } + } + } + } + } + + //output connection slots + + ctx.textAlign = outputs_horizontal ? "center" : "right"; + ctx.strokeStyle = "black"; + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + + var slot_type = slot.type; + var slot_shape = slot.shape; + + //change opacity of incompatible slots when dragging a connection + if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { + ctx.globalAlpha = 0.4 * editor_alpha; + } + + var pos = node.getConnectionPos(false, i, slot_pos); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + if (max_y < pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5) { + max_y = pos[1] + LiteGraph.NODE_SLOT_HEIGHT * 0.5; + } + + ctx.fillStyle = + slot.links && slot.links.length + ? slot.color_on || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.output_on + : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || + this.default_connection_color.output_off; + ctx.beginPath(); + //ctx.rect( node.size[0] - 14,i*14,10,10); + + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; + } + + var doStroke = true; + + if ( + slot_type === LiteGraph.EVENT || + slot_shape === LiteGraph.BOX_SHAPE + ) { + if (outputs_horizontal) { + ctx.rect( + pos[0] - 5 + 0.5, + pos[1] - 8 + 0.5, + 10, + 14 + ); + } else { + ctx.rect( + pos[0] - 6 + 0.5, + pos[1] - 5 + 0.5, + 14, + 10 + ); + } + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(pos[0] + 8, pos[1] + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); + ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); + ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; + } else { + if(low_quality) + ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); + else + ctx.arc(pos[0], pos[1], 4, 0, Math.PI * 2); + } + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + if(!low_quality && doStroke) + ctx.stroke(); + + //render output name + if (render_text) { + var text = slot.label != null ? slot.label : slot.name; + if (text) { + ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; + if (outputs_horizontal || slot.dir == LiteGraph.DOWN) { + ctx.fillText(text, pos[0], pos[1] - 8); + } else { + ctx.fillText(text, pos[0] - 10, pos[1] + 5); + } + } + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1; + + if (node.widgets) { + var widgets_y = max_y; + if (inputs_horizontal || outputs_horizontal || node.widgets_up) { + widgets_y = 2; + } + if( node.widgets_start_y != null ) + widgets_y = node.widgets_start_y; + this.drawNodeWidgets( + node, + widgets_y, + ctx, + this.node_widget && this.node_widget[0] == node + ? this.node_widget[1] + : null + ); + } + } else if (this.render_collapsed_slots) { + //if collapsed + var input_slot = null; + var output_slot = null; + + //get first connected slot to render + if (node.inputs) { + for (var i = 0; i < node.inputs.length; i++) { + var slot = node.inputs[i]; + if (slot.link == null) { + continue; + } + input_slot = slot; + break; + } + } + if (node.outputs) { + for (var i = 0; i < node.outputs.length; i++) { + var slot = node.outputs[i]; + if (!slot.links || !slot.links.length) { + continue; + } + output_slot = slot; + } + } + + if (input_slot) { + var x = 0; + var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + // For collapsed nodes, use inputs_horizontal if available, otherwise fall back to general horizontal + var input_is_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + if (input_is_horizontal) { + x = node._collapsed_width * 0.5; + y = -LiteGraph.NODE_TITLE_HEIGHT; + } + ctx.fillStyle = "#686"; + ctx.beginPath(); + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(x + 8, y); + ctx.lineTo(x + -4, y - 4); + ctx.lineTo(x + -4, y + 4); + ctx.closePath(); + } else { + ctx.arc(x, y, 4, 0, Math.PI * 2); + } + ctx.fill(); + } + + if (output_slot) { + var x = node._collapsed_width; + var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center + // For collapsed nodes, use outputs_horizontal if available, otherwise fall back to general horizontal + var output_is_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + if (output_is_horizontal) { + x = node._collapsed_width * 0.5; + y = 0; + } + ctx.fillStyle = "#686"; + ctx.strokeStyle = "black"; + ctx.beginPath(); + if ( + slot.type === LiteGraph.EVENT || + slot.shape === LiteGraph.BOX_SHAPE + ) { + ctx.rect(x - 7 + 0.5, y - 4, 14, 8); + } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(x + 6, y); + ctx.lineTo(x - 6, y - 4); + ctx.lineTo(x - 6, y + 4); + ctx.closePath(); + } else { + ctx.arc(x, y, 4, 0, Math.PI * 2); + } + ctx.fill(); + //ctx.stroke(); + } + } + + if (node.clip_area) { + ctx.restore(); + } + + ctx.globalAlpha = 1.0; + }; + + //used by this.over_link_center + LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) + { + var pos = link._pos; + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.arc( pos[0], pos[1], 3, 0, Math.PI * 2 ); + ctx.fill(); + + if(link.data == null) + return; + + if(this.onDrawLinkTooltip) + if( this.onDrawLinkTooltip(ctx,link,this) == true ) + return; + + var data = link.data; + var text = null; + + if( data.constructor === Number ) + text = data.toFixed(2); + else if( data.constructor === String ) + text = "\"" + data + "\""; + else if( data.constructor === Boolean ) + text = String(data); + else if (data.toToolTip) + text = data.toToolTip(); + else + text = "[" + data.constructor.name + "]"; + + if(text == null) + return; + text = text.substr(0,30); //avoid weird + + ctx.font = "14px Courier New"; + var info = ctx.measureText(text); + var w = info.width + 20; + var h = 24; + ctx.shadowColor = "black"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 3; + ctx.fillStyle = "#454"; + ctx.beginPath(); + ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); + ctx.moveTo( pos[0] - 10, pos[1] - 15 ); + ctx.lineTo( pos[0] + 10, pos[1] - 15 ); + ctx.lineTo( pos[0], pos[1] - 5 ); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.textAlign = "center"; + ctx.fillStyle = "#CEC"; + ctx.fillText(text, pos[0], pos[1] - 15 - h * 0.3); + } + + /** + * draws the shape of the given node in the canvas + * @method drawNodeShape + **/ + var tmp_area = new Float32Array(4); + + LGraphCanvas.prototype.drawNodeShape = function( + node, + ctx, + size, + fgcolor, + bgcolor, + selected, + mouse_over + ) { + //bg rect + ctx.strokeStyle = fgcolor; + ctx.fillStyle = bgcolor; + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + var low_quality = this.ds.scale < 0.5; + + //render node area depending on shape + var shape = + node._shape || node.constructor.shape || LiteGraph.ROUND_SHAPE; + + var title_mode = node.constructor.title_mode; + + var render_title = true; + if (title_mode == LiteGraph.TRANSPARENT_TITLE || title_mode == LiteGraph.NO_TITLE) { + render_title = false; + } else if (title_mode == LiteGraph.AUTOHIDE_TITLE && mouse_over) { + render_title = true; + } + + var area = tmp_area; + area[0] = 0; //x + area[1] = render_title ? -title_height : 0; //y + area[2] = size[0] + 1; //w + area[3] = render_title ? size[1] + title_height : size[1]; //h + + var old_alpha = ctx.globalAlpha; + + //full node shape + //if(node.flags.collapsed) + { + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE || low_quality) { + ctx.fillRect(area[0], area[1], area[2], area[3]); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + shape == LiteGraph.CARD_SHAPE + ) { + ctx.roundRect( + area[0], + area[1], + area[2], + area[3], + shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] + ); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + + //separator + if(!node.flags.collapsed && render_title) + { + ctx.shadowColor = "transparent"; + ctx.fillStyle = "rgba(0,0,0,0.2)"; + ctx.fillRect(0, -1, area[2], 2); + } + } + ctx.shadowColor = "transparent"; + + if (node.onDrawBackground) { + node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); + } + + //title bg (remember, it is rendered ABOVE the node) + if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { + //title bar + if (node.onDrawTitleBar) { + node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); + } else if ( + title_mode != LiteGraph.TRANSPARENT_TITLE && + (node.constructor.title_color || this.render_title_colored) + ) { + var title_color = node.constructor.title_color || fgcolor; + + if (node.flags.collapsed) { + ctx.shadowColor = LiteGraph.DEFAULT_SHADOW_COLOR; + } + + //* gradient test + if (this.use_gradients) { + var grad = LGraphCanvas.gradients[title_color]; + if (!grad) { + grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); + grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException + grad.addColorStop(1, "#000"); + } + ctx.fillStyle = grad; + } else { + ctx.fillStyle = title_color; + } + + //ctx.globalAlpha = 0.5 * old_alpha; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE || low_quality) { + ctx.rect(0, -title_height, size[0] + 1, title_height); + } else if ( shape == LiteGraph.ROUND_SHAPE || shape == LiteGraph.CARD_SHAPE ) { + ctx.roundRect( + 0, + -title_height, + size[0] + 1, + title_height, + node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] + ); + } + ctx.fill(); + ctx.shadowColor = "transparent"; + } + + var colState = false; + if (LiteGraph.node_box_coloured_by_mode){ + if(LiteGraph.NODE_MODES_COLORS[node.mode]){ + colState = LiteGraph.NODE_MODES_COLORS[node.mode]; + } + } + if (LiteGraph.node_box_coloured_when_on){ + colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); + } + + //title box + var box_size = 10; + if (node.onDrawTitleBox) { + node.onDrawTitleBox(ctx, title_height, size, this.ds.scale); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + shape == LiteGraph.CIRCLE_SHAPE || + shape == LiteGraph.CARD_SHAPE + ) { + if (low_quality) { + ctx.fillStyle = "black"; + ctx.beginPath(); + ctx.arc( + title_height * 0.5, + title_height * -0.5, + box_size * 0.5 + 1, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + if(low_quality) + ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); + else + { + ctx.beginPath(); + ctx.arc( + title_height * 0.5, + title_height * -0.5, + box_size * 0.5, + 0, + Math.PI * 2 + ); + ctx.fill(); + } + } else { + if (low_quality) { + ctx.fillStyle = "black"; + ctx.fillRect( + (title_height - box_size) * 0.5 - 1, + (title_height + box_size) * -0.5 - 1, + box_size + 2, + box_size + 2 + ); + } + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.fillRect( + (title_height - box_size) * 0.5, + (title_height + box_size) * -0.5, + box_size, + box_size + ); + } + ctx.globalAlpha = old_alpha; + + //title text + if (node.onDrawTitleText) { + node.onDrawTitleText( + ctx, + title_height, + size, + this.ds.scale, + this.title_text_font, + selected + ); + } + if (!low_quality) { + ctx.font = this.title_text_font; + var title = String(node.getTitle()); + if (title) { + if (selected) { + ctx.fillStyle = LiteGraph.NODE_SELECTED_TITLE_COLOR; + } else { + ctx.fillStyle = + node.constructor.title_text_color || + this.node_title_color; + } + if (node.flags.collapsed) { + ctx.textAlign = "left"; + var measure = ctx.measureText(title); + ctx.fillText( + title.substr(0,20), //avoid urls too long + title_height,// + measure.width * 0.5, + LiteGraph.NODE_TITLE_TEXT_Y - title_height + ); + ctx.textAlign = "left"; + } else { + ctx.textAlign = "left"; + ctx.fillText( + title, + title_height, + LiteGraph.NODE_TITLE_TEXT_Y - title_height + ); + } + } + } + + //subgraph box + if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { + var w = LiteGraph.NODE_TITLE_HEIGHT; + var x = node.size[0] - w; + var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); + ctx.fillStyle = over ? "#888" : "#555"; + if( shape == LiteGraph.BOX_SHAPE || low_quality) + ctx.fillRect(x+2, -w+2, w-4, w-4); + else + { + ctx.beginPath(); + ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); + ctx.fill(); + } + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + } + + //custom title render + if (node.onDrawTitle) { + node.onDrawTitle(ctx); + } + } + + //render selection marker + if (selected) { + if (node.onBounding) { + node.onBounding(area); + } + + if (title_mode == LiteGraph.TRANSPARENT_TITLE) { + area[1] -= title_height; + area[3] += title_height; + } + ctx.lineWidth = 1; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + if (shape == LiteGraph.BOX_SHAPE) { + ctx.rect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3] + ); + } else if ( + shape == LiteGraph.ROUND_SHAPE || + (shape == LiteGraph.CARD_SHAPE && node.flags.collapsed) + ) { + ctx.roundRect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3], + [this.round_radius * 2] + ); + } else if (shape == LiteGraph.CARD_SHAPE) { + ctx.roundRect( + -6 + area[0], + -6 + area[1], + 12 + area[2], + 12 + area[3], + [this.round_radius * 2,2,this.round_radius * 2,2] + ); + } else if (shape == LiteGraph.CIRCLE_SHAPE) { + ctx.arc( + size[0] * 0.5, + size[1] * 0.5, + size[0] * 0.5 + 6, + 0, + Math.PI * 2 + ); + } + ctx.strokeStyle = LiteGraph.NODE_BOX_OUTLINE_COLOR; + ctx.stroke(); + ctx.strokeStyle = fgcolor; + ctx.globalAlpha = 1; + } + + // these counter helps in conditioning drawing based on if the node has been executed or an action occurred + if (node.execute_triggered>0) node.execute_triggered--; + if (node.action_triggered>0) node.action_triggered--; + }; + + var margin_area = new Float32Array(4); + var link_bounding = new Float32Array(4); + var tempA = new Float32Array(2); + var tempB = new Float32Array(2); + + /** + * draws every connection visible in the canvas + * OPTIMIZE THIS: pre-catch connections position instead of recomputing them every time + * @method drawConnections + **/ + LGraphCanvas.prototype.drawConnections = function(ctx) { + var now = LiteGraph.getTime(); + var visible_area = this.visible_area; + margin_area[0] = visible_area[0] - 20; + margin_area[1] = visible_area[1] - 20; + margin_area[2] = visible_area[2] + 40; + margin_area[3] = visible_area[3] + 40; + + //draw connections + ctx.lineWidth = this.connections_width; + + ctx.fillStyle = "#AAA"; + ctx.strokeStyle = "#AAA"; + ctx.globalAlpha = this.editor_alpha; + //for every node + var nodes = this.graph._nodes; + for (var n = 0, l = nodes.length; n < l; ++n) { + var node = nodes[n]; + //for every input (we render just inputs because it is easier as every slot can only have one input) + if (!node.inputs || !node.inputs.length) { + continue; + } + + for (var i = 0; i < node.inputs.length; ++i) { + var input = node.inputs[i]; + if (!input || input.link == null) { + continue; + } + var link_id = input.link; + var link = this.graph.links[link_id]; + if (!link) { + continue; + } + + //find link info + var start_node = this.graph.getNodeById(link.origin_id); + if (start_node == null) { + continue; + } + var start_node_slot = link.origin_slot; + var start_node_slotpos = null; + if (start_node_slot == -1) { + start_node_slotpos = [ + start_node.pos[0] + 10, + start_node.pos[1] + 10 + ]; + } else { + start_node_slotpos = start_node.getConnectionPos( + false, + start_node_slot, + tempA + ); + } + var end_node_slotpos = node.getConnectionPos(true, i, tempB); + + //compute link bounding + link_bounding[0] = start_node_slotpos[0]; + link_bounding[1] = start_node_slotpos[1]; + link_bounding[2] = end_node_slotpos[0] - start_node_slotpos[0]; + link_bounding[3] = end_node_slotpos[1] - start_node_slotpos[1]; + if (link_bounding[2] < 0) { + link_bounding[0] += link_bounding[2]; + link_bounding[2] = Math.abs(link_bounding[2]); + } + if (link_bounding[3] < 0) { + link_bounding[1] += link_bounding[3]; + link_bounding[3] = Math.abs(link_bounding[3]); + } + + //skip links outside of the visible area of the canvas + if (!overlapBounding(link_bounding, margin_area)) { + continue; + } + + var start_slot = start_node.outputs[start_node_slot]; + var end_slot = node.inputs[i]; + if (!start_slot || !end_slot) { + continue; + } + var start_dir = + start_slot.dir || + (start_node.outputs_horizontal !== undefined && start_node.outputs_horizontal !== null ? + (start_node.outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT) : + (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT)); + var end_dir = + end_slot.dir || + (node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? + (node.inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT) : + (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT)); + + this.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + link, + false, + 0, + null, + start_dir, + end_dir + ); + + //event triggered rendered on top + if (link && link._last_time && now - link._last_time < 1000) { + var f = 2.0 - (now - link._last_time) * 0.002; + var tmp = ctx.globalAlpha; + ctx.globalAlpha = tmp * f; + this.renderLink( + ctx, + start_node_slotpos, + end_node_slotpos, + link, + true, + f, + "white", + start_dir, + end_dir + ); + ctx.globalAlpha = tmp; + } + } + } + ctx.globalAlpha = 1; + }; + + /** + * draws a link between two points + * @method renderLink + * @param {vec2} a start pos + * @param {vec2} b end pos + * @param {Object} link the link object with all the link info + * @param {boolean} skip_border ignore the shadow of the link + * @param {boolean} flow show flow animation (for events) + * @param {string} color the color for the link + * @param {number} start_dir the direction enum + * @param {number} end_dir the direction enum + * @param {number} num_sublines number of sublines (useful to represent vec3 or rgb) + **/ + LGraphCanvas.prototype.renderLink = function( + ctx, + a, + b, + link, + skip_border, + flow, + color, + start_dir, + end_dir, + num_sublines + ) { + if (link) { + this.visible_links.push(link); + } + + //choose color + if (!color && link) { + color = link.color || LGraphCanvas.link_type_colors[link.type]; + } + if (!color) { + color = this.default_link_color; + } + if (link != null && this.highlighted_links[link.id]) { + color = "#FFF"; + } + + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a, b); + + // Auto-detect when to use straight lines for better visual appearance + var should_use_straight = false; + var dx = Math.abs(a[0] - b[0]); + var dy = Math.abs(a[1] - b[1]); + + // Use straight lines when connecting opposite directions and nodes are reasonably aligned + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + // Vertical to vertical connections (like mixed nodes with vertical outputs to vertical inputs) + if ((start_dir == LiteGraph.RIGHT && end_dir == LiteGraph.LEFT) || + (start_dir == LiteGraph.LEFT && end_dir == LiteGraph.RIGHT)) { + // If nodes are roughly vertically aligned (small horizontal distance relative to vertical) + if (dx < 50 && dy > 20) { + should_use_straight = true; + } + } + // Horizontal to horizontal connections + else if ((start_dir == LiteGraph.DOWN && end_dir == LiteGraph.UP) || + (start_dir == LiteGraph.UP && end_dir == LiteGraph.DOWN)) { + // If nodes are roughly horizontally aligned (small vertical distance relative to horizontal) + if (dy < 50 && dx > 20) { + should_use_straight = true; + } + } + } + + if (this.render_connections_border && this.ds.scale > 0.6) { + ctx.lineWidth = this.connections_width + 4; + } + ctx.lineJoin = "round"; + num_sublines = num_sublines || 1; + if (num_sublines > 1) { + ctx.lineWidth = 0.5; + } + + //begin line shape + ctx.beginPath(); + for (var i = 0; i < num_sublines; i += 1) { + var offsety = (i - (num_sublines - 1) * 0.5) * 5; + + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + ctx.moveTo(a[0], a[1] + offsety); + + if (should_use_straight) { + // Use straight line logic for better visual alignment + var start_x = a[0]; + var start_y = a[1] + offsety; + var end_x = b[0]; + var end_y = b[1] + offsety; + + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else if (start_dir == LiteGraph.LEFT) { + start_x -= 10; + } else if (start_dir == LiteGraph.DOWN) { + start_y += 10; + } else if (start_dir == LiteGraph.UP) { + start_y -= 10; + } + + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else if (end_dir == LiteGraph.RIGHT) { + end_x += 10; + } else if (end_dir == LiteGraph.UP) { + end_y -= 10; + } else if (end_dir == LiteGraph.DOWN) { + end_y += 10; + } + + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1] + offsety); + } else { + // Original spline logic + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + start_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + start_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + start_offset_y = dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + end_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + end_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + end_offset_y = dist * 0.25; + break; + } + ctx.bezierCurveTo( + a[0] + start_offset_x, + a[1] + start_offset_y + offsety, + b[0] + end_offset_x, + b[1] + end_offset_y + offsety, + b[0], + b[1] + offsety + ); + } + } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { + ctx.moveTo(a[0], a[1] + offsety); + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = -1; + break; + case LiteGraph.RIGHT: + start_offset_x = 1; + break; + case LiteGraph.UP: + start_offset_y = -1; + break; + case LiteGraph.DOWN: + start_offset_y = 1; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = -1; + break; + case LiteGraph.RIGHT: + end_offset_x = 1; + break; + case LiteGraph.UP: + end_offset_y = -1; + break; + case LiteGraph.DOWN: + end_offset_y = 1; + break; + } + var l = 15; + ctx.lineTo( + a[0] + start_offset_x * l, + a[1] + start_offset_y * l + offsety + ); + ctx.lineTo( + b[0] + end_offset_x * l, + b[1] + end_offset_y * l + offsety + ); + ctx.lineTo(b[0], b[1] + offsety); + } else if (this.links_render_mode == LiteGraph.STRAIGHT_LINK) { + ctx.moveTo(a[0], a[1]); + var start_x = a[0]; + var start_y = a[1]; + var end_x = b[0]; + var end_y = b[1]; + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else { + start_y += 10; + } + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else { + end_y -= 10; + } + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1]); + } else { + return; + } //unknown + } + + //rendering the outline of the connection can be a little bit slow + if ( + this.render_connections_border && + this.ds.scale > 0.6 && + !skip_border + ) { + ctx.strokeStyle = "rgba(0,0,0,0.5)"; + ctx.stroke(); + } + + ctx.lineWidth = this.connections_width; + ctx.fillStyle = ctx.strokeStyle = color; + ctx.stroke(); + //end line shape + + var pos = this.computeConnectionPoint(a, b, 0.5, start_dir, end_dir); + if (link && link._pos) { + link._pos[0] = pos[0]; + link._pos[1] = pos[1]; + } + + //render arrow in the middle + if ( + this.ds.scale >= 0.6 && + this.highquality_render && + end_dir != LiteGraph.CENTER + ) { + //render arrow + if (this.render_connection_arrows) { + //compute two points in the connection + var posA = this.computeConnectionPoint( + a, + b, + 0.25, + start_dir, + end_dir + ); + var posB = this.computeConnectionPoint( + a, + b, + 0.26, + start_dir, + end_dir + ); + var posC = this.computeConnectionPoint( + a, + b, + 0.75, + start_dir, + end_dir + ); + var posD = this.computeConnectionPoint( + a, + b, + 0.76, + start_dir, + end_dir + ); + + //compute the angle between them so the arrow points in the right direction + var angleA = 0; + var angleB = 0; + if (this.render_curved_connections) { + angleA = -Math.atan2(posB[0] - posA[0], posB[1] - posA[1]); + angleB = -Math.atan2(posD[0] - posC[0], posD[1] - posC[1]); + } else { + angleB = angleA = b[1] > a[1] ? 0 : Math.PI; + } + + //render arrow + ctx.save(); + ctx.translate(posA[0], posA[1]); + ctx.rotate(angleA); + ctx.beginPath(); + ctx.moveTo(-5, -3); + ctx.lineTo(0, +7); + ctx.lineTo(+5, -3); + ctx.fill(); + ctx.restore(); + ctx.save(); + ctx.translate(posC[0], posC[1]); + ctx.rotate(angleB); + ctx.beginPath(); + ctx.moveTo(-5, -3); + ctx.lineTo(0, +7); + ctx.lineTo(+5, -3); + ctx.fill(); + ctx.restore(); + } + + //circle + ctx.beginPath(); + ctx.arc(pos[0], pos[1], 5, 0, Math.PI * 2); + ctx.fill(); + } + + //render flowing points + if (flow) { + ctx.fillStyle = color; + for (var i = 0; i < 5; ++i) { + var f = (LiteGraph.getTime() * 0.001 + i * 0.2) % 1; + var pos = this.computeConnectionPoint( + a, + b, + f, + start_dir, + end_dir + ); + ctx.beginPath(); + ctx.arc(pos[0], pos[1], 5, 0, 2 * Math.PI); + ctx.fill(); + } + } + }; + + //returns the link center point based on curvature + LGraphCanvas.prototype.computeConnectionPoint = function( + a, + b, + t, + start_dir, + end_dir + ) { + start_dir = start_dir || LiteGraph.RIGHT; + end_dir = end_dir || LiteGraph.LEFT; + + var dist = distance(a, b); + var p0 = a; + var p1 = [a[0], a[1]]; + var p2 = [b[0], b[1]]; + var p3 = b; + + switch (start_dir) { + case LiteGraph.LEFT: + p1[0] += dist * -0.25; + break; + case LiteGraph.RIGHT: + p1[0] += dist * 0.25; + break; + case LiteGraph.UP: + p1[1] += dist * -0.25; + break; + case LiteGraph.DOWN: + p1[1] += dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + p2[0] += dist * -0.25; + break; + case LiteGraph.RIGHT: + p2[0] += dist * 0.25; + break; + case LiteGraph.UP: + p2[1] += dist * -0.25; + break; + case LiteGraph.DOWN: + p2[1] += dist * 0.25; + break; + } + + var c1 = (1 - t) * (1 - t) * (1 - t); + var c2 = 3 * ((1 - t) * (1 - t)) * t; + var c3 = 3 * (1 - t) * (t * t); + var c4 = t * t * t; + + var x = c1 * p0[0] + c2 * p1[0] + c3 * p2[0] + c4 * p3[0]; + var y = c1 * p0[1] + c2 * p1[1] + c3 * p2[1] + c4 * p3[1]; + return [x, y]; + }; + + LGraphCanvas.prototype.drawExecutionOrder = function(ctx) { + ctx.shadowColor = "transparent"; + ctx.globalAlpha = 0.25; + + ctx.textAlign = "center"; + ctx.strokeStyle = "white"; + ctx.globalAlpha = 0.75; + + var visible_nodes = this.visible_nodes; + for (var i = 0; i < visible_nodes.length; ++i) { + var node = visible_nodes[i]; + ctx.fillStyle = "black"; + ctx.fillRect( + node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT, + node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT + ); + if (node.order == 0) { + ctx.strokeRect( + node.pos[0] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, + node.pos[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5, + LiteGraph.NODE_TITLE_HEIGHT, + LiteGraph.NODE_TITLE_HEIGHT + ); + } + ctx.fillStyle = "#FFF"; + ctx.fillText( + node.order, + node.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * -0.5, + node.pos[1] - 6 + ); + } + ctx.globalAlpha = 1; + }; + + /** + * draws the widgets stored inside a node + * @method drawNodeWidgets + **/ + LGraphCanvas.prototype.drawNodeWidgets = function( + node, + posY, + ctx, + active_widget + ) { + if (!node.widgets || !node.widgets.length) { + return 0; + } + var width = node.size[0]; + var widgets = node.widgets; + posY += 2; + var H = LiteGraph.NODE_WIDGET_HEIGHT; + var show_text = this.ds.scale > 0.5; + ctx.save(); + ctx.globalAlpha = this.editor_alpha; + var outline_color = LiteGraph.WIDGET_OUTLINE_COLOR; + var background_color = LiteGraph.WIDGET_BGCOLOR; + var text_color = LiteGraph.WIDGET_TEXT_COLOR; + var secondary_text_color = LiteGraph.WIDGET_SECONDARY_TEXT_COLOR; + var margin = 15; + + for (var i = 0; i < widgets.length; ++i) { + var w = widgets[i]; + var y = posY; + if (w.y) { + y = w.y; + } + w.last_y = y; + ctx.strokeStyle = outline_color; + ctx.fillStyle = "#222"; + ctx.textAlign = "left"; + //ctx.lineWidth = 2; + if(w.disabled) + ctx.globalAlpha *= 0.5; + var widget_width = w.width || width; + + switch (w.type) { + case "button": + if (w.clicked) { + ctx.fillStyle = "#AAA"; + w.clicked = false; + this.dirty_canvas = true; + } + ctx.fillRect(margin, y, widget_width - margin * 2, H); + if(show_text && !w.disabled) + ctx.strokeRect( margin, y, widget_width - margin * 2, H ); + if (show_text) { + ctx.textAlign = "center"; + ctx.fillStyle = text_color; + ctx.fillText(w.label || w.name, widget_width * 0.5, y + H * 0.7); + } + break; + case "toggle": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); + else + ctx.rect(margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if(show_text && !w.disabled) + ctx.stroke(); + ctx.fillStyle = w.value ? "#89A" : "#333"; + ctx.beginPath(); + ctx.arc( widget_width - margin * 2, y + H * 0.5, H * 0.36, 0, Math.PI * 2 ); + ctx.fill(); + if (show_text) { + ctx.fillStyle = secondary_text_color; + const label = w.label || w.name; + if (label != null) { + ctx.fillText(label, margin * 2, y + H * 0.7); + } + ctx.fillStyle = w.value ? text_color : secondary_text_color; + ctx.textAlign = "right"; + ctx.fillText( + w.value + ? w.options.on || "true" + : w.options.off || "false", + widget_width - 40, + y + H * 0.7 + ); + } + break; + case "slider": + ctx.fillStyle = background_color; + ctx.fillRect(margin, y, widget_width - margin * 2, H); + var range = w.options.max - w.options.min; + var nvalue = (w.value - w.options.min) / range; + if(nvalue < 0.0) nvalue = 0.0; + if(nvalue > 1.0) nvalue = 1.0; + ctx.fillStyle = w.options.hasOwnProperty("slider_color") ? w.options.slider_color : (active_widget == w ? "#89A" : "#678"); + ctx.fillRect(margin, y, nvalue * (widget_width - margin * 2), H); + if(show_text && !w.disabled) + ctx.strokeRect(margin, y, widget_width - margin * 2, H); + if (w.marker) { + var marker_nvalue = (w.marker - w.options.min) / range; + if(marker_nvalue < 0.0) marker_nvalue = 0.0; + if(marker_nvalue > 1.0) marker_nvalue = 1.0; + ctx.fillStyle = w.options.hasOwnProperty("marker_color") ? w.options.marker_color : "#AA9"; + ctx.fillRect( margin + marker_nvalue * (widget_width - margin * 2), y, 2, H ); + } + if (show_text) { + ctx.textAlign = "center"; + ctx.fillStyle = text_color; + ctx.fillText( + w.label || w.name + " " + Number(w.value).toFixed( + w.options.precision != null + ? w.options.precision + : 3 + ), + widget_width * 0.5, + y + H * 0.7 + ); + } + break; + case "number": + case "combo": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if(show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5] ); + else + ctx.rect(margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if (show_text) { + if(!w.disabled) + ctx.stroke(); + ctx.fillStyle = text_color; + if(!w.disabled) + { + ctx.beginPath(); + ctx.moveTo(margin + 16, y + 5); + ctx.lineTo(margin + 6, y + H * 0.5); + ctx.lineTo(margin + 16, y + H - 5); + ctx.fill(); + ctx.beginPath(); + ctx.moveTo(widget_width - margin - 16, y + 5); + ctx.lineTo(widget_width - margin - 6, y + H * 0.5); + ctx.lineTo(widget_width - margin - 16, y + H - 5); + ctx.fill(); + } + ctx.fillStyle = secondary_text_color; + ctx.fillText(w.label || w.name, margin * 2 + 5, y + H * 0.7); + ctx.fillStyle = text_color; + ctx.textAlign = "right"; + if (w.type == "number") { + ctx.fillText( + Number(w.value).toFixed( + w.options.precision !== undefined + ? w.options.precision + : 3 + ), + widget_width - margin * 2 - 20, + y + H * 0.7 + ); + } else { + var v = w.value; + if( w.options.values ) + { + var values = w.options.values; + if( values.constructor === Function ) + values = values(); + if(values && values.constructor !== Array) + v = values[ w.value ]; + } + ctx.fillText( + v, + widget_width - margin * 2 - 20, + y + H * 0.7 + ); + } + } + break; + case "string": + case "text": + ctx.textAlign = "left"; + ctx.strokeStyle = outline_color; + ctx.fillStyle = background_color; + ctx.beginPath(); + if (show_text) + ctx.roundRect(margin, y, widget_width - margin * 2, H, [H * 0.5]); + else + ctx.rect( margin, y, widget_width - margin * 2, H ); + ctx.fill(); + if (show_text) { + if(!w.disabled) + ctx.stroke(); + ctx.save(); + ctx.beginPath(); + ctx.rect(margin, y, widget_width - margin * 2, H); + ctx.clip(); + + //ctx.stroke(); + ctx.fillStyle = secondary_text_color; + const label = w.label || w.name; + if (label != null) { + ctx.fillText(label, margin * 2, y + H * 0.7); + } + ctx.fillStyle = text_color; + ctx.textAlign = "right"; + ctx.fillText(String(w.value).substr(0,30), widget_width - margin * 2, y + H * 0.7); //30 chars max + ctx.restore(); + } + break; + default: + if (w.draw) { + w.draw(ctx, node, widget_width, y, H); + } + break; + } + posY += (w.computeSize ? w.computeSize(widget_width)[1] : H) + 4; + ctx.globalAlpha = this.editor_alpha; + + } + ctx.restore(); + ctx.textAlign = "left"; + }; + + /** + * process an event on widgets + * @method processNodeWidgets + **/ + LGraphCanvas.prototype.processNodeWidgets = function( + node, + pos, + event, + active_widget + ) { + if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { + return null; + } + + var x = pos[0] - node.pos[0]; + var y = pos[1] - node.pos[1]; + var width = node.size[0]; + var deltaX = event.deltaX || event.deltax || 0; + var that = this; + var ref_window = this.getCanvasWindow(); + + for (var i = 0; i < node.widgets.length; ++i) { + var w = node.widgets[i]; + if(!w || w.disabled) + continue; + var widget_height = w.computeSize ? w.computeSize(width)[1] : LiteGraph.NODE_WIDGET_HEIGHT; + var widget_width = w.width || width; + //outside + if ( w != active_widget && + (x < 6 || x > widget_width - 12 || y < w.last_y || y > w.last_y + widget_height || w.last_y === undefined) ) + continue; + + var old_value = w.value; + + //if ( w == active_widget || (x > 6 && x < widget_width - 12 && y > w.last_y && y < w.last_y + widget_height) ) { + //inside widget + switch (w.type) { + case "button": + if (event.type === LiteGraph.pointerevents_method+"down") { + if (w.callback) { + setTimeout(function() { + w.callback(w, that, node, pos, event); + }, 20); + } + w.clicked = true; + this.dirty_canvas = true; + } + break; + case "slider": + var old_value = w.value; + var nvalue = clamp((x - 15) / (widget_width - 30), 0, 1); + if(w.options.read_only) break; + w.value = w.options.min + (w.options.max - w.options.min) * nvalue; + if (old_value != w.value) { + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + this.dirty_canvas = true; + break; + case "number": + case "combo": + var old_value = w.value; + if (event.type == LiteGraph.pointerevents_method+"move" && w.type == "number") { + if(deltaX) + w.value += deltaX * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (event.type == LiteGraph.pointerevents_method+"down") { + var values = w.options.values; + if (values && values.constructor === Function) { + values = w.options.values(w, node); + } + var values_list = null; + + if( w.type != "number") + values_list = values.constructor === Array ? values : Object.keys(values); + + var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + if (w.type == "number") { + w.value += delta * 0.1 * (w.options.step || 1); + if ( w.options.min != null && w.value < w.options.min ) { + w.value = w.options.min; + } + if ( w.options.max != null && w.value > w.options.max ) { + w.value = w.options.max; + } + } else if (delta) { //clicked in arrow, used for combos + var index = -1; + this.last_mouseclick = 0; //avoids dobl click event + if(values.constructor === Object) + index = values_list.indexOf( String( w.value ) ) + delta; + else + index = values_list.indexOf( w.value ) + delta; + if (index >= values_list.length) { + index = values_list.length - 1; + } + if (index < 0) { + index = 0; + } + if( values.constructor === Array ) + w.value = values[index]; + else + w.value = index; + } else { //combo clicked + var text_values = values != values_list ? Object.values(values) : values; + var menu = new LiteGraph.ContextMenu(text_values, { + scale: Math.max(1, this.ds.scale), + event: event, + className: "dark", + callback: inner_clicked.bind(w) + }, + ref_window); + function inner_clicked(v, option, event) { + if(values != values_list) + v = text_values.indexOf(v); + this.value = v; + inner_value_change(this, v); + that.dirty_canvas = true; + return false; + } + } + } //end mousedown + else if(event.type == LiteGraph.pointerevents_method+"up" && w.type == "number") + { + var delta = x < 40 ? -1 : x > widget_width - 40 ? 1 : 0; + if (event.click_time < 200 && delta == 0) { + this.prompt("Value",w.value,function(v) { + // check if v is a valid equation or a number + if (/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(v)) { + try {//solve the equation if possible + v = eval(v); + } catch (e) { } + } + this.value = Number(v); + inner_value_change(this, this.value); + }.bind(w), + event); + } + } + + if( old_value != w.value ) + setTimeout( + function() { + inner_value_change(this, this.value); + }.bind(w), + 20 + ); + this.dirty_canvas = true; + break; + case "toggle": + if (event.type == LiteGraph.pointerevents_method+"down") { + w.value = !w.value; + setTimeout(function() { + inner_value_change(w, w.value); + }, 20); + } + break; + case "string": + case "text": + if (event.type == LiteGraph.pointerevents_method+"down") { + this.prompt("Value",w.value,function(v) { + inner_value_change(this, v); + }.bind(w), + event,w.options ? w.options.multiline : false ); + } + break; + default: + if (w.mouse) { + this.dirty_canvas = w.mouse(event, [x, y], node); + } + break; + } //end switch + + //value changed + if( old_value != w.value ) + { + if(node.onWidgetChanged) + node.onWidgetChanged( w.name,w.value,old_value,w ); + node.graph._version++; + } + + return w; + }//end for + + function inner_value_change(widget, value) { + if(widget.type == "number"){ + value = Number(value); + } + widget.value = value; + if ( widget.options && widget.options.property && node.properties[widget.options.property] !== undefined ) { + node.setProperty( widget.options.property, value ); + } + if (widget.callback) { + widget.callback(widget.value, that, node, pos, event); + } + } + + return null; + }; + + /** + * draws every group area in the background + * @method drawGroups + **/ + LGraphCanvas.prototype.drawGroups = function(canvas, ctx) { + if (!this.graph) { + return; + } + + var groups = this.graph._groups; + + ctx.save(); + ctx.globalAlpha = 0.5 * this.editor_alpha; + + for (var i = 0; i < groups.length; ++i) { + var group = groups[i]; + + if (!overlapBounding(this.visible_area, group._bounding)) { + continue; + } //out of the visible area + + ctx.fillStyle = group.color || "#335"; + ctx.strokeStyle = group.color || "#335"; + var pos = group._pos; + var size = group._size; + ctx.globalAlpha = 0.25 * this.editor_alpha; + ctx.beginPath(); + ctx.rect(pos[0] + 0.5, pos[1] + 0.5, size[0], size[1]); + ctx.fill(); + ctx.globalAlpha = this.editor_alpha; + ctx.stroke(); + + ctx.beginPath(); + ctx.moveTo(pos[0] + size[0], pos[1] + size[1]); + ctx.lineTo(pos[0] + size[0] - 10, pos[1] + size[1]); + ctx.lineTo(pos[0] + size[0], pos[1] + size[1] - 10); + ctx.fill(); + + var font_size = + group.font_size || LiteGraph.DEFAULT_GROUP_FONT_SIZE; + ctx.font = font_size + "px Arial"; + ctx.textAlign = "left"; + ctx.fillText(group.title, pos[0] + 4, pos[1] + font_size); + } + + ctx.restore(); + }; + + LGraphCanvas.prototype.adjustNodesSize = function() { + var nodes = this.graph._nodes; + for (var i = 0; i < nodes.length; ++i) { + nodes[i].size = nodes[i].computeSize(); + } + this.setDirty(true, true); + }; + + /** + * resizes the canvas to a given size, if no size is passed, then it tries to fill the parentNode + * @method resize + **/ + LGraphCanvas.prototype.resize = function(width, height) { + if (!width && !height) { + var parent = this.canvas.parentNode; + width = parent.offsetWidth; + height = parent.offsetHeight; + } + + if (this.canvas.width == width && this.canvas.height == height) { + return; + } + + this.canvas.width = width; + this.canvas.height = height; + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + this.setDirty(true, true); + }; + + /** + * switches to live mode (node shapes are not rendered, only the content) + * this feature was designed when graphs where meant to create user interfaces + * @method switchLiveMode + **/ + LGraphCanvas.prototype.switchLiveMode = function(transition) { + if (!transition) { + this.live_mode = !this.live_mode; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + return; + } + + var self = this; + var delta = this.live_mode ? 1.1 : 0.9; + if (this.live_mode) { + this.live_mode = false; + this.editor_alpha = 0.1; + } + + var t = setInterval(function() { + self.editor_alpha *= delta; + self.dirty_canvas = true; + self.dirty_bgcanvas = true; + + if (delta < 1 && self.editor_alpha < 0.01) { + clearInterval(t); + if (delta < 1) { + self.live_mode = true; + } + } + if (delta > 1 && self.editor_alpha > 0.99) { + clearInterval(t); + self.editor_alpha = 1; + } + }, 1); + }; + + LGraphCanvas.prototype.onNodeSelectionChange = function(node) { + return; //disabled + }; + + /* this is an implementation for touch not in production and not ready + */ + /*LGraphCanvas.prototype.touchHandler = function(event) { + //alert("foo"); + var touches = event.changedTouches, + first = touches[0], + type = ""; + + switch (event.type) { + case "touchstart": + type = "mousedown"; + break; + case "touchmove": + type = "mousemove"; + break; + case "touchend": + type = "mouseup"; + break; + default: + return; + } + + //initMouseEvent(type, canBubble, cancelable, view, clickCount, + // screenX, screenY, clientX, clientY, ctrlKey, + // altKey, shiftKey, metaKey, button, relatedTarget); + + // this is eventually a Dom object, get the LGraphCanvas back + if(typeof this.getCanvasWindow == "undefined"){ + var window = this.lgraphcanvas.getCanvasWindow(); + }else{ + var window = this.getCanvasWindow(); + } + + var document = window.document; + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent( + type, + true, + true, + window, + 1, + first.screenX, + first.screenY, + first.clientX, + first.clientY, + false, + false, + false, + false, + 0, //left + null + ); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); + };*/ + + /* CONTEXT MENU ********************/ + + LGraphCanvas.onGroupAdd = function(info, entry, mouse_event) { + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var group = new LiteGraph.LGraphGroup(); + group.pos = canvas.convertEventToCanvasOffset(mouse_event); + canvas.graph.add(group); + }; + + /** + * Determines the furthest nodes in each direction + * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.getBoundaryNodes = function(nodes) { + let top = null; + let right = null; + let bottom = null; + let left = null; + for (const nID in nodes) { + const node = nodes[nID]; + const [x, y] = node.pos; + const [width, height] = node.size; + + if (top === null || y < top.pos[1]) { + top = node; + } + if (right === null || x + width > right.pos[0] + right.size[0]) { + right = node; + } + if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { + bottom = node; + } + if (left === null || x < left.pos[0]) { + left = node; + } + } + + return { + "top": top, + "right": right, + "bottom": bottom, + "left": left + }; + } + /** + * Determines the furthest nodes in each direction for the currently selected nodes + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.prototype.boundaryNodesForSelection = function() { + return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); + } + + /** + * + * @param {LGraphNode[]} nodes a list of nodes + * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes + * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) + */ + LGraphCanvas.alignNodes = function (nodes, direction, align_to) { + if (!nodes) { + return; + } + + const canvas = LGraphCanvas.active_canvas; + let boundaryNodes = [] + if (align_to === undefined) { + boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) + } else { + boundaryNodes = { + "top": align_to, + "right": align_to, + "bottom": align_to, + "left": align_to + } + } + + for (const [_, node] of Object.entries(canvas.selected_nodes)) { + switch (direction) { + case "right": + node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; + break; + case "left": + node.pos[0] = boundaryNodes["left"].pos[0]; + break; + case "top": + node.pos[1] = boundaryNodes["top"].pos[1]; + break; + case "bottom": + node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; + break; + } + } + + canvas.dirty_canvas = true; + canvas.dirty_bgcanvas = true; + }; + + LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); + } + } + + LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); + } + } + + LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { + + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + var graph = canvas.graph; + if (!graph) + return; + + function inner_onMenuAdded(base_category ,prev_menu){ + + var categories = LiteGraph.getNodeTypesCategories(canvas.filter || graph.filter).filter(function(category){return category.startsWith(base_category)}); + var entries = []; + + categories.map(function(category){ + + if (!category) + return; + + var base_category_regex = new RegExp('^(' + base_category + ')'); + var category_name = category.replace(base_category_regex,"").split('/')[0]; + var category_path = base_category === '' ? category_name + '/' : base_category + category_name + '/'; + + var name = category_name; + if(name.indexOf("::") != -1) //in case it has a namespace like "shader::math/rand" it hides the namespace + name = name.split("::")[1]; + + var index = entries.findIndex(function(entry){return entry.value === category_path}); + if (index === -1) { + entries.push({ value: category_path, content: name, has_submenu: true, callback : function(value, event, mouseEvent, contextMenu){ + inner_onMenuAdded(value.value, contextMenu) + }}); + } + + }); + + var nodes = LiteGraph.getNodeTypesInCategory(base_category.slice(0, -1), canvas.filter || graph.filter ); + nodes.map(function(node){ + + if (node.skip_list) + return; + + var entry = { value: node.type, content: node.title, has_submenu: false , callback : function(value, event, mouseEvent, contextMenu){ + + var first_event = contextMenu.getFirstEvent(); + canvas.graph.beforeChange(); + var node = LiteGraph.createNode(value.value); + if (node) { + node.pos = canvas.convertEventToCanvasOffset(first_event); + canvas.graph.add(node); + } + if(callback) + callback(node); + canvas.graph.afterChange(); + + } + } + + entries.push(entry); + + }); + + new LiteGraph.ContextMenu( entries, { event: e, parentMenu: prev_menu }, ref_window ); + + } + + inner_onMenuAdded('',prev_menu); + return false; + + }; + + LGraphCanvas.onMenuCollapseAll = function() {}; + + LGraphCanvas.onMenuNodeEdit = function() {}; + + LGraphCanvas.showMenuNodeOptionalInputs = function( + v, + options, + e, + prev_menu, + node + ) { + if (!node) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_inputs; + if (node.onGetInputs) { + options = node.onGetInputs(); + } + + var entries = []; + if (options) { + for (var i=0; i < options.length; i++) { + var entry = options[i]; + if (!entry) { + entries.push(null); + continue; + } + var label = entry[0]; + if(!entry[2]) + entry[2] = {}; + + if (entry[2].label) { + label = entry[2].label; + } + + entry[2].removable = true; + var data = { content: label, value: entry }; + if (entry[1] == LiteGraph.ACTION) { + data.className = "event"; + } + entries.push(data); + } + } + + if (node.onMenuNodeInputs) { + var retEntries = node.onMenuNodeInputs(entries); + if(retEntries) entries = retEntries; + } + + if (!entries.length) { + console.log("no input entries"); + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }, + ref_window + ); + + function inner_clicked(v, e, prev) { + if (!node) { + return; + } + + if (v.callback) { + v.callback.call(that, node, v, e, prev); + } + + if (v.value) { + node.graph.beforeChange(); + node.addInput(v.value[0], v.value[1], v.value[2]); + + if (node.onNodeInputAdd) { // callback to the node when adding a slot + node.onNodeInputAdd(v.value); + } + node.setDirtyCanvas(true, true); + node.graph.afterChange(); + } + } + + return false; + }; + + LGraphCanvas.showMenuNodeOptionalOutputs = function( + v, + options, + e, + prev_menu, + node + ) { + if (!node) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var options = node.optional_outputs; + if (node.onGetOutputs) { + options = node.onGetOutputs(); + } + + var entries = []; + if (options) { + for (var i=0; i < options.length; i++) { + var entry = options[i]; + if (!entry) { + //separator? + entries.push(null); + continue; + } + + if ( + node.flags && + node.flags.skip_repeated_outputs && + node.findOutputSlot(entry[0]) != -1 + ) { + continue; + } //skip the ones already on + var label = entry[0]; + if(!entry[2]) + entry[2] = {}; + if (entry[2].label) { + label = entry[2].label; + } + entry[2].removable = true; + var data = { content: label, value: entry }; + if (entry[1] == LiteGraph.EVENT) { + data.className = "event"; + } + entries.push(data); + } + } + + if (this.onMenuNodeOutputs) { + entries = this.onMenuNodeOutputs(entries); + } + if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted + if (node.findOutputSlot("onExecuted") == -1){ + entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} + } + } + // add callback for modifing the menu elements onMenuNodeOutputs + if (node.onMenuNodeOutputs) { + var retEntries = node.onMenuNodeOutputs(entries); + if(retEntries) entries = retEntries; + } + + if (!entries.length) { + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }, + ref_window + ); + + function inner_clicked(v, e, prev) { + if (!node) { + return; + } + + if (v.callback) { + v.callback.call(that, node, v, e, prev); + } + + if (!v.value) { + return; + } + + var value = v.value[1]; + + if ( + value && + (value.constructor === Object || value.constructor === Array) + ) { + //submenu why? + var entries = []; + for (var i in value) { + entries.push({ content: i, value: value[i] }); + } + new LiteGraph.ContextMenu(entries, { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + node: node + }); + return false; + } else { + node.graph.beforeChange(); + node.addOutput(v.value[0], v.value[1], v.value[2]); + + if (node.onNodeOutputAdd) { // a callback to the node when adding a slot + node.onNodeOutputAdd(v.value); + } + node.setDirtyCanvas(true, true); + node.graph.afterChange(); + } + } + + return false; + }; + + LGraphCanvas.onShowMenuNodeProperties = function( + value, + options, + e, + prev_menu, + node + ) { + if (!node || !node.properties) { + return; + } + + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var entries = []; + for (var i in node.properties) { + var value = node.properties[i] !== undefined ? node.properties[i] : " "; + if( typeof value == "object" ) + value = JSON.stringify(value); + var info = node.getPropertyInfo(i); + if(info.type == "enum" || info.type == "combo") + value = LGraphCanvas.getPropertyPrintableValue( value, info.values ); + + //value could contain invalid html characters, clean that + value = LGraphCanvas.decodeHTML(value); + entries.push({ + content: + "" + + (info.label ? info.label : i) + + "" + + "" + + value + + "", + value: i + }); + } + if (!entries.length) { + return; + } + + var menu = new LiteGraph.ContextMenu( + entries, + { + event: e, + callback: inner_clicked, + parentMenu: prev_menu, + allow_html: true, + node: node + }, + ref_window + ); + + function inner_clicked(v, options, e, prev) { + if (!node) { + return; + } + var rect = this.getBoundingClientRect(); + canvas.showEditPropertyValue(node, v.value, { + position: [rect.left, rect.top] + }); + } + + return false; + }; + + LGraphCanvas.decodeHTML = function(str) { + var e = document.createElement("div"); + e.innerText = str; + return e.innerHTML; + }; + + LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { + if (!node) { + return; + } + + var fApplyMultiNode = function(node){ + node.size = node.computeSize(); + if (node.onResize) + node.onResize(node.size); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.prototype.showLinkMenu = function(link, e) { + var that = this; + // console.log(link); + var node_left = that.graph.getNodeById( link.origin_id ); + var node_right = that.graph.getNodeById( link.target_id ); + var fromType = false; + if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; + var destType = false; + if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; + + var options = ["Add Node",null,"Delete",null]; + + + var menu = new LiteGraph.ContextMenu(options, { + event: e, + title: link.data != null ? link.data.constructor.name : null, + callback: inner_clicked + }); + + function inner_clicked(v,options,e) { + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + // console.debug("node autoconnect"); + if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ + return; + } + // leave the connection type checking inside connectByType + if (node_left.connectByType( link.origin_slot, node, fromType )){ + node.connectByType( link.target_slot, node_right, destType ); + node.pos[0] -= node.size[0] * 0.5; + } + }); + break; + + case "Delete": + that.graph.removeLink(link.id); + break; + default: + /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left + ,slotFrom: link.origin_slot + ,nodeTo: node + ,slotTo: link.target_slot + ,e: e + ,nodeType: "AUTO" + }); + if(nodeCreated) console.log("new node in beetween "+v+" created");*/ + } + } + + return false; + }; + + LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,position: [] // pass the event coords + ,nodeType: null // choose a nodetype to add, AUTO to set at first good + ,posAdd:[0,0] // adjust x,y + ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom!==null; + var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; + + if (!isFrom && !isTo){ + console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); + return false; + } + if (!opts.nodeType){ + console.warn("No type to createDefaultNodeForSlot"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "undefined": + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + if (slotX===false || iSlotConn===false){ + console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); + } + + // check for defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + nodeNewType = false; + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ + nodeNewType = slotTypesDefault[fromSlotType][typeX]; + // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); + break; // -------- + } + } + }else{ + if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; + } + if (nodeNewType) { + var nodeNewOpts = false; + if (typeof nodeNewType == "object" && nodeNewType.node){ + nodeNewOpts = nodeNewType; + nodeNewType = nodeNewType.node; + } + + //that.graph.beforeChange(); + + var newNode = LiteGraph.createNode(nodeNewType); + if(newNode){ + // if is object pass options + if (nodeNewOpts){ + if (nodeNewOpts.properties) { + for (var i in nodeNewOpts.properties) { + newNode.addProperty( i, nodeNewOpts.properties[i] ); + } + } + if (nodeNewOpts.inputs) { + newNode.inputs = []; + for (var i in nodeNewOpts.inputs) { + newNode.addOutput( + nodeNewOpts.inputs[i][0], + nodeNewOpts.inputs[i][1] + ); + } + } + if (nodeNewOpts.outputs) { + newNode.outputs = []; + for (var i in nodeNewOpts.outputs) { + newNode.addOutput( + nodeNewOpts.outputs[i][0], + nodeNewOpts.outputs[i][1] + ); + } + } + if (nodeNewOpts.title) { + newNode.title = nodeNewOpts.title; + } + if (nodeNewOpts.json) { + newNode.configure(nodeNewOpts.json); + } + + } + + // add the node + that.graph.add(newNode); + newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) + ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ + + //that.graph.afterChange(); + + // connect the two! + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); + } + + // if connecting in between + if (isFrom && isTo){ + // TODO + } + + return true; + + }else{ + console.log("failed creating "+nodeNewType); + } + } + } + return false; + } + + LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,e: null + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom; + var isTo = !isFrom && opts.nodeTo && opts.slotTo; + + if (!isFrom && !isTo){ + console.warn("No data passed to showConnectionMenu"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + var options = ["Add Node",null]; + + if (that.allow_searchbox){ + options.push("Search"); + options.push(null); + } + + // get defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + options.push(slotTypesDefault[fromSlotType][typeX]); + } + }else{ + options.push(slotTypesDefault[fromSlotType]); + } + } + + // build menu + var menu = new LiteGraph.ContextMenu(options, { + event: opts.e, + title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), + callback: inner_clicked + }); + + // callback + function inner_clicked(v,options,e) { + //console.log("Process showConnectionMenu selection"); + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); + } + }); + break; + case "Search": + if(isFrom){ + that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); + }else{ + that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); + } + break; + default: + // check for defaults nodes for this slottype + var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] + ,nodeType: v + })); + if (nodeCreated){ + // new node created + //console.log("node "+v+" created") + }else{ + // failed or v is not in defaults + } + break; + } + } + + return false; + }; + + // TODO refactor :: this is used fot title but not for properties! + LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { + var input_html = ""; + var property = item.property || "title"; + var value = node[property]; + + // TODO refactor :: use createDialog ? + + var dialog = document.createElement("div"); + dialog.is_modified = false; + dialog.className = "graphdialog"; + dialog.innerHTML = + ""; + dialog.close = function() { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + var title = dialog.querySelector(".name"); + title.innerText = property; + var input = dialog.querySelector(".value"); + if (input) { + input.value = value; + input.addEventListener("blur", function(e) { + this.focus(); + }); + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + } + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner); + canvas.parentNode.appendChild(dialog); + + if(input) input.focus(); + + var dialogCloseTimer = null; + dialog.addEventListener("mouseleave", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + dialog.addEventListener("mouseenter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + + function inner() { + if(input) setValue(input.value); + } + + function setValue(value) { + if (item.type == "Number") { + value = Number(value); + } else if (item.type == "Boolean") { + value = Boolean(value); + } + node[property] = value; + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + node.setDirtyCanvas(true, true); + } + }; + + // refactor: there are different dialogs, some uses createDialog some dont + LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { + var that = this; + var input_html = ""; + title = title || ""; + + var dialog = document.createElement("div"); + dialog.is_modified = false; + dialog.className = "graphdialog rounded"; + if(multiline) + dialog.innerHTML = " "; + else + dialog.innerHTML = " "; + dialog.close = function() { + that.prompt_box = null; + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + canvas.parentNode.appendChild(dialog); + + if (this.ds.scale > 1) { + dialog.style.transform = "scale(" + this.ds.scale + ")"; + } + + var dialogCloseTimer = null; + var prevent_timeout = false; + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout) + return; + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + var selInDia = dialog.querySelectorAll("select"); + if (selInDia){ + // if filtering, check focus changed to comboboxes and prevent closing + selInDia.forEach(function(selIn) { + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + }); + } + + if (that.prompt_box) { + that.prompt_box.close(); + } + that.prompt_box = dialog; + + var first = null; + var timeout = null; + var selected = null; + + var name_element = dialog.querySelector(".name"); + name_element.innerText = title; + var value_element = dialog.querySelector(".value"); + value_element.value = value; + + var input = value_element; + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13 && e.target.localName != "textarea") { + if (callback) { + callback(this.value); + } + dialog.close(); + } else { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + + var button = dialog.querySelector("button"); + button.addEventListener("click", function(e) { + if (callback) { + callback(input.value); + } + that.setDirty(true); + dialog.close(); + }); + + var rect = canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + + setTimeout(function() { + input.focus(); + }, 10); + + return dialog; + }; + + LGraphCanvas.search_limit = -1; + LGraphCanvas.prototype.showSearchBox = function(event, options) { + // proposed defaults + var def_options = { slot_from: null + ,node_from: null + ,node_to: null + ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out + ,type_filter_in: false // these are default: pass to set initially set values + ,type_filter_out: false + ,show_general_if_none_on_typefilter: true + ,show_general_after_typefiltered: true + ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave + ,show_all_if_empty: true + ,show_all_on_open: LiteGraph.search_show_all_on_open + }; + options = Object.assign(def_options, options || {}); + + //console.log(options); + + var that = this; + var input_html = ""; + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + var root_document = canvas.ownerDocument || document; + + var dialog = document.createElement("div"); + dialog.className = "litegraph litesearchbox graphdialog rounded"; + dialog.innerHTML = "Search "; + if (options.do_type_filter){ + dialog.innerHTML += ""; + dialog.innerHTML += ""; + } + dialog.innerHTML += "
"; + + if( root_document.fullscreenElement ) + root_document.fullscreenElement.appendChild(dialog); + else + { + root_document.body.appendChild(dialog); + root_document.body.style.overflow = "hidden"; + } + // dialog element has been appended + + if (options.do_type_filter){ + var selIn = dialog.querySelector(".slot_in_type_filter"); + var selOut = dialog.querySelector(".slot_out_type_filter"); + } + + dialog.close = function() { + that.search_box = null; + this.blur(); + canvas.focus(); + root_document.body.style.overflow = ""; + + setTimeout(function() { + that.canvas.focus(); + }, 20); //important, if canvas loses focus keys wont be captured + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + if (this.ds.scale > 1) { + dialog.style.transform = "scale(" + this.ds.scale + ")"; + } + + // hide on mouse leave + if(options.hide_on_mouse_leave){ + var prevent_timeout = false; + var timeout_close = null; + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if (timeout_close) { + clearTimeout(timeout_close); + timeout_close = null; + } + }); + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout){ + return; + } + timeout_close = setTimeout(function() { + dialog.close(); + }, 500); + }); + // if filtering, check focus changed to comboboxes and prevent closing + if (options.do_type_filter){ + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + selOut.addEventListener("click", function(e) { + prevent_timeout++; + }); + selOut.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selOut.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + } + } + + if (that.search_box) { + that.search_box.close(); + } + that.search_box = dialog; + + var helper = dialog.querySelector(".helper"); + + var first = null; + var timeout = null; + var selected = null; + + var input = dialog.querySelector("input"); + if (input) { + input.addEventListener("blur", function(e) { + if(that.search_box) + this.focus(); + }); + input.addEventListener("keydown", function(e) { + if (e.keyCode == 38) { + //UP + changeSelection(false); + } else if (e.keyCode == 40) { + //DOWN + changeSelection(true); + } else if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + refreshHelper(); + if (selected) { + select(selected.innerHTML); + } else if (first) { + select(first); + } else { + dialog.close(); + } + } else { + if (timeout) { + clearInterval(timeout); + } + timeout = setTimeout(refreshHelper, 250); + return; + } + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); + return true; + }); + } + + // if should filter on type, load and fill selected and choose elements if passed + if (options.do_type_filter){ + if (selIn){ + var aSlots = LiteGraph.slot_types_in; + var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; + + if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) + options.type_filter_in = "_event_"; + /* this will filter on * .. but better do it manually in case + else if(options.type_filter_in === "" || options.type_filter_in === 0) + options.type_filter_in = "*";*/ + + for (var iK=0; iK (rect.height - 200)) + helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; + + /* + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (event) { + dialog.style.left = event.clientX + offsetx + "px"; + dialog.style.top = event.clientY + offsety + "px"; + } else { + dialog.style.left = canvas.width * 0.5 + offsetx + "px"; + dialog.style.top = canvas.height * 0.5 + offsety + "px"; + } + canvas.parentNode.appendChild(dialog); + */ + + input.focus(); + if (options.show_all_on_open) refreshHelper(); + + function select(name) { + if (name) { + if (that.onSearchBoxSelection) { + that.onSearchBoxSelection(name, event, graphcanvas); + } else { + var extra = LiteGraph.searchbox_extras[name.toLowerCase()]; + if (extra) { + name = extra.type; + } + + graphcanvas.graph.beforeChange(); + var node = LiteGraph.createNode(name); + if (node) { + node.pos = graphcanvas.convertEventToCanvasOffset( + event + ); + graphcanvas.graph.add(node, false); + } + + if (extra && extra.data) { + if (extra.data.properties) { + for (var i in extra.data.properties) { + node.addProperty( i, extra.data.properties[i] ); + } + } + if (extra.data.inputs) { + node.inputs = []; + for (var i in extra.data.inputs) { + node.addOutput( + extra.data.inputs[i][0], + extra.data.inputs[i][1] + ); + } + } + if (extra.data.outputs) { + node.outputs = []; + for (var i in extra.data.outputs) { + node.addOutput( + extra.data.outputs[i][0], + extra.data.outputs[i][1] + ); + } + } + if (extra.data.title) { + node.title = extra.data.title; + } + if (extra.data.json) { + node.configure(extra.data.json); + } + + } + + // join node after inserting + if (options.node_from){ + var iS = false; + switch (typeof options.slot_from){ + case "string": + iS = options.node_from.findOutputSlot(options.slot_from); + break; + case "object": + if (options.slot_from.name){ + iS = options.node_from.findOutputSlot(options.slot_from.name); + }else{ + iS = -1; + } + if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; + break; + case "number": + iS = options.slot_from; + break; + default: + iS = 0; // try with first if no name set + } + if (typeof options.node_from.outputs[iS] !== "undefined"){ + if (iS!==false && iS>-1){ + options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); + } + }else{ + // console.warn("cant find slot " + options.slot_from); + } + } + if (options.node_to){ + var iS = false; + switch (typeof options.slot_from){ + case "string": + iS = options.node_to.findInputSlot(options.slot_from); + break; + case "object": + if (options.slot_from.name){ + iS = options.node_to.findInputSlot(options.slot_from.name); + }else{ + iS = -1; + } + if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; + break; + case "number": + iS = options.slot_from; + break; + default: + iS = 0; // try with first if no name set + } + if (typeof options.node_to.inputs[iS] !== "undefined"){ + if (iS!==false && iS>-1){ + // try connection + options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); + } + }else{ + // console.warn("cant find slot_nodeTO " + options.slot_from); + } + } + + graphcanvas.graph.afterChange(); + } + } + + dialog.close(); + } + + function changeSelection(forward) { + var prev = selected; + if (selected) { + selected.classList.remove("selected"); + } + if (!selected) { + selected = forward + ? helper.childNodes[0] + : helper.childNodes[helper.childNodes.length]; + } else { + selected = forward + ? selected.nextSibling + : selected.previousSibling; + if (!selected) { + selected = prev; + } + } + if (!selected) { + return; + } + selected.classList.add("selected"); + selected.scrollIntoView({block: "end", behavior: "smooth"}); + } + + function refreshHelper() { + timeout = null; + var str = input.value; + first = null; + helper.innerHTML = ""; + if (!str && !options.show_all_if_empty) { + return; + } + + if (that.onSearchBox) { + var list = that.onSearchBox(helper, str, graphcanvas); + if (list) { + for (var i = 0; i < list.length; ++i) { + addResult(list[i]); + } + } + } else { + var c = 0; + str = str.toLowerCase(); + var filter = graphcanvas.filter || graphcanvas.graph.filter; + + // filter by type preprocess + if(options.do_type_filter && that.search_box){ + var sIn = that.search_box.querySelector(".slot_in_type_filter"); + var sOut = that.search_box.querySelector(".slot_out_type_filter"); + }else{ + var sIn = false; + var sOut = false; + } + + //extras + for (var i in LiteGraph.searchbox_extras) { + var extra = LiteGraph.searchbox_extras[i]; + if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { + continue; + } + var ctor = LiteGraph.registered_node_types[ extra.type ]; + if( ctor && ctor.filter != filter ) + continue; + if( ! inner_test_filter(extra.type) ) + continue; + addResult( extra.desc, "searchbox_extra" ); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + + var filtered = null; + if (Array.prototype.filter) { //filter supported + var keys = Object.keys( LiteGraph.registered_node_types ); //types + var filtered = keys.filter( inner_test_filter ); + } else { + filtered = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i) ) + filtered.push(i); + } + } + + for (var i = 0; i < filtered.length; i++) { + addResult(filtered[i]); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + + // add general type if filtering + if (options.show_general_after_typefiltered + && (sIn.value || sOut.value) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "generic_type"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + // check il filtering gave no results + if ((sIn.value || sOut.value) && + ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {skipFilter: true}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "not_in_filter"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + function inner_test_filter( type, optsIn ) + { + var optsIn = optsIn || {}; + var optsDef = { skipFilter: false + ,inTypeOverride: false + ,outTypeOverride: false + }; + var opts = Object.assign(optsDef,optsIn); + var ctor = LiteGraph.registered_node_types[ type ]; + if(filter && ctor.filter != filter ) + return false; + if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) + return false; + + // filter by slot IN, OUT types + if(options.do_type_filter && !opts.skipFilter){ + var sType = type; + + var sV = sIn.value; + if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sIn && sV){ + //console.log("will check filter against "+sV); + if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_in_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + + var sV = sOut.value; + if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sOut && sV){ + //console.log("search will check filter against "+sV); + if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_out_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + } + return true; + } + } + + function addResult(type, className) { + var help = document.createElement("div"); + if (!first) { + first = type; + } + help.innerText = type; + help.dataset["type"] = escape(type); + help.className = "litegraph lite-search-item"; + if (className) { + help.className += " " + className; + } + help.addEventListener("click", function(e) { + select(unescape(this.dataset["type"])); + }); + helper.appendChild(help); + } + } + + return dialog; + }; + + LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options ) { + if (!node || node.properties[property] === undefined) { + return; + } + + options = options || {}; + var that = this; + + var info = node.getPropertyInfo(property); + var type = info.type; + + var input_html = ""; + + if (type == "string" || type == "number" || type == "array" || type == "object") { + input_html = ""; + } else if ( (type == "enum" || type == "combo") && info.values) { + input_html = ""; + } else if (type == "boolean" || type == "toggle") { + input_html = + ""; + } else { + console.warn("unknown type: " + type); + return; + } + + var dialog = this.createDialog( + "" + + (info.label ? info.label : property) + + "" + + input_html + + "", + options + ); + + var input = false; + if ((type == "enum" || type == "combo") && info.values) { + input = dialog.querySelector("select"); + input.addEventListener("change", function(e) { + dialog.modified(); + setValue(e.target.value); + //var index = e.target.value; + //setValue( e.options[e.selectedIndex].value ); + }); + } else if (type == "boolean" || type == "toggle") { + input = dialog.querySelector("input"); + if (input) { + input.addEventListener("click", function(e) { + dialog.modified(); + setValue(!!input.checked); + }); + } + } else { + input = dialog.querySelector("input"); + if (input) { + input.addEventListener("blur", function(e) { + this.focus(); + }); + + var v = node.properties[property] !== undefined ? node.properties[property] : ""; + if (type !== 'string') { + v = JSON.stringify(v); + } + + input.value = v; + input.addEventListener("keydown", function(e) { + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + // ENTER + inner(); // save + } else if (e.keyCode != 13) { + dialog.modified(); + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + } + } + if (input) input.focus(); + + var button = dialog.querySelector("button"); + button.addEventListener("click", inner); + + function inner() { + setValue(input.value); + } + + function setValue(value) { + + if(info && info.values && info.values.constructor === Object && info.values[value] != undefined ) + value = info.values[value]; + + if (typeof node.properties[property] == "number") { + value = Number(value); + } + if (type == "array" || type == "object") { + value = JSON.parse(value); + } + node.properties[property] = value; + if (node.graph) { + node.graph._version++; + } + if (node.onPropertyChanged) { + node.onPropertyChanged(property, value); + } + if(options.onclose) + options.onclose(); + dialog.close(); + node.setDirtyCanvas(true, true); + } + + return dialog; + }; + + // TODO refactor, theer are different dialog, some uses createDialog, some dont + LGraphCanvas.prototype.createDialog = function(html, options) { + var def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; + options = Object.assign(def_options, options || {}); + + var dialog = document.createElement("div"); + dialog.className = "graphdialog"; + dialog.innerHTML = html; + dialog.is_modified = false; + + var rect = this.canvas.getBoundingClientRect(); + var offsetx = -20; + var offsety = -20; + if (rect) { + offsetx -= rect.left; + offsety -= rect.top; + } + + if (options.position) { + offsetx += options.position[0]; + offsety += options.position[1]; + } else if (options.event) { + offsetx += options.event.clientX; + offsety += options.event.clientY; + } //centered + else { + offsetx += this.canvas.width * 0.5; + offsety += this.canvas.height * 0.5; + } + + dialog.style.left = offsetx + "px"; + dialog.style.top = offsety + "px"; + + this.canvas.parentNode.appendChild(dialog); + + // acheck for input and use default behaviour: save on enter, close on esc + if (options.checkForInput){ + var aI = []; + var focused = false; + if (aI = dialog.querySelectorAll("input")){ + aI.forEach(function(iX) { + iX.addEventListener("keydown",function(e){ + dialog.modified(); + if (e.keyCode == 27) { + dialog.close(); + } else if (e.keyCode != 13) { + return; + } + // set value ? + e.preventDefault(); + e.stopPropagation(); + }); + if (!focused) iX.focus(); + }); + } + } + + dialog.modified = function(){ + dialog.is_modified = true; + } + dialog.close = function() { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; + + var dialogCloseTimer = null; + var prevent_timeout = false; + dialog.addEventListener("mouseleave", function(e) { + if (prevent_timeout) + return; + if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + dialog.addEventListener("mouseenter", function(e) { + if(options.closeOnLeave || LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + var selInDia = dialog.querySelectorAll("select"); + if (selInDia){ + // if filtering, check focus changed to comboboxes and prevent closing + selInDia.forEach(function(selIn) { + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + }); + } + + return dialog; + }; + + LGraphCanvas.prototype.createPanel = function(title, options) { + options = options || {}; + + var ref_window = options.window || window; + var root = document.createElement("div"); + root.className = "litegraph dialog"; + root.innerHTML = "
"; + root.header = root.querySelector(".dialog-header"); + + if(options.width) + root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); + if(options.height) + root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); + if(options.closable) + { + var close = document.createElement("span"); + close.innerHTML = "✕"; + close.classList.add("close"); + close.addEventListener("click",function(){ + root.close(); + }); + root.header.appendChild(close); + } + root.title_element = root.querySelector(".dialog-title"); + root.title_element.innerText = title; + root.content = root.querySelector(".dialog-content"); + root.alt_content = root.querySelector(".dialog-alt-content"); + root.footer = root.querySelector(".dialog-footer"); + + root.close = function() + { + if (root.onClose && typeof root.onClose == "function"){ + root.onClose(); + } + if(root.parentNode) + root.parentNode.removeChild(root); + /* XXX CHECK THIS */ + if(this.parentNode){ + this.parentNode.removeChild(this); + } + /* XXX this was not working, was fixed with an IF, check this */ + } + + // function to swap panel content + root.toggleAltContent = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + var vAlt = force ? "none" : "block"; + }else{ + var vTo = root.alt_content.style.display != "block" ? "block" : "none"; + var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; + } + root.alt_content.style.display = vTo; + root.content.style.display = vAlt; + } + + root.toggleFooterVisibility = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + }else{ + var vTo = root.footer.style.display != "block" ? "block" : "none"; + } + root.footer.style.display = vTo; + } + + root.clear = function() + { + this.content.innerHTML = ""; + } + + root.addHTML = function(code, classname, on_footer) + { + var elem = document.createElement("div"); + if(classname) + elem.className = classname; + elem.innerHTML = code; + if(on_footer) + root.footer.appendChild(elem); + else + root.content.appendChild(elem); + return elem; + } + + root.addButton = function( name, callback, options ) + { + var elem = document.createElement("button"); + elem.innerText = name; + elem.options = options; + elem.classList.add("btn"); + elem.addEventListener("click",callback); + root.footer.appendChild(elem); + return elem; + } + + root.addSeparator = function() + { + var elem = document.createElement("div"); + elem.className = "separator"; + root.content.appendChild(elem); + } + + root.addWidget = function( type, name, value, options, callback ) + { + options = options || {}; + var str_value = String(value); + type = type.toLowerCase(); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = options.label || name; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = name; + elem.dataset["type"] = options.type || type; + elem.options = options; + elem.value = value; + + if( type == "code" ) + elem.addEventListener("click", function(e){ root.inner_showCodePad( this.dataset["property"] ); }); + else if (type == "boolean") + { + elem.classList.add("boolean"); + if(value) + elem.classList.add("bool-on"); + elem.addEventListener("click", function(){ + //var v = node.properties[this.dataset["property"]]; + //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + var propname = this.dataset["property"]; + this.value = !this.value; + this.classList.toggle("bool-on"); + this.querySelector(".property_value").innerText = this.value ? "true" : "false"; + innerChange(propname, this.value ); + }); + } + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("keydown", function(e){ + if(e.code == "Enter" && (type != "string" || !e.shiftKey)) // allow for multiline + { + e.preventDefault(); + this.blur(); + } + }); + value_element.addEventListener("blur", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + var proptype = this.parentNode.dataset["type"]; + if( proptype == "number") + v = Number(v); + innerChange(propname, v); + }); + } + else if (type == "enum" || type == "combo") { + var str_value = LGraphCanvas.getPropertyPrintableValue( value, options.values ); + value_element.innerText = str_value; + + value_element.addEventListener("click", function(event){ + var values = options.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + //node.setProperty(propname,v); + //graphcanvas.dirty_canvas = true; + elem_that.innerText = v; + innerChange(propname,v); + return false; + } + }); + } + + root.content.appendChild(elem); + + function innerChange(name, value) + { + //console.log("change",name,value); + //that.dirty_canvas = true; + if(options.callback) + options.callback(name,value,options); + if(callback) + callback(name,value,options); + } + + return elem; + } + + if (root.onOpen && typeof root.onOpen == "function") root.onOpen(); + + return root; + }; + + LGraphCanvas.getPropertyPrintableValue = function(value, values) + { + if(!values) + return String(value); + + if(values.constructor === Array) + { + return String(value); + } + + if(values.constructor === Object) + { + var desc_value = ""; + for(var k in values) + { + if(values[k] != value) + continue; + desc_value = k; + break; + } + return String(value) + " ("+desc_value+")"; + } + } + + LGraphCanvas.prototype.closePanels = function(){ + var panel = document.querySelector("#node-panel"); + if(panel) + panel.close(); + var panel = document.querySelector("#option-panel"); + if(panel) + panel.close(); + } + + LGraphCanvas.prototype.showShowGraphOptionsPanel = function(refOpts, obEv, refMenu, refMenu2){ + if(this.constructor && this.constructor.name == "HTMLDivElement"){ + // assume coming from the menu event click + if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ + console.warn("Canvas not found"); // need a ref to canvas obj + /*console.debug(event); + console.debug(event.target);*/ + return; + } + var graphcanvas = obEv.event.target.lgraphcanvas; + }else{ + // assume called internally + var graphcanvas = this; + } + graphcanvas.closePanels(); + var ref_window = graphcanvas.getCanvasWindow(); + panel = graphcanvas.createPanel("Options",{ + closable: true + ,window: ref_window + ,onOpen: function(){ + graphcanvas.OPTIONPANEL_IS_OPEN = true; + } + ,onClose: function(){ + graphcanvas.OPTIONPANEL_IS_OPEN = false; + graphcanvas.options_panel = null; + } + }); + graphcanvas.options_panel = panel; + panel.id = "option-panel"; + panel.classList.add("settings"); + + function inner_refresh(){ + + panel.content.innerHTML = ""; //clear + + var fUpdate = function(name, value, options){ + switch(name){ + /*case "Render mode": + // Case "".. + if (options.values && options.key){ + var kV = Object.values(options.values).indexOf(value); + if (kV>=0 && options.values[kV]){ + console.debug("update graph options: "+options.key+": "+kV); + graphcanvas[options.key] = kV; + //console.debug(graphcanvas); + break; + } + } + console.warn("unexpected options"); + console.debug(options); + break;*/ + default: + //console.debug("want to update graph options: "+name+": "+value); + if (options && options.key){ + name = options.key; + } + if (options.values){ + value = Object.values(options.values).indexOf(value); + } + //console.debug("update graph option: "+name+": "+value); + graphcanvas[name] = value; + break; + } + }; + + // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement + + var aProps = LiteGraph.availableCanvasOptions; + aProps.sort(); + for(var pI in aProps){ + var pX = aProps[pI]; + panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); + } + + var aLinks = [ graphcanvas.links_render_mode ]; + panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); + + panel.addSeparator(); + + panel.footer.innerHTML = ""; // clear + + } + inner_refresh(); + + graphcanvas.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showShowNodePanel = function( node ) + { + this.SELECTED_NODE = node; + this.closePanels(); + var ref_window = this.getCanvasWindow(); + var that = this; + var graphcanvas = this; + var panel = this.createPanel(node.title || "",{ + closable: true + ,window: ref_window + ,onOpen: function(){ + graphcanvas.NODEPANEL_IS_OPEN = true; + } + ,onClose: function(){ + graphcanvas.NODEPANEL_IS_OPEN = false; + graphcanvas.node_panel = null; + } + }); + graphcanvas.node_panel = panel; + panel.id = "node-panel"; + panel.node = node; + panel.classList.add("settings"); + + function inner_refresh() + { + panel.content.innerHTML = ""; //clear + panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); + + panel.addHTML("

Properties

"); + + var fUpdate = function(name,value){ + graphcanvas.graph.beforeChange(node); + switch(name){ + case "Title": + node.title = value; + break; + case "Mode": + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); + if (kV>=0 && LiteGraph.NODE_MODES[kV]){ + node.changeMode(kV); + }else{ + console.warn("unexpected mode: "+value); + } + break; + case "Color": + if (LGraphCanvas.node_colors[value]){ + node.color = LGraphCanvas.node_colors[value].color; + node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; + }else{ + console.warn("unexpected color: "+value); + } + break; + default: + node.setProperty(name,value); + break; + } + graphcanvas.graph.afterChange(); + graphcanvas.dirty_canvas = true; + }; + + panel.addWidget( "string", "Title", node.title, {}, fUpdate); + + panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); + + var nodeCol = ""; + if (node.color !== undefined){ + nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); + } + + panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); + + for(var pName in node.properties) + { + var value = node.properties[pName]; + var info = node.getPropertyInfo(pName); + var type = info.type || "string"; + + //in case the user wants control over the side panel widget + if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) + continue; + + panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); + } + + panel.addSeparator(); + + if(node.onShowCustomPanelInfo) + node.onShowCustomPanelInfo(panel); + + panel.footer.innerHTML = ""; // clear + panel.addButton("Delete",function(){ + if(node.block_delete) + return; + node.graph.remove(node); + panel.close(); + }).classList.add("delete"); + } + + panel.inner_showCodePad = function( propname ) + { + panel.classList.remove("settings"); + panel.classList.add("centered"); + + + /*if(window.CodeFlask) //disabled for now + { + panel.content.innerHTML = "
"; + var flask = new CodeFlask( "div.code", { language: 'js' }); + flask.updateCode(node.properties[propname]); + flask.onUpdate( function(code) { + node.setProperty(propname, code); + }); + } + else + {*/ + panel.alt_content.innerHTML = ""; + var textarea = panel.alt_content.querySelector("textarea"); + var fDoneWith = function(){ + panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); + panel.toggleFooterVisibility(true); + textarea.parentNode.removeChild(textarea); + panel.classList.add("settings"); + panel.classList.remove("centered"); + inner_refresh(); + } + textarea.value = node.properties[propname]; + textarea.addEventListener("keydown", function(e){ + if(e.code == "Enter" && e.ctrlKey ) + { + node.setProperty(propname, textarea.value); + fDoneWith(); + } + }); + panel.toggleAltContent(true); + panel.toggleFooterVisibility(false); + textarea.style.height = "calc(100% - 40px)"; + /*}*/ + var assign = panel.addButton( "Assign", function(){ + node.setProperty(propname, textarea.value); + fDoneWith(); + }); + panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); + var button = panel.addButton( "Close", fDoneWith); + button.style.float = "right"; + panel.alt_content.appendChild(button); // panel.content.appendChild(button); + } + + inner_refresh(); + + this.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) + { + console.log("showing subgraph properties dialog"); + + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if(old_panel) + old_panel.close(); + + var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() + { + panel.clear(); + + //show currents + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + if(input.not_subgraph_input) + continue; + var html = " "; + var elem = panel.addHTML(html,"subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click",function(e){ + node.removeInput( Number( this.parentNode.dataset["slot"] ) ); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html,"subgraph_property extra", true); + elem.querySelector("button").addEventListener("click", function(e){ + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if(!name || node.findInputSlot(name) != -1) + return; + node.addInput(name,type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + }); + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + LGraphCanvas.prototype.showSubgraphPropertiesDialogRight = function (node) { + + // console.log("showing subgraph properties dialog"); + var that = this; + // old_panel if old_panel is exist close it + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if (old_panel) + old_panel.close(); + // new panel + var panel = this.createPanel("Subgraph Outputs", { closable: true, width: 500 }); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() { + panel.clear(); + //show currents + if (node.outputs) + for (var i = 0; i < node.outputs.length; ++i) { + var input = node.outputs[i]; + if (input.not_subgraph_output) + continue; + var html = " "; + var elem = panel.addHTML(html, "subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click", function (e) { + node.removeOutput(Number(this.parentNode.dataset["slot"])); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html, "subgraph_property extra", true); + elem.querySelector(".name").addEventListener("keydown", function (e) { + if (e.keyCode == 13) { + addOutput.apply(this) + } + }) + elem.querySelector("button").addEventListener("click", function (e) { + addOutput.apply(this) + }); + function addOutput() { + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if (!name || node.findOutputSlot(name) != -1) + return; + node.addOutput(name, type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + } + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + LGraphCanvas.prototype.checkPanels = function() + { + if(!this.canvas) + return; + var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); + for(var i = 0; i < panels.length; ++i) + { + var panel = panels[i]; + if( !panel.node ) + continue; + if( !panel.node.graph || panel.graph != this.graph ) + panel.close(); + } + } + + LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { + node.graph.beforeChange(/*?*/); + + var fApplyMultiNode = function(node){ + node.collapse(); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); + }; + + LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { + node.pin(); + }; + + LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { + new LiteGraph.ContextMenu( + LiteGraph.NODE_MODES, + { event: e, callback: inner_clicked, parentMenu: menu, node: node } + ); + + function inner_clicked(v) { + if (!node) { + return; + } + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); + var fApplyMultiNode = function(node){ + if (kV>=0 && LiteGraph.NODE_MODES[kV]) + node.changeMode(kV); + else{ + console.warn("unexpected mode: "+v); + node.changeMode(LiteGraph.ALWAYS); + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + } + + return false; + }; + + LGraphCanvas.onMenuNodeColors = function(value, options, e, menu, node) { + if (!node) { + throw "no node for color"; + } + + var values = []; + values.push({ + value: null, + content: + "No color" + }); + + for (var i in LGraphCanvas.node_colors) { + var color = LGraphCanvas.node_colors[i]; + var value = { + value: i, + content: + "" + + i + + "" + }; + values.push(value); + } + new LiteGraph.ContextMenu(values, { + event: e, + callback: inner_clicked, + parentMenu: menu, + node: node + }); + + function inner_clicked(v) { + if (!node) { + return; + } + + var color = v.value ? LGraphCanvas.node_colors[v.value] : null; + + var fApplyColor = function(node){ + if (color) { + if (node.constructor === LiteGraph.LGraphGroup) { + node.color = color.groupcolor; + } else { + node.color = color.color; + node.bgcolor = color.bgcolor; + } + } else { + delete node.color; + delete node.bgcolor; + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyColor(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyColor(graphcanvas.selected_nodes[i]); + } + } + node.setDirtyCanvas(true, true); + } + + return false; + }; + + LGraphCanvas.onMenuNodeShapes = function(value, options, e, menu, node) { + if (!node) { + throw "no node passed"; + } + + new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES, { + event: e, + callback: inner_clicked, + parentMenu: menu, + node: node + }); + + function inner_clicked(v) { + if (!node) { + return; + } + node.graph.beforeChange(/*?*/); //node + + var fApplyMultiNode = function(node){ + node.shape = v; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); //node + node.setDirtyCanvas(true); + } + + return false; + }; + + LGraphCanvas.onMenuNodeRemove = function(value, options, e, menu, node) { + if (!node) { + throw "no node passed"; + } + + var graph = node.graph; + graph.beforeChange(); + + + var fApplyMultiNode = function(node){ + if (node.removable === false) { + return; + } + graph.remove(node); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + graph.afterChange(); + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.onMenuNodeToSubgraph = function(value, options, e, menu, node) { + var graph = node.graph; + var graphcanvas = LGraphCanvas.active_canvas; + if(!graphcanvas) //?? + return; + + var nodes_list = Object.values( graphcanvas.selected_nodes || {} ); + if( !nodes_list.length ) + nodes_list = [ node ]; + + var subgraph_node = LiteGraph.createNode("graph/subgraph"); + subgraph_node.pos = node.pos.concat(); + graph.add(subgraph_node); + + subgraph_node.buildFromNodes( nodes_list ); + + graphcanvas.deselectAllNodes(); + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { + + node.graph.beforeChange(); + + var newSelected = {}; + + var fApplyMultiNode = function(node){ + if (node.clonable === false) { + return; + } + var newnode = node.clone(); + if (!newnode) { + return; + } + newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; + node.graph.add(newnode); + newSelected[newnode.id] = newnode; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + if(Object.keys(newSelected).length){ + graphcanvas.selectNodes(newSelected); + } + + node.graph.afterChange(); + + node.setDirtyCanvas(true, true); + }; + + LGraphCanvas.node_colors = { + red: { color: "#322", bgcolor: "#533", groupcolor: "#A88" }, + brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, + green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, + blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, + pale_blue: { + color: "#2a363b", + bgcolor: "#3f5159", + groupcolor: "#3f789e" + }, + cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, + purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, + yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, + black: { color: "#222", bgcolor: "#000", groupcolor: "#444" } + }; + + LGraphCanvas.prototype.getCanvasMenuOptions = function() { + var options = null; + var that = this; + if (this.getMenuOptions) { + options = this.getMenuOptions(); + } else { + options = [ + { + content: "Add Node", + has_submenu: true, + callback: LGraphCanvas.onMenuAdd + }, + { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, + //{ content: "Arrange", callback: that.graph.arrange }, + //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + ]; + /*if (LiteGraph.showCanvasOptions){ + options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); + }*/ + + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align", + has_submenu: true, + callback: LGraphCanvas.onGroupAlign, + }) + } + + if (this._graph_stack && this._graph_stack.length > 0) { + options.push(null, { + content: "Close subgraph", + callback: this.closeSubgraph.bind(this) + }); + } + } + + if (this.getExtraMenuOptions) { + var extra = this.getExtraMenuOptions(this, options); + if (extra) { + options = options.concat(extra); + } + } + + return options; + }; + + //called by processContextMenu to extract the menu list + LGraphCanvas.prototype.getNodeMenuOptions = function(node) { + var options = null; + + if (node.getMenuOptions) { + options = node.getMenuOptions(this); + } else { + options = [ + { + content: "Inputs", + has_submenu: true, + disabled: true, + callback: LGraphCanvas.showMenuNodeOptionalInputs + }, + { + content: "Outputs", + has_submenu: true, + disabled: true, + callback: LGraphCanvas.showMenuNodeOptionalOutputs + }, + null, + { + content: "Properties", + has_submenu: true, + callback: LGraphCanvas.onShowMenuNodeProperties + }, + null, + { + content: "Title", + callback: LGraphCanvas.onShowPropertyEditor + }, + { + content: "Mode", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeMode + }]; + if(node.resizable !== false){ + options.push({ + content: "Resize", callback: LGraphCanvas.onMenuResizeNode + }); + } + options.push( + { + content: "Collapse", + callback: LGraphCanvas.onMenuNodeCollapse + }, + { content: "Pin", callback: LGraphCanvas.onMenuNodePin }, + { + content: "Colors", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeColors + }, + { + content: "Shapes", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeShapes + }, + null + ); + } + + if (node.onGetInputs) { + var inputs = node.onGetInputs(); + if (inputs && inputs.length) { + options[0].disabled = false; + } + } + + if (node.onGetOutputs) { + var outputs = node.onGetOutputs(); + if (outputs && outputs.length) { + options[1].disabled = false; + } + } + + if (node.getExtraMenuOptions) { + var extra = node.getExtraMenuOptions(this, options); + if (extra) { + extra.push(null); + options = extra.concat(options); + } + } + + if (node.clonable !== false) { + options.push({ + content: "Clone", + callback: LGraphCanvas.onMenuNodeClone + }); + } + + if(0) //TODO + options.push({ + content: "To Subgraph", + callback: LGraphCanvas.onMenuNodeToSubgraph + }); + + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align Selected To", + has_submenu: true, + callback: LGraphCanvas.onNodeAlign, + }) + } + + options.push(null, { + content: "Remove", + disabled: !(node.removable !== false && !node.block_delete ), + callback: LGraphCanvas.onMenuNodeRemove + }); + + if (node.graph && node.graph.onGetNodeMenuOptions) { + node.graph.onGetNodeMenuOptions(options, node); + } + + return options; + }; + + LGraphCanvas.prototype.getGroupMenuOptions = function(node) { + var o = [ + { content: "Title", callback: LGraphCanvas.onShowPropertyEditor }, + { + content: "Color", + has_submenu: true, + callback: LGraphCanvas.onMenuNodeColors + }, + { + content: "Font size", + property: "font_size", + type: "Number", + callback: LGraphCanvas.onShowPropertyEditor + }, + null, + { content: "Remove", callback: LGraphCanvas.onMenuNodeRemove } + ]; + + return o; + }; + + LGraphCanvas.prototype.processContextMenu = function(node, event) { + var that = this; + var canvas = LGraphCanvas.active_canvas; + var ref_window = canvas.getCanvasWindow(); + + var menu_info = null; + var options = { + event: event, + callback: inner_option_clicked, + extra: node + }; + + if(node) + options.title = node.type; + + //check if mouse is in input + var slot = null; + if (node) { + slot = node.getSlotInPosition(event.canvasX, event.canvasY); + LGraphCanvas.active_node = node; + } + + if (slot) { + //on slot + menu_info = []; + if (node.getSlotMenuOptions) { + menu_info = node.getSlotMenuOptions(slot); + } else { + if ( + slot && + slot.output && + slot.output.links && + slot.output.links.length + ) { + menu_info.push({ content: "Disconnect Links", slot: slot }); + } + var _slot = slot.input || slot.output; + if (_slot.removable){ + menu_info.push( + _slot.locked + ? "Cannot remove" + : { content: "Remove Slot", slot: slot } + ); + } + if (!_slot.nameLocked){ + menu_info.push({ content: "Rename Slot", slot: slot }); + } + + } + options.title = + (slot.input ? slot.input.type : slot.output.type) || "*"; + if (slot.input && slot.input.type == LiteGraph.ACTION) { + options.title = "Action"; + } + if (slot.output && slot.output.type == LiteGraph.EVENT) { + options.title = "Event"; + } + } else { + if (node) { + //on node + menu_info = this.getNodeMenuOptions(node); + } else { + menu_info = this.getCanvasMenuOptions(); + var group = this.graph.getGroupOnPos( + event.canvasX, + event.canvasY + ); + if (group) { + //on group + menu_info.push(null, { + content: "Edit Group", + has_submenu: true, + submenu: { + title: "Group", + extra: group, + options: this.getGroupMenuOptions(group) + } + }); + } + } + } + + //show menu + if (!menu_info) { + return; + } + + var menu = new LiteGraph.ContextMenu(menu_info, options, ref_window); + + function inner_option_clicked(v, options, e) { + if (!v) { + return; + } + + if (v.content == "Remove Slot") { + var info = v.slot; + node.graph.beforeChange(); + if (info.input) { + node.removeInput(info.slot); + } else if (info.output) { + node.removeOutput(info.slot); + } + node.graph.afterChange(); + return; + } else if (v.content == "Disconnect Links") { + var info = v.slot; + node.graph.beforeChange(); + if (info.output) { + node.disconnectOutput(info.slot); + } else if (info.input) { + node.disconnectInput(info.slot); + } + node.graph.afterChange(); + return; + } else if (v.content == "Rename Slot") { + var info = v.slot; + var slot_info = info.input + ? node.getInputInfo(info.slot) + : node.getOutputInfo(info.slot); + var dialog = that.createDialog( + "Name", + options + ); + var input = dialog.querySelector("input"); + if (input && slot_info) { + input.value = slot_info.label || ""; + } + var inner = function(){ + node.graph.beforeChange(); + if (input.value) { + if (slot_info) { + slot_info.label = input.value; + } + that.setDirty(true); + } + dialog.close(); + node.graph.afterChange(); + } + dialog.querySelector("button").addEventListener("click", inner); + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + input.focus(); + } + + //if(v.callback) + // return v.callback.call(that, node, options, e, menu, that, event ); + } + }; + + //API ************************************************* + function compareObjects(a, b) { + for (var i in a) { + if (a[i] != b[i]) { + return false; + } + } + return true; + } + LiteGraph.compareObjects = compareObjects; + + function distance(a, b) { + return Math.sqrt( + (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) + ); + } + LiteGraph.distance = distance; + + function colorToString(c) { + return ( + "rgba(" + + Math.round(c[0] * 255).toFixed() + + "," + + Math.round(c[1] * 255).toFixed() + + "," + + Math.round(c[2] * 255).toFixed() + + "," + + (c.length == 4 ? c[3].toFixed(2) : "1.0") + + ")" + ); + } + LiteGraph.colorToString = colorToString; + + function isInsideRectangle(x, y, left, top, width, height) { + if (left < x && left + width > x && top < y && top + height > y) { + return true; + } + return false; + } + LiteGraph.isInsideRectangle = isInsideRectangle; + + //[minx,miny,maxx,maxy] + function growBounding(bounding, x, y) { + if (x < bounding[0]) { + bounding[0] = x; + } else if (x > bounding[2]) { + bounding[2] = x; + } + + if (y < bounding[1]) { + bounding[1] = y; + } else if (y > bounding[3]) { + bounding[3] = y; + } + } + LiteGraph.growBounding = growBounding; + + //point inside bounding box + function isInsideBounding(p, bb) { + if ( + p[0] < bb[0][0] || + p[1] < bb[0][1] || + p[0] > bb[1][0] || + p[1] > bb[1][1] + ) { + return false; + } + return true; + } + LiteGraph.isInsideBounding = isInsideBounding; + + //bounding overlap, format: [ startx, starty, width, height ] + function overlapBounding(a, b) { + var A_end_x = a[0] + a[2]; + var A_end_y = a[1] + a[3]; + var B_end_x = b[0] + b[2]; + var B_end_y = b[1] + b[3]; + + if ( + a[0] > B_end_x || + a[1] > B_end_y || + A_end_x < b[0] || + A_end_y < b[1] + ) { + return false; + } + return true; + } + LiteGraph.overlapBounding = overlapBounding; + + //Convert a hex value to its decimal value - the inputted hex must be in the + // format of a hex triplet - the kind we use for HTML colours. The function + // will return an array with three values. + function hex2num(hex) { + if (hex.charAt(0) == "#") { + hex = hex.slice(1); + } //Remove the '#' char - if there is one. + hex = hex.toUpperCase(); + var hex_alphabets = "0123456789ABCDEF"; + var value = new Array(3); + var k = 0; + var int1, int2; + for (var i = 0; i < 6; i += 2) { + int1 = hex_alphabets.indexOf(hex.charAt(i)); + int2 = hex_alphabets.indexOf(hex.charAt(i + 1)); + value[k] = int1 * 16 + int2; + k++; + } + return value; + } + + LiteGraph.hex2num = hex2num; + + //Give a array with three values as the argument and the function will return + // the corresponding hex triplet. + function num2hex(triplet) { + var hex_alphabets = "0123456789ABCDEF"; + var hex = "#"; + var int1, int2; + for (var i = 0; i < 3; i++) { + int1 = triplet[i] / 16; + int2 = triplet[i] % 16; + + hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); + } + return hex; + } + + LiteGraph.num2hex = num2hex; + + /* LiteGraph GUI elements used for canvas editing *************************************/ + + /** + * ContextMenu from LiteGUI + * + * @class ContextMenu + * @constructor + * @param {Array} values (allows object { title: "Nice text", callback: function ... }) + * @param {Object} options [optional] Some options:\ + * - title: title to show on top of the menu + * - callback: function to call when an option is clicked, it receives the item information + * - ignore_item_callbacks: ignores the callback inside the item, it just calls the options.callback + * - event: you can pass a MouseEvent, this way the ContextMenu appears in that position + */ + function ContextMenu(values, options) { + options = options || {}; + this.options = options; + var that = this; + + //to link a menu with its parent + if (options.parentMenu) { + if (options.parentMenu.constructor !== this.constructor) { + console.error( + "parentMenu must be of class ContextMenu, ignoring it" + ); + options.parentMenu = null; + } else { + this.parentMenu = options.parentMenu; + this.parentMenu.lock = true; + this.parentMenu.current_submenu = this; + } + } + + var eventClass = null; + if(options.event) //use strings because comparing classes between windows doesnt work + eventClass = options.event.constructor.name; + if ( eventClass !== "MouseEvent" && + eventClass !== "CustomEvent" && + eventClass !== "PointerEvent" + ) { + console.error( + "Event passed to ContextMenu is not of type MouseEvent or CustomEvent. Ignoring it. ("+eventClass+")" + ); + options.event = null; + } + + var root = document.createElement("div"); + root.className = "litegraph litecontextmenu litemenubar-panel"; + if (options.className) { + root.className += " " + options.className; + } + root.style.minWidth = 100; + root.style.minHeight = 100; + root.style.pointerEvents = "none"; + setTimeout(function() { + root.style.pointerEvents = "auto"; + }, 100); //delay so the mouse up event is not caught by this element + + //this prevents the default context browser menu to open in case this menu was created when pressing right button + LiteGraph.pointerListenerAdd(root,"up", + function(e) { + //console.log("pointerevents: ContextMenu up root prevent"); + e.preventDefault(); + return true; + }, + true + ); + root.addEventListener( + "contextmenu", + function(e) { + if (e.button != 2) { + //right button + return false; + } + e.preventDefault(); + return false; + }, + true + ); + + LiteGraph.pointerListenerAdd(root,"down", + function(e) { + //console.log("pointerevents: ContextMenu down"); + if (e.button == 2) { + that.close(); + e.preventDefault(); + return true; + } + }, + true + ); + + function on_mouse_wheel(e) { + var pos = parseInt(root.style.top); + root.style.top = + (pos + e.deltaY * options.scroll_speed).toFixed() + "px"; + e.preventDefault(); + return true; + } + + if (!options.scroll_speed) { + options.scroll_speed = 0.1; + } + + root.addEventListener("wheel", on_mouse_wheel, true); + root.addEventListener("mousewheel", on_mouse_wheel, true); + + this.root = root; + + //title + if (options.title) { + var element = document.createElement("div"); + element.className = "litemenu-title"; + element.innerHTML = options.title; + root.appendChild(element); + } + + //entries + var num = 0; + for (var i=0; i < values.length; i++) { + var name = values.constructor == Array ? values[i] : i; + if (name != null && name.constructor !== String) { + name = name.content === undefined ? String(name) : name.content; + } + var value = values[i]; + this.addItem(name, value, options); + num++; + } + + //close on leave? touch enabled devices won't work TODO use a global device detector and condition on that + /*LiteGraph.pointerListenerAdd(root,"leave", function(e) { + console.log("pointerevents: ContextMenu leave"); + if (that.lock) { + return; + } + if (root.closing_timer) { + clearTimeout(root.closing_timer); + } + root.closing_timer = setTimeout(that.close.bind(that, e), 500); + //that.close(e); + });*/ + + LiteGraph.pointerListenerAdd(root,"enter", function(e) { + //console.log("pointerevents: ContextMenu enter"); + if (root.closing_timer) { + clearTimeout(root.closing_timer); + } + }); + + //insert before checking position + var root_document = document; + if (options.event) { + root_document = options.event.target.ownerDocument; + } + + if (!root_document) { + root_document = document; + } + + if( root_document.fullscreenElement ) + root_document.fullscreenElement.appendChild(root); + else + root_document.body.appendChild(root); + + //compute best position + var left = options.left || 0; + var top = options.top || 0; + if (options.event) { + left = options.event.clientX - 10; + top = options.event.clientY - 10; + if (options.title) { + top -= 20; + } + + if (options.parentMenu) { + var rect = options.parentMenu.root.getBoundingClientRect(); + left = rect.left + rect.width; + } + + var body_rect = document.body.getBoundingClientRect(); + var root_rect = root.getBoundingClientRect(); + if(body_rect.height == 0) + console.error("document.body height is 0. That is dangerous, set html,body { height: 100%; }"); + + if (body_rect.width && left > body_rect.width - root_rect.width - 10) { + left = body_rect.width - root_rect.width - 10; + } + if (body_rect.height && top > body_rect.height - root_rect.height - 10) { + top = body_rect.height - root_rect.height - 10; + } + } + + root.style.left = left + "px"; + root.style.top = top + "px"; + + if (options.scale) { + root.style.transform = "scale(" + options.scale + ")"; + } + } + + ContextMenu.prototype.addItem = function(name, value, options) { + var that = this; + options = options || {}; + + var element = document.createElement("div"); + element.className = "litemenu-entry submenu"; + + var disabled = false; + + if (value === null) { + element.classList.add("separator"); + //element.innerHTML = "
" + //continue; + } else { + element.innerHTML = value && value.title ? value.title : name; + element.value = value; + + if (value) { + if (value.disabled) { + disabled = true; + element.classList.add("disabled"); + } + if (value.submenu || value.has_submenu) { + element.classList.add("has_submenu"); + } + } + + if (typeof value == "function") { + element.dataset["value"] = name; + element.onclick_callback = value; + } else { + element.dataset["value"] = value; + } + + if (value.className) { + element.className += " " + value.className; + } + } + + this.root.appendChild(element); + if (!disabled) { + element.addEventListener("click", inner_onclick); + } + if (!disabled && options.autoopen) { + LiteGraph.pointerListenerAdd(element,"enter",inner_over); + } + + function inner_over(e) { + var value = this.value; + if (!value || !value.has_submenu) { + return; + } + //if it is a submenu, autoopen like the item was clicked + inner_onclick.call(this, e); + } + + //menu option clicked + function inner_onclick(e) { + var value = this.value; + var close_parent = true; + + if (that.current_submenu) { + that.current_submenu.close(e); + } + + //global callback + if (options.callback) { + var r = options.callback.call( + this, + value, + options, + e, + that, + options.node + ); + if (r === true) { + close_parent = false; + } + } + + //special cases + if (value) { + if ( + value.callback && + !options.ignore_item_callbacks && + value.disabled !== true + ) { + //item callback + var r = value.callback.call( + this, + value, + options, + e, + that, + options.extra + ); + if (r === true) { + close_parent = false; + } + } + if (value.submenu) { + if (!value.submenu.options) { + throw "ContextMenu submenu needs options"; + } + var submenu = new that.constructor(value.submenu.options, { + callback: value.submenu.callback, + event: e, + parentMenu: that, + ignore_item_callbacks: + value.submenu.ignore_item_callbacks, + title: value.submenu.title, + extra: value.submenu.extra, + autoopen: options.autoopen + }); + close_parent = false; + } + } + + if (close_parent && !that.lock) { + that.close(); + } + } + + return element; + }; + + ContextMenu.prototype.close = function(e, ignore_parent_menu) { + if (this.root.parentNode) { + this.root.parentNode.removeChild(this.root); + } + if (this.parentMenu && !ignore_parent_menu) { + this.parentMenu.lock = false; + this.parentMenu.current_submenu = null; + if (e === undefined) { + this.parentMenu.close(); + } else if ( + e && + !ContextMenu.isCursorOverElement(e, this.parentMenu.root) + ) { + ContextMenu.trigger(this.parentMenu.root, LiteGraph.pointerevents_method+"leave", e); + } + } + if (this.current_submenu) { + this.current_submenu.close(e, true); + } + + if (this.root.closing_timer) { + clearTimeout(this.root.closing_timer); + } + + // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu + // on key press, allow filtering/selecting the context menu elements + }; + + //this code is used to trigger events easily (used in the context menu mouseleave + ContextMenu.trigger = function(element, event_name, params, origin) { + var evt = document.createEvent("CustomEvent"); + evt.initCustomEvent(event_name, true, true, params); //canBubble, cancelable, detail + evt.srcElement = origin; + if (element.dispatchEvent) { + element.dispatchEvent(evt); + } else if (element.__events) { + element.__events.dispatchEvent(evt); + } + //else nothing seems binded here so nothing to do + return evt; + }; + + //returns the top most menu + ContextMenu.prototype.getTopMenu = function() { + if (this.options.parentMenu) { + return this.options.parentMenu.getTopMenu(); + } + return this; + }; + + ContextMenu.prototype.getFirstEvent = function() { + if (this.options.parentMenu) { + return this.options.parentMenu.getFirstEvent(); + } + return this.options.event; + }; + + ContextMenu.isCursorOverElement = function(event, element) { + var left = event.clientX; + var top = event.clientY; + var rect = element.getBoundingClientRect(); + if (!rect) { + return false; + } + if ( + top > rect.top && + top < rect.top + rect.height && + left > rect.left && + left < rect.left + rect.width + ) { + return true; + } + return false; + }; + + LiteGraph.ContextMenu = ContextMenu; + + LiteGraph.closeAllContextMenus = function(ref_window) { + ref_window = ref_window || window; + + var elements = ref_window.document.querySelectorAll(".litecontextmenu"); + if (!elements.length) { + return; + } + + var result = []; + for (var i = 0; i < elements.length; i++) { + result.push(elements[i]); + } + + for (var i=0; i < result.length; i++) { + if (result[i].close) { + result[i].close(); + } else if (result[i].parentNode) { + result[i].parentNode.removeChild(result[i]); + } + } + }; + + LiteGraph.extendClass = function(target, origin) { + for (var i in origin) { + //copy class properties + if (target.hasOwnProperty(i)) { + continue; + } + target[i] = origin[i]; + } + + if (origin.prototype) { + //copy prototype properties + for (var i in origin.prototype) { + //only enumerable + if (!origin.prototype.hasOwnProperty(i)) { + continue; + } + + if (target.prototype.hasOwnProperty(i)) { + //avoid overwriting existing ones + continue; + } + + //copy getters + if (origin.prototype.__lookupGetter__(i)) { + target.prototype.__defineGetter__( + i, + origin.prototype.__lookupGetter__(i) + ); + } else { + target.prototype[i] = origin.prototype[i]; + } + + //and setters + if (origin.prototype.__lookupSetter__(i)) { + target.prototype.__defineSetter__( + i, + origin.prototype.__lookupSetter__(i) + ); + } + } + } + }; + + //used by some widgets to render a curve editor + function CurveEditor( points ) + { + this.points = points; + this.selected = -1; + this.nearest = -1; + this.size = null; //stores last size used + this.must_update = true; + this.margin = 5; + } + + CurveEditor.sampleCurve = function(f,points) + { + if(!points) + return; + for(var i = 0; i < points.length - 1; ++i) + { + var p = points[i]; + var pn = points[i+1]; + if(pn[0] < f) + continue; + var r = (pn[0] - p[0]); + if( Math.abs(r) < 0.00001 ) + return p[1]; + var local_f = (f - p[0]) / r; + return p[1] * (1.0 - local_f) + pn[1] * local_f; + } + return 0; + } + + CurveEditor.prototype.draw = function( ctx, size, graphcanvas, background_color, line_color, inactive ) + { + var points = this.points; + if(!points) + return; + this.size = size; + var w = size[0] - this.margin * 2; + var h = size[1] - this.margin * 2; + + line_color = line_color || "#666"; + + ctx.save(); + ctx.translate(this.margin,this.margin); + + if(background_color) + { + ctx.fillStyle = "#111"; + ctx.fillRect(0,0,w,h); + ctx.fillStyle = "#222"; + ctx.fillRect(w*0.5,0,1,h); + ctx.strokeStyle = "#333"; + ctx.strokeRect(0,0,w,h); + } + ctx.strokeStyle = line_color; + if(inactive) + ctx.globalAlpha = 0.5; + ctx.beginPath(); + for(var i = 0; i < points.length; ++i) + { + var p = points[i]; + ctx.lineTo( p[0] * w, (1.0 - p[1]) * h ); + } + ctx.stroke(); + ctx.globalAlpha = 1; + if(!inactive) + for(var i = 0; i < points.length; ++i) + { + var p = points[i]; + ctx.fillStyle = this.selected == i ? "#FFF" : (this.nearest == i ? "#DDD" : "#AAA"); + ctx.beginPath(); + ctx.arc( p[0] * w, (1.0 - p[1]) * h, 2, 0, Math.PI * 2 ); + ctx.fill(); + } + ctx.restore(); + } + + //localpos is mouse in curve editor space + CurveEditor.prototype.onMouseDown = function( localpos, graphcanvas ) + { + var points = this.points; + if(!points) + return; + if( localpos[1] < 0 ) + return; + + //this.captureInput(true); + var w = this.size[0] - this.margin * 2; + var h = this.size[1] - this.margin * 2; + var x = localpos[0] - this.margin; + var y = localpos[1] - this.margin; + var pos = [x,y]; + var max_dist = 30 / graphcanvas.ds.scale; + //search closer one + this.selected = this.getCloserPoint(pos, max_dist); + //create one + if(this.selected == -1) + { + var point = [x / w, 1 - y / h]; + points.push(point); + points.sort(function(a,b){ return a[0] - b[0]; }); + this.selected = points.indexOf(point); + this.must_update = true; + } + if(this.selected != -1) + return true; + } + + CurveEditor.prototype.onMouseMove = function( localpos, graphcanvas ) + { + var points = this.points; + if(!points) + return; + var s = this.selected; + if(s < 0) + return; + var x = (localpos[0] - this.margin) / (this.size[0] - this.margin * 2 ); + var y = (localpos[1] - this.margin) / (this.size[1] - this.margin * 2 ); + var curvepos = [(localpos[0] - this.margin),(localpos[1] - this.margin)]; + var max_dist = 30 / graphcanvas.ds.scale; + this._nearest = this.getCloserPoint(curvepos, max_dist); + var point = points[s]; + if(point) + { + var is_edge_point = s == 0 || s == points.length - 1; + if( !is_edge_point && (localpos[0] < -10 || localpos[0] > this.size[0] + 10 || localpos[1] < -10 || localpos[1] > this.size[1] + 10) ) + { + points.splice(s,1); + this.selected = -1; + return; + } + if( !is_edge_point ) //not edges + point[0] = clamp(x, 0, 1); + else + point[0] = s == 0 ? 0 : 1; + point[1] = 1.0 - clamp(y, 0, 1); + points.sort(function(a,b){ return a[0] - b[0]; }); + this.selected = points.indexOf(point); + this.must_update = true; + } + } + + CurveEditor.prototype.onMouseUp = function( localpos, graphcanvas ) + { + this.selected = -1; + return false; + } + + CurveEditor.prototype.getCloserPoint = function(pos, max_dist) + { + var points = this.points; + if(!points) + return -1; + max_dist = max_dist || 30; + var w = (this.size[0] - this.margin * 2); + var h = (this.size[1] - this.margin * 2); + var num = points.length; + var p2 = [0,0]; + var min_dist = 1000000; + var closest = -1; + var last_valid = -1; + for(var i = 0; i < num; ++i) + { + var p = points[i]; + p2[0] = p[0] * w; + p2[1] = (1.0 - p[1]) * h; + if(p2[0] < pos[0]) + last_valid = i; + var dist = vec2.distance(pos,p2); + if(dist > min_dist || dist > max_dist) + continue; + closest = i; + min_dist = dist; + } + return closest; + } + + LiteGraph.CurveEditor = CurveEditor; + + //used to create nodes from wrapping functions + LiteGraph.getParameterNames = function(func) { + return (func + "") + .replace(/[/][/].*$/gm, "") // strip single-line comments + .replace(/\s+/g, "") // strip white space + .replace(/[/][*][^/*]*[*][/]/g, "") // strip multi-line comments /**/ + .split("){", 1)[0] + .replace(/^[^(]*[(]/, "") // extract the parameters + .replace(/=[^,]+/g, "") // strip any ES6 defaults + .split(",") + .filter(Boolean); // split & filter [""] + }; + + /* helper for interaction: pointer, touch, mouse Listeners + used by LGraphCanvas DragAndScale ContextMenu*/ + LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { + if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ + //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); + return; // -- break -- + } + + var sMethod = LiteGraph.pointerevents_method; + var sEvent = sEvIn; + + // UNDER CONSTRUCTION + // convert pointerevents to touch event when not available + if (sMethod=="pointer" && !window.PointerEvent){ + console.warn("sMethod=='pointer' && !window.PointerEvent"); + console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); + switch(sEvent){ + case "down":{ + sMethod = "touch"; + sEvent = "start"; + break; + } + case "move":{ + sMethod = "touch"; + //sEvent = "move"; + break; + } + case "up":{ + sMethod = "touch"; + sEvent = "end"; + break; + } + case "cancel":{ + sMethod = "touch"; + //sEvent = "cancel"; + break; + } + case "enter":{ + console.log("debug: Should I send a move event?"); // ??? + break; + } + // case "over": case "out": not used at now + default:{ + console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); + } + } + } + + switch(sEvent){ + //both pointer and move events + case "down": case "up": case "move": case "over": case "out": case "enter": + { + oDOM.addEventListener(sMethod+sEvent, fCall, capture); + } + // only pointerevents + case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": + { + if (sMethod!="mouse"){ + return oDOM.addEventListener(sMethod+sEvent, fCall, capture); + } + } + // not "pointer" || "mouse" + default: + return oDOM.addEventListener(sEvent, fCall, capture); + } + } + LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { + if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ + //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); + return; // -- break -- + } + switch(sEvent){ + //both pointer and move events + case "down": case "up": case "move": case "over": case "out": case "enter": + { + if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ + oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + } + } + // only pointerevents + case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": + { + if (LiteGraph.pointerevents_method=="pointer"){ + return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + } + } + // not "pointer" || "mouse" + default: + return oDOM.removeEventListener(sEvent, fCall, capture); + } + } + + function clamp(v, a, b) { + return a > v ? a : b < v ? b : v; + }; + global.clamp = clamp; + + if (typeof window != "undefined" && !window["requestAnimationFrame"]) { + window.requestAnimationFrame = + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function(callback) { + window.setTimeout(callback, 1000 / 60); + }; + } +})(this); + +if (typeof exports != "undefined") { + exports.LiteGraph = this.LiteGraph; + exports.LGraph = this.LGraph; + exports.LLink = this.LLink; + exports.LGraphNode = this.LGraphNode; + exports.LGraphGroup = this.LGraphGroup; + exports.DragAndScale = this.DragAndScale; + exports.LGraphCanvas = this.LGraphCanvas; + exports.ContextMenu = this.ContextMenu; +} + + +//basic nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + //Constant + function Time() { + this.addOutput("in ms", "number"); + this.addOutput("in sec", "number"); + } + + Time.title = "Time"; + Time.desc = "Time"; + + Time.prototype.onExecute = function() { + this.setOutputData(0, this.graph.globaltime * 1000); + this.setOutputData(1, this.graph.globaltime); + }; + + LiteGraph.registerNodeType("basic/time", Time); + + //Subgraph: a node that contains a graph + function Subgraph() { + var that = this; + this.size = [140, 80]; + this.properties = { enabled: true }; + this.enabled = true; + + //create inner graph + this.subgraph = new LiteGraph.LGraph(); + this.subgraph._subgraph_node = this; + this.subgraph._is_subgraph = true; + + this.subgraph.onTrigger = this.onSubgraphTrigger.bind(this); + + //nodes input node added inside + this.subgraph.onInputAdded = this.onSubgraphNewInput.bind(this); + this.subgraph.onInputRenamed = this.onSubgraphRenamedInput.bind(this); + this.subgraph.onInputTypeChanged = this.onSubgraphTypeChangeInput.bind(this); + this.subgraph.onInputRemoved = this.onSubgraphRemovedInput.bind(this); + + this.subgraph.onOutputAdded = this.onSubgraphNewOutput.bind(this); + this.subgraph.onOutputRenamed = this.onSubgraphRenamedOutput.bind(this); + this.subgraph.onOutputTypeChanged = this.onSubgraphTypeChangeOutput.bind(this); + this.subgraph.onOutputRemoved = this.onSubgraphRemovedOutput.bind(this); + } + + Subgraph.title = "Subgraph"; + Subgraph.desc = "Graph inside a node"; + Subgraph.title_color = "#334"; + + Subgraph.prototype.onGetInputs = function() { + return [["enabled", "boolean"]]; + }; + + /* + Subgraph.prototype.onDrawTitle = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.fillStyle = "#555"; + var w = LiteGraph.NODE_TITLE_HEIGHT; + var x = this.size[0] - w; + ctx.fillRect(x, -w, w, w); + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + }; + */ + + Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { + var that = this; + setTimeout(function() { + graphcanvas.openSubgraph(that.subgraph); + }, 10); + }; + + /* + Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { + if ( + !this.flags.collapsed && + pos[0] > this.size[0] - LiteGraph.NODE_TITLE_HEIGHT && + pos[1] < 0 + ) { + var that = this; + setTimeout(function() { + graphcanvas.openSubgraph(that.subgraph); + }, 10); + } + }; + */ + + Subgraph.prototype.onAction = function(action, param) { + this.subgraph.onAction(action, param); + }; + + Subgraph.prototype.onExecute = function() { + this.enabled = this.getInputOrProperty("enabled"); + if (!this.enabled) { + return; + } + + //send inputs to subgraph global inputs + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var value = this.getInputData(i); + this.subgraph.setInputData(input.name, value); + } + } + + //execute + this.subgraph.runStep(); + + //send subgraph global outputs to outputs + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + var value = this.subgraph.getOutputData(output.name); + this.setOutputData(i, value); + } + } + }; + + Subgraph.prototype.sendEventToAllNodes = function(eventname, param, mode) { + if (this.enabled) { + this.subgraph.sendEventToAllNodes(eventname, param, mode); + } + }; + + Subgraph.prototype.onDrawBackground = function (ctx, graphcanvas, canvas, pos) { + if (this.flags.collapsed) + return; + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // button + var over = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0], LiteGraph.NODE_TITLE_HEIGHT); + let overleft = LiteGraph.isInsideRectangle(pos[0], pos[1], this.pos[0], this.pos[1] + y, this.size[0] / 2, LiteGraph.NODE_TITLE_HEIGHT) + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + if (this._shape == LiteGraph.BOX_SHAPE) { + if (overleft) { + ctx.rect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + } else { + ctx.rect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT); + } + } + else { + if (overleft) { + ctx.roundRect(0, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + } else { + ctx.roundRect(this.size[0] / 2, y, this.size[0] / 2 + 1, LiteGraph.NODE_TITLE_HEIGHT, [0,0, 8,8]); + } + } + if (over) { + ctx.fill(); + } else { + ctx.fillRect(0, y, this.size[0] + 1, LiteGraph.NODE_TITLE_HEIGHT); + } + // button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText("+", this.size[0] * 0.25, y + 24); + ctx.fillText("+", this.size[0] * 0.75, y + 24); + } + + // Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + // { + // var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + // if(localpos[1] > y) + // { + // graphcanvas.showSubgraphPropertiesDialog(this); + // } + // } + Subgraph.prototype.onMouseDown = function (e, localpos, graphcanvas) { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + console.log(0) + if (localpos[1] > y) { + if (localpos[0] < this.size[0] / 2) { + console.log(1) + graphcanvas.showSubgraphPropertiesDialog(this); + } else { + console.log(2) + graphcanvas.showSubgraphPropertiesDialogRight(this); + } + } + } + Subgraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; + } + + //**** INPUTS *********************************** + Subgraph.prototype.onSubgraphTrigger = function(event, param) { + var slot = this.findOutputSlot(event); + if (slot != -1) { + this.triggerSlot(slot); + } + }; + + Subgraph.prototype.onSubgraphNewInput = function(name, type) { + var slot = this.findInputSlot(name); + if (slot == -1) { + //add input to the node + this.addInput(name, type); + } + }; + + Subgraph.prototype.onSubgraphRenamedInput = function(oldname, name) { + var slot = this.findInputSlot(oldname); + if (slot == -1) { + return; + } + var info = this.getInputInfo(slot); + info.name = name; + }; + + Subgraph.prototype.onSubgraphTypeChangeInput = function(name, type) { + var slot = this.findInputSlot(name); + if (slot == -1) { + return; + } + var info = this.getInputInfo(slot); + info.type = type; + }; + + Subgraph.prototype.onSubgraphRemovedInput = function(name) { + var slot = this.findInputSlot(name); + if (slot == -1) { + return; + } + this.removeInput(slot); + }; + + //**** OUTPUTS *********************************** + Subgraph.prototype.onSubgraphNewOutput = function(name, type) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + this.addOutput(name, type); + } + }; + + Subgraph.prototype.onSubgraphRenamedOutput = function(oldname, name) { + var slot = this.findOutputSlot(oldname); + if (slot == -1) { + return; + } + var info = this.getOutputInfo(slot); + info.name = name; + }; + + Subgraph.prototype.onSubgraphTypeChangeOutput = function(name, type) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + return; + } + var info = this.getOutputInfo(slot); + info.type = type; + }; + + Subgraph.prototype.onSubgraphRemovedOutput = function(name) { + var slot = this.findOutputSlot(name); + if (slot == -1) { + return; + } + this.removeOutput(slot); + }; + // ***************************************************** + + Subgraph.prototype.getExtraMenuOptions = function(graphcanvas) { + var that = this; + return [ + { + content: "Open", + callback: function() { + graphcanvas.openSubgraph(that.subgraph); + } + } + ]; + }; + + Subgraph.prototype.onResize = function(size) { + size[1] += 20; + }; + + Subgraph.prototype.serialize = function() { + var data = LiteGraph.LGraphNode.prototype.serialize.call(this); + data.subgraph = this.subgraph.serialize(); + return data; + }; + //no need to define node.configure, the default method detects node.subgraph and passes the object to node.subgraph.configure() + + Subgraph.prototype.reassignSubgraphUUIDs = function(graph) { + const idMap = { nodeIDs: {}, linkIDs: {} } + + for (const node of graph.nodes) { + const oldID = node.id + const newID = LiteGraph.uuidv4() + node.id = newID + + if (idMap.nodeIDs[oldID] || idMap.nodeIDs[newID]) { + throw new Error(`New/old node UUID wasn't unique in changed map! ${oldID} ${newID}`) + } + + idMap.nodeIDs[oldID] = newID + idMap.nodeIDs[newID] = oldID + } + + for (const link of graph.links) { + const oldID = link[0] + const newID = LiteGraph.uuidv4(); + link[0] = newID + + if (idMap.linkIDs[oldID] || idMap.linkIDs[newID]) { + throw new Error(`New/old link UUID wasn't unique in changed map! ${oldID} ${newID}`) + } + + idMap.linkIDs[oldID] = newID + idMap.linkIDs[newID] = oldID + + const nodeFrom = link[1] + const nodeTo = link[3] + + if (!idMap.nodeIDs[nodeFrom]) { + throw new Error(`Old node UUID not found in mapping! ${nodeFrom}`) + } + + link[1] = idMap.nodeIDs[nodeFrom] + + if (!idMap.nodeIDs[nodeTo]) { + throw new Error(`Old node UUID not found in mapping! ${nodeTo}`) + } + + link[3] = idMap.nodeIDs[nodeTo] + } + + // Reconnect links + for (const node of graph.nodes) { + if (node.inputs) { + for (const input of node.inputs) { + if (input.link) { + input.link = idMap.linkIDs[input.link] + } + } + } + if (node.outputs) { + for (const output of node.outputs) { + if (output.links) { + output.links = output.links.map(l => idMap.linkIDs[l]); + } + } + } + } + + // Recurse! + for (const node of graph.nodes) { + if (node.type === "graph/subgraph") { + const merge = reassignGraphUUIDs(node.subgraph); + idMap.nodeIDs.assign(merge.nodeIDs) + idMap.linkIDs.assign(merge.linkIDs) + } + } + }; + + Subgraph.prototype.clone = function() { + var node = LiteGraph.createNode(this.type); + var data = this.serialize(); + + if (LiteGraph.use_uuids) { + // LGraph.serialize() seems to reuse objects in the original graph. But we + // need to change node IDs here, so clone it first. + const subgraph = LiteGraph.cloneObject(data.subgraph) + + this.reassignSubgraphUUIDs(subgraph); + + data.subgraph = subgraph; + } + + delete data["id"]; + delete data["inputs"]; + delete data["outputs"]; + node.configure(data); + return node; + }; + + Subgraph.prototype.buildFromNodes = function(nodes) + { + //clear all? + //TODO + + //nodes that connect data between parent graph and subgraph + var subgraph_inputs = []; + var subgraph_outputs = []; + + //mark inner nodes + var ids = {}; + var min_x = 0; + var max_x = 0; + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + ids[ node.id ] = node; + min_x = Math.min( node.pos[0], min_x ); + max_x = Math.max( node.pos[0], min_x ); + } + + var last_input_y = 0; + var last_output_y = 0; + + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + //check inputs + if( node.inputs ) + for(var j = 0; j < node.inputs.length; ++j) + { + var input = node.inputs[j]; + if( !input || !input.link ) + continue; + var link = node.graph.links[ input.link ]; + if(!link) + continue; + if( ids[ link.origin_id ] ) + continue; + //this.addInput(input.name,link.type); + this.subgraph.addInput(input.name,link.type); + /* + var input_node = LiteGraph.createNode("graph/input"); + this.subgraph.add( input_node ); + input_node.pos = [min_x - 200, last_input_y ]; + last_input_y += 100; + */ + } + + //check outputs + if( node.outputs ) + for(var j = 0; j < node.outputs.length; ++j) + { + var output = node.outputs[j]; + if( !output || !output.links || !output.links.length ) + continue; + var is_external = false; + for(var k = 0; k < output.links.length; ++k) + { + var link = node.graph.links[ output.links[k] ]; + if(!link) + continue; + if( ids[ link.target_id ] ) + continue; + is_external = true; + break; + } + if(!is_external) + continue; + //this.addOutput(output.name,output.type); + /* + var output_node = LiteGraph.createNode("graph/output"); + this.subgraph.add( output_node ); + output_node.pos = [max_x + 50, last_output_y ]; + last_output_y += 100; + */ + } + } + + //detect inputs and outputs + //split every connection in two data_connection nodes + //keep track of internal connections + //connect external connections + + //clone nodes inside subgraph and try to reconnect them + + //connect edge subgraph nodes to extarnal connections nodes + } + + LiteGraph.Subgraph = Subgraph; + LiteGraph.registerNodeType("graph/subgraph", Subgraph); + + //Input for a subgraph + function GraphInput() { + this.addOutput("", "number"); + + this.name_in_graph = ""; + this.properties = { + name: "", + type: "number", + value: 0 + }; + + var that = this; + + this.name_widget = this.addWidget( + "text", + "Name", + this.properties.name, + function(v) { + if (!v) { + return; + } + that.setProperty("name",v); + } + ); + this.type_widget = this.addWidget( + "text", + "Type", + this.properties.type, + function(v) { + that.setProperty("type",v); + } + ); + + this.value_widget = this.addWidget( + "number", + "Value", + this.properties.value, + function(v) { + that.setProperty("value",v); + } + ); + + this.widgets_up = true; + this.size = [180, 90]; + } + + GraphInput.title = "Input"; + GraphInput.desc = "Input of the graph"; + + GraphInput.prototype.onConfigure = function() + + { + this.updateType(); + } + + //ensures the type in the node output and the type in the associated graph input are the same + GraphInput.prototype.updateType = function() + { + var type = this.properties.type; + this.type_widget.value = type; + + //update output + if(this.outputs[0].type != type) + { + if (!LiteGraph.isValidConnection(this.outputs[0].type,type)) + this.disconnectOutput(0); + this.outputs[0].type = type; + } + + //update widget + if(type == "number") + { + this.value_widget.type = "number"; + this.value_widget.value = 0; + } + else if(type == "boolean") + { + this.value_widget.type = "toggle"; + this.value_widget.value = true; + } + else if(type == "string") + { + this.value_widget.type = "text"; + this.value_widget.value = ""; + } + else + { + this.value_widget.type = null; + this.value_widget.value = null; + } + this.properties.value = this.value_widget.value; + + //update graph + if (this.graph && this.name_in_graph) { + this.graph.changeInputType(this.name_in_graph, type); + } + } + + //this is executed AFTER the property has changed + GraphInput.prototype.onPropertyChanged = function(name,v) + { + if( name == "name" ) + { + if (v == "" || v == this.name_in_graph || v == "enabled") { + return false; + } + if(this.graph) + { + if (this.name_in_graph) { + //already added + this.graph.renameInput( this.name_in_graph, v ); + } else { + this.graph.addInput( v, this.properties.type ); + } + } //what if not?! + this.name_widget.value = v; + this.name_in_graph = v; + } + else if( name == "type" ) + { + this.updateType(); + } + else if( name == "value" ) + { + } + } + + GraphInput.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.name; + } + return this.title; + }; + + GraphInput.prototype.onAction = function(action, param) { + if (this.properties.type == LiteGraph.EVENT) { + this.triggerSlot(0, param); + } + }; + + GraphInput.prototype.onExecute = function() { + var name = this.properties.name; + //read from global input + var data = this.graph.inputs[name]; + if (!data) { + this.setOutputData(0, this.properties.value ); + return; + } + + this.setOutputData(0, data.value !== undefined ? data.value : this.properties.value ); + }; + + GraphInput.prototype.onRemoved = function() { + if (this.name_in_graph) { + this.graph.removeInput(this.name_in_graph); + } + }; + + LiteGraph.GraphInput = GraphInput; + LiteGraph.registerNodeType("graph/input", GraphInput); + + //Output for a subgraph + function GraphOutput() { + this.addInput("", ""); + + this.name_in_graph = ""; + this.properties = { name: "", type: "" }; + var that = this; + + // Object.defineProperty(this.properties, "name", { + // get: function() { + // return that.name_in_graph; + // }, + // set: function(v) { + // if (v == "" || v == that.name_in_graph) { + // return; + // } + // if (that.name_in_graph) { + // //already added + // that.graph.renameOutput(that.name_in_graph, v); + // } else { + // that.graph.addOutput(v, that.properties.type); + // } + // that.name_widget.value = v; + // that.name_in_graph = v; + // }, + // enumerable: true + // }); + + // Object.defineProperty(this.properties, "type", { + // get: function() { + // return that.inputs[0].type; + // }, + // set: function(v) { + // if (v == "action" || v == "event") { + // v = LiteGraph.ACTION; + // } + // if (!LiteGraph.isValidConnection(that.inputs[0].type,v)) + // that.disconnectInput(0); + // that.inputs[0].type = v; + // if (that.name_in_graph) { + // //already added + // that.graph.changeOutputType( + // that.name_in_graph, + // that.inputs[0].type + // ); + // } + // that.type_widget.value = v || ""; + // }, + // enumerable: true + // }); + + this.name_widget = this.addWidget("text","Name",this.properties.name,"name"); + this.type_widget = this.addWidget("text","Type",this.properties.type,"type"); + this.widgets_up = true; + this.size = [180, 60]; + } + + GraphOutput.title = "Output"; + GraphOutput.desc = "Output of the graph"; + + GraphOutput.prototype.onPropertyChanged = function (name, v) { + if (name == "name") { + if (v == "" || v == this.name_in_graph || v == "enabled") { + return false; + } + if (this.graph) { + if (this.name_in_graph) { + //already added + this.graph.renameOutput(this.name_in_graph, v); + } else { + this.graph.addOutput(v, this.properties.type); + } + } //what if not?! + this.name_widget.value = v; + this.name_in_graph = v; + } + else if (name == "type") { + this.updateType(); + } + else if (name == "value") { + } + } + + GraphOutput.prototype.updateType = function () { + var type = this.properties.type; + if (this.type_widget) + this.type_widget.value = type; + + //update output + if (this.inputs[0].type != type) { + + if ( type == "action" || type == "event") + type = LiteGraph.EVENT; + if (!LiteGraph.isValidConnection(this.inputs[0].type, type)) + this.disconnectInput(0); + this.inputs[0].type = type; + } + + //update graph + if (this.graph && this.name_in_graph) { + this.graph.changeOutputType(this.name_in_graph, type); + } + } + + + + GraphOutput.prototype.onExecute = function() { + this._value = this.getInputData(0); + this.graph.setOutputData(this.properties.name, this._value); + }; + + GraphOutput.prototype.onAction = function(action, param) { + if (this.properties.type == LiteGraph.ACTION) { + this.graph.trigger( this.properties.name, param ); + } + }; + + GraphOutput.prototype.onRemoved = function() { + if (this.name_in_graph) { + this.graph.removeOutput(this.name_in_graph); + } + }; + + GraphOutput.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.name; + } + return this.title; + }; + + LiteGraph.GraphOutput = GraphOutput; + LiteGraph.registerNodeType("graph/output", GraphOutput); + + //Constant + function ConstantNumber() { + this.addOutput("value", "number"); + this.addProperty("value", 1.0); + this.widget = this.addWidget("number","value",1,"value"); + this.widgets_up = true; + this.size = [180, 30]; + } + + ConstantNumber.title = "Const Number"; + ConstantNumber.desc = "Constant number"; + + ConstantNumber.prototype.onExecute = function() { + this.setOutputData(0, parseFloat(this.properties["value"])); + }; + + ConstantNumber.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.value; + } + return this.title; + }; + + ConstantNumber.prototype.setValue = function(v) + { + this.setProperty("value",v); + } + + ConstantNumber.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = this.properties["value"].toFixed(3); + }; + + LiteGraph.registerNodeType("basic/const", ConstantNumber); + + function ConstantBoolean() { + this.addOutput("bool", "boolean"); + this.addProperty("value", true); + this.widget = this.addWidget("toggle","value",true,"value"); + this.serialize_widgets = true; + this.widgets_up = true; + this.size = [140, 30]; + } + + ConstantBoolean.title = "Const Boolean"; + ConstantBoolean.desc = "Constant boolean"; + ConstantBoolean.prototype.getTitle = ConstantNumber.prototype.getTitle; + + ConstantBoolean.prototype.onExecute = function() { + this.setOutputData(0, this.properties["value"]); + }; + + ConstantBoolean.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantBoolean.prototype.onGetInputs = function() { + return [["toggle", LiteGraph.ACTION]]; + }; + + ConstantBoolean.prototype.onAction = function(action) + { + this.setValue( !this.properties.value ); + } + + LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); + + function ConstantString() { + this.addOutput("string", "string"); + this.addProperty("value", ""); + this.widget = this.addWidget("text","value","","value"); //link to property value + this.widgets_up = true; + this.size = [180, 30]; + } + + ConstantString.title = "Const String"; + ConstantString.desc = "Constant string"; + + ConstantString.prototype.getTitle = ConstantNumber.prototype.getTitle; + + ConstantString.prototype.onExecute = function() { + this.setOutputData(0, this.properties["value"]); + }; + + ConstantString.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantString.prototype.onDropFile = function(file) + { + var that = this; + var reader = new FileReader(); + reader.onload = function(e) + { + that.setProperty("value",e.target.result); + } + reader.readAsText(file); + } + + LiteGraph.registerNodeType("basic/string", ConstantString); + + function ConstantObject() { + this.addOutput("obj", "object"); + this.size = [120, 30]; + this._object = {}; + } + + ConstantObject.title = "Const Object"; + ConstantObject.desc = "Constant Object"; + + ConstantObject.prototype.onExecute = function() { + this.setOutputData(0, this._object); + }; + + LiteGraph.registerNodeType( "basic/object", ConstantObject ); + + function ConstantFile() { + this.addInput("url", "string"); + this.addOutput("file", "string"); + this.addProperty("url", ""); + this.addProperty("type", "text"); + this.widget = this.addWidget("text","url","","url"); + this._data = null; + } + + ConstantFile.title = "Const File"; + ConstantFile.desc = "Fetches a file from an url"; + ConstantFile["@type"] = { type: "enum", values: ["text","arraybuffer","blob","json"] }; + + ConstantFile.prototype.onPropertyChanged = function(name, value) { + if (name == "url") + { + if( value == null || value == "") + this._data = null; + else + { + this.fetchFile(value); + } + } + } + + ConstantFile.prototype.onExecute = function() { + var url = this.getInputData(0) || this.properties.url; + if(url && (url != this._url || this._type != this.properties.type)) + this.fetchFile(url); + this.setOutputData(0, this._data ); + }; + + ConstantFile.prototype.setValue = ConstantNumber.prototype.setValue; + + ConstantFile.prototype.fetchFile = function(url) { + var that = this; + if(!url || url.constructor !== String) + { + that._data = null; + that.boxcolor = null; + return; + } + + this._url = url; + this._type = this.properties.type; + if (url.substr(0, 4) == "http" && LiteGraph.proxy) { + url = LiteGraph.proxy + url.substr(url.indexOf(":") + 3); + } + fetch(url) + .then(function(response) { + if(!response.ok) + throw new Error("File not found"); + + if(that.properties.type == "arraybuffer") + return response.arrayBuffer(); + else if(that.properties.type == "text") + return response.text(); + else if(that.properties.type == "json") + return response.json(); + else if(that.properties.type == "blob") + return response.blob(); + }) + .then(function(data) { + that._data = data; + that.boxcolor = "#AEA"; + }) + .catch(function(error) { + that._data = null; + that.boxcolor = "red"; + console.error("error fetching file:",url); + }); + }; + + ConstantFile.prototype.onDropFile = function(file) + { + var that = this; + this._url = file.name; + this._type = this.properties.type; + this.properties.url = file.name; + var reader = new FileReader(); + reader.onload = function(e) + { + that.boxcolor = "#AEA"; + var v = e.target.result; + if( that.properties.type == "json" ) + v = JSON.parse(v); + that._data = v; + } + if(that.properties.type == "arraybuffer") + reader.readAsArrayBuffer(file); + else if(that.properties.type == "text" || that.properties.type == "json") + reader.readAsText(file); + else if(that.properties.type == "blob") + return reader.readAsBinaryString(file); + } + + LiteGraph.registerNodeType("basic/file", ConstantFile); + + +//to store json objects +function JSONParse() { + this.addInput("parse", LiteGraph.ACTION); + this.addInput("json", "string"); + this.addOutput("done", LiteGraph.EVENT); + this.addOutput("object", "object"); + this.widget = this.addWidget("button","parse","",this.parse.bind(this)); + this._str = null; + this._obj = null; +} + +JSONParse.title = "JSON Parse"; +JSONParse.desc = "Parses JSON String into object"; + +JSONParse.prototype.parse = function() +{ + if(!this._str) + return; + + try { + this._str = this.getInputData(1); + this._obj = JSON.parse(this._str); + this.boxcolor = "#AEA"; + this.triggerSlot(0); + } catch (err) { + this.boxcolor = "red"; + } +} + +JSONParse.prototype.onExecute = function() { + this._str = this.getInputData(1); + this.setOutputData(1, this._obj); +}; + +JSONParse.prototype.onAction = function(name) { + if(name == "parse") + this.parse(); +} + +LiteGraph.registerNodeType("basic/jsonparse", JSONParse); + + //to store json objects + function ConstantData() { + this.addOutput("data", "object"); + this.addProperty("value", ""); + this.widget = this.addWidget("text","json","","value"); + this.widgets_up = true; + this.size = [140, 30]; + this._value = null; + } + + ConstantData.title = "Const Data"; + ConstantData.desc = "Constant Data"; + + ConstantData.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + if (value == null || value == "") { + return; + } + + try { + this._value = JSON.parse(value); + this.boxcolor = "#AEA"; + } catch (err) { + this.boxcolor = "red"; + } + }; + + ConstantData.prototype.onExecute = function() { + this.setOutputData(0, this._value); + }; + + ConstantData.prototype.setValue = ConstantNumber.prototype.setValue; + + LiteGraph.registerNodeType("basic/data", ConstantData); + + //to store json objects + function ConstantArray() { + this._value = []; + this.addInput("json", ""); + this.addOutput("arrayOut", "array"); + this.addOutput("length", "number"); + this.addProperty("value", "[]"); + this.widget = this.addWidget("text","array",this.properties.value,"value"); + this.widgets_up = true; + this.size = [140, 50]; + } + + ConstantArray.title = "Const Array"; + ConstantArray.desc = "Constant Array"; + + ConstantArray.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + if (value == null || value == "") { + return; + } + + try { + if(value[0] != "[") + this._value = JSON.parse("[" + value + "]"); + else + this._value = JSON.parse(value); + this.boxcolor = "#AEA"; + } catch (err) { + this.boxcolor = "red"; + } + }; + + ConstantArray.prototype.onExecute = function() { + var v = this.getInputData(0); + if(v && v.length) //clone + { + if(!this._value) + this._value = new Array(); + this._value.length = v.length; + for(var i = 0; i < v.length; ++i) + this._value[i] = v[i]; + } + this.setOutputData(0, this._value); + this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); + }; + + ConstantArray.prototype.setValue = ConstantNumber.prototype.setValue; + + LiteGraph.registerNodeType("basic/array", ConstantArray); + + function SetArray() + { + this.addInput("arr", "array"); + this.addInput("value", ""); + this.addOutput("arr", "array"); + this.properties = { index: 0 }; + this.widget = this.addWidget("number","i",this.properties.index,"index",{precision: 0, step: 10, min: 0}); + } + + SetArray.title = "Set Array"; + SetArray.desc = "Sets index of array"; + + SetArray.prototype.onExecute = function() { + var arr = this.getInputData(0); + if(!arr) + return; + var v = this.getInputData(1); + if(v === undefined ) + return; + if(this.properties.index) + arr[ Math.floor(this.properties.index) ] = v; + this.setOutputData(0,arr); + }; + + LiteGraph.registerNodeType("basic/set_array", SetArray ); + + function ArrayElement() { + this.addInput("array", "array,table,string"); + this.addInput("index", "number"); + this.addOutput("value", ""); + this.addProperty("index",0); + } + + ArrayElement.title = "Array[i]"; + ArrayElement.desc = "Returns an element from an array"; + + ArrayElement.prototype.onExecute = function() { + var array = this.getInputData(0); + var index = this.getInputData(1); + if(index == null) + index = this.properties.index; + if(array == null || index == null ) + return; + this.setOutputData(0, array[Math.floor(Number(index))] ); + }; + + LiteGraph.registerNodeType("basic/array[]", ArrayElement); + + function TableElement() { + this.addInput("table", "table"); + this.addInput("row", "number"); + this.addInput("col", "number"); + this.addOutput("value", ""); + this.addProperty("row",0); + this.addProperty("column",0); + } + + TableElement.title = "Table[row][col]"; + TableElement.desc = "Returns an element from a table"; + + TableElement.prototype.onExecute = function() { + var table = this.getInputData(0); + var row = this.getInputData(1); + var col = this.getInputData(2); + if(row == null) + row = this.properties.row; + if(col == null) + col = this.properties.column; + if(table == null || row == null || col == null) + return; + var row = table[Math.floor(Number(row))]; + if(row) + this.setOutputData(0, row[Math.floor(Number(col))] ); + else + this.setOutputData(0, null ); + }; + + LiteGraph.registerNodeType("basic/table[][]", TableElement); + + function ObjectProperty() { + this.addInput("obj", "object"); + this.addOutput("property", 0); + this.addProperty("value", 0); + this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); + this.widgets_up = true; + this.size = [140, 30]; + this._value = null; + } + + ObjectProperty.title = "Object property"; + ObjectProperty.desc = "Outputs the property of an object"; + + ObjectProperty.prototype.setValue = function(v) { + this.properties.value = v; + this.widget.value = v; + }; + + ObjectProperty.prototype.getTitle = function() { + if (this.flags.collapsed) { + return "in." + this.properties.value; + } + return this.title; + }; + + ObjectProperty.prototype.onPropertyChanged = function(name, value) { + this.widget.value = value; + }; + + ObjectProperty.prototype.onExecute = function() { + var data = this.getInputData(0); + if (data != null) { + this.setOutputData(0, data[this.properties.value]); + } + }; + + LiteGraph.registerNodeType("basic/object_property", ObjectProperty); + + function ObjectKeys() { + this.addInput("obj", ""); + this.addOutput("keys", "array"); + this.size = [140, 30]; + } + + ObjectKeys.title = "Object keys"; + ObjectKeys.desc = "Outputs an array with the keys of an object"; + + ObjectKeys.prototype.onExecute = function() { + var data = this.getInputData(0); + if (data != null) { + this.setOutputData(0, Object.keys(data) ); + } + }; + + LiteGraph.registerNodeType("basic/object_keys", ObjectKeys); + + + function SetObject() + { + this.addInput("obj", ""); + this.addInput("value", ""); + this.addOutput("obj", ""); + this.properties = { property: "" }; + this.name_widget = this.addWidget("text","prop.",this.properties.property,"property"); + } + + SetObject.title = "Set Object"; + SetObject.desc = "Adds propertiesrty to object"; + + SetObject.prototype.onExecute = function() { + var obj = this.getInputData(0); + if(!obj) + return; + var v = this.getInputData(1); + if(v === undefined ) + return; + if(this.properties.property) + obj[ this.properties.property ] = v; + this.setOutputData(0,obj); + }; + + LiteGraph.registerNodeType("basic/set_object", SetObject ); + + + function MergeObjects() { + this.addInput("A", "object"); + this.addInput("B", "object"); + this.addOutput("out", "object"); + this._result = {}; + var that = this; + this.addWidget("button","clear","",function(){ + that._result = {}; + }); + this.size = this.computeSize(); + } + + MergeObjects.title = "Merge Objects"; + MergeObjects.desc = "Creates an object copying properties from others"; + + MergeObjects.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + var C = this._result; + if(A) + for(var i in A) + C[i] = A[i]; + if(B) + for(var i in B) + C[i] = B[i]; + this.setOutputData(0,C); + }; + + LiteGraph.registerNodeType("basic/merge_objects", MergeObjects ); + + //Store as variable + function Variable() { + this.size = [60, 30]; + this.addInput("in"); + this.addOutput("out"); + this.properties = { varname: "myname", container: Variable.LITEGRAPH }; + this.value = null; + } + + Variable.title = "Variable"; + Variable.desc = "store/read variable value"; + + Variable.LITEGRAPH = 0; //between all graphs + Variable.GRAPH = 1; //only inside this graph + Variable.GLOBALSCOPE = 2; //attached to Window + + Variable["@container"] = { type: "enum", values: {"litegraph":Variable.LITEGRAPH, "graph":Variable.GRAPH,"global": Variable.GLOBALSCOPE} }; + + Variable.prototype.onExecute = function() { + var container = this.getContainer(); + + if(this.isInputConnected(0)) + { + this.value = this.getInputData(0); + container[ this.properties.varname ] = this.value; + this.setOutputData(0, this.value ); + return; + } + + this.setOutputData( 0, container[ this.properties.varname ] ); + }; + + Variable.prototype.getContainer = function() + { + switch(this.properties.container) + { + case Variable.GRAPH: + if(this.graph) + return this.graph.vars; + return {}; + break; + case Variable.GLOBALSCOPE: + return global; + break; + case Variable.LITEGRAPH: + default: + return LiteGraph.Globals; + break; + } + } + + Variable.prototype.getTitle = function() { + return this.properties.varname; + }; + + LiteGraph.registerNodeType("basic/variable", Variable); + + function length(v) { + if(v && v.length != null) + return Number(v.length); + return 0; + } + + LiteGraph.wrapFunctionAsNode( + "basic/length", + length, + [""], + "number" + ); + + function length(v) { + if(v && v.length != null) + return Number(v.length); + return 0; + } + + LiteGraph.wrapFunctionAsNode( + "basic/not", + function(a){ return !a; }, + [""], + "boolean" + ); + + function DownloadData() { + this.size = [60, 30]; + this.addInput("data", 0 ); + this.addInput("download", LiteGraph.ACTION ); + this.properties = { filename: "data.json" }; + this.value = null; + var that = this; + this.addWidget("button","Download","", function(v){ + if(!that.value) + return; + that.downloadAsFile(); + }); + } + + DownloadData.title = "Download"; + DownloadData.desc = "Download some data"; + + DownloadData.prototype.downloadAsFile = function() + { + if(this.value == null) + return; + + var str = null; + if(this.value.constructor === String) + str = this.value; + else + str = JSON.stringify(this.value); + + var file = new Blob([str]); + var url = URL.createObjectURL( file ); + var element = document.createElement("a"); + element.setAttribute('href', url); + element.setAttribute('download', this.properties.filename ); + element.style.display = 'none'; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + setTimeout( function(){ URL.revokeObjectURL( url ); }, 1000*60 ); //wait one minute to revoke url + } + + DownloadData.prototype.onAction = function(action, param) { + var that = this; + setTimeout( function(){ that.downloadAsFile(); }, 100); //deferred to avoid blocking the renderer with the popup + } + + DownloadData.prototype.onExecute = function() { + if (this.inputs[0]) { + this.value = this.getInputData(0); + } + }; + + DownloadData.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.filename; + } + return this.title; + }; + + LiteGraph.registerNodeType("basic/download", DownloadData); + + + + //Watch a value in the editor + function Watch() { + this.size = [60, 30]; + this.addInput("value", 0, { label: "" }); + this.value = 0; + } + + Watch.title = "Watch"; + Watch.desc = "Show value of input"; + + Watch.prototype.onExecute = function() { + if (this.inputs[0]) { + this.value = this.getInputData(0); + } + }; + + Watch.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.inputs[0].label; + } + return this.title; + }; + + Watch.toString = function(o) { + if (o == null) { + return "null"; + } else if (o.constructor === Number) { + return o.toFixed(3); + } else if (o.constructor === Array) { + var str = "["; + for (var i = 0; i < o.length; ++i) { + str += Watch.toString(o[i]) + (i + 1 != o.length ? "," : ""); + } + str += "]"; + return str; + } else { + return String(o); + } + }; + + Watch.prototype.onDrawBackground = function(ctx) { + //show the current value + this.inputs[0].label = Watch.toString(this.value); + }; + + LiteGraph.registerNodeType("basic/watch", Watch); + + //in case one type doesnt match other type but you want to connect them anyway + function Cast() { + this.addInput("in", 0); + this.addOutput("out", 0); + this.size = [40, 30]; + } + + Cast.title = "Cast"; + Cast.desc = "Allows to connect different types"; + + Cast.prototype.onExecute = function() { + this.setOutputData(0, this.getInputData(0)); + }; + + LiteGraph.registerNodeType("basic/cast", Cast); + + //Show value inside the debug console + function Console() { + this.mode = LiteGraph.ON_EVENT; + this.size = [80, 30]; + this.addProperty("msg", ""); + this.addInput("log", LiteGraph.EVENT); + this.addInput("msg", 0); + } + + Console.title = "Console"; + Console.desc = "Show value inside the console"; + + Console.prototype.onAction = function(action, param) { + // param is the action + var msg = this.getInputData(1); //getInputDataByName("msg"); + //if (msg == null || typeof msg == "undefined") return; + if (!msg) msg = this.properties.msg; + if (!msg) msg = "Event: "+param; // msg is undefined if the slot is lost? + if (action == "log") { + console.log(msg); + } else if (action == "warn") { + console.warn(msg); + } else if (action == "error") { + console.error(msg); + } + }; + + Console.prototype.onExecute = function() { + var msg = this.getInputData(1); //getInputDataByName("msg"); + if (!msg) msg = this.properties.msg; + if (msg != null && typeof msg != "undefined") { + this.properties.msg = msg; + console.log(msg); + } + }; + + Console.prototype.onGetInputs = function() { + return [ + ["log", LiteGraph.ACTION], + ["warn", LiteGraph.ACTION], + ["error", LiteGraph.ACTION] + ]; + }; + + LiteGraph.registerNodeType("basic/console", Console); + + //Show value inside the debug console + function Alert() { + this.mode = LiteGraph.ON_EVENT; + this.addProperty("msg", ""); + this.addInput("", LiteGraph.EVENT); + var that = this; + this.widget = this.addWidget("text", "Text", "", "msg"); + this.widgets_up = true; + this.size = [200, 30]; + } + + Alert.title = "Alert"; + Alert.desc = "Show an alert window"; + Alert.color = "#510"; + + Alert.prototype.onConfigure = function(o) { + this.widget.value = o.properties.msg; + }; + + Alert.prototype.onAction = function(action, param) { + var msg = this.properties.msg; + setTimeout(function() { + alert(msg); + }, 10); + }; + + LiteGraph.registerNodeType("basic/alert", Alert); + + //Execites simple code + function NodeScript() { + this.size = [60, 30]; + this.addProperty("onExecute", "return A;"); + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("out", 0); + + this._func = null; + this.data = {}; + } + + NodeScript.prototype.onConfigure = function(o) { + if (o.properties.onExecute && LiteGraph.allow_scripts) + this.compileCode(o.properties.onExecute); + else + console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + }; + + NodeScript.title = "Script"; + NodeScript.desc = "executes a code (max 256 characters)"; + + NodeScript.widgets_info = { + onExecute: { type: "code" } + }; + + NodeScript.prototype.onPropertyChanged = function(name, value) { + if (name == "onExecute" && LiteGraph.allow_scripts) + this.compileCode(value); + else + console.warn("Script not compiled, LiteGraph.allow_scripts is false"); + }; + + NodeScript.prototype.compileCode = function(code) { + this._func = null; + if (code.length > 256) { + console.warn("Script too long, max 256 chars"); + } else { + var code_low = code.toLowerCase(); + var forbidden_words = [ + "script", + "body", + "document", + "eval", + "nodescript", + "function" + ]; //bad security solution + for (var i = 0; i < forbidden_words.length; ++i) { + if (code_low.indexOf(forbidden_words[i]) != -1) { + console.warn("invalid script"); + return; + } + } + try { + this._func = new Function("A", "B", "C", "DATA", "node", code); + } catch (err) { + console.error("Error parsing script"); + console.error(err); + } + } + }; + + NodeScript.prototype.onExecute = function() { + if (!this._func) { + return; + } + + try { + var A = this.getInputData(0); + var B = this.getInputData(1); + var C = this.getInputData(2); + this.setOutputData(0, this._func(A, B, C, this.data, this)); + } catch (err) { + console.error("Error in script"); + console.error(err); + } + }; + + NodeScript.prototype.onGetOutputs = function() { + return [["C", ""]]; + }; + + LiteGraph.registerNodeType("basic/script", NodeScript); + + + function GenericCompare() { + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "==", "enum", { values: GenericCompare.values }); + this.addWidget("combo","Op.",this.properties.OP,{ property: "OP", values: GenericCompare.values } ); + + this.size = [80, 60]; + } + + GenericCompare.values = ["==", "!="]; //[">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + GenericCompare["@OP"] = { + type: "enum", + title: "operation", + values: GenericCompare.values + }; + + GenericCompare.title = "Compare *"; + GenericCompare.desc = "evaluates condition between A and B"; + + GenericCompare.prototype.getTitle = function() { + return "*A " + this.properties.OP + " *B"; + }; + + GenericCompare.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A === undefined) { + A = this.properties.A; + } else { + this.properties.A = A; + } + + var B = this.getInputData(1); + if (B === undefined) { + B = this.properties.B; + } else { + this.properties.B = B; + } + + var result = false; + if (typeof A == typeof B){ + switch (this.properties.OP) { + case "==": + case "!=": + // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual() + result = true; + switch(typeof A){ + case "object": + var aProps = Object.getOwnPropertyNames(A); + var bProps = Object.getOwnPropertyNames(B); + if (aProps.length != bProps.length){ + result = false; + break; + } + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + if (A[propName] !== B[propName]) { + result = false; + break; + } + } + break; + default: + result = A == B; + } + if (this.properties.OP == "!=") result = !result; + break; + /*case ">": + result = A > B; + break; + case "<": + result = A < B; + break; + case "<=": + result = A <= B; + break; + case ">=": + result = A >= B; + break; + case "||": + result = A || B; + break; + case "&&": + result = A && B; + break;*/ + } + } + this.setOutputData(0, result); + this.setOutputData(1, !result); + }; + + LiteGraph.registerNodeType("basic/CompareValues", GenericCompare); + +})(this); + +//event related nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + //Show value inside the debug console + function LogEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + } + + LogEvent.title = "Log Event"; + LogEvent.desc = "Log event in console"; + + LogEvent.prototype.onAction = function(action, param, options) { + console.log(action, param); + }; + + LiteGraph.registerNodeType("events/log", LogEvent); + + //convert to Event if the value is true + function TriggerEvent() { + this.size = [60, 30]; + this.addInput("if", ""); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.properties = { only_on_change: true }; + this.prev = 0; + } + + TriggerEvent.title = "TriggerEvent"; + TriggerEvent.desc = "Triggers event if input evaluates to true"; + + TriggerEvent.prototype.onExecute = function( param, options) { + var v = this.getInputData(0); + var changed = (v != this.prev); + if(this.prev === 0) + changed = false; + var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); + if(v && must_resend ) + this.triggerSlot(0, param, null, options); + if(!v && must_resend) + this.triggerSlot(2, param, null, options); + if(changed) + this.triggerSlot(1, param, null, options); + this.prev = v; + }; + + LiteGraph.registerNodeType("events/trigger", TriggerEvent); + + //Sequence of events + function Sequence() { + var that = this; + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addWidget("button","+",null,function(){ + that.addInput("", LiteGraph.ACTION); + that.addOutput("", LiteGraph.EVENT); + }); + this.size = [90, 70]; + this.flags = { horizontal: true, render_box: false }; + } + + Sequence.title = "Sequence"; + Sequence.desc = "Triggers a sequence of events when an event arrives"; + + Sequence.prototype.getTitle = function() { + return ""; + }; + + Sequence.prototype.onAction = function(action, param, options) { + if (this.outputs) { + options = options || {}; + for (var i = 0; i < this.outputs.length; ++i) { + var output = this.outputs[i]; + //needs more info about this... + if( options.action_call ) // CREATE A NEW ID FOR THE ACTION + options.action_call = options.action_call + "_seq_" + i; + else + options.action_call = this.id + "_" + (action ? action : "action")+"_seq_"+i+"_"+Math.floor(Math.random()*9999); + this.triggerSlot(i, param, null, options); + } + } + }; + + LiteGraph.registerNodeType("events/sequence", Sequence); + + + //Sequence of events + function WaitAll() { + var that = this; + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addOutput("", LiteGraph.EVENT); + this.addWidget("button","+",null,function(){ + that.addInput("", LiteGraph.ACTION); + that.size[0] = 90; + }); + this.size = [90, 70]; + this.ready = []; +} + +WaitAll.title = "WaitAll"; +WaitAll.desc = "Wait until all input events arrive then triggers output"; + +WaitAll.prototype.getTitle = function() { + return ""; +}; + +WaitAll.prototype.onDrawBackground = function(ctx) +{ + if (this.flags.collapsed) { + return; + } + for(var i = 0; i < this.inputs.length; ++i) + { + var y = i * LiteGraph.NODE_SLOT_HEIGHT + 10; + ctx.fillStyle = this.ready[i] ? "#AFB" : "#000"; + ctx.fillRect(20, y, 10, 10); + } +} + +WaitAll.prototype.onAction = function(action, param, options, slot_index) { + if(slot_index == null) + return; + + //check all + this.ready.length = this.outputs.length; + this.ready[slot_index] = true; + for(var i = 0; i < this.ready.length;++i) + if(!this.ready[i]) + return; + //pass + this.reset(); + this.triggerSlot(0); +}; + +WaitAll.prototype.reset = function() +{ + this.ready.length = 0; +} + +LiteGraph.registerNodeType("events/waitAll", WaitAll); + + + //Sequencer for events + function Stepper() { + var that = this; + this.properties = { index: 0 }; + this.addInput("index", "number"); + this.addInput("step", LiteGraph.ACTION); + this.addInput("reset", LiteGraph.ACTION); + this.addOutput("index", "number"); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT,{removable:true}); + this.addWidget("button","+",null,function(){ + that.addOutput("", LiteGraph.EVENT, {removable:true}); + }); + this.size = [120, 120]; + this.flags = { render_box: false }; + } + + Stepper.title = "Stepper"; + Stepper.desc = "Trigger events sequentially when an tick arrives"; + + Stepper.prototype.onDrawBackground = function(ctx) + { + if (this.flags.collapsed) { + return; + } + var index = this.properties.index || 0; + ctx.fillStyle = "#AFB"; + var w = this.size[0]; + var y = (index + 1)* LiteGraph.NODE_SLOT_HEIGHT + 4; + ctx.beginPath(); + ctx.moveTo(w - 30, y); + ctx.lineTo(w - 30, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(w - 15, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + } + + Stepper.prototype.onExecute = function() + { + var index = this.getInputData(0); + if(index != null) + { + index = Math.floor(index); + index = clamp( index, 0, this.outputs ? (this.outputs.length - 2) : 0 ); + if( index != this.properties.index ) + { + this.properties.index = index; + this.triggerSlot( index+1 ); + } + } + + this.setOutputData(0, this.properties.index ); + } + + Stepper.prototype.onAction = function(action, param) { + if(action == "reset") + this.properties.index = 0; + else if(action == "step") + { + this.triggerSlot(this.properties.index+1, param); + var n = this.outputs ? this.outputs.length - 1 : 0; + this.properties.index = (this.properties.index + 1) % n; + } + }; + + LiteGraph.registerNodeType("events/stepper", Stepper); + + //Filter events + function FilterEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + this.addOutput("event", LiteGraph.EVENT); + this.properties = { + equal_to: "", + has_property: "", + property_equal_to: "" + }; + } + + FilterEvent.title = "Filter Event"; + FilterEvent.desc = "Blocks events that do not match the filter"; + + FilterEvent.prototype.onAction = function(action, param, options) { + if (param == null) { + return; + } + + if (this.properties.equal_to && this.properties.equal_to != param) { + return; + } + + if (this.properties.has_property) { + var prop = param[this.properties.has_property]; + if (prop == null) { + return; + } + + if ( + this.properties.property_equal_to && + this.properties.property_equal_to != prop + ) { + return; + } + } + + this.triggerSlot(0, param, null, options); + }; + + LiteGraph.registerNodeType("events/filter", FilterEvent); + + + function EventBranch() { + this.addInput("in", LiteGraph.ACTION); + this.addInput("cond", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.size = [120, 60]; + this._value = false; + } + + EventBranch.title = "Branch"; + EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + + EventBranch.prototype.onExecute = function() { + this._value = this.getInputData(1); + } + + EventBranch.prototype.onAction = function(action, param, options) { + this._value = this.getInputData(1); + this.triggerSlot(this._value ? 0 : 1, param, null, options); + } + + LiteGraph.registerNodeType("events/branch", EventBranch); + + //Show value inside the debug console + function EventCounter() { + this.addInput("inc", LiteGraph.ACTION); + this.addInput("dec", LiteGraph.ACTION); + this.addInput("reset", LiteGraph.ACTION); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("num", "number"); + this.addProperty("doCountExecution", false, "boolean", {name: "Count Executions"}); + this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"); + this.num = 0; + } + + EventCounter.title = "Counter"; + EventCounter.desc = "Counts events"; + + EventCounter.prototype.getTitle = function() { + if (this.flags.collapsed) { + return String(this.num); + } + return this.title; + }; + + EventCounter.prototype.onAction = function(action, param, options) { + var v = this.num; + if (action == "inc") { + this.num += 1; + } else if (action == "dec") { + this.num -= 1; + } else if (action == "reset") { + this.num = 0; + } + if (this.num != v) { + this.trigger("change", this.num); + } + }; + + EventCounter.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + ctx.fillStyle = "#AAA"; + ctx.font = "20px Arial"; + ctx.textAlign = "center"; + ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); + }; + + EventCounter.prototype.onExecute = function() { + if(this.properties.doCountExecution){ + this.num += 1; + } + this.setOutputData(1, this.num); + }; + + LiteGraph.registerNodeType("events/counter", EventCounter); + + //Show value inside the debug console + function DelayEvent() { + this.size = [60, 30]; + this.addProperty("time_in_ms", 1000); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; + } + + DelayEvent.title = "Delay"; + DelayEvent.desc = "Delays one event"; + + DelayEvent.prototype.onAction = function(action, param, options) { + var time = this.properties.time_in_ms; + if (time <= 0) { + this.trigger(null, param, options); + } else { + this._pending.push([time, param]); + } + }; + + DelayEvent.prototype.onExecute = function(param, options) { + var dt = this.graph.elapsed_time * 1000; //in ms + + if (this.isInputConnected(1)) { + this.properties.time_in_ms = this.getInputData(1); + } + + for (var i = 0; i < this._pending.length; ++i) { + var actionPass = this._pending[i]; + actionPass[0] -= dt; + if (actionPass[0] > 0) { + continue; + } + + //remove + this._pending.splice(i, 1); + --i; + + //trigger + this.trigger(null, actionPass[1], options); + } + }; + + DelayEvent.prototype.onGetInputs = function() { + return [["event", LiteGraph.ACTION], ["time_in_ms", "number"]]; + }; + + LiteGraph.registerNodeType("events/delay", DelayEvent); + + //Show value inside the debug console + function TimerEvent() { + this.addProperty("interval", 1000); + this.addProperty("event", "tick"); + this.addOutput("on_tick", LiteGraph.EVENT); + this.time = 0; + this.last_interval = 1000; + this.triggered = false; + } + + TimerEvent.title = "Timer"; + TimerEvent.desc = "Sends an event every N milliseconds"; + + TimerEvent.prototype.onStart = function() { + this.time = 0; + }; + + TimerEvent.prototype.getTitle = function() { + return "Timer: " + this.last_interval.toString() + "ms"; + }; + + TimerEvent.on_color = "#AAA"; + TimerEvent.off_color = "#222"; + + TimerEvent.prototype.onDrawBackground = function() { + this.boxcolor = this.triggered + ? TimerEvent.on_color + : TimerEvent.off_color; + this.triggered = false; + }; + + TimerEvent.prototype.onExecute = function() { + var dt = this.graph.elapsed_time * 1000; //in ms + + var trigger = this.time == 0; + + this.time += dt; + this.last_interval = Math.max( + 1, + this.getInputOrProperty("interval") | 0 + ); + + if ( + !trigger && + (this.time < this.last_interval || isNaN(this.last_interval)) + ) { + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, false); + } + return; + } + + this.triggered = true; + this.time = this.time % this.last_interval; + this.trigger("on_tick", this.properties.event); + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, true); + } + }; + + TimerEvent.prototype.onGetInputs = function() { + return [["interval", "number"]]; + }; + + TimerEvent.prototype.onGetOutputs = function() { + return [["tick", "boolean"]]; + }; + + LiteGraph.registerNodeType("events/timer", TimerEvent); + + + + function SemaphoreEvent() { + this.addInput("go", LiteGraph.ACTION ); + this.addInput("green", LiteGraph.ACTION ); + this.addInput("red", LiteGraph.ACTION ); + this.addOutput("continue", LiteGraph.EVENT ); + this.addOutput("blocked", LiteGraph.EVENT ); + this.addOutput("is_green", "boolean" ); + this._ready = false; + this.properties = {}; + var that = this; + this.addWidget("button","reset","",function(){ + that._ready = false; + }); + } + + SemaphoreEvent.title = "Semaphore Event"; + SemaphoreEvent.desc = "Until both events are not triggered, it doesnt continue."; + + SemaphoreEvent.prototype.onExecute = function() + { + this.setOutputData(1,this._ready); + this.boxcolor = this._ready ? "#9F9" : "#FA5"; + } + + SemaphoreEvent.prototype.onAction = function(action, param) { + if( action == "go" ) + this.triggerSlot( this._ready ? 0 : 1 ); + else if( action == "green" ) + this._ready = true; + else if( action == "red" ) + this._ready = false; + }; + + LiteGraph.registerNodeType("events/semaphore", SemaphoreEvent); + + function OnceEvent() { + this.addInput("in", LiteGraph.ACTION ); + this.addInput("reset", LiteGraph.ACTION ); + this.addOutput("out", LiteGraph.EVENT ); + this._once = false; + this.properties = {}; + var that = this; + this.addWidget("button","reset","",function(){ + that._once = false; + }); + } + + OnceEvent.title = "Once"; + OnceEvent.desc = "Only passes an event once, then gets locked"; + + OnceEvent.prototype.onAction = function(action, param) { + if( action == "in" && !this._once ) + { + this._once = true; + this.triggerSlot( 0, param ); + } + else if( action == "reset" ) + this._once = false; + }; + + LiteGraph.registerNodeType("events/once", OnceEvent); + + function DataStore() { + this.addInput("data", 0); + this.addInput("assign", LiteGraph.ACTION); + this.addOutput("data", 0); + this._last_value = null; + this.properties = { data: null, serialize: true }; + var that = this; + this.addWidget("button","store","",function(){ + that.properties.data = that._last_value; + }); + } + + DataStore.title = "Data Store"; + DataStore.desc = "Stores data and only changes when event is received"; + + DataStore.prototype.onExecute = function() + { + this._last_value = this.getInputData(0); + this.setOutputData(0, this.properties.data ); + } + + DataStore.prototype.onAction = function(action, param, options) { + this.properties.data = this._last_value; + }; + + DataStore.prototype.onSerialize = function(o) + { + if(o.data == null) + return; + if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) + o.data = null; + } + + LiteGraph.registerNodeType("basic/data_store", DataStore); + + + +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + function GamepadInput() { + this.addOutput("left_x_axis", "number"); + this.addOutput("left_y_axis", "number"); + this.addOutput("button_pressed", LiteGraph.EVENT); + this.properties = { gamepad_index: 0, threshold: 0.1 }; + + this._left_axis = new Float32Array(2); + this._right_axis = new Float32Array(2); + this._triggers = new Float32Array(2); + this._previous_buttons = new Uint8Array(17); + this._current_buttons = new Uint8Array(17); + } + + GamepadInput.title = "Gamepad"; + GamepadInput.desc = "gets the input of the gamepad"; + + GamepadInput.CENTER = 0; + GamepadInput.LEFT = 1; + GamepadInput.RIGHT = 2; + GamepadInput.UP = 4; + GamepadInput.DOWN = 8; + + GamepadInput.zero = new Float32Array(2); + GamepadInput.buttons = [ + "a", + "b", + "x", + "y", + "lb", + "rb", + "lt", + "rt", + "back", + "start", + "ls", + "rs", + "home" + ]; + + GamepadInput.prototype.onExecute = function() { + //get gamepad + var gamepad = this.getGamepad(); + var threshold = this.properties.threshold || 0.0; + + if (gamepad) { + this._left_axis[0] = + Math.abs(gamepad.xbox.axes["lx"]) > threshold + ? gamepad.xbox.axes["lx"] + : 0; + this._left_axis[1] = + Math.abs(gamepad.xbox.axes["ly"]) > threshold + ? gamepad.xbox.axes["ly"] + : 0; + this._right_axis[0] = + Math.abs(gamepad.xbox.axes["rx"]) > threshold + ? gamepad.xbox.axes["rx"] + : 0; + this._right_axis[1] = + Math.abs(gamepad.xbox.axes["ry"]) > threshold + ? gamepad.xbox.axes["ry"] + : 0; + this._triggers[0] = + Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold + ? gamepad.xbox.axes["ltrigger"] + : 0; + this._triggers[1] = + Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold + ? gamepad.xbox.axes["rtrigger"] + : 0; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var v = null; + + if (gamepad) { + switch (output.name) { + case "left_axis": + v = this._left_axis; + break; + case "right_axis": + v = this._right_axis; + break; + case "left_x_axis": + v = this._left_axis[0]; + break; + case "left_y_axis": + v = this._left_axis[1]; + break; + case "right_x_axis": + v = this._right_axis[0]; + break; + case "right_y_axis": + v = this._right_axis[1]; + break; + case "trigger_left": + v = this._triggers[0]; + break; + case "trigger_right": + v = this._triggers[1]; + break; + case "a_button": + v = gamepad.xbox.buttons["a"] ? 1 : 0; + break; + case "b_button": + v = gamepad.xbox.buttons["b"] ? 1 : 0; + break; + case "x_button": + v = gamepad.xbox.buttons["x"] ? 1 : 0; + break; + case "y_button": + v = gamepad.xbox.buttons["y"] ? 1 : 0; + break; + case "lb_button": + v = gamepad.xbox.buttons["lb"] ? 1 : 0; + break; + case "rb_button": + v = gamepad.xbox.buttons["rb"] ? 1 : 0; + break; + case "ls_button": + v = gamepad.xbox.buttons["ls"] ? 1 : 0; + break; + case "rs_button": + v = gamepad.xbox.buttons["rs"] ? 1 : 0; + break; + case "hat_left": + v = gamepad.xbox.hatmap & GamepadInput.LEFT; + break; + case "hat_right": + v = gamepad.xbox.hatmap & GamepadInput.RIGHT; + break; + case "hat_up": + v = gamepad.xbox.hatmap & GamepadInput.UP; + break; + case "hat_down": + v = gamepad.xbox.hatmap & GamepadInput.DOWN; + break; + case "hat": + v = gamepad.xbox.hatmap; + break; + case "start_button": + v = gamepad.xbox.buttons["start"] ? 1 : 0; + break; + case "back_button": + v = gamepad.xbox.buttons["back"] ? 1 : 0; + break; + case "button_pressed": + for ( + var j = 0; + j < this._current_buttons.length; + ++j + ) { + if ( + this._current_buttons[j] && + !this._previous_buttons[j] + ) { + this.triggerSlot( + i, + GamepadInput.buttons[j] + ); + } + } + break; + default: + break; + } + } else { + //if no gamepad is connected, output 0 + switch (output.name) { + case "button_pressed": + break; + case "left_axis": + case "right_axis": + v = GamepadInput.zero; + break; + default: + v = 0; + } + } + this.setOutputData(i, v); + } + } + }; + + GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; + GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; + + GamepadInput.prototype.getGamepad = function() { + var getGamepads = + navigator.getGamepads || + navigator.webkitGetGamepads || + navigator.mozGetGamepads; + if (!getGamepads) { + return null; + } + var gamepads = getGamepads.call(navigator); + var gamepad = null; + + this._previous_buttons.set(this._current_buttons); + + //pick the first connected + for (var i = this.properties.gamepad_index; i < 4; i++) { + if (!gamepads[i]) { + continue; + } + gamepad = gamepads[i]; + + //xbox controller mapping + var xbox = this.xbox_mapping; + if (!xbox) { + xbox = this.xbox_mapping = { + axes: [], + buttons: {}, + hat: "", + hatmap: GamepadInput.CENTER + }; + } + + xbox.axes["lx"] = gamepad.axes[0]; + xbox.axes["ly"] = gamepad.axes[1]; + xbox.axes["rx"] = gamepad.axes[2]; + xbox.axes["ry"] = gamepad.axes[3]; + xbox.axes["ltrigger"] = gamepad.buttons[6].value; + xbox.axes["rtrigger"] = gamepad.buttons[7].value; + xbox.hat = ""; + xbox.hatmap = GamepadInput.CENTER; + + for (var j = 0; j < gamepad.buttons.length; j++) { + this._current_buttons[j] = gamepad.buttons[j].pressed; + + if(j < 12) + { + xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; + if(gamepad.buttons[j].was_pressed) + this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); + } + else //mapping of XBOX + switch ( j ) //I use a switch to ensure that a player with another gamepad could play + { + case 12: + if (gamepad.buttons[j].pressed) { + xbox.hat += "up"; + xbox.hatmap |= GamepadInput.UP; + } + break; + case 13: + if (gamepad.buttons[j].pressed) { + xbox.hat += "down"; + xbox.hatmap |= GamepadInput.DOWN; + } + break; + case 14: + if (gamepad.buttons[j].pressed) { + xbox.hat += "left"; + xbox.hatmap |= GamepadInput.LEFT; + } + break; + case 15: + if (gamepad.buttons[j].pressed) { + xbox.hat += "right"; + xbox.hatmap |= GamepadInput.RIGHT; + } + break; + case 16: + xbox.buttons["home"] = gamepad.buttons[j].pressed; + break; + default: + } + } + gamepad.xbox = xbox; + return gamepad; + } + }; + + GamepadInput.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + //render gamepad state? + var la = this._left_axis; + var ra = this._right_axis; + ctx.strokeStyle = "#88A"; + ctx.strokeRect( + (la[0] + 1) * 0.5 * this.size[0] - 4, + (la[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + ctx.strokeStyle = "#8A8"; + ctx.strokeRect( + (ra[0] + 1) * 0.5 * this.size[0] - 4, + (ra[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + var h = this.size[1] / this._current_buttons.length; + ctx.fillStyle = "#AEB"; + for (var i = 0; i < this._current_buttons.length; ++i) { + if (this._current_buttons[i]) { + ctx.fillRect(0, h * i, 6, h); + } + } + }; + + GamepadInput.prototype.onGetOutputs = function() { + return [ + ["left_axis", "vec2"], + ["right_axis", "vec2"], + ["left_x_axis", "number"], + ["left_y_axis", "number"], + ["right_x_axis", "number"], + ["right_y_axis", "number"], + ["trigger_left", "number"], + ["trigger_right", "number"], + ["a_button", "number"], + ["b_button", "number"], + ["x_button", "number"], + ["y_button", "number"], + ["lb_button", "number"], + ["rb_button", "number"], + ["ls_button", "number"], + ["rs_button", "number"], + ["start_button", "number"], + ["back_button", "number"], + ["a_button_event", LiteGraph.EVENT ], + ["b_button_event", LiteGraph.EVENT ], + ["x_button_event", LiteGraph.EVENT ], + ["y_button_event", LiteGraph.EVENT ], + ["lb_button_event", LiteGraph.EVENT ], + ["rb_button_event", LiteGraph.EVENT ], + ["ls_button_event", LiteGraph.EVENT ], + ["rs_button_event", LiteGraph.EVENT ], + ["start_button_event", LiteGraph.EVENT ], + ["back_button_event", LiteGraph.EVENT ], + ["hat_left", "number"], + ["hat_right", "number"], + ["hat_up", "number"], + ["hat_down", "number"], + ["hat", "number"], + ["button_pressed", LiteGraph.EVENT] + ]; + }; + + LiteGraph.registerNodeType("input/gamepad", GamepadInput); + +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + //Converter + function Converter() { + this.addInput("in", 0); + this.addOutput("out", 0); + this.size = [80, 30]; + } + + Converter.title = "Converter"; + Converter.desc = "type A to type B"; + + Converter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + + var result = null; + switch (output.name) { + case "number": + result = v.length ? v[0] : parseFloat(v); + break; + case "vec2": + case "vec3": + case "vec4": + var result = null; + var count = 1; + switch (output.name) { + case "vec2": + count = 2; + break; + case "vec3": + count = 3; + break; + case "vec4": + count = 4; + break; + } + + var result = new Float32Array(count); + if (v.length) { + for ( + var j = 0; + j < v.length && j < result.length; + j++ + ) { + result[j] = v[j]; + } + } else { + result[0] = parseFloat(v); + } + break; + } + this.setOutputData(i, result); + } + } + }; + + Converter.prototype.onGetOutputs = function() { + return [ + ["number", "number"], + ["vec2", "vec2"], + ["vec3", "vec3"], + ["vec4", "vec4"] + ]; + }; + + LiteGraph.registerNodeType("math/converter", Converter); + + //Bypass + function Bypass() { + this.addInput("in"); + this.addOutput("out"); + this.size = [80, 30]; + } + + Bypass.title = "Bypass"; + Bypass.desc = "removes the type"; + + Bypass.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/bypass", Bypass); + + function ToNumber() { + this.addInput("in"); + this.addOutput("out"); + } + + ToNumber.title = "to Number"; + ToNumber.desc = "Cast to number"; + + ToNumber.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, Number(v)); + }; + + LiteGraph.registerNodeType("math/to_number", ToNumber); + + function MathRange() { + this.addInput("in", "number", { locked: true }); + this.addOutput("out", "number", { locked: true }); + this.addOutput("clamped", "number", { locked: true }); + + this.addProperty("in", 0); + this.addProperty("in_min", 0); + this.addProperty("in_max", 1); + this.addProperty("out_min", 0); + this.addProperty("out_max", 1); + + this.size = [120, 50]; + } + + MathRange.title = "Range"; + MathRange.desc = "Convert a number from one range to another"; + + MathRange.prototype.getTitle = function() { + if (this.flags.collapsed) { + return (this._last_v || 0).toFixed(2); + } + return this.title; + }; + + MathRange.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var v = this.properties["in"]; + if (v === undefined || v === null || v.constructor !== Number) { + v = 0; + } + + var in_min = this.properties.in_min; + var in_max = this.properties.in_max; + var out_min = this.properties.out_min; + var out_max = this.properties.out_max; + /* + if( in_min > in_max ) + { + in_min = in_max; + in_max = this.properties.in_min; + } + if( out_min > out_max ) + { + out_min = out_max; + out_max = this.properties.out_min; + } + */ + + this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; + this.setOutputData(0, this._last_v); + this.setOutputData(1, clamp( this._last_v, out_min, out_max )); + }; + + MathRange.prototype.onDrawBackground = function(ctx) { + //show the current value + if (this._last_v) { + this.outputs[0].label = this._last_v.toFixed(3); + } else { + this.outputs[0].label = "?"; + } + }; + + MathRange.prototype.onGetInputs = function() { + return [ + ["in_min", "number"], + ["in_max", "number"], + ["out_min", "number"], + ["out_max", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/range", MathRange); + + function MathRand() { + this.addOutput("value", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.size = [80, 30]; + } + + MathRand.title = "Rand"; + MathRand.desc = "Random number"; + + MathRand.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var min = this.properties.min; + var max = this.properties.max; + this._last_v = Math.random() * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathRand.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + MathRand.prototype.onGetInputs = function() { + return [["min", "number"], ["max", "number"]]; + }; + + LiteGraph.registerNodeType("math/rand", MathRand); + + //basic continuous noise + function MathNoise() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.addProperty("smooth", true); + this.addProperty("seed", 0); + this.addProperty("octaves", 1); + this.addProperty("persistence", 0.8); + this.addProperty("speed", 1); + this.size = [90, 30]; + } + + MathNoise.title = "Noise"; + MathNoise.desc = "Random number with temporal continuity"; + MathNoise.data = null; + + MathNoise.getValue = function(f, smooth) { + if (!MathNoise.data) { + MathNoise.data = new Float32Array(1024); + for (var i = 0; i < MathNoise.data.length; ++i) { + MathNoise.data[i] = Math.random(); + } + } + f = f % 1024; + if (f < 0) { + f += 1024; + } + var f_min = Math.floor(f); + var f = f - f_min; + var r1 = MathNoise.data[f_min]; + var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; + if (smooth) { + f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); + } + return r1 * (1 - f) + r2 * f; + }; + + MathNoise.prototype.onExecute = function() { + var f = this.getInputData(0) || 0; + var iterations = this.properties.octaves || 1; + var r = 0; + var amp = 1; + var seed = this.properties.seed || 0; + f += seed; + var speed = this.properties.speed || 1; + var total_amp = 0; + for(var i = 0; i < iterations; ++i) + { + r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; + total_amp += amp; + amp *= this.properties.persistence; + if(amp < 0.001) + break; + } + r /= total_amp; + var min = this.properties.min; + var max = this.properties.max; + this._last_v = r * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathNoise.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + LiteGraph.registerNodeType("math/noise", MathNoise); + + //generates spikes every random time + function MathSpikes() { + this.addOutput("out", "number"); + this.addProperty("min_time", 1); + this.addProperty("max_time", 2); + this.addProperty("duration", 0.2); + this.size = [90, 30]; + this._remaining_time = 0; + this._blink_time = 0; + } + + MathSpikes.title = "Spikes"; + MathSpikes.desc = "spike every random time"; + + MathSpikes.prototype.onExecute = function() { + var dt = this.graph.elapsed_time; //in secs + + this._remaining_time -= dt; + this._blink_time -= dt; + + var v = 0; + if (this._blink_time > 0) { + var f = this._blink_time / this.properties.duration; + v = 1 / (Math.pow(f * 8 - 4, 4) + 1); + } + + if (this._remaining_time < 0) { + this._remaining_time = + Math.random() * + (this.properties.max_time - this.properties.min_time) + + this.properties.min_time; + this._blink_time = this.properties.duration; + this.boxcolor = "#FFF"; + } else { + this.boxcolor = "#000"; + } + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/spikes", MathSpikes); + + //Math clamp + function MathClamp() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("min", 0); + this.addProperty("max", 1); + } + + MathClamp.title = "Clamp"; + MathClamp.desc = "Clamp number between min and max"; + //MathClamp.filter = "shader"; + + MathClamp.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + v = Math.max(this.properties.min, v); + v = Math.min(this.properties.max, v); + this.setOutputData(0, v); + }; + + MathClamp.prototype.getCode = function(lang) { + var code = ""; + if (this.isInputConnected(0)) { + code += + "clamp({{0}}," + + this.properties.min + + "," + + this.properties.max + + ")"; + } + return code; + }; + + LiteGraph.registerNodeType("math/clamp", MathClamp); + + //Math ABS + function MathLerp() { + this.properties = { f: 0.5 }; + this.addInput("A", "number"); + this.addInput("B", "number"); + + this.addOutput("out", "number"); + } + + MathLerp.title = "Lerp"; + MathLerp.desc = "Linear Interpolation"; + + MathLerp.prototype.onExecute = function() { + var v1 = this.getInputData(0); + if (v1 == null) { + v1 = 0; + } + var v2 = this.getInputData(1); + if (v2 == null) { + v2 = 0; + } + + var f = this.properties.f; + + var _f = this.getInputData(2); + if (_f !== undefined) { + f = _f; + } + + this.setOutputData(0, v1 * (1 - f) + v2 * f); + }; + + MathLerp.prototype.onGetInputs = function() { + return [["f", "number"]]; + }; + + LiteGraph.registerNodeType("math/lerp", MathLerp); + + //Math ABS + function MathAbs() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathAbs.title = "Abs"; + MathAbs.desc = "Absolute"; + + MathAbs.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.abs(v)); + }; + + LiteGraph.registerNodeType("math/abs", MathAbs); + + //Math Floor + function MathFloor() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFloor.title = "Floor"; + MathFloor.desc = "Floor number to remove fractional part"; + + MathFloor.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.floor(v)); + }; + + LiteGraph.registerNodeType("math/floor", MathFloor); + + //Math frac + function MathFrac() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFrac.title = "Frac"; + MathFrac.desc = "Returns fractional part"; + + MathFrac.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, v % 1); + }; + + LiteGraph.registerNodeType("math/frac", MathFrac); + + //Math Floor + function MathSmoothStep() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.properties = { A: 0, B: 1 }; + } + + MathSmoothStep.title = "Smoothstep"; + MathSmoothStep.desc = "Smoothstep"; + + MathSmoothStep.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v === undefined) { + return; + } + + var edge0 = this.properties.A; + var edge1 = this.properties.B; + + // Scale, bias and saturate x to 0..1 range + v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0); + // Evaluate polynomial + v = v * v * (3 - 2 * v); + + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep); + + //Math scale + function MathScale() { + this.addInput("in", "number", { label: "" }); + this.addOutput("out", "number", { label: "" }); + this.size = [80, 30]; + this.addProperty("factor", 1); + } + + MathScale.title = "Scale"; + MathScale.desc = "v * factor"; + + MathScale.prototype.onExecute = function() { + var value = this.getInputData(0); + if (value != null) { + this.setOutputData(0, value * this.properties.factor); + } + }; + + LiteGraph.registerNodeType("math/scale", MathScale); + + //Gate + function Gate() { + this.addInput("v","boolean"); + this.addInput("A"); + this.addInput("B"); + this.addOutput("out"); + } + + Gate.title = "Gate"; + Gate.desc = "if v is true, then outputs A, otherwise B"; + + Gate.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, this.getInputData( v ? 1 : 2 )); + }; + + LiteGraph.registerNodeType("math/gate", Gate); + + + //Math Average + function MathAverageFilter() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("samples", 10); + this._values = new Float32Array(10); + this._current = 0; + } + + MathAverageFilter.title = "Average"; + MathAverageFilter.desc = "Average Filter"; + + MathAverageFilter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + + var num_samples = this._values.length; + + this._values[this._current % num_samples] = v; + this._current += 1; + if (this._current > num_samples) { + this._current = 0; + } + + var avr = 0; + for (var i = 0; i < num_samples; ++i) { + avr += this._values[i]; + } + + this.setOutputData(0, avr / num_samples); + }; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (value < 1) { + value = 1; + } + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array(this.properties.samples); + if (old.length <= this._values.length) { + this._values.set(old); + } else { + this._values.set(old.subarray(0, this._values.length)); + } + }; + + LiteGraph.registerNodeType("math/average", MathAverageFilter); + + //Math + function MathTendTo() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("factor", 0.1); + this.size = [80, 30]; + this._value = null; + } + + MathTendTo.title = "TendTo"; + MathTendTo.desc = "moves the output value always closer to the input"; + + MathTendTo.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var f = this.properties.factor; + if (this._value == null) { + this._value = v; + } else { + this._value = this._value * (1 - f) + v * f; + } + this.setOutputData(0, this._value); + }; + + LiteGraph.registerNodeType("math/tendTo", MathTendTo); + + //Math operation + function MathOperation() { + this.addInput("A", "number,array,object"); + this.addInput("B", "number"); + this.addOutput("=", "number"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "+", "enum", { values: MathOperation.values }); + this._func = MathOperation.funcs[this.properties.OP]; + this._result = []; //only used for arrays + } + + MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; + MathOperation.funcs = { + "+": function(A,B) { return A + B; }, + "-": function(A,B) { return A - B; }, + "x": function(A,B) { return A * B; }, + "X": function(A,B) { return A * B; }, + "*": function(A,B) { return A * B; }, + "/": function(A,B) { return A / B; }, + "%": function(A,B) { return A % B; }, + "^": function(A,B) { return Math.pow(A, B); }, + "max": function(A,B) { return Math.max(A, B); }, + "min": function(A,B) { return Math.min(A, B); } + }; + + MathOperation.title = "Operation"; + MathOperation.desc = "Easy math operators"; + MathOperation["@OP"] = { + type: "enum", + title: "operation", + values: MathOperation.values + }; + MathOperation.size = [100, 60]; + + MathOperation.prototype.getTitle = function() { + if(this.properties.OP == "max" || this.properties.OP == "min") + return this.properties.OP + "(A,B)"; + return "A " + this.properties.OP + " B"; + }; + + MathOperation.prototype.setValue = function(v) { + if (typeof v == "string") { + v = parseFloat(v); + } + this.properties["value"] = v; + }; + + MathOperation.prototype.onPropertyChanged = function(name, value) + { + if (name != "OP") + return; + this._func = MathOperation.funcs[this.properties.OP]; + if(!this._func) + { + console.warn("Unknown operation: " + this.properties.OP); + this._func = function(A) { return A; }; + } + } + + MathOperation.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if ( A != null ) { + if( A.constructor === Number ) + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B != null) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + var func = MathOperation.funcs[this.properties.OP]; + + var result; + if(A.constructor === Number) + { + result = 0; + result = func(A,B); + } + else if(A.constructor === Array) + { + result = this._result; + result.length = A.length; + for(var i = 0; i < A.length; ++i) + result[i] = func(A[i],B); + } + else + { + result = {}; + for(var i in A) + result[i] = func(A[i],B); + } + this.setOutputData(0, result); + }; + + MathOperation.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.font = "40px Arial"; + ctx.fillStyle = "#666"; + ctx.textAlign = "center"; + ctx.fillText( + this.properties.OP, + this.size[0] * 0.5, + (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 + ); + ctx.textAlign = "left"; + }; + + LiteGraph.registerNodeType("math/operation", MathOperation); + + LiteGraph.registerSearchboxExtra("math/operation", "MAX", { + properties: {OP:"max"}, + title: "MAX()" + }); + + LiteGraph.registerSearchboxExtra("math/operation", "MIN", { + properties: {OP:"min"}, + title: "MIN()" + }); + + + //Math compare + function MathCompare() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("A==B", "boolean"); + this.addOutput("A!=B", "boolean"); + this.addProperty("A", 0); + this.addProperty("B", 0); + } + + MathCompare.title = "Compare"; + MathCompare.desc = "compares between two values"; + + MathCompare.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if (A !== undefined) { + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B !== undefined) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var value; + switch (output.name) { + case "A==B": + value = A == B; + break; + case "A!=B": + value = A != B; + break; + case "A>B": + value = A > B; + break; + case "A=B": + value = A >= B; + break; + } + this.setOutputData(i, value); + } + }; + + MathCompare.prototype.onGetOutputs = function() { + return [ + ["A==B", "boolean"], + ["A!=B", "boolean"], + ["A>B", "boolean"], + ["A=B", "boolean"], + ["A<=B", "boolean"] + ]; + }; + + LiteGraph.registerNodeType("math/compare", MathCompare); + + LiteGraph.registerSearchboxExtra("math/compare", "==", { + outputs: [["A==B", "boolean"]], + title: "A==B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "!=", { + outputs: [["A!=B", "boolean"]], + title: "A!=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", ">", { + outputs: [["A>B", "boolean"]], + title: "A>B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<", { + outputs: [["A=", { + outputs: [["A>=B", "boolean"]], + title: "A>=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<=", { + outputs: [["A<=B", "boolean"]], + title: "A<=B" + }); + + function MathCondition() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", ">", "enum", { values: MathCondition.values }); + this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); + + this.size = [80, 60]; + } + + MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + MathCondition["@OP"] = { + type: "enum", + title: "operation", + values: MathCondition.values + }; + + MathCondition.title = "Condition"; + MathCondition.desc = "evaluates condition between A and B"; + + MathCondition.prototype.getTitle = function() { + return "A " + this.properties.OP + " B"; + }; + + MathCondition.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A === undefined) { + A = this.properties.A; + } else { + this.properties.A = A; + } + + var B = this.getInputData(1); + if (B === undefined) { + B = this.properties.B; + } else { + this.properties.B = B; + } + + var result = true; + switch (this.properties.OP) { + case ">": + result = A > B; + break; + case "<": + result = A < B; + break; + case "==": + result = A == B; + break; + case "!=": + result = A != B; + break; + case "<=": + result = A <= B; + break; + case ">=": + result = A >= B; + break; + case "||": + result = A || B; + break; + case "&&": + result = A && B; + break; + } + + this.setOutputData(0, result); + this.setOutputData(1, !result); + }; + + LiteGraph.registerNodeType("math/condition", MathCondition); + + + function MathBranch() { + this.addInput("in", 0); + this.addInput("cond", "boolean"); + this.addOutput("true", 0); + this.addOutput("false", 0); + this.size = [80, 60]; + } + + MathBranch.title = "Branch"; + MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; + + MathBranch.prototype.onExecute = function() { + var V = this.getInputData(0); + var cond = this.getInputData(1); + + if(cond) + { + this.setOutputData(0, V); + this.setOutputData(1, null); + } + else + { + this.setOutputData(0, null); + this.setOutputData(1, V); + } + } + + LiteGraph.registerNodeType("math/branch", MathBranch); + + + function MathAccumulate() { + this.addInput("inc", "number"); + this.addOutput("total", "number"); + this.addProperty("increment", 1); + this.addProperty("value", 0); + } + + MathAccumulate.title = "Accumulate"; + MathAccumulate.desc = "Increments a value every time"; + + MathAccumulate.prototype.onExecute = function() { + if (this.properties.value === null) { + this.properties.value = 0; + } + + var inc = this.getInputData(0); + if (inc !== null) { + this.properties.value += inc; + } else { + this.properties.value += this.properties.increment; + } + this.setOutputData(0, this.properties.value); + }; + + LiteGraph.registerNodeType("math/accumulate", MathAccumulate); + + //Math Trigonometry + function MathTrigonometry() { + this.addInput("v", "number"); + this.addOutput("sin", "number"); + + this.addProperty("amplitude", 1); + this.addProperty("offset", 0); + this.bgImageUrl = "nodes/imgs/icon-sin.png"; + } + + MathTrigonometry.title = "Trigonometry"; + MathTrigonometry.desc = "Sin Cos Tan"; + //MathTrigonometry.filter = "shader"; + + MathTrigonometry.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var amplitude = this.properties["amplitude"]; + var slot = this.findInputSlot("amplitude"); + if (slot != -1) { + amplitude = this.getInputData(slot); + } + var offset = this.properties["offset"]; + slot = this.findInputSlot("offset"); + if (slot != -1) { + offset = this.getInputData(slot); + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + var value; + switch (output.name) { + case "sin": + value = Math.sin(v); + break; + case "cos": + value = Math.cos(v); + break; + case "tan": + value = Math.tan(v); + break; + case "asin": + value = Math.asin(v); + break; + case "acos": + value = Math.acos(v); + break; + case "atan": + value = Math.atan(v); + break; + } + this.setOutputData(i, amplitude * value + offset); + } + }; + + MathTrigonometry.prototype.onGetInputs = function() { + return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; + }; + + MathTrigonometry.prototype.onGetOutputs = function() { + return [ + ["sin", "number"], + ["cos", "number"], + ["tan", "number"], + ["asin", "number"], + ["acos", "number"], + ["atan", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry); + + LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { + outputs: [["sin", "number"]], + title: "SIN()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { + outputs: [["cos", "number"]], + title: "COS()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { + outputs: [["tan", "number"]], + title: "TAN()" + }); + + //math library for safe math operations without eval + function MathFormula() { + this.addInput("x", "number"); + this.addInput("y", "number"); + this.addOutput("", "number"); + this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; + this.code_widget = this.addWidget( + "text", + "F(x,y)", + this.properties.formula, + function(v, canvas, node) { + node.properties.formula = v; + } + ); + this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { + LiteGraph.allow_scripts = v; + }); + this._func = null; + } + + MathFormula.title = "Formula"; + MathFormula.desc = "Compute formula"; + MathFormula.size = [160, 100]; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (name == "formula") { + this.code_widget.value = value; + } + }; + + MathFormula.prototype.onExecute = function() { + if (!LiteGraph.allow_scripts) { + return; + } + + var x = this.getInputData(0); + var y = this.getInputData(1); + if (x != null) { + this.properties["x"] = x; + } else { + x = this.properties["x"]; + } + + if (y != null) { + this.properties["y"] = y; + } else { + y = this.properties["y"]; + } + + var f = this.properties["formula"]; + + var value; + try { + if (!this._func || this._func_code != this.properties.formula) { + this._func = new Function( + "x", + "y", + "TIME", + "return " + this.properties.formula + ); + this._func_code = this.properties.formula; + } + value = this._func(x, y, this.graph.globaltime); + this.boxcolor = null; + } catch (err) { + this.boxcolor = "red"; + } + this.setOutputData(0, value); + }; + + MathFormula.prototype.getTitle = function() { + return this._func_code || "Formula"; + }; + + MathFormula.prototype.onDrawBackground = function() { + var f = this.properties["formula"]; + if (this.outputs && this.outputs.length) { + this.outputs[0].label = f; + } + }; + + LiteGraph.registerNodeType("math/formula", MathFormula); + + function Math3DVec2ToXY() { + this.addInput("vec2", "vec2"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + } + + Math3DVec2ToXY.title = "Vec2->XY"; + Math3DVec2ToXY.desc = "vector 2 to components"; + + Math3DVec2ToXY.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + }; + + LiteGraph.registerNodeType("math3d/vec2-to-xy", Math3DVec2ToXY); + + function Math3DXYToVec2() { + this.addInputs([["x", "number"], ["y", "number"]]); + this.addOutput("vec2", "vec2"); + this.properties = { x: 0, y: 0 }; + this._data = new Float32Array(2); + } + + Math3DXYToVec2.title = "XY->Vec2"; + Math3DXYToVec2.desc = "components to vector2"; + + Math3DXYToVec2.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; } + + var data = this._data; + data[0] = x; + data[1] = y; + + this.setOutputData(0, data); }; - GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; - GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; + LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); - GamepadInput.prototype.getGamepad = function() { - var getGamepads = - navigator.getGamepads || - navigator.webkitGetGamepads || - navigator.mozGetGamepads; - if (!getGamepads) { - return null; - } - var gamepads = getGamepads.call(navigator); - var gamepad = null; + function Math3DVec3ToXYZ() { + this.addInput("vec3", "vec3"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + } - this._previous_buttons.set(this._current_buttons); + Math3DVec3ToXYZ.title = "Vec3->XYZ"; + Math3DVec3ToXYZ.desc = "vector 3 to components"; - //pick the first connected - for (var i = this.properties.gamepad_index; i < 4; i++) { - if (!gamepads[i]) { - continue; - } - gamepad = gamepads[i]; + Math3DVec3ToXYZ.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } - //xbox controller mapping - var xbox = this.xbox_mapping; - if (!xbox) { - xbox = this.xbox_mapping = { - axes: [], - buttons: {}, - hat: "", - hatmap: GamepadInput.CENTER - }; - } + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + }; - xbox.axes["lx"] = gamepad.axes[0]; - xbox.axes["ly"] = gamepad.axes[1]; - xbox.axes["rx"] = gamepad.axes[2]; - xbox.axes["ry"] = gamepad.axes[3]; - xbox.axes["ltrigger"] = gamepad.buttons[6].value; - xbox.axes["rtrigger"] = gamepad.buttons[7].value; - xbox.hat = ""; - xbox.hatmap = GamepadInput.CENTER; + LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); - for (var j = 0; j < gamepad.buttons.length; j++) { - this._current_buttons[j] = gamepad.buttons[j].pressed; + function Math3DXYZToVec3() { + this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); + this.addOutput("vec3", "vec3"); + this.properties = { x: 0, y: 0, z: 0 }; + this._data = new Float32Array(3); + } - if(j < 12) - { - xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; - if(gamepad.buttons[j].was_pressed) - this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); - } - else //mapping of XBOX - switch ( j ) //I use a switch to ensure that a player with another gamepad could play - { - case 12: - if (gamepad.buttons[j].pressed) { - xbox.hat += "up"; - xbox.hatmap |= GamepadInput.UP; - } - break; - case 13: - if (gamepad.buttons[j].pressed) { - xbox.hat += "down"; - xbox.hatmap |= GamepadInput.DOWN; - } - break; - case 14: - if (gamepad.buttons[j].pressed) { - xbox.hat += "left"; - xbox.hatmap |= GamepadInput.LEFT; - } - break; - case 15: - if (gamepad.buttons[j].pressed) { - xbox.hat += "right"; - xbox.hatmap |= GamepadInput.RIGHT; - } - break; - case 16: - xbox.buttons["home"] = gamepad.buttons[j].pressed; - break; - default: - } - } - gamepad.xbox = xbox; - return gamepad; + Math3DXYZToVec3.title = "XYZ->Vec3"; + Math3DXYZToVec3.desc = "components to vector3"; + + Math3DXYZToVec3.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + + this.setOutputData(0, data); }; - GamepadInput.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { + LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); + + function Math3DVec4ToXYZW() { + this.addInput("vec4", "vec4"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + this.addOutput("w", "number"); + } + + Math3DVec4ToXYZW.title = "Vec4->XYZW"; + Math3DVec4ToXYZW.desc = "vector 4 to components"; + + Math3DVec4ToXYZW.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { return; } - //render gamepad state? - var la = this._left_axis; - var ra = this._right_axis; - ctx.strokeStyle = "#88A"; - ctx.strokeRect( - (la[0] + 1) * 0.5 * this.size[0] - 4, - (la[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - ctx.strokeStyle = "#8A8"; - ctx.strokeRect( - (ra[0] + 1) * 0.5 * this.size[0] - 4, - (ra[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - var h = this.size[1] / this._current_buttons.length; - ctx.fillStyle = "#AEB"; - for (var i = 0; i < this._current_buttons.length; ++i) { - if (this._current_buttons[i]) { - ctx.fillRect(0, h * i, 6, h); - } - } + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + this.setOutputData(3, v[3]); }; - GamepadInput.prototype.onGetOutputs = function() { - return [ - ["left_axis", "vec2"], - ["right_axis", "vec2"], - ["left_x_axis", "number"], - ["left_y_axis", "number"], - ["right_x_axis", "number"], - ["right_y_axis", "number"], - ["trigger_left", "number"], - ["trigger_right", "number"], - ["a_button", "number"], - ["b_button", "number"], - ["x_button", "number"], - ["y_button", "number"], - ["lb_button", "number"], - ["rb_button", "number"], - ["ls_button", "number"], - ["rs_button", "number"], - ["start_button", "number"], - ["back_button", "number"], - ["a_button_event", LiteGraph.EVENT ], - ["b_button_event", LiteGraph.EVENT ], - ["x_button_event", LiteGraph.EVENT ], - ["y_button_event", LiteGraph.EVENT ], - ["lb_button_event", LiteGraph.EVENT ], - ["rb_button_event", LiteGraph.EVENT ], - ["ls_button_event", LiteGraph.EVENT ], - ["rs_button_event", LiteGraph.EVENT ], - ["start_button_event", LiteGraph.EVENT ], - ["back_button_event", LiteGraph.EVENT ], - ["hat_left", "number"], - ["hat_right", "number"], - ["hat_up", "number"], - ["hat_down", "number"], - ["hat", "number"], - ["button_pressed", LiteGraph.EVENT] - ]; + LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); + + function Math3DXYZWToVec4() { + this.addInputs([ + ["x", "number"], + ["y", "number"], + ["z", "number"], + ["w", "number"] + ]); + this.addOutput("vec4", "vec4"); + this.properties = { x: 0, y: 0, z: 0, w: 0 }; + this._data = new Float32Array(4); + } + + Math3DXYZWToVec4.title = "XYZW->Vec4"; + Math3DXYZWToVec4.desc = "components to vector4"; + + Math3DXYZWToVec4.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + var w = this.getInputData(3); + if (w == null) { + w = this.properties.w; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + + this.setOutputData(0, data); }; - LiteGraph.registerNodeType("input/gamepad", GamepadInput); + LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); })(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - //Converter - function Converter() { - this.addInput("in", 0); - this.addOutput("out", 0); - this.size = [80, 30]; - } - - Converter.title = "Converter"; - Converter.desc = "type A to type B"; - - Converter.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - - var result = null; - switch (output.name) { - case "number": - result = v.length ? v[0] : parseFloat(v); - break; - case "vec2": - case "vec3": - case "vec4": - var result = null; - var count = 1; - switch (output.name) { - case "vec2": - count = 2; - break; - case "vec3": - count = 3; - break; - case "vec4": - count = 4; - break; - } - - var result = new Float32Array(count); - if (v.length) { - for ( - var j = 0; - j < v.length && j < result.length; - j++ - ) { - result[j] = v[j]; - } - } else { - result[0] = parseFloat(v); - } - break; - } - this.setOutputData(i, result); - } - } - }; - - Converter.prototype.onGetOutputs = function() { - return [ - ["number", "number"], - ["vec2", "vec2"], - ["vec3", "vec3"], - ["vec4", "vec4"] - ]; - }; - - LiteGraph.registerNodeType("math/converter", Converter); - - //Bypass - function Bypass() { - this.addInput("in"); - this.addOutput("out"); - this.size = [80, 30]; - } - - Bypass.title = "Bypass"; - Bypass.desc = "removes the type"; - - Bypass.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/bypass", Bypass); - - function ToNumber() { - this.addInput("in"); - this.addOutput("out"); - } - - ToNumber.title = "to Number"; - ToNumber.desc = "Cast to number"; - - ToNumber.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, Number(v)); - }; - - LiteGraph.registerNodeType("math/to_number", ToNumber); - - function MathRange() { - this.addInput("in", "number", { locked: true }); - this.addOutput("out", "number", { locked: true }); - this.addOutput("clamped", "number", { locked: true }); - - this.addProperty("in", 0); - this.addProperty("in_min", 0); - this.addProperty("in_max", 1); - this.addProperty("out_min", 0); - this.addProperty("out_max", 1); - - this.size = [120, 50]; - } - - MathRange.title = "Range"; - MathRange.desc = "Convert a number from one range to another"; - - MathRange.prototype.getTitle = function() { - if (this.flags.collapsed) { - return (this._last_v || 0).toFixed(2); - } - return this.title; - }; - - MathRange.prototype.onExecute = function() { - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - this.properties[input.name] = v; - } - } - - var v = this.properties["in"]; - if (v === undefined || v === null || v.constructor !== Number) { - v = 0; - } - - var in_min = this.properties.in_min; - var in_max = this.properties.in_max; - var out_min = this.properties.out_min; - var out_max = this.properties.out_max; - /* - if( in_min > in_max ) - { - in_min = in_max; - in_max = this.properties.in_min; - } - if( out_min > out_max ) - { - out_min = out_max; - out_max = this.properties.out_min; - } - */ - - this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; - this.setOutputData(0, this._last_v); - this.setOutputData(1, clamp( this._last_v, out_min, out_max )); - }; - - MathRange.prototype.onDrawBackground = function(ctx) { - //show the current value - if (this._last_v) { - this.outputs[0].label = this._last_v.toFixed(3); - } else { - this.outputs[0].label = "?"; - } - }; - - MathRange.prototype.onGetInputs = function() { - return [ - ["in_min", "number"], - ["in_max", "number"], - ["out_min", "number"], - ["out_max", "number"] - ]; - }; - - LiteGraph.registerNodeType("math/range", MathRange); - - function MathRand() { - this.addOutput("value", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.size = [80, 30]; - } - - MathRand.title = "Rand"; - MathRand.desc = "Random number"; - - MathRand.prototype.onExecute = function() { - if (this.inputs) { - for (var i = 0; i < this.inputs.length; i++) { - var input = this.inputs[i]; - var v = this.getInputData(i); - if (v === undefined) { - continue; - } - this.properties[input.name] = v; - } - } - - var min = this.properties.min; - var max = this.properties.max; - this._last_v = Math.random() * (max - min) + min; - this.setOutputData(0, this._last_v); - }; - - MathRand.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - MathRand.prototype.onGetInputs = function() { - return [["min", "number"], ["max", "number"]]; - }; - - LiteGraph.registerNodeType("math/rand", MathRand); - - //basic continuous noise - function MathNoise() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("min", 0); - this.addProperty("max", 1); - this.addProperty("smooth", true); - this.addProperty("seed", 0); - this.addProperty("octaves", 1); - this.addProperty("persistence", 0.8); - this.addProperty("speed", 1); - this.size = [90, 30]; - } - - MathNoise.title = "Noise"; - MathNoise.desc = "Random number with temporal continuity"; - MathNoise.data = null; - - MathNoise.getValue = function(f, smooth) { - if (!MathNoise.data) { - MathNoise.data = new Float32Array(1024); - for (var i = 0; i < MathNoise.data.length; ++i) { - MathNoise.data[i] = Math.random(); - } - } - f = f % 1024; - if (f < 0) { - f += 1024; - } - var f_min = Math.floor(f); - var f = f - f_min; - var r1 = MathNoise.data[f_min]; - var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; - if (smooth) { - f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); - } - return r1 * (1 - f) + r2 * f; - }; - - MathNoise.prototype.onExecute = function() { - var f = this.getInputData(0) || 0; - var iterations = this.properties.octaves || 1; - var r = 0; - var amp = 1; - var seed = this.properties.seed || 0; - f += seed; - var speed = this.properties.speed || 1; - var total_amp = 0; - for(var i = 0; i < iterations; ++i) - { - r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; - total_amp += amp; - amp *= this.properties.persistence; - if(amp < 0.001) - break; - } - r /= total_amp; - var min = this.properties.min; - var max = this.properties.max; - this._last_v = r * (max - min) + min; - this.setOutputData(0, this._last_v); - }; - - MathNoise.prototype.onDrawBackground = function(ctx) { - //show the current value - this.outputs[0].label = (this._last_v || 0).toFixed(3); - }; - - LiteGraph.registerNodeType("math/noise", MathNoise); - - //generates spikes every random time - function MathSpikes() { - this.addOutput("out", "number"); - this.addProperty("min_time", 1); - this.addProperty("max_time", 2); - this.addProperty("duration", 0.2); - this.size = [90, 30]; - this._remaining_time = 0; - this._blink_time = 0; - } - - MathSpikes.title = "Spikes"; - MathSpikes.desc = "spike every random time"; - - MathSpikes.prototype.onExecute = function() { - var dt = this.graph.elapsed_time; //in secs - - this._remaining_time -= dt; - this._blink_time -= dt; - - var v = 0; - if (this._blink_time > 0) { - var f = this._blink_time / this.properties.duration; - v = 1 / (Math.pow(f * 8 - 4, 4) + 1); - } - - if (this._remaining_time < 0) { - this._remaining_time = - Math.random() * - (this.properties.max_time - this.properties.min_time) + - this.properties.min_time; - this._blink_time = this.properties.duration; - this.boxcolor = "#FFF"; - } else { - this.boxcolor = "#000"; - } - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/spikes", MathSpikes); - - //Math clamp - function MathClamp() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("min", 0); - this.addProperty("max", 1); - } - - MathClamp.title = "Clamp"; - MathClamp.desc = "Clamp number between min and max"; - //MathClamp.filter = "shader"; - - MathClamp.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - v = Math.max(this.properties.min, v); - v = Math.min(this.properties.max, v); - this.setOutputData(0, v); - }; - - MathClamp.prototype.getCode = function(lang) { - var code = ""; - if (this.isInputConnected(0)) { - code += - "clamp({{0}}," + - this.properties.min + - "," + - this.properties.max + - ")"; - } - return code; - }; - - LiteGraph.registerNodeType("math/clamp", MathClamp); - - //Math ABS - function MathLerp() { - this.properties = { f: 0.5 }; - this.addInput("A", "number"); - this.addInput("B", "number"); - - this.addOutput("out", "number"); - } - - MathLerp.title = "Lerp"; - MathLerp.desc = "Linear Interpolation"; - - MathLerp.prototype.onExecute = function() { - var v1 = this.getInputData(0); - if (v1 == null) { - v1 = 0; - } - var v2 = this.getInputData(1); - if (v2 == null) { - v2 = 0; - } - - var f = this.properties.f; - - var _f = this.getInputData(2); - if (_f !== undefined) { - f = _f; - } - - this.setOutputData(0, v1 * (1 - f) + v2 * f); - }; - - MathLerp.prototype.onGetInputs = function() { - return [["f", "number"]]; - }; - - LiteGraph.registerNodeType("math/lerp", MathLerp); - - //Math ABS - function MathAbs() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathAbs.title = "Abs"; - MathAbs.desc = "Absolute"; - - MathAbs.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, Math.abs(v)); - }; - - LiteGraph.registerNodeType("math/abs", MathAbs); - - //Math Floor - function MathFloor() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathFloor.title = "Floor"; - MathFloor.desc = "Floor number to remove fractional part"; - - MathFloor.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, Math.floor(v)); - }; - - LiteGraph.registerNodeType("math/floor", MathFloor); - - //Math frac - function MathFrac() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - } - - MathFrac.title = "Frac"; - MathFrac.desc = "Returns fractional part"; - - MathFrac.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - this.setOutputData(0, v % 1); - }; - - LiteGraph.registerNodeType("math/frac", MathFrac); - - //Math Floor - function MathSmoothStep() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.properties = { A: 0, B: 1 }; - } - - MathSmoothStep.title = "Smoothstep"; - MathSmoothStep.desc = "Smoothstep"; - - MathSmoothStep.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v === undefined) { - return; - } - - var edge0 = this.properties.A; - var edge1 = this.properties.B; - - // Scale, bias and saturate x to 0..1 range - v = clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0); - // Evaluate polynomial - v = v * v * (3 - 2 * v); - - this.setOutputData(0, v); - }; - - LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep); - - //Math scale - function MathScale() { - this.addInput("in", "number", { label: "" }); - this.addOutput("out", "number", { label: "" }); - this.size = [80, 30]; - this.addProperty("factor", 1); - } - - MathScale.title = "Scale"; - MathScale.desc = "v * factor"; - - MathScale.prototype.onExecute = function() { - var value = this.getInputData(0); - if (value != null) { - this.setOutputData(0, value * this.properties.factor); - } - }; - - LiteGraph.registerNodeType("math/scale", MathScale); - - //Gate - function Gate() { - this.addInput("v","boolean"); - this.addInput("A"); - this.addInput("B"); - this.addOutput("out"); - } - - Gate.title = "Gate"; - Gate.desc = "if v is true, then outputs A, otherwise B"; - - Gate.prototype.onExecute = function() { - var v = this.getInputData(0); - this.setOutputData(0, this.getInputData( v ? 1 : 2 )); - }; - - LiteGraph.registerNodeType("math/gate", Gate); - - - //Math Average - function MathAverageFilter() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.size = [80, 30]; - this.addProperty("samples", 10); - this._values = new Float32Array(10); - this._current = 0; - } - - MathAverageFilter.title = "Average"; - MathAverageFilter.desc = "Average Filter"; - - MathAverageFilter.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - - var num_samples = this._values.length; - - this._values[this._current % num_samples] = v; - this._current += 1; - if (this._current > num_samples) { - this._current = 0; - } - - var avr = 0; - for (var i = 0; i < num_samples; ++i) { - avr += this._values[i]; - } - - this.setOutputData(0, avr / num_samples); - }; - - MathAverageFilter.prototype.onPropertyChanged = function(name, value) { - if (value < 1) { - value = 1; - } - this.properties.samples = Math.round(value); - var old = this._values; - - this._values = new Float32Array(this.properties.samples); - if (old.length <= this._values.length) { - this._values.set(old); - } else { - this._values.set(old.subarray(0, this._values.length)); - } - }; - - LiteGraph.registerNodeType("math/average", MathAverageFilter); - - //Math - function MathTendTo() { - this.addInput("in", "number"); - this.addOutput("out", "number"); - this.addProperty("factor", 0.1); - this.size = [80, 30]; - this._value = null; - } - - MathTendTo.title = "TendTo"; - MathTendTo.desc = "moves the output value always closer to the input"; - - MathTendTo.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - var f = this.properties.factor; - if (this._value == null) { - this._value = v; - } else { - this._value = this._value * (1 - f) + v * f; - } - this.setOutputData(0, this._value); - }; - - LiteGraph.registerNodeType("math/tendTo", MathTendTo); - - //Math operation - function MathOperation() { - this.addInput("A", "number,array,object"); - this.addInput("B", "number"); - this.addOutput("=", "number"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", "+", "enum", { values: MathOperation.values }); - this._func = MathOperation.funcs[this.properties.OP]; - this._result = []; //only used for arrays - } - - MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; - MathOperation.funcs = { - "+": function(A,B) { return A + B; }, - "-": function(A,B) { return A - B; }, - "x": function(A,B) { return A * B; }, - "X": function(A,B) { return A * B; }, - "*": function(A,B) { return A * B; }, - "/": function(A,B) { return A / B; }, - "%": function(A,B) { return A % B; }, - "^": function(A,B) { return Math.pow(A, B); }, - "max": function(A,B) { return Math.max(A, B); }, - "min": function(A,B) { return Math.min(A, B); } - }; - - MathOperation.title = "Operation"; - MathOperation.desc = "Easy math operators"; - MathOperation["@OP"] = { - type: "enum", - title: "operation", - values: MathOperation.values - }; - MathOperation.size = [100, 60]; - - MathOperation.prototype.getTitle = function() { - if(this.properties.OP == "max" || this.properties.OP == "min") - return this.properties.OP + "(A,B)"; - return "A " + this.properties.OP + " B"; - }; - - MathOperation.prototype.setValue = function(v) { - if (typeof v == "string") { - v = parseFloat(v); - } - this.properties["value"] = v; - }; - - MathOperation.prototype.onPropertyChanged = function(name, value) - { - if (name != "OP") - return; - this._func = MathOperation.funcs[this.properties.OP]; - if(!this._func) - { - console.warn("Unknown operation: " + this.properties.OP); - this._func = function(A) { return A; }; - } - } - - MathOperation.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - if ( A != null ) { - if( A.constructor === Number ) - this.properties["A"] = A; - } else { - A = this.properties["A"]; - } - - if (B != null) { - this.properties["B"] = B; - } else { - B = this.properties["B"]; - } - - var func = MathOperation.funcs[this.properties.OP]; - - var result; - if(A.constructor === Number) - { - result = 0; - result = func(A,B); - } - else if(A.constructor === Array) - { - result = this._result; - result.length = A.length; - for(var i = 0; i < A.length; ++i) - result[i] = func(A[i],B); - } - else - { - result = {}; - for(var i in A) - result[i] = func(A[i],B); - } - this.setOutputData(0, result); - }; - - MathOperation.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - ctx.font = "40px Arial"; - ctx.fillStyle = "#666"; - ctx.textAlign = "center"; - ctx.fillText( - this.properties.OP, - this.size[0] * 0.5, - (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 - ); - ctx.textAlign = "left"; - }; - - LiteGraph.registerNodeType("math/operation", MathOperation); - - LiteGraph.registerSearchboxExtra("math/operation", "MAX", { - properties: {OP:"max"}, - title: "MAX()" - }); - - LiteGraph.registerSearchboxExtra("math/operation", "MIN", { - properties: {OP:"min"}, - title: "MIN()" - }); - - - //Math compare - function MathCompare() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("A==B", "boolean"); - this.addOutput("A!=B", "boolean"); - this.addProperty("A", 0); - this.addProperty("B", 0); - } - - MathCompare.title = "Compare"; - MathCompare.desc = "compares between two values"; - - MathCompare.prototype.onExecute = function() { - var A = this.getInputData(0); - var B = this.getInputData(1); - if (A !== undefined) { - this.properties["A"] = A; - } else { - A = this.properties["A"]; - } - - if (B !== undefined) { - this.properties["B"] = B; - } else { - B = this.properties["B"]; - } - - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - var value; - switch (output.name) { - case "A==B": - value = A == B; - break; - case "A!=B": - value = A != B; - break; - case "A>B": - value = A > B; - break; - case "A=B": - value = A >= B; - break; - } - this.setOutputData(i, value); - } - }; - - MathCompare.prototype.onGetOutputs = function() { - return [ - ["A==B", "boolean"], - ["A!=B", "boolean"], - ["A>B", "boolean"], - ["A=B", "boolean"], - ["A<=B", "boolean"] - ]; - }; - - LiteGraph.registerNodeType("math/compare", MathCompare); - - LiteGraph.registerSearchboxExtra("math/compare", "==", { - outputs: [["A==B", "boolean"]], - title: "A==B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "!=", { - outputs: [["A!=B", "boolean"]], - title: "A!=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", ">", { - outputs: [["A>B", "boolean"]], - title: "A>B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<", { - outputs: [["A=", { - outputs: [["A>=B", "boolean"]], - title: "A>=B" - }); - LiteGraph.registerSearchboxExtra("math/compare", "<=", { - outputs: [["A<=B", "boolean"]], - title: "A<=B" - }); - - function MathCondition() { - this.addInput("A", "number"); - this.addInput("B", "number"); - this.addOutput("true", "boolean"); - this.addOutput("false", "boolean"); - this.addProperty("A", 1); - this.addProperty("B", 1); - this.addProperty("OP", ">", "enum", { values: MathCondition.values }); - this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); - - this.size = [80, 60]; - } - - MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; - MathCondition["@OP"] = { - type: "enum", - title: "operation", - values: MathCondition.values - }; - - MathCondition.title = "Condition"; - MathCondition.desc = "evaluates condition between A and B"; - - MathCondition.prototype.getTitle = function() { - return "A " + this.properties.OP + " B"; - }; - - MathCondition.prototype.onExecute = function() { - var A = this.getInputData(0); - if (A === undefined) { - A = this.properties.A; - } else { - this.properties.A = A; - } - - var B = this.getInputData(1); - if (B === undefined) { - B = this.properties.B; - } else { - this.properties.B = B; - } - - var result = true; - switch (this.properties.OP) { - case ">": - result = A > B; - break; - case "<": - result = A < B; - break; - case "==": - result = A == B; - break; - case "!=": - result = A != B; - break; - case "<=": - result = A <= B; - break; - case ">=": - result = A >= B; - break; - case "||": - result = A || B; - break; - case "&&": - result = A && B; - break; - } - - this.setOutputData(0, result); - this.setOutputData(1, !result); - }; - - LiteGraph.registerNodeType("math/condition", MathCondition); - - - function MathBranch() { - this.addInput("in", 0); - this.addInput("cond", "boolean"); - this.addOutput("true", 0); - this.addOutput("false", 0); - this.size = [80, 60]; - } - - MathBranch.title = "Branch"; - MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; - - MathBranch.prototype.onExecute = function() { - var V = this.getInputData(0); - var cond = this.getInputData(1); - - if(cond) - { - this.setOutputData(0, V); - this.setOutputData(1, null); - } - else - { - this.setOutputData(0, null); - this.setOutputData(1, V); - } - } - - LiteGraph.registerNodeType("math/branch", MathBranch); - - - function MathAccumulate() { - this.addInput("inc", "number"); - this.addOutput("total", "number"); - this.addProperty("increment", 1); - this.addProperty("value", 0); - } - - MathAccumulate.title = "Accumulate"; - MathAccumulate.desc = "Increments a value every time"; - - MathAccumulate.prototype.onExecute = function() { - if (this.properties.value === null) { - this.properties.value = 0; - } - - var inc = this.getInputData(0); - if (inc !== null) { - this.properties.value += inc; - } else { - this.properties.value += this.properties.increment; - } - this.setOutputData(0, this.properties.value); - }; - - LiteGraph.registerNodeType("math/accumulate", MathAccumulate); - - //Math Trigonometry - function MathTrigonometry() { - this.addInput("v", "number"); - this.addOutput("sin", "number"); - - this.addProperty("amplitude", 1); - this.addProperty("offset", 0); - this.bgImageUrl = "nodes/imgs/icon-sin.png"; - } - - MathTrigonometry.title = "Trigonometry"; - MathTrigonometry.desc = "Sin Cos Tan"; - //MathTrigonometry.filter = "shader"; - - MathTrigonometry.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - v = 0; - } - var amplitude = this.properties["amplitude"]; - var slot = this.findInputSlot("amplitude"); - if (slot != -1) { - amplitude = this.getInputData(slot); - } - var offset = this.properties["offset"]; - slot = this.findInputSlot("offset"); - if (slot != -1) { - offset = this.getInputData(slot); - } - - for (var i = 0, l = this.outputs.length; i < l; ++i) { - var output = this.outputs[i]; - var value; - switch (output.name) { - case "sin": - value = Math.sin(v); - break; - case "cos": - value = Math.cos(v); - break; - case "tan": - value = Math.tan(v); - break; - case "asin": - value = Math.asin(v); - break; - case "acos": - value = Math.acos(v); - break; - case "atan": - value = Math.atan(v); - break; - } - this.setOutputData(i, amplitude * value + offset); - } - }; - - MathTrigonometry.prototype.onGetInputs = function() { - return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; - }; - - MathTrigonometry.prototype.onGetOutputs = function() { - return [ - ["sin", "number"], - ["cos", "number"], - ["tan", "number"], - ["asin", "number"], - ["acos", "number"], - ["atan", "number"] - ]; - }; - - LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry); - - LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { - outputs: [["sin", "number"]], - title: "SIN()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { - outputs: [["cos", "number"]], - title: "COS()" - }); - LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { - outputs: [["tan", "number"]], - title: "TAN()" - }); - - //math library for safe math operations without eval - function MathFormula() { - this.addInput("x", "number"); - this.addInput("y", "number"); - this.addOutput("", "number"); - this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; - this.code_widget = this.addWidget( - "text", - "F(x,y)", - this.properties.formula, - function(v, canvas, node) { - node.properties.formula = v; - } - ); - this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { - LiteGraph.allow_scripts = v; - }); - this._func = null; - } - - MathFormula.title = "Formula"; - MathFormula.desc = "Compute formula"; - MathFormula.size = [160, 100]; - - MathAverageFilter.prototype.onPropertyChanged = function(name, value) { - if (name == "formula") { - this.code_widget.value = value; - } - }; - - MathFormula.prototype.onExecute = function() { - if (!LiteGraph.allow_scripts) { - return; - } - - var x = this.getInputData(0); - var y = this.getInputData(1); - if (x != null) { - this.properties["x"] = x; - } else { - x = this.properties["x"]; - } - - if (y != null) { - this.properties["y"] = y; - } else { - y = this.properties["y"]; - } - - var f = this.properties["formula"]; - - var value; - try { - if (!this._func || this._func_code != this.properties.formula) { - this._func = new Function( - "x", - "y", - "TIME", - "return " + this.properties.formula - ); - this._func_code = this.properties.formula; - } - value = this._func(x, y, this.graph.globaltime); - this.boxcolor = null; - } catch (err) { - this.boxcolor = "red"; - } - this.setOutputData(0, value); - }; - - MathFormula.prototype.getTitle = function() { - return this._func_code || "Formula"; - }; - - MathFormula.prototype.onDrawBackground = function() { - var f = this.properties["formula"]; - if (this.outputs && this.outputs.length) { - this.outputs[0].label = f; - } - }; - - LiteGraph.registerNodeType("math/formula", MathFormula); - - function Math3DVec2ToXY() { - this.addInput("vec2", "vec2"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - } - - Math3DVec2ToXY.title = "Vec2->XY"; - Math3DVec2ToXY.desc = "vector 2 to components"; - - Math3DVec2ToXY.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - }; - - LiteGraph.registerNodeType("math3d/vec2-to-xy", Math3DVec2ToXY); - - function Math3DXYToVec2() { - this.addInputs([["x", "number"], ["y", "number"]]); - this.addOutput("vec2", "vec2"); - this.properties = { x: 0, y: 0 }; - this._data = new Float32Array(2); - } - - Math3DXYToVec2.title = "XY->Vec2"; - Math3DXYToVec2.desc = "components to vector2"; - - Math3DXYToVec2.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - - var data = this._data; - data[0] = x; - data[1] = y; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); - - function Math3DVec3ToXYZ() { - this.addInput("vec3", "vec3"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - } - - Math3DVec3ToXYZ.title = "Vec3->XYZ"; - Math3DVec3ToXYZ.desc = "vector 3 to components"; - - Math3DVec3ToXYZ.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - }; - - LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); - - function Math3DXYZToVec3() { - this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); - this.addOutput("vec3", "vec3"); - this.properties = { x: 0, y: 0, z: 0 }; - this._data = new Float32Array(3); - } - - Math3DXYZToVec3.title = "XYZ->Vec3"; - Math3DXYZToVec3.desc = "components to vector3"; - - Math3DXYZToVec3.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); - - function Math3DVec4ToXYZW() { - this.addInput("vec4", "vec4"); - this.addOutput("x", "number"); - this.addOutput("y", "number"); - this.addOutput("z", "number"); - this.addOutput("w", "number"); - } - - Math3DVec4ToXYZW.title = "Vec4->XYZW"; - Math3DVec4ToXYZW.desc = "vector 4 to components"; - - Math3DVec4ToXYZW.prototype.onExecute = function() { - var v = this.getInputData(0); - if (v == null) { - return; - } - - this.setOutputData(0, v[0]); - this.setOutputData(1, v[1]); - this.setOutputData(2, v[2]); - this.setOutputData(3, v[3]); - }; - - LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); - - function Math3DXYZWToVec4() { - this.addInputs([ - ["x", "number"], - ["y", "number"], - ["z", "number"], - ["w", "number"] - ]); - this.addOutput("vec4", "vec4"); - this.properties = { x: 0, y: 0, z: 0, w: 0 }; - this._data = new Float32Array(4); - } - - Math3DXYZWToVec4.title = "XYZW->Vec4"; - Math3DXYZWToVec4.desc = "components to vector4"; - - Math3DXYZWToVec4.prototype.onExecute = function() { - var x = this.getInputData(0); - if (x == null) { - x = this.properties.x; - } - var y = this.getInputData(1); - if (y == null) { - y = this.properties.y; - } - var z = this.getInputData(2); - if (z == null) { - z = this.properties.z; - } - var w = this.getInputData(3); - if (w == null) { - w = this.properties.w; - } - - var data = this._data; - data[0] = x; - data[1] = y; - data[2] = z; - data[3] = w; - - this.setOutputData(0, data); - }; - - LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); - -})(this); - + //basic nodes (function(global) { var LiteGraph = global.LiteGraph; @@ -18597,209 +18683,209 @@ LiteGraph.registerNodeType("events/waitAll", WaitAll); LiteGraph.registerNodeType("string/toTable", StringToTable); })(this); - -(function(global) { - var LiteGraph = global.LiteGraph; - - function Selector() { - this.addInput("sel", "number"); - this.addInput("A"); - this.addInput("B"); - this.addInput("C"); - this.addInput("D"); - this.addOutput("out"); - - this.selected = 0; - } - - Selector.title = "Selector"; - Selector.desc = "selects an output"; - - Selector.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - ctx.fillStyle = "#AFB"; - var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6; - ctx.beginPath(); - ctx.moveTo(50, y); - ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); - ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); - ctx.fill(); - }; - - Selector.prototype.onExecute = function() { - var sel = this.getInputData(0); - if (sel == null || sel.constructor !== Number) - sel = 0; - this.selected = sel = Math.round(sel) % (this.inputs.length - 1); - var v = this.getInputData(sel + 1); - if (v !== undefined) { - this.setOutputData(0, v); - } - }; - - Selector.prototype.onGetInputs = function() { - return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; - }; - - LiteGraph.registerNodeType("logic/selector", Selector); - - function Sequence() { - this.properties = { - sequence: "A,B,C" - }; - this.addInput("index", "number"); - this.addInput("seq"); - this.addOutput("out"); - - this.index = 0; - this.values = this.properties.sequence.split(","); - } - - Sequence.title = "Sequence"; - Sequence.desc = "select one element from a sequence from a string"; - - Sequence.prototype.onPropertyChanged = function(name, value) { - if (name == "sequence") { - this.values = value.split(","); - } - }; - - Sequence.prototype.onExecute = function() { - var seq = this.getInputData(1); - if (seq && seq != this.current_sequence) { - this.values = seq.split(","); - this.current_sequence = seq; - } - var index = this.getInputData(0); - if (index == null) { - index = 0; - } - this.index = index = Math.round(index) % this.values.length; - - this.setOutputData(0, this.values[index]); - }; - - LiteGraph.registerNodeType("logic/sequence", Sequence); - - - function logicAnd(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicAnd.title = "AND"; - logicAnd.desc = "Return true if all inputs are true"; - logicAnd.prototype.onExecute = function() { - var ret = true; - for (var inX in this.inputs){ - if (!this.getInputData(inX)){ - var ret = false; - break; - } - } - this.setOutputData(0, ret); - }; - logicAnd.prototype.onGetInputs = function() { - return [ - ["and", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/AND", logicAnd); - - - function logicOr(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicOr.title = "OR"; - logicOr.desc = "Return true if at least one input is true"; - logicOr.prototype.onExecute = function() { - var ret = false; - for (var inX in this.inputs){ - if (this.getInputData(inX)){ - ret = true; - break; - } - } - this.setOutputData(0, ret); - }; - logicOr.prototype.onGetInputs = function() { - return [ - ["or", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/OR", logicOr); - - - function logicNot(){ - this.properties = { }; - this.addInput("in", "boolean"); - this.addOutput("out", "boolean"); - } - logicNot.title = "NOT"; - logicNot.desc = "Return the logical negation"; - logicNot.prototype.onExecute = function() { - var ret = !this.getInputData(0); - this.setOutputData(0, ret); - }; - LiteGraph.registerNodeType("logic/NOT", logicNot); - - - function logicCompare(){ - this.properties = { }; - this.addInput("a", "boolean"); - this.addInput("b", "boolean"); - this.addOutput("out", "boolean"); - } - logicCompare.title = "bool == bool"; - logicCompare.desc = "Compare for logical equality"; - logicCompare.prototype.onExecute = function() { - var last = null; - var ret = true; - for (var inX in this.inputs){ - if (last === null) last = this.getInputData(inX); - else - if (last != this.getInputData(inX)){ - ret = false; - break; - } - } - this.setOutputData(0, ret); - }; - logicCompare.prototype.onGetInputs = function() { - return [ - ["bool", "boolean"] - ]; - }; - LiteGraph.registerNodeType("logic/CompareBool", logicCompare); - - - function logicBranch(){ - this.properties = { }; - this.addInput("onTrigger", LiteGraph.ACTION); - this.addInput("condition", "boolean"); - this.addOutput("true", LiteGraph.EVENT); - this.addOutput("false", LiteGraph.EVENT); - this.mode = LiteGraph.ON_TRIGGER; - } - logicBranch.title = "Branch"; - logicBranch.desc = "Branch execution on condition"; - logicBranch.prototype.onExecute = function(param, options) { - var condtition = this.getInputData(1); - if (condtition){ - this.triggerSlot(0); - }else{ - this.triggerSlot(1); - } - }; - LiteGraph.registerNodeType("logic/IF", logicBranch); -})(this); - + +(function(global) { + var LiteGraph = global.LiteGraph; + + function Selector() { + this.addInput("sel", "number"); + this.addInput("A"); + this.addInput("B"); + this.addInput("C"); + this.addInput("D"); + this.addOutput("out"); + + this.selected = 0; + } + + Selector.title = "Selector"; + Selector.desc = "selects an output"; + + Selector.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + ctx.fillStyle = "#AFB"; + var y = (this.selected + 1) * LiteGraph.NODE_SLOT_HEIGHT + 6; + ctx.beginPath(); + ctx.moveTo(50, y); + ctx.lineTo(50, y + LiteGraph.NODE_SLOT_HEIGHT); + ctx.lineTo(34, y + LiteGraph.NODE_SLOT_HEIGHT * 0.5); + ctx.fill(); + }; + + Selector.prototype.onExecute = function() { + var sel = this.getInputData(0); + if (sel == null || sel.constructor !== Number) + sel = 0; + this.selected = sel = Math.round(sel) % (this.inputs.length - 1); + var v = this.getInputData(sel + 1); + if (v !== undefined) { + this.setOutputData(0, v); + } + }; + + Selector.prototype.onGetInputs = function() { + return [["E", 0], ["F", 0], ["G", 0], ["H", 0]]; + }; + + LiteGraph.registerNodeType("logic/selector", Selector); + + function Sequence() { + this.properties = { + sequence: "A,B,C" + }; + this.addInput("index", "number"); + this.addInput("seq"); + this.addOutput("out"); + + this.index = 0; + this.values = this.properties.sequence.split(","); + } + + Sequence.title = "Sequence"; + Sequence.desc = "select one element from a sequence from a string"; + + Sequence.prototype.onPropertyChanged = function(name, value) { + if (name == "sequence") { + this.values = value.split(","); + } + }; + + Sequence.prototype.onExecute = function() { + var seq = this.getInputData(1); + if (seq && seq != this.current_sequence) { + this.values = seq.split(","); + this.current_sequence = seq; + } + var index = this.getInputData(0); + if (index == null) { + index = 0; + } + this.index = index = Math.round(index) % this.values.length; + + this.setOutputData(0, this.values[index]); + }; + + LiteGraph.registerNodeType("logic/sequence", Sequence); + + + function logicAnd(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicAnd.title = "AND"; + logicAnd.desc = "Return true if all inputs are true"; + logicAnd.prototype.onExecute = function() { + var ret = true; + for (var inX in this.inputs){ + if (!this.getInputData(inX)){ + var ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicAnd.prototype.onGetInputs = function() { + return [ + ["and", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/AND", logicAnd); + + + function logicOr(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicOr.title = "OR"; + logicOr.desc = "Return true if at least one input is true"; + logicOr.prototype.onExecute = function() { + var ret = false; + for (var inX in this.inputs){ + if (this.getInputData(inX)){ + ret = true; + break; + } + } + this.setOutputData(0, ret); + }; + logicOr.prototype.onGetInputs = function() { + return [ + ["or", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/OR", logicOr); + + + function logicNot(){ + this.properties = { }; + this.addInput("in", "boolean"); + this.addOutput("out", "boolean"); + } + logicNot.title = "NOT"; + logicNot.desc = "Return the logical negation"; + logicNot.prototype.onExecute = function() { + var ret = !this.getInputData(0); + this.setOutputData(0, ret); + }; + LiteGraph.registerNodeType("logic/NOT", logicNot); + + + function logicCompare(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicCompare.title = "bool == bool"; + logicCompare.desc = "Compare for logical equality"; + logicCompare.prototype.onExecute = function() { + var last = null; + var ret = true; + for (var inX in this.inputs){ + if (last === null) last = this.getInputData(inX); + else + if (last != this.getInputData(inX)){ + ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicCompare.prototype.onGetInputs = function() { + return [ + ["bool", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/CompareBool", logicCompare); + + + function logicBranch(){ + this.properties = { }; + this.addInput("onTrigger", LiteGraph.ACTION); + this.addInput("condition", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.mode = LiteGraph.ON_TRIGGER; + } + logicBranch.title = "Branch"; + logicBranch.desc = "Branch execution on condition"; + logicBranch.prototype.onExecute = function(param, options) { + var condtition = this.getInputData(1); + if (condtition){ + this.triggerSlot(0); + }else{ + this.triggerSlot(1); + } + }; + LiteGraph.registerNodeType("logic/IF", logicBranch); +})(this); + //event related nodes (function(global) { var LiteGraph = global.LiteGraph; @@ -19228,4 +19314,3 @@ LiteGraph.registerNodeType("network/httprequest", HTTPRequestNode); })(this); - diff --git a/build/litegraph_mini.min.js b/build/litegraph_mini.min.js index 67b223fa8..1c8bc1a67 100644 --- a/build/litegraph_mini.min.js +++ b/build/litegraph_mini.min.js @@ -1,495 +1 @@ -var $jscomp=$jscomp||{};$jscomp.scope={};$jscomp.ASSUME_ES5=!1;$jscomp.ASSUME_NO_NATIVE_MAP=!1;$jscomp.ASSUME_NO_NATIVE_SET=!1;$jscomp.defineProperty=$jscomp.ASSUME_ES5||"function"==typeof Object.defineProperties?Object.defineProperty:function(w,m,r){w!=Array.prototype&&w!=Object.prototype&&(w[m]=r.value)};$jscomp.getGlobal=function(w){return"undefined"!=typeof window&&window===w?w:"undefined"!=typeof global&&null!=global?global:w};$jscomp.global=$jscomp.getGlobal(this);$jscomp.SYMBOL_PREFIX="jscomp_symbol_"; -$jscomp.initSymbol=function(){$jscomp.initSymbol=function(){};$jscomp.global.Symbol||($jscomp.global.Symbol=$jscomp.Symbol)};$jscomp.Symbol=function(){var w=0;return function(m){return $jscomp.SYMBOL_PREFIX+(m||"")+w++}}(); -$jscomp.initSymbolIterator=function(){$jscomp.initSymbol();var w=$jscomp.global.Symbol.iterator;w||(w=$jscomp.global.Symbol.iterator=$jscomp.global.Symbol("iterator"));"function"!=typeof Array.prototype[w]&&$jscomp.defineProperty(Array.prototype,w,{configurable:!0,writable:!0,value:function(){return $jscomp.arrayIterator(this)}});$jscomp.initSymbolIterator=function(){}};$jscomp.arrayIterator=function(w){var m=0;return $jscomp.iteratorPrototype(function(){return mr&&(r=Math.max(0,y+r));if(null==h||h>y)h=y;h=Number(h);0>h&&(h=Math.max(0,y+h));for(r=Number(r||0);r=z}},"es6","es3");$jscomp.findInternal=function(w,m,r){w instanceof String&&(w=String(w));for(var h=w.length,y=0;ya&&eb?!0:!1}function D(a,b){var c=a[0]+a[2],e=a[1]+a[3],d=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>d||ck.width-n.width-10&&(d=k.width-n.width-10),k.height&&a>k.height-n.height-10&&(a=k.height-n.height-10));g.style.left=d+"px";g.style.top=a+"px";b.scale&&(g.style.transform="scale("+b.scale+")")}function H(a){this.points=a;this.nearest=this.selected=-1;this.size=null;this.must_update=!0;this.margin=5}function I(a,b,c){return b>a?b:c>a/4).toString(16)})},isValidConnection:function(a, -b){if(""==a||"*"===a)a=0;if(""==b||"*"===b)b=0;if(!a||!b||a==b||a==f.EVENT&&b==f.ACTION)return!0;a=String(a);b=String(b);a=a.toLowerCase();b=b.toLowerCase();if(-1==a.indexOf(",")&&-1==b.indexOf(","))return a==b;a=a.split(",");b=b.split(",");for(var c=0;ck&&(k=p.size[q]);n+=p.size[b==f.VERTICAL_LAYOUT?0:1]+a+f.NODE_TITLE_HEIGHT}c+=k+a}this.setDirtyCanvas(!0,!0)};m.prototype.getTime=function(){return this.globaltime};m.prototype.getFixedTime=function(){return this.fixedtime};m.prototype.getElapsedTime=function(){return this.elapsed_time};m.prototype.sendEventToAllNodes=function(a, -b,c){c=c||f.ALWAYS;var e=this._nodes_in_order?this._nodes_in_order:this._nodes;if(e)for(var d=0,g=e.length;d=f.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";if(f.use_uuids){if(null==a.id||-1==a.id)a.id=f.uuidv4()}else null==a.id||-1==a.id? -a.id=++this.last_node_id:this.last_node_ida.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=f.use_uuids?f.uuidv4():-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.flags={}};h.prototype.configure=function(a){this.graph&& -this.graph._version++;for(var b in a)if("properties"==b)for(var c in a.properties){if(this.properties[c]=a.properties[c],this.onPropertyChanged)this.onPropertyChanged(c,a.properties[c])}else null!=a[b]&&("object"==typeof a[b]?this[b]&&this[b].configure?this[b].configure(a[b]):this[b]=f.cloneObject(a[b],this[b]):this[b]=a[b]);a.title||(this.title=this.constructor.title);if(this.inputs)for(c=0;c=this.outputs.length)){var c=this.outputs[a];if(c&&(c._data=b,this.outputs[a].links))for(c=0;c=this.outputs.length)){var c=this.outputs[a];if(c&&(c.type=b,this.outputs[a].links))for(c=0;c=this.inputs.length||null==this.inputs[a].link)){a=this.graph.links[this.inputs[a].link];if(!a)return null;if(!b)return a.data;b=this.graph.getNodeById(a.origin_id);if(!b)return a.data;if(b.updateOutputData)b.updateOutputData(a.origin_slot);else if(b.onExecute)b.onExecute();return a.data}};h.prototype.getInputDataType=function(a){if(!this.inputs||a>=this.inputs.length||null==this.inputs[a].link)return null;a=this.graph.links[this.inputs[a].link];if(!a)return null; -var b=this.graph.getNodeById(a.origin_id);return b?(a=b.outputs[a.origin_slot])?a.type:null:a.type};h.prototype.getInputDataByName=function(a,b){a=this.findInputSlot(a);return-1==a?null:this.getInputData(a,b)};h.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length)return null;a=this.inputs[a];return a&&null!==a.link?(a=this.graph.links[a.link])?this.graph.getNodeById(a.origin_id):null:null};h.prototype.getInputOrProperty=function(a){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[a]:null;for(var b=0,c=this.inputs.length;b=this.outputs.length?null:this.outputs[a]._data};h.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return null;a=this.outputs[a];if(!a.links||0==a.links.length)return null;for(var b=[],c=0;ca&&this.pos[1]-d-cb)return!0;return!1};h.prototype.getSlotInPosition=function(a,b){var c=new Float32Array(2);if(this.inputs)for(var e=0,d=this.inputs.length;e=this.outputs.length)return f.debug&&console.log("Connect: Error, slot number not found"),null;b&&b.constructor=== -Number&&(b=this.graph.getNodeById(b));if(!b)throw"target node is null";if(b==this)return null;if(c.constructor===String){if(c=b.findInputSlot(c),-1==c)return f.debug&&console.log("Connect: Error, no slot of name "+c),null}else if(c===f.EVENT)if(f.do_add_triggers_slots)b.changeMode(f.ON_TRIGGER),c=b.findInputSlot("onTrigger");else return null;else if(!b.inputs||c>=b.inputs.length)return f.debug&&console.log("Connect: Error, slot number not found"),null;var e=b.inputs[c],d=this.outputs[a];if(!this.outputs[a])return null; -b.onBeforeConnectInput&&(c=b.onBeforeConnectInput(c));if(!1===c||null===c||!f.isValidConnection(d.type,e.type))return this.setDirtyCanvas(!1,!0),null;if(b.onConnectInput&&!1===b.onConnectInput(c,d.type,d,this,a)||this.onConnectOutput&&!1===this.onConnectOutput(a,e.type,e,b,c))return null;b.inputs[c]&&null!=b.inputs[c].link&&(this.graph.beforeChange(),b.disconnectInput(c,{doProcessChange:!1}));if(null!==d.links&&d.links.length)switch(d.type){case f.EVENT:f.allow_multi_output_for_events||(this.graph.beforeChange(), -this.disconnectOutput(a,!1,{doProcessChange:!1}))}var g=f.use_uuids?f.uuidv4():++this.graph.last_link_id;g=new r(g,e.type||d.type,this.id,a,b.id,c);this.graph.links[g.id]=g;null==d.links&&(d.links=[]);d.links.push(g.id);b.inputs[c].link=g.id;this.graph&&this.graph._version++;if(this.onConnectionsChange)this.onConnectionsChange(f.OUTPUT,a,!0,g,d);if(b.onConnectionsChange)b.onConnectionsChange(f.INPUT,c,!0,g,e);this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(f.INPUT, -b,c,this,a),this.graph.onNodeConnectionChange(f.OUTPUT,this,a,b,c));this.setDirtyCanvas(!1,!0);this.graph.afterChange();this.graph.connectionChange(this,g);return g};h.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return f.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return f.debug&&console.log("Connect: Error, slot number not found"),!1;var c=this.outputs[a];if(!c||!c.links||0==c.links.length)return!1; -if(b){b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Target Node not found";for(var e=0,d=c.links.length;e=this.inputs.length)return f.debug&&console.log("Connect: Error, slot number not found"),!1;var b=this.inputs[a];if(!b)return!1;var c=this.inputs[a].link;if(null!=c){this.inputs[a].link=null;var e=this.graph.links[c];if(e){var d=this.graph.getNodeById(e.origin_id);if(!d)return!1;var g=d.outputs[e.origin_slot];if(!g||!g.links||0==g.links.length)return!1;for(var k=0,n=g.links.length;kb&&this.inputs[b].pos)return c[0]=this.pos[0]+this.inputs[b].pos[0],c[1]=this.pos[1]+ -this.inputs[b].pos[1],c;if(!a&&e>b&&this.outputs[b].pos)return c[0]=this.pos[0]+this.outputs[b].pos[0],c[1]=this.pos[1]+this.outputs[b].pos[1],c;if(this.horizontal)return c[0]=this.pos[0]+this.size[0]/e*(b+.5),c[1]=a?this.pos[1]-f.NODE_TITLE_HEIGHT:this.pos[1]+this.size[1],c;c[0]=a?this.pos[0]+d:this.pos[0]+this.size[0]+1-d;c[1]=this.pos[1]+(b+.7)*f.NODE_SLOT_HEIGHT+(this.constructor.slot_start_y||0);return c};h.prototype.alignToGrid=function(){this.pos[0]=f.CANVAS_GRID_SIZE*Math.round(this.pos[0]/ -f.CANVAS_GRID_SIZE);this.pos[1]=f.CANVAS_GRID_SIZE*Math.round(this.pos[1]/f.CANVAS_GRID_SIZE)};h.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>h.MAX_CONSOLE&&this.console.shift();if(this.graph.onNodeTrace)this.graph.onNodeTrace(this,a)};h.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};h.prototype.loadImage=function(a){var b=new Image;b.src=f.node_images_path+a;b.ready=!1;var c=this;b.onload= -function(){this.ready=!0;c.setDirtyCanvas(!0)};return b};h.prototype.captureInput=function(a){if(this.graph&&this.graph.list_of_graphcanvas)for(var b=this.graph.list_of_graphcanvas,c=0;ca.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});Object.defineProperty(this,"size",{set:function(a){!a||2>a.length||(this._size[0]=Math.max(140,a[0]),this._size[1]=Math.max(80,a[1]))},get:function(){return this._size},enumerable:!0})};y.prototype.configure=function(a){this.title=a.title;this._bounding.set(a.bounding);this.color=a.color;this.font_size=a.font_size};y.prototype.serialize=function(){var a= -this._bounding;return{title:this.title,bounding:[Math.round(a[0]),Math.round(a[1]),Math.round(a[2]),Math.round(a[3])],color:this.color,font_size:this.font_size}};y.prototype.move=function(a,b,c){this._pos[0]+=a;this._pos[1]+=b;if(!c)for(c=0;c=this.viewport[0]&&e=this.viewport[1]&&cthis.max_scale&&(a=this.max_scale);if(a!=this.scale&&this.element){var c=this.element.getBoundingClientRect();if(c&&(b= -b||[.5*c.width,.5*c.height],c=this.convertCanvasToOffset(b),this.scale=a,.01>Math.abs(this.scale-1)&&(this.scale=1),a=this.convertCanvasToOffset(b),a=[a[0]-c[0],a[1]-c[1]],this.offset[0]+=a[0],this.offset[1]+=a[1],this.onredraw))this.onredraw(this)}};z.prototype.changeDeltaScale=function(a,b){this.changeScale(this.scale*a,b)};z.prototype.reset=function(){this.scale=1;this.offset[0]=0;this.offset[1]=0};w.LGraphCanvas=f.LGraphCanvas=l;l.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII="; -l.link_type_colors={"-1":f.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"};l.gradients={};l.prototype.clear=function(){this.fps=this.render_time=this.last_draw_time=this.frame=0;this.dragging_rectangle=null;this.selected_nodes={};this.selected_group=null;this.visible_nodes=[];this.connecting_node=this.node_capturing_input=this.node_over=this.node_dragged=null;this.highlighted_links={};this.dragging_canvas=!1;this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_widget=this.node_in_panel=this.dirty_area= -null;this.last_mouse=[0,0];this.last_mouseclick=0;this.pointer_is_double=this.pointer_is_down=!1;this.visible_area.set([0,0,0,0]);if(this.onClear)this.onClear()};l.prototype.setGraph=function(a,b){this.graph!=a&&(b||this.clear(),!a&&this.graph?this.graph.detachCanvas(this):(a.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))};l.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph};l.prototype.openSubgraph=function(a){if(!a)throw"graph cannot be null"; -if(this.graph==a)throw"graph cannot be the same";this.clear();this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph));a.attachCanvas(this);this.checkPanels();this.setDirty(!0,!0)};l.prototype.closeSubgraph=function(){if(this._graph_stack&&0!=this._graph_stack.length){var a=this.graph._subgraph_node,b=this._graph_stack.pop();this.selected_nodes={};this.highlighted_links={};b.attachCanvas(this);this.setDirty(!0,!0);a&&(this.centerOnNode(a),this.selectNodes([a]));this.ds.offset= -[0,0];this.ds.scale=1}};l.prototype.getCurrentGraph=function(){return this.graph};l.prototype.setCanvas=function(a,b){if(a&&a.constructor===String&&(a=document.getElementById(a),!a))throw"Error creating LiteGraph canvas: Canvas not found";if(a!==this.canvas&&(!a&&this.canvas&&(b||this.unbindEvents()),this.canvas=a,this.ds.element=a)){a.className+=" lgraphcanvas";a.data=this;a.tabindex="1";this.bgcanvas=null;this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width, -this.bgcanvas.height=this.canvas.height);if(null==a.getContext){if("canvas"!=a.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+a.localName;throw"This browser doesn't support Canvas";}null==(this.ctx=a.getContext("2d"))&&(a.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL());b||this.bindEvents()}};l.prototype._doNothing=function(a){a.preventDefault();return!1};l.prototype._doReturnTrue=function(a){a.preventDefault(); -return!0};l.prototype.bindEvents=function(){if(this._events_binded)console.warn("LGraphCanvas: events already binded");else{var a=this.canvas,b=this.getCanvasWindow().document;this._mousedown_callback=this.processMouseDown.bind(this);this._mousewheel_callback=this.processMouseWheel.bind(this);this._mousemove_callback=this.processMouseMove.bind(this);this._mouseup_callback=this.processMouseUp.bind(this);f.pointerListenerAdd(a,"down",this._mousedown_callback,!0);a.addEventListener("mousewheel",this._mousewheel_callback, -!1);f.pointerListenerAdd(a,"up",this._mouseup_callback,!0);f.pointerListenerAdd(a,"move",this._mousemove_callback);a.addEventListener("contextmenu",this._doNothing);a.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1);this._key_callback=this.processKey.bind(this);a.addEventListener("keydown",this._key_callback,!0);b.addEventListener("keyup",this._key_callback,!0);this._ondrop_callback=this.processDrop.bind(this);a.addEventListener("dragover",this._doNothing,!1);a.addEventListener("dragend", -this._doNothing,!1);a.addEventListener("drop",this._ondrop_callback,!1);a.addEventListener("dragenter",this._doReturnTrue,!1);this._events_binded=!0}};l.prototype.unbindEvents=function(){if(this._events_binded){var a=this.getCanvasWindow().document;f.pointerListenerRemove(this.canvas,"move",this._mousedown_callback);f.pointerListenerRemove(this.canvas,"up",this._mousedown_callback);f.pointerListenerRemove(this.canvas,"down",this._mousedown_callback);this.canvas.removeEventListener("mousewheel",this._mousewheel_callback); -this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback);this.canvas.removeEventListener("keydown",this._key_callback);a.removeEventListener("keyup",this._key_callback);this.canvas.removeEventListener("contextmenu",this._doNothing);this.canvas.removeEventListener("drop",this._ondrop_callback);this.canvas.removeEventListener("dragenter",this._doReturnTrue);this._ondrop_callback=this._key_callback=this._mousewheel_callback=this._mousedown_callback=null;this._events_binded=!1}else console.warn("LGraphCanvas: no events binded")}; -l.getFileExtension=function(a){var b=a.indexOf("?");-1!=b&&(a=a.substr(0,b));b=a.lastIndexOf(".");return-1==b?"":a.substr(b+1).toLowerCase()};l.prototype.enableWebGL=function(){if("undefined"===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"===typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas);this.ctx.webgl=!0;this.bgcanvas=this.canvas;this.bgctx=this.gl;this.canvas.webgl_enabled=!0}; -l.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};l.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};l.prototype.startRendering=function(){function a(){this.pause_rendering||this.draw();var b=this.getCanvasWindow();this.is_rendering&&b.requestAnimationFrame(a.bind(this))}this.is_rendering||(this.is_rendering=!0,a.call(this))};l.prototype.stopRendering=function(){this.is_rendering= -!1};l.prototype.blockClick=function(){this.block_click=!0;this.last_mouseclick=0};l.prototype.processMouseDown=function(a){this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();l.active_canvas=this;var c=this,e=a.clientX,d=a.clientY;this.ds.viewport=this.viewport;e=!this.viewport||this.viewport&&e>=this.viewport[0]&&e=this.viewport[1]&&dd-this.last_mouseclick&&k;this.mouse[0]=a.clientX;this.mouse[1]=a.clientY;this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;this.last_click_position= -[this.mouse[0],this.mouse[1]];this.pointer_is_double=this.pointer_is_down&&k?!0:!1;this.pointer_is_down=!0;this.canvas.focus();f.closeAllContextMenus(b);if(!this.onMouse||1!=this.onMouse(a)){if(1!=a.which||this.pointer_is_double)if(2==a.which)if(f.middle_click_slot_add_default_node){if(g&&this.allow_interaction&&!e&&!this.read_only&&!this.connecting_node&&!g.flags.collapsed&&!this.live_mode){d=e=k=!1;if(g.outputs)for(q=0,n=g.outputs.length;qg.size[0]-f.NODE_TITLE_HEIGHT&& -0>n[1]&&(c=this,setTimeout(function(){c.openSubgraph(g.subgraph)},10)),this.live_mode&&(q=k=!0));q?g.is_selected||this.processNodeSelected(g,a):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=g),this.processNodeSelected(g,a));this.dirty_canvas=!0}}else if(!e){if(!this.read_only)for(q=0;qn[0]+4||a.canvasYn[1]+4)){this.showLinkMenu(k,a);this.over_link_center=null; -break}this.selected_group=this.graph.getGroupOnPos(a.canvasX,a.canvasY);this.selected_group_resizing=!1;this.selected_group&&!this.read_only&&(a.ctrlKey&&(this.dragging_rectangle=null),10>C([a.canvasX,a.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes());d&&!this.read_only&&this.allow_searchbox&&(this.showSearchBox(a),a.preventDefault(),a.stopPropagation()); -k=!0}!e&&k&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}this.last_mouse[0]=a.clientX;this.last_mouse[1]=a.clientY;this.last_mouseclick=f.getTime();this.last_mouse_dragging=!0;this.graph.change();(!b.document.activeElement||"input"!=b.document.activeElement.nodeName.toLowerCase()&&"textarea"!=b.document.activeElement.nodeName.toLowerCase())&&a.preventDefault();a.stopPropagation();if(this.onMouseDown)this.onMouseDown(a);return!1}}}};l.prototype.processMouseMove=function(a){this.autoresize&&this.resize(); -this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){l.active_canvas=this;this.adjustMouseEvent(a);var b=[a.clientX,a.clientY];this.mouse[0]=b[0];this.mouse[1]=b[1];var c=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.graph_mouse[0]=a.canvasX;this.graph_mouse[1]=a.canvasY;if(this.block_click)return a.preventDefault(),!1;a.dragging=this.last_mouse_dragging;this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.graph_mouse,a,this.node_widget[1]), -this.dirty_canvas=!0);var e=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=a.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=a.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[a.canvasX-this.selected_group.pos[0],a.canvasY-this.selected_group.pos[1]]:(this.selected_group.move(c[0]/this.ds.scale,c[1]/this.ds.scale, -a.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=c[0]/this.ds.scale,this.ds.offset[1]+=c[1]/this.ds.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else if((this.allow_interaction||e&&e.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);b=0;for(var d=this.graph._nodes.length;bk[0]+4||a.canvasYk[1]+4)){d=g;break}d!=this.over_link_center&&(this.over_link_center=d,this.dirty_canvas=!0);this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&& -this.node_capturing_input!=e&&this.node_capturing_input.onMouseMove)this.node_capturing_input.onMouseMove(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]],this);if(this.node_dragged&&!this.live_mode){for(b in this.selected_nodes)e=this.selected_nodes[b],e.pos[0]+=c[0]/this.ds.scale,e.pos[1]+=c[1]/this.ds.scale,e.is_selected||this.processNodeSelected(e,a);this.dirty_bgcanvas=this.dirty_canvas=!0}this.resizing_node&&!this.live_mode&&(c=[a.canvasX-this.resizing_node.pos[0], -a.canvasY-this.resizing_node.pos[1]],b=this.resizing_node.computeSize(),c[0]=Math.max(b[0],c[0]),c[1]=Math.max(b[1],c[1]),this.resizing_node.setSize(c),this.canvas.style.cursor="se-resize",this.dirty_bgcanvas=this.dirty_canvas=!0)}a.preventDefault();return!1}};l.prototype.processMouseUp=function(a){var b=void 0===a.isPrimary||a.isPrimary;if(!b)return!1;this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0);if(this.graph){var c=this.getCanvasWindow().document;l.active_canvas=this;this.options.skip_events|| -(f.pointerListenerRemove(c,"move",this._mousemove_callback,!0),f.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),f.pointerListenerRemove(c,"up",this._mouseup_callback,!0));this.adjustMouseEvent(a);c=f.getTime();a.click_time=c-this.last_mouseclick;this.last_mouse_dragging=!1;this.last_click_position=null;this.block_click&&(this.block_click=!1);if(1==a.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,a);this.node_widget=null;this.selected_group&& -(this.selected_group.move(this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),a.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null);this.selected_group_resizing=!1;var e=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){c= -this.graph._nodes;var d=new Float32Array(4),g=Math.abs(this.dragging_rectangle[2]),k=Math.abs(this.dragging_rectangle[3]),n=0>this.dragging_rectangle[3]?this.dragging_rectangle[1]-k:this.dragging_rectangle[1];this.dragging_rectangle[0]=0>this.dragging_rectangle[2]?this.dragging_rectangle[0]-g:this.dragging_rectangle[0];this.dragging_rectangle[1]=n;this.dragging_rectangle[2]=g;this.dragging_rectangle[3]=k;if(!e||10a.click_time&&B(a.canvasX,a.canvasY,e.pos[0],e.pos[1]-f.NODE_TITLE_HEIGHT,f.NODE_TITLE_HEIGHT,f.NODE_TITLE_HEIGHT)&&e.collapse();this.dirty_bgcanvas=this.dirty_canvas=!0;this.node_dragged.pos[0]=Math.round(this.node_dragged.pos[0]);this.node_dragged.pos[1]=Math.round(this.node_dragged.pos[1]);(this.graph.config.align_to_grid||this.align_to_grid)&&this.node_dragged.alignToGrid();if(this.onNodeMoved)this.onNodeMoved(this.node_dragged);this.graph.afterChange(this.node_dragged); -this.node_dragged=null}else{e=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);!e&&300>a.click_time&&this.deselectAllNodes();this.dirty_canvas=!0;this.dragging_canvas=!1;if(this.node_over&&this.node_over.onMouseUp)this.node_over.onMouseUp(a,[a.canvasX-this.node_over.pos[0],a.canvasY-this.node_over.pos[1]],this);if(this.node_capturing_input&&this.node_capturing_input.onMouseUp)this.node_capturing_input.onMouseUp(a,[a.canvasX-this.node_capturing_input.pos[0],a.canvasY-this.node_capturing_input.pos[1]])}}else 2== -a.which?(this.dirty_canvas=!0,this.dragging_canvas=!1):3==a.which&&(this.dirty_canvas=!0,this.dragging_canvas=!1);b&&(this.pointer_is_double=this.pointer_is_down=!1);this.graph.change();a.stopPropagation();a.preventDefault();return!1}};l.prototype.processMouseWheel=function(a){if(this.graph&&this.allow_dragcanvas){var b=null!=a.wheelDeltaY?a.wheelDeltaY:-60*a.detail;this.adjustMouseEvent(a);var c=a.clientX,e=a.clientY;if(!this.viewport||this.viewport&&c>=this.viewport[0]&&c=this.viewport[1]&&eb&&(c*=1/1.1),this.ds.changeScale(c,[a.clientX,a.clientY]),this.graph.change(),a.preventDefault(),!1}};l.prototype.isOverNodeBox=function(a,b,c){var e=f.NODE_TITLE_HEIGHT;return B(b,c,a.pos[0]+2,a.pos[1]+2-e,e-4,e-4)?!0:!1};l.prototype.isOverNodeInput=function(a,b,c,e){if(a.inputs)for(var d=0,g=a.inputs.length;db.nodes[d].pos[0]&&(c[0]=b.nodes[d].pos[0],e[0]=d),c[1]>b.nodes[d].pos[1]&&(c[1]=b.nodes[d].pos[1],e[1]=d)):(c=[b.nodes[d].pos[0],b.nodes[d].pos[1]],e=[d,d]);e=[];for(d=0;d=this.viewport[0]&&b=this.viewport[1]&&cc-this.graph._last_trigger_time)&&this.drawBackCanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps=this.render_time?1/this.render_time:0;this.frame+=1}};l.prototype.drawFrontCanvas= -function(){this.dirty_canvas=!1;this.ctx||(this.ctx=this.bgcanvas.getContext("2d"));var a=this.ctx;if(a){var b=this.canvas;a.start2D&&!this.viewport&&(a.start2D(),a.restore(),a.setTransform(1,0,0,1,0,0));var c=this.viewport||this.dirty_area;c&&(a.save(),a.beginPath(),a.rect(c[0],c[1],c[2],c[3]),a.clip());this.clear_background&&(c?a.clearRect(c[0],c[1],c[2],c[3]):a.clearRect(0,0,b.width,b.height));this.bgcanvas==this.canvas?this.drawBackCanvas():a.drawImage(this.bgcanvas,0,0);if(this.onRender)this.onRender(b, -a);this.show_info&&this.renderInfo(a,c?c[0]:0,c?c[1]:0);if(this.graph){a.save();this.ds.toCanvasContext(a);b=this.computeVisibleNodes(null,this.visible_nodes);for(var e=0;e> ";b.fillText(e+c.getTitle(),.5*a.width,40);b.restore()}c=!1;this.onRenderBackground&&(c=this.onRenderBackground(a,b));this.viewport||(b.restore(),b.setTransform(1,0,0,1,0,0));this.visible_links.length=0;if(this.graph){b.save();this.ds.toCanvasContext(b);1.5>this.ds.scale&&!c&&this.clear_background_color&& -(b.fillStyle=this.clear_background_color,b.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3]));if(this.background_image&&.5this.ds.scale;if(this.live_mode){if(!a.flags.collapsed&&(b.shadowColor="transparent",a.onDrawForeground))a.onDrawForeground(b,this,this.canvas)}else{var g=this.editor_alpha;b.globalAlpha=g;this.render_shadows&&!d?(b.shadowColor=f.DEFAULT_SHADOW_COLOR,b.shadowOffsetX=2*this.ds.scale, -b.shadowOffsetY=2*this.ds.scale,b.shadowBlur=3*this.ds.scale):b.shadowColor="transparent";if(!a.flags.collapsed||!a.onDrawCollapsed||1!=a.onDrawCollapsed(b,this)){var k=a._shape||f.BOX_SHAPE;K.set(a.size);var n=a.horizontal;if(a.flags.collapsed){b.font=this.inner_text_font;var t=a.getTitle?a.getTitle():a.title;null!=t&&(a._collapsed_width=Math.min(a.size[0],b.measureText(t).width+2*f.NODE_TITLE_HEIGHT),K[0]=a._collapsed_width,K[1]=0)}a.clip_area&&(b.save(),b.beginPath(),k==f.BOX_SHAPE?b.rect(0,0, -K[0],K[1]):k==f.ROUND_SHAPE?b.roundRect(0,0,K[0],K[1],[10]):k==f.CIRCLE_SHAPE&&b.arc(.5*K[0],.5*K[1],.5*K[0],0,2*Math.PI),b.clip());a.has_errors&&(e="red");this.drawNodeShape(a,b,K,c,e,a.is_selected,a.mouseOver);b.shadowColor="transparent";if(a.onDrawForeground)a.onDrawForeground(b,this,this.canvas);b.textAlign=n?"center":"left";b.font=this.inner_text_font;e=!d;var p=this.connecting_output;k=this.connecting_input;b.lineWidth=1;t=0;var q=new Float32Array(2);if(!a.flags.collapsed){if(a.inputs)for(c= -0;cthis.ds.scale,t=a._shape||a.constructor.shape||f.ROUND_SHAPE,p=a.constructor.title_mode,q=!0;p==f.TRANSPARENT_TITLE||p==f.NO_TITLE?q=!1:p==f.AUTOHIDE_TITLE&&k&&(q=!0);A[0]=0;A[1]=q?-d:0;A[2]=c[0]+1;A[3]=q?c[1]+d:c[1];k=b.globalAlpha;b.beginPath();t==f.BOX_SHAPE||n?b.fillRect(A[0],A[1],A[2],A[3]):t==f.ROUND_SHAPE||t==f.CARD_SHAPE?b.roundRect(A[0],A[1],A[2],A[3],t==f.CARD_SHAPE?[this.round_radius, -this.round_radius,0,0]:[this.round_radius]):t==f.CIRCLE_SHAPE&&b.arc(.5*c[0],.5*c[1],.5*c[0],0,2*Math.PI);b.fill();!a.flags.collapsed&&q&&(b.shadowColor="transparent",b.fillStyle="rgba(0,0,0,0.2)",b.fillRect(0,-1,A[2],2));b.shadowColor="transparent";if(a.onDrawBackground)a.onDrawBackground(b,this,this.canvas,this.graph_mouse);if(q||p==f.TRANSPARENT_TITLE){if(a.onDrawTitleBar)a.onDrawTitleBar(b,d,c,this.ds.scale,e);else if(p!=f.TRANSPARENT_TITLE&&(a.constructor.title_color||this.render_title_colored)){q= -a.constructor.title_color||e;a.flags.collapsed&&(b.shadowColor=f.DEFAULT_SHADOW_COLOR);if(this.use_gradients){var x=l.gradients[q];x||(x=l.gradients[q]=b.createLinearGradient(0,0,400,0),x.addColorStop(0,q),x.addColorStop(1,"#000"));b.fillStyle=x}else b.fillStyle=q;b.beginPath();t==f.BOX_SHAPE||n?b.rect(0,-d,c[0]+1,d):(t==f.ROUND_SHAPE||t==f.CARD_SHAPE)&&b.roundRect(0,-d,c[0]+1,d,a.flags.collapsed?[this.round_radius]:[this.round_radius,this.round_radius,0,0]);b.fill();b.shadowColor="transparent"}q= -!1;f.node_box_coloured_by_mode&&f.NODE_MODES_COLORS[a.mode]&&(q=f.NODE_MODES_COLORS[a.mode]);f.node_box_coloured_when_on&&(q=a.action_triggered?"#FFF":a.execute_triggered?"#AAA":q);if(a.onDrawTitleBox)a.onDrawTitleBox(b,d,c,this.ds.scale);else t==f.ROUND_SHAPE||t==f.CIRCLE_SHAPE||t==f.CARD_SHAPE?(n&&(b.fillStyle="black",b.beginPath(),b.arc(.5*d,-.5*d,6,0,2*Math.PI),b.fill()),b.fillStyle=a.boxcolor||q||f.NODE_DEFAULT_BOXCOLOR,n?b.fillRect(.5*d-5,-.5*d-5,10,10):(b.beginPath(),b.arc(.5*d,-.5*d,5,0,2* -Math.PI),b.fill())):(n&&(b.fillStyle="black",b.fillRect(.5*(d-10)-1,-.5*(d+10)-1,12,12)),b.fillStyle=a.boxcolor||q||f.NODE_DEFAULT_BOXCOLOR,b.fillRect(.5*(d-10),-.5*(d+10),10,10));b.globalAlpha=k;if(a.onDrawTitleText)a.onDrawTitleText(b,d,c,this.ds.scale,this.title_text_font,g);!n&&(b.font=this.title_text_font,k=String(a.getTitle()))&&(b.fillStyle=g?f.NODE_SELECTED_TITLE_COLOR:a.constructor.title_text_color||this.node_title_color,a.flags.collapsed?(b.textAlign="left",b.measureText(k),b.fillText(k.substr(0, -20),d,f.NODE_TITLE_TEXT_Y-d),b.textAlign="left"):(b.textAlign="left",b.fillText(k,d,f.NODE_TITLE_TEXT_Y-d)));a.flags.collapsed||!a.subgraph||a.skip_subgraph_button||(k=f.NODE_TITLE_HEIGHT,q=a.size[0]-k,x=f.isInsideRectangle(this.graph_mouse[0]-a.pos[0],this.graph_mouse[1]-a.pos[1],q+2,-k+2,k-4,k-4),b.fillStyle=x?"#888":"#555",t==f.BOX_SHAPE||n?b.fillRect(q+2,-k+2,k-4,k-4):(b.beginPath(),b.roundRect(q+2,-k+2,k-4,k-4,[4]),b.fill()),b.fillStyle="#333",b.beginPath(),b.moveTo(q+.2*k,.6*-k),b.lineTo(q+ -.8*k,.6*-k),b.lineTo(q+.5*k,.3*-k),b.fill());if(a.onDrawTitle)a.onDrawTitle(b)}if(g){if(a.onBounding)a.onBounding(A);p==f.TRANSPARENT_TITLE&&(A[1]-=d,A[3]+=d);b.lineWidth=1;b.globalAlpha=.8;b.beginPath();t==f.BOX_SHAPE?b.rect(-6+A[0],-6+A[1],12+A[2],12+A[3]):t==f.ROUND_SHAPE||t==f.CARD_SHAPE&&a.flags.collapsed?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],[2*this.round_radius]):t==f.CARD_SHAPE?b.roundRect(-6+A[0],-6+A[1],12+A[2],12+A[3],[2*this.round_radius,2,2*this.round_radius,2]):t==f.CIRCLE_SHAPE&& -b.arc(.5*c[0],.5*c[1],.5*c[0]+6,0,2*Math.PI);b.strokeStyle=f.NODE_BOX_OUTLINE_COLOR;b.stroke();b.strokeStyle=e;b.globalAlpha=1}0E[2]&&(E[0]+=E[2],E[2]=Math.abs(E[2]));0>E[3]&& -(E[1]+=E[3],E[3]=Math.abs(E[3]));if(D(E,L)){var u=t.outputs[p];p=g.inputs[k];if(u&&p&&(t=u.dir||(t.horizontal?f.DOWN:f.RIGHT),p=p.dir||(g.horizontal?f.UP:f.LEFT),this.renderLink(a,q,x,n,!1,0,null,t,p),n&&n._last_time&&1E3>b-n._last_time)){u=2-.002*(b-n._last_time);var l=a.globalAlpha;a.globalAlpha=l*u;this.renderLink(a,q,x,n,!0,u,"white",t,p);a.globalAlpha=l}}}}}}a.globalAlpha=1};l.prototype.renderLink=function(a,b,c,e,d,g,k,n,t,p){e&&this.visible_links.push(e);!k&&e&&(k=e.color||l.link_type_colors[e.type]); -k||(k=this.default_link_color);null!=e&&this.highlighted_links[e.id]&&(k="#FFF");n=n||f.RIGHT;t=t||f.LEFT;var q=C(b,c);this.render_connections_border&&.6b[1]?0:Math.PI,a.save(),a.translate(x[0],x[1]),a.rotate(q),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5, --3),a.fill(),a.restore(),a.save(),a.translate(e[0],e[1]),a.rotate(p),a.beginPath(),a.moveTo(-5,-3),a.lineTo(0,7),a.lineTo(5,-3),a.fill(),a.restore()),a.beginPath(),a.arc(d[0],d[1],5,0,2*Math.PI),a.fill());if(g)for(a.fillStyle=k,x=0;5>x;++x)g=(.001*f.getTime()+.2*x)%1,d=this.computeConnectionPoint(b,c,g,n,t),a.beginPath(),a.arc(d[0],d[1],5,0,2*Math.PI),a.fill()};l.prototype.computeConnectionPoint=function(a,b,c,e,d){e=e||f.RIGHT;d=d||f.LEFT;var g=C(a,b),k=[a[0],a[1]],n=[b[0],b[1]];switch(e){case f.LEFT:k[0]+= --.25*g;break;case f.RIGHT:k[0]+=.25*g;break;case f.UP:k[1]+=-.25*g;break;case f.DOWN:k[1]+=.25*g}switch(d){case f.LEFT:n[0]+=-.25*g;break;case f.RIGHT:n[0]+=.25*g;break;case f.UP:n[1]+=-.25*g;break;case f.DOWN:n[1]+=.25*g}e=(1-c)*(1-c)*(1-c);d=3*(1-c)*(1-c)*c;g=3*(1-c)*c*c;c*=c*c;return[e*a[0]+d*k[0]+g*n[0]+c*b[0],e*a[1]+d*k[1]+g*n[1]+c*b[1]]};l.prototype.drawExecutionOrder=function(a){a.shadowColor="transparent";a.globalAlpha=.25;a.textAlign="center";a.strokeStyle="white";a.globalAlpha=.75;for(var b= -this.visible_nodes,c=0;cz&&(z=0);1r&&(r=0),1g||g>v-12||ku.last_y+h||void 0===u.last_y)){e=u.value;switch(u.type){case "button":c.type===f.pointerevents_method+"down"&&(u.callback&&setTimeout(function(){u.callback(u,p,a,b,c)},20),this.dirty_canvas=u.clicked=!0);break;case "slider":e=u.value;q=I((g-15)/(v-30),0,1);if(u.options.read_only)break;u.value=u.options.min+(u.options.max-u.options.min)*q;e!=u.value&&setTimeout(function(){d(u,u.value)},20);this.dirty_canvas= -!0;break;case "number":case "combo":e=u.value;if(c.type==f.pointerevents_method+"move"&&"number"==u.type)l&&(u.value+=.1*l*(u.options.step||1)),null!=u.options.min&&u.valueu.options.max&&(u.value=u.options.max);else if(c.type==f.pointerevents_method+"down"){var m=u.options.values;m&&m.constructor===Function&&(m=u.options.values(u,a));var r=null;"number"!=u.type&&(r=m.constructor===Array?m:Object.keys(m));g=40>g?-1:g>v-40?1:0;if("number"== -u.type)u.value+=.1*g*(u.options.step||1),null!=u.options.min&&u.valueu.options.max&&(u.value=u.options.max);else if(g)q=-1,this.last_mouseclick=0,q=m.constructor===Object?r.indexOf(String(u.value))+g:r.indexOf(u.value)+g,q>=r.length&&(q=r.length-1),0>q&&(q=0),u.value=m.constructor===Array?m[q]:q;else{var z=m!=r?Object.values(m):m;new f.ContextMenu(z,{scale:Math.max(1,this.ds.scale),event:c,className:"dark",callback:function(a,b, -c){m!=r&&(a=z.indexOf(a));this.value=a;d(this,a);p.dirty_canvas=!0;return!1}.bind(u)},q)}}else c.type==f.pointerevents_method+"up"&&"number"==u.type&&(g=40>g?-1:g>v-40?1:0,200>c.click_time&&0==g&&this.prompt("Value",u.value,function(a){if(/^[0-9+\-*/()\s]+|\d+\.\d+$/.test(a))try{a=eval(a)}catch(O){}this.value=Number(a);d(this,this.value)}.bind(u),c));e!=u.value&&setTimeout(function(){d(this,this.value)}.bind(u),20);this.dirty_canvas=!0;break;case "toggle":c.type==f.pointerevents_method+"down"&&(u.value= -!u.value,setTimeout(function(){d(u,u.value)},20));break;case "string":case "text":c.type==f.pointerevents_method+"down"&&this.prompt("Value",u.value,function(a){d(this,a)}.bind(u),c,u.options?u.options.multiline:!1);break;default:u.mouse&&(this.dirty_canvas=u.mouse(c,[g,k],a))}if(e!=u.value){if(a.onWidgetChanged)a.onWidgetChanged(u.name,u.value,e,u);a.graph._version++}return u}}}return null};l.prototype.drawGroups=function(a,b){if(this.graph){a=this.graph._groups;b.save();b.globalAlpha=.5*this.editor_alpha; -for(var c=0;cc&&.01>b.editor_alpha&&(clearInterval(e),1>c&&(b.live_mode=!0));1c.pos[0]+c.size[0])c=f;if(null===e||n+p>e.pos[1]+e.size[1])e=f;if(null===d||l"+(h.label?h.label:n)+""+a+"",value:n})}if(k.length)return new f.ContextMenu(k,{event:c,callback:function(a,b,c,e){d&&(b=this.getBoundingClientRect(),g.showEditPropertyValue(d,a.value,{position:[b.left,b.top]}))},parentMenu:e,allow_html:!0, -node:d},b),!1}};l.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};l.onMenuResizeNode=function(a,b,c,e,d){if(d){a=function(a){a.size=a.computeSize();if(a.onResize)a.onResize(a.size)};b=l.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(d);else for(var g in b.selected_nodes)a(b.selected_nodes[g]);d.setDirtyCanvas(!0,!0)}};l.prototype.showLinkMenu=function(a,b){var c=this,e=c.graph.getNodeById(a.origin_id),d=c.graph.getNodeById(a.target_id), -g=!1;e&&e.outputs&&e.outputs[a.origin_slot]&&(g=e.outputs[a.origin_slot].type);var k=!1;d&&d.outputs&&d.outputs[a.target_slot]&&(k=d.inputs[a.target_slot].type);var n=new f.ContextMenu(["Add Node",null,"Delete",null],{event:b,title:null!=a.data?a.data.constructor.name:null,callback:function(b,f,q){switch(b){case "Add Node":l.onMenuAdd(null,null,q,n,function(b){b.inputs&&b.inputs.length&&b.outputs&&b.outputs.length&&e.connectByType(a.origin_slot,b,g)&&(b.connectByType(a.target_slot,d,k),b.pos[0]-= -.5*b.size[0])});break;case "Delete":c.graph.removeLink(a.id)}}});return!1};l.prototype.createDefaultNodeForSlot=function(a){a=a||{};a=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},a);var b=a.nodeFrom&&null!==a.slotFrom,c=!b&&a.nodeTo&&null!==a.slotTo;if(!b&&!c)return console.warn("No data passed to createDefaultNodeForSlot "+a.nodeFrom+" "+a.slotFrom+" "+a.nodeTo+" "+a.slotTo),!1;if(!a.nodeType)return console.warn("No type to createDefaultNodeForSlot"), -!1;var e=b?a.nodeFrom:a.nodeTo,d=b?a.slotFrom:a.slotTo;switch(typeof d){case "string":c=b?e.findOutputSlot(d,!1):e.findInputSlot(d,!1);d=b?e.outputs[d]:e.inputs[d];break;case "object":c=b?e.findOutputSlot(d.name):e.findInputSlot(d.name);break;case "number":c=d;d=b?e.outputs[d]:e.inputs[d];break;default:return console.warn("Cant get slot information "+d),!1}!1!==d&&!1!==c||console.warn("createDefaultNodeForSlot bad slotX "+d+" "+c);e=d.type==f.EVENT?"_event_":d.type;if((d=b?f.slot_types_default_out: -f.slot_types_default_in)&&d[e]){nodeNewType=!1;if("object"==typeof d[e]||"array"==typeof d[e])for(var g in d[e]){if(a.nodeType==d[e][g]||"AUTO"==a.nodeType){nodeNewType=d[e][g];break}}else if(a.nodeType==d[e]||"AUTO"==a.nodeType)nodeNewType=d[e];if(nodeNewType){g=!1;"object"==typeof nodeNewType&&nodeNewType.node&&(g=nodeNewType,nodeNewType=nodeNewType.node);if(d=f.createNode(nodeNewType)){if(g){if(g.properties)for(var k in g.properties)d.addProperty(k,g.properties[k]);if(g.inputs)for(k in d.inputs= -[],g.inputs)d.addOutput(g.inputs[k][0],g.inputs[k][1]);if(g.outputs)for(k in d.outputs=[],g.outputs)d.addOutput(g.outputs[k][0],g.outputs[k][1]);g.title&&(d.title=g.title);g.json&&d.configure(g.json)}this.graph.add(d);d.pos=[a.position[0]+a.posAdd[0]+(a.posSizeFix[0]?a.posSizeFix[0]*d.size[0]:0),a.position[1]+a.posAdd[1]+(a.posSizeFix[1]?a.posSizeFix[1]*d.size[1]:0)];b?a.nodeFrom.connectByType(c,d,e):a.nodeTo.connectByTypeOutput(c,d,e);return!0}console.log("failed creating "+nodeNewType)}}return!1}; -l.prototype.showConnectionMenu=function(a){a=a||{};var b=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},a),c=this,e=b.nodeFrom&&b.slotFrom;a=!e&&b.nodeTo&&b.slotTo;if(!e&&!a)return console.warn("No data passed to showConnectionMenu"),!1;a=e?b.nodeFrom:b.nodeTo;var d=e?b.slotFrom:b.slotTo,g=!1;switch(typeof d){case "string":g=e?a.findOutputSlot(d,!1):a.findInputSlot(d,!1);d=e?a.outputs[d]:a.inputs[d];break;case "object":g=e?a.findOutputSlot(d.name):a.findInputSlot(d.name); -break;case "number":g=d;d=e?a.outputs[d]:a.inputs[d];break;default:return console.warn("Cant get slot information "+d),!1}a=["Add Node",null];c.allow_searchbox&&(a.push("Search"),a.push(null));var k=d.type==f.EVENT?"_event_":d.type,n=e?f.slot_types_default_out:f.slot_types_default_in;if(n&&n[k])if("object"==typeof n[k]||"array"==typeof n[k])for(var h in n[k])a.push(n[k][h]);else a.push(n[k]);var p=new f.ContextMenu(a,{event:b.e,title:(d&&""!=d.name?d.name+(k?" | ":""):"")+(d&&k?k:""),callback:function(a, -f,n){switch(a){case "Add Node":l.onMenuAdd(null,null,n,p,function(a){e?b.nodeFrom.connectByType(g,a,k):b.nodeTo.connectByTypeOutput(g,a,k)});break;case "Search":e?c.showSearchBox(n,{node_from:b.nodeFrom,slot_from:d,type_filter_in:k}):c.showSearchBox(n,{node_to:b.nodeTo,slot_from:d,type_filter_out:k});break;default:c.createDefaultNodeForSlot(Object.assign(b,{position:[b.e.canvasX,b.e.canvasY],nodeType:a}))}}});return!1};l.onShowPropertyEditor=function(a,b,c,e,d){function g(){if(h){var b=h.value;"Number"== -a.type?b=Number(b):"Boolean"==a.type&&(b=!!b);d[k]=b;n.parentNode&&n.parentNode.removeChild(n);d.setDirtyCanvas(!0,!0)}}var k=a.property||"title";b=d[k];var n=document.createElement("div");n.is_modified=!1;n.className="graphdialog";n.innerHTML="";n.close=function(){n.parentNode&&n.parentNode.removeChild(n)};n.querySelector(".name").innerText=k;var h=n.querySelector(".value");h&&(h.value=b,h.addEventListener("blur", -function(a){this.focus()}),h.addEventListener("keydown",function(a){n.is_modified=!0;if(27==a.keyCode)n.close();else if(13==a.keyCode)g();else if(13!=a.keyCode&&"textarea"!=a.target.localName)return;a.preventDefault();a.stopPropagation()}));b=l.active_canvas.canvas;c=b.getBoundingClientRect();var p=e=-20;c&&(e-=c.left,p-=c.top);event?(n.style.left=event.clientX+e+"px",n.style.top=event.clientY+p+"px"):(n.style.left=.5*b.width+e+"px",n.style.top=.5*b.height+p+"px");n.querySelector("button").addEventListener("click", -g);b.parentNode.appendChild(n);h&&h.focus();var q=null;n.addEventListener("mouseleave",function(a){f.dialog_close_on_mouse_leave&&!n.is_modified&&f.dialog_close_on_mouse_leave&&(q=setTimeout(n.close,f.dialog_close_on_mouse_leave_delay))});n.addEventListener("mouseenter",function(a){f.dialog_close_on_mouse_leave&&q&&clearTimeout(q)})};l.prototype.prompt=function(a,b,c,e,d){var g=this;a=a||"";var k=document.createElement("div");k.is_modified=!1;k.className="graphdialog rounded";k.innerHTML=d?" ": -" ";k.close=function(){g.prompt_box=null;k.parentNode&&k.parentNode.removeChild(k)};d=l.active_canvas.canvas;d.parentNode.appendChild(k);1l.search_limit))break}}u=null;if(Array.prototype.filter)u=Object.keys(f.registered_node_types).filter(e);else for(p in u=[],f.registered_node_types)e(p)&&u.push(p);for(p=0;pl.search_limit);p++);if(b.show_general_after_typefiltered&&(n.value|| -h.value)){filtered_extra=[];for(p in f.registered_node_types)e(p,{inTypeOverride:n&&n.value?"*":!1,outTypeOverride:h&&h.value?"*":!1})&&filtered_extra.push(p);for(p=0;pl.search_limit);p++);}if((n.value||h.value)&&0==v.childNodes.length&&b.show_general_if_none_on_typefilter){filtered_extra=[];for(p in f.registered_node_types)e(p,{skipFilter:!0})&&filtered_extra.push(p);for(p=0;pl.search_limit);p++);}}}b=Object.assign({slot_from:null,node_from:null,node_to:null,do_type_filter:f.search_filter_enabled,type_filter_in:!1,type_filter_out:!1,show_general_if_none_on_typefilter:!0,show_general_after_typefiltered:!0,hide_on_mouse_leave:f.search_hide_on_mouse_leave,show_all_if_empty:!0,show_all_on_open:f.search_show_all_on_open},b||{});var g=this,k=l.active_canvas,n=k.canvas,h=n.ownerDocument||document,p=document.createElement("div");p.className= -"litegraph litesearchbox graphdialog rounded";p.innerHTML="Search ";b.do_type_filter&&(p.innerHTML+="",p.innerHTML+="");p.innerHTML+="
";h.fullscreenElement?h.fullscreenElement.appendChild(p):(h.body.appendChild(p),h.body.style.overflow="hidden");if(b.do_type_filter)var q= -p.querySelector(".slot_in_type_filter"),x=p.querySelector(".slot_out_type_filter");p.close=function(){g.search_box=null;this.blur();n.focus();h.body.style.overflow="";setTimeout(function(){g.canvas.focus()},20);p.parentNode&&p.parentNode.removeChild(p)};1q.height-200&&(v.style.maxHeight=q.height-a.layerY-20+"px");w.focus();b.show_all_on_open&&d();return p};l.prototype.showEditPropertyValue=function(a,b,c){function e(){d(x.value)}function d(d){g&&g.values&&g.values.constructor===Object&&void 0!=g.values[d]&&(d=g.values[d]);"number"==typeof a.properties[b]&& -(d=Number(d));if("array"==f||"object"==f)d=JSON.parse(d);a.properties[b]=d;a.graph&&a.graph._version++;if(a.onPropertyChanged)a.onPropertyChanged(b,d);if(c.onclose)c.onclose();q.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var g=a.getPropertyInfo(b),f=g.type,n="";if("string"==f||"number"==f||"array"==f||"object"==f)n="";else if("enum"!=f&&"combo"!=f||!g.values)if("boolean"==f||"toggle"==f)n="";else{console.warn("unknown type: "+f);return}else{n=""}var q=this.createDialog(""+(g.label?g.label:b)+""+n+"",c),x=!1;if("enum"!=f&&"combo"!=f||!g.values)if("boolean"==f||"toggle"==f)(x=q.querySelector("input"))&& -x.addEventListener("click",function(a){q.modified();d(!!x.checked)});else{if(x=q.querySelector("input"))x.addEventListener("blur",function(a){this.focus()}),p=void 0!==a.properties[b]?a.properties[b]:"","string"!==f&&(p=JSON.stringify(p)),x.value=p,x.addEventListener("keydown",function(a){if(27==a.keyCode)q.close();else if(13==a.keyCode)e();else if(13!=a.keyCode){q.modified();return}a.preventDefault();a.stopPropagation()})}else x=q.querySelector("select"),x.addEventListener("change",function(a){q.modified(); -d(a.target.value)});x&&x.focus();q.querySelector("button").addEventListener("click",e);return q}};l.prototype.createDialog=function(a,b){b=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},b||{});var c=document.createElement("div");c.className="graphdialog";c.innerHTML=a;c.is_modified=!1;a=this.canvas.getBoundingClientRect();var e=-20,d=-20;a&&(e-=a.left,d-=a.top);b.position?(e+=b.position[0],d+=b.position[1]):b.event?(e+=b.event.clientX,d+=b.event.clientY):(e+=.5*this.canvas.width, -d+=.5*this.canvas.height);c.style.left=e+"px";c.style.top=d+"px";this.canvas.parentNode.appendChild(c);b.checkForInput&&(a=[],(a=c.querySelectorAll("input"))&&a.forEach(function(a){a.addEventListener("keydown",function(a){c.modified();if(27==a.keyCode)c.close();else if(13!=a.keyCode)return;a.preventDefault();a.stopPropagation()});a.focus()}));c.modified=function(){c.is_modified=!0};c.close=function(){c.parentNode&&c.parentNode.removeChild(c)};var g=null,k=!1;c.addEventListener("mouseleave",function(a){k|| -(b.closeOnLeave||f.dialog_close_on_mouse_leave)&&!c.is_modified&&f.dialog_close_on_mouse_leave&&(g=setTimeout(c.close,f.dialog_close_on_mouse_leave_delay))});c.addEventListener("mouseenter",function(a){(b.closeOnLeave||f.dialog_close_on_mouse_leave)&&g&&clearTimeout(g)});(a=c.querySelectorAll("select"))&&a.forEach(function(a){a.addEventListener("click",function(a){k++});a.addEventListener("blur",function(a){k=0});a.addEventListener("change",function(a){k=-1})});return c};l.prototype.createPanel=function(a, -b){b=b||{};var c=b.window||window,e=document.createElement("div");e.className="litegraph dialog";e.innerHTML="
";e.header=e.querySelector(".dialog-header");b.width&&(e.style.width=b.width+(b.width.constructor===Number?"px":""));b.height&&(e.style.height=b.height+(b.height.constructor===Number?"px":""));b.closable&& -(b=document.createElement("span"),b.innerHTML="✕",b.classList.add("close"),b.addEventListener("click",function(){e.close()}),e.header.appendChild(b));e.title_element=e.querySelector(".dialog-title");e.title_element.innerText=a;e.content=e.querySelector(".dialog-content");e.alt_content=e.querySelector(".dialog-alt-content");e.footer=e.querySelector(".dialog-footer");e.close=function(){if(e.onClose&&"function"==typeof e.onClose)e.onClose();e.parentNode&&e.parentNode.removeChild(e);this.parentNode&& -this.parentNode.removeChild(this)};e.toggleAltContent=function(a){if("undefined"!=typeof a){var b=a?"block":"none";a=a?"none":"block"}else b="block"!=e.alt_content.style.display?"block":"none",a="block"!=e.alt_content.style.display?"none":"block";e.alt_content.style.display=b;e.content.style.display=a};e.toggleFooterVisibility=function(a){e.footer.style.display="undefined"!=typeof a?a?"block":"none":"block"!=e.footer.style.display?"block":"none"};e.clear=function(){this.content.innerHTML=""};e.addHTML= -function(a,b,c){var d=document.createElement("div");b&&(d.className=b);d.innerHTML=a;c?e.footer.appendChild(d):e.content.appendChild(d);return d};e.addButton=function(a,b,c){var d=document.createElement("button");d.innerText=a;d.options=c;d.classList.add("btn");d.addEventListener("click",b);e.footer.appendChild(d);return d};e.addSeparator=function(){var a=document.createElement("div");a.className="separator";e.content.appendChild(a)};e.addWidget=function(a,b,k,n,h){function d(a,b){n.callback&&n.callback(a, -b,n);h&&h(a,b,n)}n=n||{};var g=String(k);a=a.toLowerCase();"number"==a&&(g=k.toFixed(3));var x=document.createElement("div");x.className="property";x.innerHTML="";x.querySelector(".property_name").innerText=n.label||b;var u=x.querySelector(".property_value");u.innerText=g;x.dataset.property=b;x.dataset.type=n.type||a;x.options=n;x.value=k;if("code"==a)x.addEventListener("click",function(a){e.inner_showCodePad(this.dataset.property)}); -else if("boolean"==a)x.classList.add("boolean"),k&&x.classList.add("bool-on"),x.addEventListener("click",function(){var a=this.dataset.property;this.value=!this.value;this.classList.toggle("bool-on");this.querySelector(".property_value").innerText=this.value?"true":"false";d(a,this.value)});else if("string"==a||"number"==a)u.setAttribute("contenteditable",!0),u.addEventListener("keydown",function(b){"Enter"!=b.code||"string"==a&&b.shiftKey||(b.preventDefault(),this.blur())}),u.addEventListener("blur", -function(){var a=this.innerText,b=this.parentNode.dataset.property;"number"==this.parentNode.dataset.type&&(a=Number(a));d(b,a)});else if("enum"==a||"combo"==a)g=l.getPropertyPrintableValue(k,n.values),u.innerText=g,u.addEventListener("click",function(a){var b=this.parentNode.dataset.property,e=this;new f.ContextMenu(n.values||[],{event:a,className:"dark",callback:function(a,c,f){e.innerText=a;d(b,a);return!1}},c)});e.content.appendChild(x);return x};if(e.onOpen&&"function"==typeof e.onOpen)e.onOpen(); -return e};l.getPropertyPrintableValue=function(a,b){if(!b||b.constructor===Array)return String(a);if(b.constructor===Object){var c="",e;for(e in b)if(b[e]==a){c=e;break}return String(a)+" ("+c+")"}};l.prototype.closePanels=function(){var a=document.querySelector("#node-panel");a&&a.close();(a=document.querySelector("#option-panel"))&&a.close()};l.prototype.showShowGraphOptionsPanel=function(a,b,c,e){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(b&&b.event&&b.event.target&&b.event.target.lgraphcanvas)){console.warn("Canvas not found"); -return}var d=b.event.target.lgraphcanvas}else d=this;d.closePanels();a=d.getCanvasWindow();panel=d.createPanel("Options",{closable:!0,window:a,onOpen:function(){d.OPTIONPANEL_IS_OPEN=!0},onClose:function(){d.OPTIONPANEL_IS_OPEN=!1;d.options_panel=null}});d.options_panel=panel;panel.id="option-panel";panel.classList.add("settings");(function(){panel.content.innerHTML="";var a=function(a,b,c){c&&c.key&&(a=c.key);c.values&&(b=Object.values(c.values).indexOf(b));d[a]=b},b=f.availableCanvasOptions;b.sort(); -for(var c in b){var e=b[c];panel.addWidget("boolean",e,d[e],{key:e,on:"True",off:"False"},a)}panel.addWidget("combo","Render mode",f.LINK_RENDER_MODES[d.links_render_mode],{key:"links_render_mode",values:f.LINK_RENDER_MODES},a);panel.addSeparator();panel.footer.innerHTML=""})();d.canvas.parentNode.appendChild(panel)};l.prototype.showShowNodePanel=function(a){function b(){d.content.innerHTML="";d.addHTML(""+a.type+""+(a.constructor.desc||"")+""); -d.addHTML("

Properties

");var b=function(b,c){e.graph.beforeChange(a);switch(b){case "Title":a.title=c;break;case "Mode":b=Object.values(f.NODE_MODES).indexOf(c);0<=b&&f.NODE_MODES[b]?a.changeMode(b):console.warn("unexpected mode: "+c);break;case "Color":l.node_colors[c]?(a.color=l.node_colors[c].color,a.bgcolor=l.node_colors[c].bgcolor):console.warn("unexpected color: "+c);break;default:a.setProperty(b,c)}e.graph.afterChange();e.dirty_canvas=!0};d.addWidget("string","Title",a.title,{},b); -d.addWidget("combo","Mode",f.NODE_MODES[a.mode],{values:f.NODE_MODES},b);var c="";void 0!==a.color&&(c=Object.keys(l.node_colors).filter(function(b){return l.node_colors[b].color==a.color}));d.addWidget("combo","Color",c,{values:Object.keys(l.node_colors)},b);for(var n in a.properties){c=a.properties[n];var h=a.getPropertyInfo(n);a.onAddPropertyToPanel&&a.onAddPropertyToPanel(n,d)||d.addWidget(h.widget||h.type,n,c,h,b)}d.addSeparator();if(a.onShowCustomPanelInfo)a.onShowCustomPanelInfo(d);d.footer.innerHTML= -"";d.addButton("Delete",function(){a.block_delete||(a.graph.remove(a),d.close())}).classList.add("delete")}this.SELECTED_NODE=a;this.closePanels();var c=this.getCanvasWindow(),e=this,d=this.createPanel(a.title||"",{closable:!0,window:c,onOpen:function(){e.NODEPANEL_IS_OPEN=!0},onClose:function(){e.NODEPANEL_IS_OPEN=!1;e.node_panel=null}});e.node_panel=d;d.id="node-panel";d.node=a;d.classList.add("settings");d.inner_showCodePad=function(c){d.classList.remove("settings");d.classList.add("centered"); -d.alt_content.innerHTML="";var e=d.alt_content.querySelector("textarea"),f=function(){d.toggleAltContent(!1);d.toggleFooterVisibility(!0);e.parentNode.removeChild(e);d.classList.add("settings");d.classList.remove("centered");b()};e.value=a.properties[c];e.addEventListener("keydown",function(b){"Enter"==b.code&&b.ctrlKey&&(a.setProperty(c,e.value),f())});d.toggleAltContent(!0);d.toggleFooterVisibility(!1);e.style.height="calc(100% - 40px)";var g=d.addButton("Assign", -function(){a.setProperty(c,e.value);f()});d.alt_content.appendChild(g);g=d.addButton("Close",f);g.style.float="right";d.alt_content.appendChild(g)};b();this.canvas.parentNode.appendChild(d)};l.prototype.showSubgraphPropertiesDialog=function(a){function b(){e.clear();if(a.inputs)for(var c=0;c","subgraph_property"); -k.dataset.name=f.name;k.dataset.slot=c;k.querySelector(".name").innerText=f.name;k.querySelector(".type").innerText=f.type;k.querySelector("button").addEventListener("click",function(c){a.removeInput(Number(this.parentNode.dataset.slot));b()})}}}console.log("showing subgraph properties dialog");var c=this.canvas.parentNode.querySelector(".subgraph_dialog");c&&c.close();var e=this.createPanel("Subgraph Inputs",{closable:!0,width:500});e.node=a;e.classList.add("subgraph_dialog");e.addHTML(" + NameType", -"subgraph_property extra",!0).querySelector("button").addEventListener("click",function(c){c=this.parentNode;var d=c.querySelector(".name").value,e=c.querySelector(".type").value;d&&-1==a.findInputSlot(d)&&(a.addInput(d,e),c.querySelector(".name").value="",c.querySelector(".type").value="",b())});b();this.canvas.parentNode.appendChild(e);return e};l.prototype.showSubgraphPropertiesDialogRight=function(a){function b(){d.clear();if(a.outputs)for(var c=0;c","subgraph_property");f.dataset.name=e.name;f.dataset.slot=c;f.querySelector(".name").innerText=e.name;f.querySelector(".type").innerText=e.type;f.querySelector("button").addEventListener("click",function(c){a.removeOutput(Number(this.parentNode.dataset.slot));b()})}}}function c(){var c=this.parentNode,d=c.querySelector(".name").value,e=c.querySelector(".type").value;d&&-1==a.findOutputSlot(d)&& -(a.addOutput(d,e),c.querySelector(".name").value="",c.querySelector(".type").value="",b())}var e=this.canvas.parentNode.querySelector(".subgraph_dialog");e&&e.close();var d=this.createPanel("Subgraph Outputs",{closable:!0,width:500});d.node=a;d.classList.add("subgraph_dialog");e=d.addHTML(" + NameType","subgraph_property extra",!0);e.querySelector(".name").addEventListener("keydown", -function(a){13==a.keyCode&&c.apply(this)});e.querySelector("button").addEventListener("click",function(a){c.apply(this)});b();this.canvas.parentNode.appendChild(d);return d};l.prototype.checkPanels=function(){if(this.canvas)for(var a=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),b=0;b=Object.keys(a.selected_nodes).length)d.collapse(); -else for(var f in a.selected_nodes)a.selected_nodes[f].collapse();d.graph.afterChange()};l.onMenuNodePin=function(a,b,c,e,d){d.pin()};l.onMenuNodeMode=function(a,b,c,e,d){new f.ContextMenu(f.NODE_MODES,{event:c,callback:function(a){if(d){var b=Object.values(f.NODE_MODES).indexOf(a),c=function(c){0<=b&&f.NODE_MODES[b]?c.changeMode(b):(console.warn("unexpected mode: "+a),c.changeMode(f.ALWAYS))},e=l.active_canvas;if(!e.selected_nodes||1>=Object.keys(e.selected_nodes).length)c(d);else for(var g in e.selected_nodes)c(e.selected_nodes[g])}}, -parentMenu:e,node:d});return!1};l.onMenuNodeColors=function(a,b,c,e,d){if(!d)throw"no node for color";b=[];b.push({value:null,content:"No color"});for(var g in l.node_colors)a=l.node_colors[g],a={value:g,content:""+g+""},b.push(a);new f.ContextMenu(b,{event:c,callback:function(a){if(d){var b=a.value?l.node_colors[a.value]: -null;a=function(a){b?a.constructor===f.LGraphGroup?a.color=b.groupcolor:(a.color=b.color,a.bgcolor=b.bgcolor):(delete a.color,delete a.bgcolor)};var c=l.active_canvas;if(!c.selected_nodes||1>=Object.keys(c.selected_nodes).length)a(d);else for(var e in c.selected_nodes)a(c.selected_nodes[e]);d.setDirtyCanvas(!0,!0)}},parentMenu:e,node:d});return!1};l.onMenuNodeShapes=function(a,b,c,e,d){if(!d)throw"no node passed";new f.ContextMenu(f.VALID_SHAPES,{event:c,callback:function(a){if(d){d.graph.beforeChange(); -var b=l.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)d.shape=a;else for(var c in b.selected_nodes)b.selected_nodes[c].shape=a;d.graph.afterChange();d.setDirtyCanvas(!0)}},parentMenu:e,node:d});return!1};l.onMenuNodeRemove=function(a,b,c,e,d){if(!d)throw"no node passed";a=d.graph;a.beforeChange();b=l.active_canvas;if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)!1!==d.removable&&a.remove(d);else for(var f in b.selected_nodes)c=b.selected_nodes[f],!1!==c.removable&& -a.remove(c);a.afterChange();d.setDirtyCanvas(!0,!0)};l.onMenuNodeToSubgraph=function(a,b,c,e,d){a=d.graph;if(b=l.active_canvas)c=Object.values(b.selected_nodes||{}),c.length||(c=[d]),e=f.createNode("graph/subgraph"),e.pos=d.pos.concat(),a.add(e),e.buildFromNodes(c),b.deselectAllNodes(),d.setDirtyCanvas(!0,!0)};l.onMenuNodeClone=function(a,b,c,e,d){d.graph.beforeChange();var f={};a=function(a){if(!1!==a.clonable){var b=a.clone();b&&(b.pos=[a.pos[0]+5,a.pos[1]+5],a.graph.add(b),f[b.id]=b)}};b=l.active_canvas; -if(!b.selected_nodes||1>=Object.keys(b.selected_nodes).length)a(d);else for(var k in b.selected_nodes)a(b.selected_nodes[k]);Object.keys(f).length&&b.selectNodes(f);d.graph.afterChange();d.setDirtyCanvas(!0,!0)};l.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"}, -cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}};l.prototype.getCanvasMenuOptions=function(){if(this.getMenuOptions)var a=this.getMenuOptions();else a=[{content:"Add Node",has_submenu:!0,callback:l.onMenuAdd},{content:"Add Group",callback:l.onGroupAdd}],1Name", -d),p=g.querySelector("input");p&&f&&(p.value=f.label||"");var k=function(){a.graph.beforeChange();p.value&&(f&&(f.label=p.value),c.setDirty(!0));g.close();a.graph.afterChange()};g.querySelector("button").addEventListener("click",k);p.addEventListener("keydown",function(a){g.is_modified=!0;if(27==a.keyCode)g.close();else if(13==a.keyCode)k();else if(13!=a.keyCode&&"textarea"!=a.target.localName)return;a.preventDefault();a.stopPropagation()});p.focus()}},extra:a};a&&(g.title=a.type);var k=null;a&&(k= -a.getSlotInPosition(b.canvasX,b.canvasY),l.active_node=a);k?(d=[],a.getSlotMenuOptions?d=a.getSlotMenuOptions(k):(k&&k.output&&k.output.links&&k.output.links.length&&d.push({content:"Disconnect Links",slot:k}),b=k.input||k.output,b.removable&&d.push(b.locked?"Cannot remove":{content:"Remove Slot",slot:k}),b.nameLocked||d.push({content:"Rename Slot",slot:k})),g.title=(k.input?k.input.type:k.output.type)||"*",k.input&&k.input.type==f.ACTION&&(g.title="Action"),k.output&&k.output.type==f.EVENT&&(g.title= -"Event")):a?d=this.getNodeMenuOptions(a):(d=this.getCanvasMenuOptions(),(k=this.graph.getGroupOnPos(b.canvasX,b.canvasY))&&d.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:k,options:this.getGroupMenuOptions(k)}}));d&&new f.ContextMenu(d,g,e)};f.compareObjects=function(a,b){for(var c in a)if(a[c]!=b[c])return!1;return!0};f.distance=C;f.colorToString=function(a){return"rgba("+Math.round(255*a[0]).toFixed()+","+Math.round(255*a[1]).toFixed()+","+Math.round(255*a[2]).toFixed()+ -","+(4==a.length?a[3].toFixed(2):"1.0")+")"};f.isInsideRectangle=B;f.growBounding=function(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)};f.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};f.overlapBounding=D;f.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,e,d,f=0;6>f;f+=2)e="0123456789ABCDEF".indexOf(a.charAt(f)),d="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*e+d,c++;return b}; -f.num2hex=function(a){for(var b="#",c,e,d=0;3>d;d++)c=a[d]/16,e=a[d]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(e);return b};G.prototype.addItem=function(a,b,c){function e(a){var b=this.value;b&&b.has_submenu&&d.call(this,a)}function d(a){var b=this.value,d=!0;g.current_submenu&&g.current_submenu.close(a);if(c.callback){var e=c.callback.call(this,b,c,a,g,c.node);!0===e&&(d=!1)}if(b&&(b.callback&&!c.ignore_item_callbacks&&!0!==b.disabled&&(e=b.callback.call(this,b,c,a,g,c.extra), -!0===e&&(d=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new g.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:g,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,extra:b.submenu.extra,autoopen:c.autoopen});d=!1}d&&!g.lock&&g.close()}var g=this;c=c||{};var k=document.createElement("div");k.className="litemenu-entry submenu";var h=!1;if(null===b)k.classList.add("separator");else{k.innerHTML=b&&b.title?b.title: -a;if(k.value=b)b.disabled&&(h=!0,k.classList.add("disabled")),(b.submenu||b.has_submenu)&&k.classList.add("has_submenu");"function"==typeof b?(k.dataset.value=a,k.onclick_callback=b):k.dataset.value=b;b.className&&(k.className+=" "+b.className)}this.root.appendChild(k);h||k.addEventListener("click",d);!h&&c.autoopen&&f.pointerListenerAdd(k,"enter",e);return k};G.prototype.close=function(a,b){this.root.parentNode&&this.root.parentNode.removeChild(this.root);this.parentMenu&&!b&&(this.parentMenu.lock= -!1,this.parentMenu.current_submenu=null,void 0===a?this.parentMenu.close():a&&!G.isCursorOverElement(a,this.parentMenu.root)&&G.trigger(this.parentMenu.root,f.pointerevents_method+"leave",a));this.current_submenu&&this.current_submenu.close(a,!0);this.root.closing_timer&&clearTimeout(this.root.closing_timer)};G.trigger=function(a,b,c,e){var d=document.createEvent("CustomEvent");d.initCustomEvent(b,!0,!0,c);d.srcElement=e;a.dispatchEvent?a.dispatchEvent(d):a.__events&&a.__events.dispatchEvent(d);return d}; -G.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};G.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};G.isCursorOverElement=function(a,b){var c=a.clientX;a=a.clientY;return(b=b.getBoundingClientRect())?a>b.top&&ab.left&&cMath.abs(b))return e[1];a=(a-e[0])/b;return e[1]*(1-a)+d[1]*a}}return 0}};H.prototype.draw=function(a,b,c,e,d,f){if(c=this.points){this.size=b;var g=b[0]-2*this.margin;b=b[1]-2*this.margin;d=d||"#666";a.save();a.translate(this.margin,this.margin);e&&(a.fillStyle="#111",a.fillRect(0,0,g,b),a.fillStyle="#222",a.fillRect(.5*g,0,1,b),a.strokeStyle="#333", -a.strokeRect(0,0,g,b));a.strokeStyle=d;f&&(a.globalAlpha=.5);a.beginPath();for(e=0;ea[1])){var e=this.size[0]-2*this.margin,d=this.size[1]-2*this.margin,f=a[0]-this.margin;a=a[1]-this.margin; -this.selected=this.getCloserPoint([f,a],30/b.ds.scale);-1==this.selected&&(b=[f/e,1-a/d],c.push(b),c.sort(function(a,b){return a[0]-b[0]}),this.selected=c.indexOf(b),this.must_update=!0);if(-1!=this.selected)return!0}};H.prototype.onMouseMove=function(a,b){var c=this.points;if(c){var e=this.selected;if(!(0>e)){var d=(a[0]-this.margin)/(this.size[0]-2*this.margin),f=(a[1]-this.margin)/(this.size[1]-2*this.margin);this._nearest=this.getCloserPoint([a[0]-this.margin,a[1]-this.margin],30/b.ds.scale); -if(b=c[e]){var k=0==e||e==c.length-1;!k&&(-10>a[0]||a[0]>this.size[0]+10||-10>a[1]||a[1]>this.size[1]+10)?(c.splice(e,1),this.selected=-1):(b[0]=k?0==e?0:1:I(d,0,1),b[1]=1-I(f,0,1),c.sort(function(a,b){return a[0]-b[0]}),this.selected=c.indexOf(b),this.must_update=!0)}}}};H.prototype.onMouseUp=function(a,b){this.selected=-1;return!1};H.prototype.getCloserPoint=function(a,b){var c=this.points;if(!c)return-1;b=b||30;for(var e=this.size[0]-2*this.margin,d=this.size[1]-2*this.margin,f=c.length,k=[0,0], -h=1E6,l=-1,p=0;ph||q>b||(l=p,h=q)}return l};f.CurveEditor=H;f.getParameterNames=function(a){return(a+"").replace(/[/][/].*$/gm,"").replace(/\s+/g,"").replace(/[/][*][^/*]*[*][/]/g,"").split("){",1)[0].replace(/^[^(]*[(]/,"").replace(/=[^,]+/g,"").split(",").filter(Boolean)};f.pointerListenerAdd=function(a,b,c,e){e=void 0===e?!1:e;if(a&&a.addEventListener&&b&&"function"===typeof c){var d=f.pointerevents_method;if("pointer"==d&&!window.PointerEvent)switch(console.warn("sMethod=='pointer' && !window.PointerEvent"), -console.log("Converting pointer["+b+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."),b){case "down":d="touch";b="start";break;case "move":d="touch";break;case "up":d="touch";b="end";break;case "cancel":d="touch";break;case "enter":console.log("debug: Should I send a move event?");break;default:console.warn("PointerEvent not available in this browser ? The event "+b+" would not be called")}switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":a.addEventListener(d+ -b,c,e);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("mouse"!=d)return a.addEventListener(d+b,c,e);default:return a.addEventListener(b,c,e)}}};f.pointerListenerRemove=function(a,b,c,e){e=void 0===e?!1:e;if(a&&a.removeEventListener&&b&&"function"===typeof c)switch(b){case "down":case "up":case "move":case "over":case "out":case "enter":"pointer"!=f.pointerevents_method&&"mouse"!=f.pointerevents_method||a.removeEventListener(f.pointerevents_method+b,c,e);case "leave":case "cancel":case "gotpointercapture":case "lostpointercapture":if("pointer"== -f.pointerevents_method)return a.removeEventListener(f.pointerevents_method+b,c,e);default:return a.removeEventListener(b,c,e)}};w.clamp=I;"undefined"==typeof window||window.requestAnimationFrame||(window.requestAnimationFrame=window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||function(a){window.setTimeout(a,1E3/60)})})(this); -"undefined"!=typeof exports&&(exports.LiteGraph=this.LiteGraph,exports.LGraph=this.LGraph,exports.LLink=this.LLink,exports.LGraphNode=this.LGraphNode,exports.LGraphGroup=this.LGraphGroup,exports.DragAndScale=this.DragAndScale,exports.LGraphCanvas=this.LGraphCanvas,exports.ContextMenu=this.ContextMenu); -(function(w){function m(){this.addOutput("in ms","number");this.addOutput("in sec","number")}function r(){this.size=[140,80];this.properties={enabled:!0};this.enabled=!0;this.subgraph=new t.LGraph;this.subgraph._subgraph_node=this;this.subgraph._is_subgraph=!0;this.subgraph.onTrigger=this.onSubgraphTrigger.bind(this);this.subgraph.onInputAdded=this.onSubgraphNewInput.bind(this);this.subgraph.onInputRenamed=this.onSubgraphRenamedInput.bind(this);this.subgraph.onInputTypeChanged=this.onSubgraphTypeChangeInput.bind(this); -this.subgraph.onInputRemoved=this.onSubgraphRemovedInput.bind(this);this.subgraph.onOutputAdded=this.onSubgraphNewOutput.bind(this);this.subgraph.onOutputRenamed=this.onSubgraphRenamedOutput.bind(this);this.subgraph.onOutputTypeChanged=this.onSubgraphTypeChangeOutput.bind(this);this.subgraph.onOutputRemoved=this.onSubgraphRemovedOutput.bind(this)}function h(){this.addOutput("","number");this.name_in_graph="";this.properties={name:"",type:"number",value:0};var a=this;this.name_widget=this.addWidget("text", -"Name",this.properties.name,function(b){b&&a.setProperty("name",b)});this.type_widget=this.addWidget("text","Type",this.properties.type,function(b){a.setProperty("type",b)});this.value_widget=this.addWidget("number","Value",this.properties.value,function(b){a.setProperty("value",b)});this.widgets_up=!0;this.size=[180,90]}function y(){this.addInput("","");this.name_in_graph="";this.properties={name:"",type:""};this.name_widget=this.addWidget("text","Name",this.properties.name,"name");this.type_widget= -this.addWidget("text","Type",this.properties.type,"type");this.widgets_up=!0;this.size=[180,60]}function z(){this.addOutput("value","number");this.addProperty("value",1);this.widget=this.addWidget("number","value",1,"value");this.widgets_up=!0;this.size=[180,30]}function l(){this.addOutput("bool","boolean");this.addProperty("value",!0);this.widget=this.addWidget("toggle","value",!0,"value");this.widgets_up=this.serialize_widgets=!0;this.size=[140,30]}function C(){this.addOutput("string","string"); -this.addProperty("value","");this.widget=this.addWidget("text","value","","value");this.widgets_up=!0;this.size=[180,30]}function B(){this.addOutput("obj","object");this.size=[120,30];this._object={}}function D(){this.addInput("url","string");this.addOutput("file","string");this.addProperty("url","");this.addProperty("type","text");this.widget=this.addWidget("text","url","","url");this._data=null}function G(){this.addInput("parse",t.ACTION);this.addInput("json","string");this.addOutput("done",t.EVENT); -this.addOutput("object","object");this.widget=this.addWidget("button","parse","",this.parse.bind(this));this._obj=this._str=null}function H(){this.addOutput("data","object");this.addProperty("value","");this.widget=this.addWidget("text","json","","value");this.widgets_up=!0;this.size=[140,30];this._value=null}function I(){this._value=[];this.addInput("json","");this.addOutput("arrayOut","array");this.addOutput("length","number");this.addProperty("value","[]");this.widget=this.addWidget("text","array", -this.properties.value,"value");this.widgets_up=!0;this.size=[140,50]}function f(){this.addInput("arr","array");this.addInput("value","");this.addOutput("arr","array");this.properties={index:0};this.widget=this.addWidget("number","i",this.properties.index,"index",{precision:0,step:10,min:0})}function J(){this.addInput("array","array,table,string");this.addInput("index","number");this.addOutput("value","");this.addProperty("index",0)}function K(){this.addInput("table","table");this.addInput("row","number"); -this.addInput("col","number");this.addOutput("value","");this.addProperty("row",0);this.addProperty("column",0)}function A(){this.addInput("obj","object");this.addOutput("property",0);this.addProperty("value",0);this.widget=this.addWidget("text","prop.","",this.setValue.bind(this));this.widgets_up=!0;this.size=[140,30];this._value=null}function L(){this.addInput("obj","");this.addOutput("keys","array");this.size=[140,30]}function E(){this.addInput("obj","");this.addInput("value","");this.addOutput("obj", -"");this.properties={property:""};this.name_widget=this.addWidget("text","prop.",this.properties.property,"property")}function N(){this.addInput("A","object");this.addInput("B","object");this.addOutput("out","object");this._result={};var a=this;this.addWidget("button","clear","",function(){a._result={}});this.size=this.computeSize()}function F(){this.size=[60,30];this.addInput("in");this.addOutput("out");this.properties={varname:"myname",container:F.LITEGRAPH};this.value=null}function a(a){return a&& -null!=a.length?Number(a.length):0}function a(a){return a&&null!=a.length?Number(a.length):0}function b(){this.size=[60,30];this.addInput("data",0);this.addInput("download",t.ACTION);this.properties={filename:"data.json"};this.value=null;var a=this;this.addWidget("button","Download","",function(b){a.value&&a.downloadAsFile()})}function c(){this.size=[60,30];this.addInput("value",0,{label:""});this.value=0}function e(){this.addInput("in",0);this.addOutput("out",0);this.size=[40,30]}function d(){this.mode= -t.ON_EVENT;this.size=[80,30];this.addProperty("msg","");this.addInput("log",t.EVENT);this.addInput("msg",0)}function g(){this.mode=t.ON_EVENT;this.addProperty("msg","");this.addInput("",t.EVENT);this.widget=this.addWidget("text","Text","","msg");this.widgets_up=!0;this.size=[200,30]}function k(){this.size=[60,30];this.addProperty("onExecute","return A;");this.addInput("A",0);this.addInput("B",0);this.addOutput("out",0);this._func=null;this.data={}}function n(){this.addInput("A",0);this.addInput("B", -0);this.addOutput("true","boolean");this.addOutput("false","boolean");this.addProperty("A",1);this.addProperty("B",1);this.addProperty("OP","==","enum",{values:n.values});this.addWidget("combo","Op.",this.properties.OP,{property:"OP",values:n.values});this.size=[80,60]}var t=w.LiteGraph;m.title="Time";m.desc="Time";m.prototype.onExecute=function(){this.setOutputData(0,1E3*this.graph.globaltime);this.setOutputData(1,this.graph.globaltime)};t.registerNodeType("basic/time",m);r.title="Subgraph";r.desc= -"Graph inside a node";r.title_color="#334";r.prototype.onGetInputs=function(){return[["enabled","boolean"]]};r.prototype.onDblClick=function(a,b,c){var d=this;setTimeout(function(){c.openSubgraph(d.subgraph)},10)};r.prototype.onAction=function(a,b){this.subgraph.onAction(a,b)};r.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled")){if(this.inputs)for(var a=0;aa&&(b[0]=f?this.trigger(null,h,l):this._pending.push([f,h])};B.prototype.onExecute= -function(f,h){f=1E3*this.graph.elapsed_time;this.isInputConnected(1)&&(this.properties.time_in_ms=this.getInputData(1));for(var l=0;lr?h.xbox.axes.lx:0,this._left_axis[1]=Math.abs(h.xbox.axes.ly)>r?h.xbox.axes.ly:0,this._right_axis[0]=Math.abs(h.xbox.axes.rx)>r?h.xbox.axes.rx:0,this._right_axis[1]=Math.abs(h.xbox.axes.ry)>r?h.xbox.axes.ry:0,this._triggers[0]=Math.abs(h.xbox.axes.ltrigger)>r?h.xbox.axes.ltrigger: -0,this._triggers[1]=Math.abs(h.xbox.axes.rtrigger)>r?h.xbox.axes.rtrigger:0);if(this.outputs)for(r=0;rr;r++)if(h[r]){h=h[r];r=this.xbox_mapping;r||(r=this.xbox_mapping={axes:[], -buttons:{},hat:"",hatmap:m.CENTER});r.axes.lx=h.axes[0];r.axes.ly=h.axes[1];r.axes.rx=h.axes[2];r.axes.ry=h.axes[3];r.axes.ltrigger=h.buttons[6].value;r.axes.rtrigger=h.buttons[7].value;r.hat="";r.hatmap=m.CENTER;for(var z=0;zz)r.buttons[m.mapping_array[z]]=h.buttons[z].pressed,h.buttons[z].was_pressed&&this.trigger(m.mapping_array[z]+"_button_event");else switch(z){case 12:h.buttons[z].pressed&&(r.hat+="up",r.hatmap|=m.UP); -break;case 13:h.buttons[z].pressed&&(r.hat+="down",r.hatmap|=m.DOWN);break;case 14:h.buttons[z].pressed&&(r.hat+="left",r.hatmap|=m.LEFT);break;case 15:h.buttons[z].pressed&&(r.hat+="right",r.hatmap|=m.RIGHT);break;case 16:r.buttons.home=h.buttons[z].pressed}h.xbox=r;return h}};m.prototype.onDrawBackground=function(h){if(!this.flags.collapsed){var m=this._left_axis,r=this._right_axis;h.strokeStyle="#88A";h.strokeRect(.5*(m[0]+1)*this.size[0]-4,.5*(m[1]+1)*this.size[1]-4,8,8);h.strokeStyle="#8A8"; -h.strokeRect(.5*(r[0]+1)*this.size[0]-4,.5*(r[1]+1)*this.size[1]-4,8,8);m=this.size[1]/this._current_buttons.length;h.fillStyle="#AEB";for(r=0;r","enum",{values:F.values});this.addWidget("combo", -"Cond.",this.properties.OP,{property:"OP",values:F.values});this.size=[80,60]}function a(){this.addInput("in",0);this.addInput("cond","boolean");this.addOutput("true",0);this.addOutput("false",0);this.size=[80,60]}function b(){this.addInput("inc","number");this.addOutput("total","number");this.addProperty("increment",1);this.addProperty("value",0)}function c(){this.addInput("v","number");this.addOutput("sin","number");this.addProperty("amplitude",1);this.addProperty("offset",0);this.bgImageUrl="nodes/imgs/icon-sin.png"} -function e(){this.addInput("x","number");this.addInput("y","number");this.addOutput("","number");this.properties={x:1,y:1,formula:"x+y"};this.code_widget=this.addWidget("text","F(x,y)",this.properties.formula,function(a,b,c){c.properties.formula=a});this.addWidget("toggle","allow",q.allow_scripts,function(a){q.allow_scripts=a});this._func=null}function d(){this.addInput("vec2","vec2");this.addOutput("x","number");this.addOutput("y","number")}function g(){this.addInputs([["x","number"],["y","number"]]); -this.addOutput("vec2","vec2");this.properties={x:0,y:0};this._data=new Float32Array(2)}function k(){this.addInput("vec3","vec3");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z","number")}function n(){this.addInputs([["x","number"],["y","number"],["z","number"]]);this.addOutput("vec3","vec3");this.properties={x:0,y:0,z:0};this._data=new Float32Array(3)}function t(){this.addInput("vec4","vec4");this.addOutput("x","number");this.addOutput("y","number");this.addOutput("z", -"number");this.addOutput("w","number")}function p(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]);this.addOutput("vec4","vec4");this.properties={x:0,y:0,z:0,w:0};this._data=new Float32Array(4)}var q=w.LiteGraph;m.title="Converter";m.desc="type A to type B";m.prototype.onExecute=function(){var a=this.getInputData(0);if(null!=a&&this.outputs)for(var b=0;ba&&(a+=1024);var d=Math.floor(a);a-=d;c=l.data[d];d=l.data[1023==d?0:d+1];b&&(a=a*a*a*(a*(6*a-15)+10));return c*(1-a)+d*a};l.prototype.onExecute=function(){var a=this.getInputData(0)|| -0,b=this.properties.octaves||1,c=0,d=1;a+=this.properties.seed||0;for(var e=this.properties.speed||1,f=0,g=0;gd);++g);a=this.properties.min;this._last_v=c/f*(this.properties.max-a)+a;this.setOutputData(0,this._last_v)};l.prototype.onDrawBackground=function(a){this.outputs[0].label=(this._last_v||0).toFixed(3)};q.registerNodeType("math/noise",l);C.title="Spikes";C.desc="spike every random time";C.prototype.onExecute= -function(){var a=this.graph.elapsed_time;this._remaining_time-=a;this._blink_time-=a;a=0;0this._remaining_time?(this._remaining_time=Math.random()*(this.properties.max_time-this.properties.min_time)+this.properties.min_time,this._blink_time=this.properties.duration,this.boxcolor="#FFF"):this.boxcolor="#000";this.setOutputData(0,a)};q.registerNodeType("math/spikes",C);B.title="Clamp";B.desc="Clamp number between min and max"; -B.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(a=Math.max(this.properties.min,a),a=Math.min(this.properties.max,a),this.setOutputData(0,a))};B.prototype.getCode=function(a){a="";this.isInputConnected(0)&&(a+="clamp({{0}},"+this.properties.min+","+this.properties.max+")");return a};q.registerNodeType("math/clamp",B);D.title="Lerp";D.desc="Linear Interpolation";D.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.getInputData(1);null==b&&(b=0); -var c=this.properties.f,d=this.getInputData(2);void 0!==d&&(c=d);this.setOutputData(0,a*(1-c)+b*c)};D.prototype.onGetInputs=function(){return[["f","number"]]};q.registerNodeType("math/lerp",D);G.title="Abs";G.desc="Absolute";G.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,Math.abs(a))};q.registerNodeType("math/abs",G);H.title="Floor";H.desc="Floor number to remove fractional part";H.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0, -Math.floor(a))};q.registerNodeType("math/floor",H);I.title="Frac";I.desc="Returns fractional part";I.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a%1)};q.registerNodeType("math/frac",I);f.title="Smoothstep";f.desc="Smoothstep";f.prototype.onExecute=function(){var a=this.getInputData(0);if(void 0!==a){var b=this.properties.A;a=clamp((a-b)/(this.properties.B-b),0,1);this.setOutputData(0,a*a*(3-2*a))}};q.registerNodeType("math/smoothstep",f);J.title="Scale"; -J.desc="v * factor";J.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&this.setOutputData(0,a*this.properties.factor)};q.registerNodeType("math/scale",J);K.title="Gate";K.desc="if v is true, then outputs A, otherwise B";K.prototype.onExecute=function(){var a=this.getInputData(0);this.setOutputData(0,this.getInputData(a?1:2))};q.registerNodeType("math/gate",K);A.title="Average";A.desc="Average Filter";A.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b= -this._values.length;this._values[this._current%b]=a;this._current+=1;this._current>b&&(this._current=0);for(var c=a=0;cb&&(b=1);this.properties.samples=Math.round(b);a=this._values;this._values=new Float32Array(this.properties.samples);a.length<=this._values.length?this._values.set(a):this._values.set(a.subarray(0,this._values.length))};q.registerNodeType("math/average",A);L.title="TendTo";L.desc="moves the output value always closer to the input"; -L.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.factor;this._value=null==this._value?a:this._value*(1-b)+a*b;this.setOutputData(0,this._value)};q.registerNodeType("math/tendTo",L);E.values="+ - * / % ^ max min".split(" ");E.funcs={"+":function(a,b){return a+b},"-":function(a,b){return a-b},x:function(a,b){return a*b},X:function(a,b){return a*b},"*":function(a,b){return a*b},"/":function(a,b){return a/b},"%":function(a,b){return a%b},"^":function(a, -b){return Math.pow(a,b)},max:function(a,b){return Math.max(a,b)},min:function(a,b){return Math.min(a,b)}};E.title="Operation";E.desc="Easy math operators";E["@OP"]={type:"enum",title:"operation",values:E.values};E.size=[100,60];E.prototype.getTitle=function(){return"max"==this.properties.OP||"min"==this.properties.OP?this.properties.OP+"(A,B)":"A "+this.properties.OP+" B"};E.prototype.setValue=function(a){"string"==typeof a&&(a=parseFloat(a));this.properties.value=a};E.prototype.onPropertyChanged= -function(a,b){"OP"==a&&(this._func=E.funcs[this.properties.OP],this._func||(console.warn("Unknown operation: "+this.properties.OP),this._func=function(a){return a}))};E.prototype.onExecute=function(){var a=this.getInputData(0),b=this.getInputData(1);null!=a?a.constructor===Number&&(this.properties.A=a):a=this.properties.A;null!=b?this.properties.B=b:b=this.properties.B;var c=E.funcs[this.properties.OP];if(a.constructor===Number)var d=c(a,b);else if(a.constructor===Array){d=this._result;d.length=a.length; -for(var e=0;eB":f=a>b;break;case "A=B":f=a>=b}this.setOutputData(c,f)}}};N.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]};q.registerNodeType("math/compare",N);q.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"});q.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"});q.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]], -title:"A>B"});q.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"});q.registerSearchboxExtra("math/compare","<=",{outputs:[["A<=B","boolean"]],title:"A<=B"});F.values="> < == != <= >= || &&".split(" ");F["@OP"]={type:"enum",title:"operation",values:F.values};F.title="Condition";F.desc="evaluates condition between A and B";F.prototype.getTitle=function(){return"A "+this.properties.OP+ -" B"};F.prototype.onExecute=function(){var a=this.getInputData(0);void 0===a?a=this.properties.A:this.properties.A=a;var b=this.getInputData(1);void 0===b?b=this.properties.B:this.properties.B=b;var c=!0;switch(this.properties.OP){case ">":c=a>b;break;case "<":c=a=":c=a>=b;break;case "||":c=a||b;break;case "&&":c=a&&b}this.setOutputData(0,c);this.setOutputData(1,!c)};q.registerNodeType("math/condition",F);a.title= -"Branch";a.desc="If condition is true, outputs IN in true, otherwise in false";a.prototype.onExecute=function(){var a=this.getInputData(0);this.getInputData(1)?(this.setOutputData(0,a),this.setOutputData(1,null)):(this.setOutputData(0,null),this.setOutputData(1,a))};q.registerNodeType("math/branch",a);b.title="Accumulate";b.desc="Increments a value every time";b.prototype.onExecute=function(){null===this.properties.value&&(this.properties.value=0);var a=this.getInputData(0);this.properties.value= -null!==a?this.properties.value+a:this.properties.value+this.properties.increment;this.setOutputData(0,this.properties.value)};q.registerNodeType("math/accumulate",b);c.title="Trigonometry";c.desc="Sin Cos Tan";c.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=0);var b=this.properties.amplitude,c=this.findInputSlot("amplitude");-1!=c&&(b=this.getInputData(c));var d=this.properties.offset;c=this.findInputSlot("offset");-1!=c&&(d=this.getInputData(c));c=0;for(var e=this.outputs.length;c< -e;++c){switch(this.outputs[c].name){case "sin":var f=Math.sin(a);break;case "cos":f=Math.cos(a);break;case "tan":f=Math.tan(a);break;case "asin":f=Math.asin(a);break;case "acos":f=Math.acos(a);break;case "atan":f=Math.atan(a)}this.setOutputData(c,b*f+d)}};c.prototype.onGetInputs=function(){return[["v","number"],["amplitude","number"],["offset","number"]]};c.prototype.onGetOutputs=function(){return[["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]]}; -q.registerNodeType("math/trigonometry",c);q.registerSearchboxExtra("math/trigonometry","SIN()",{outputs:[["sin","number"]],title:"SIN()"});q.registerSearchboxExtra("math/trigonometry","COS()",{outputs:[["cos","number"]],title:"COS()"});q.registerSearchboxExtra("math/trigonometry","TAN()",{outputs:[["tan","number"]],title:"TAN()"});e.title="Formula";e.desc="Compute formula";e.size=[160,100];A.prototype.onPropertyChanged=function(a,b){"formula"==a&&(this.code_widget.value=b)};e.prototype.onExecute= -function(){if(q.allow_scripts){var a=this.getInputData(0),b=this.getInputData(1);null!=a?this.properties.x=a:a=this.properties.x;null!=b?this.properties.y=b:b=this.properties.y;try{this._func&&this._func_code==this.properties.formula||(this._func=new Function("x","y","TIME","return "+this.properties.formula),this._func_code=this.properties.formula);var c=this._func(a,b,this.graph.globaltime);this.boxcolor=null}catch(v){this.boxcolor="red"}this.setOutputData(0,c)}};e.prototype.getTitle=function(){return this._func_code|| -"Formula"};e.prototype.onDrawBackground=function(){var a=this.properties.formula;this.outputs&&this.outputs.length&&(this.outputs[0].label=a)};q.registerNodeType("math/formula",e);d.title="Vec2->XY";d.desc="vector 2 to components";d.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]))};q.registerNodeType("math3d/vec2-to-xy",d);g.title="XY->Vec2";g.desc="components to vector2";g.prototype.onExecute=function(){var a=this.getInputData(0); -null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this._data;c[0]=a;c[1]=b;this.setOutputData(0,c)};q.registerNodeType("math3d/xy-to-vec2",g);k.title="Vec3->XYZ";k.desc="vector 3 to components";k.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]))};q.registerNodeType("math3d/vec3-to-xyz",k);n.title="XYZ->Vec3";n.desc="components to vector3";n.prototype.onExecute= -function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this._data;d[0]=a;d[1]=b;d[2]=c;this.setOutputData(0,d)};q.registerNodeType("math3d/xyz-to-vec3",n);t.title="Vec4->XYZW";t.desc="vector 4 to components";t.prototype.onExecute=function(){var a=this.getInputData(0);null!=a&&(this.setOutputData(0,a[0]),this.setOutputData(1,a[1]),this.setOutputData(2,a[2]),this.setOutputData(3, -a[3]))};q.registerNodeType("math3d/vec4-to-xyzw",t);p.title="XYZW->Vec4";p.desc="components to vector4";p.prototype.onExecute=function(){var a=this.getInputData(0);null==a&&(a=this.properties.x);var b=this.getInputData(1);null==b&&(b=this.properties.y);var c=this.getInputData(2);null==c&&(c=this.properties.z);var d=this.getInputData(3);null==d&&(d=this.properties.w);var e=this._data;e[0]=a;e[1]=b;e[2]=c;e[3]=d;this.setOutputData(0,e)};q.registerNodeType("math3d/xyzw-to-vec4",p)})(this); -(function(w){function m(){this.addInput("","string");this.addOutput("table","table");this.addOutput("rows","number");this.addProperty("value","");this.addProperty("separator",",");this._table=null}w=w.LiteGraph;w.wrapFunctionAsNode("string/toString",function(m){if(m&&m.constructor===Object)try{return JSON.stringify(m)}catch(h){}return String(m)},[""],"string");w.wrapFunctionAsNode("string/compare",function(m,h){return m==h},["string","string"],"boolean");w.wrapFunctionAsNode("string/concatenate", -function(m,h){return void 0===m?h:void 0===h?m:m+h},["string","string"],"string");w.wrapFunctionAsNode("string/contains",function(m,h){return void 0===m||void 0===h?!1:-1!=m.indexOf(h)},["string","string"],"boolean");w.wrapFunctionAsNode("string/toUpperCase",function(m){return null!=m&&m.constructor===String?m.toUpperCase():m},["string"],"string");w.wrapFunctionAsNode("string/split",function(m,h){null==h&&(h=this.properties.separator);if(null==m)return[];if(m.constructor===String)return m.split(h|| -" ");if(m.constructor===Array){for(var r=[],z=0;z(t^16*Math.random()>>t/4).toString(16))},isValidConnection:function(t,e){if(""!=e&&"*"!==e||(e=0),!(t=""!=t&&"*"!==t?t:0)||!e||t==e||t==LiteGraph.EVENT&&e==LiteGraph.ACTION)return!0;if(t=String(t),e=String(e),t=t.toLowerCase(),e=e.toLowerCase(),-1==t.indexOf(",")&&-1==e.indexOf(","))return t==e;for(var i=t.split(","),n=e.split(","),s=0;se&&(e=p.size[l]),s==LiteGraph.VERTICAL_LAYOUT?0:1);i+=p.size[l]+n+LiteGraph.NODE_TITLE_HEIGHT}a+=e+n}}this.setDirtyCanvas(!0,!0)},LGraph.prototype.getTime=function(){return this.globaltime},LGraph.prototype.getFixedTime=function(){return this.fixedtime},LGraph.prototype.getElapsedTime=function(){return this.elapsed_time},LGraph.prototype.sendEventToAllNodes=function(t,e,i){i=i||LiteGraph.ALWAYS;var n=this._nodes_in_order||this._nodes;if(n)for(var s=0,o=n.length;s=LiteGraph.MAX_NUMBER_OF_NODES)throw"LiteGraph: max number of nodes in a graph reached";return LiteGraph.use_uuids?null!=t.id&&-1!=t.id||(t.id=LiteGraph.uuidv4()):null==t.id||-1==t.id?t.id=++this.last_node_id:this.last_node_id=this.outputs.length)){var i=this.outputs[t];if(i&&(i._data=e,this.outputs[t].links))for(var n=0;n=this.outputs.length)){var i=this.outputs[t];if(i&&(i.type=e,this.outputs[t].links))for(var n=0;n=this.inputs.length||null==this.inputs[t].link))return t=this.inputs[t].link,(t=this.graph.links[t])?(e&&(e=this.graph.getNodeById(t.origin_id))&&(e.updateOutputData?e.updateOutputData(t.origin_slot):e.onExecute&&e.onExecute()),t.data):null},LGraphNode.prototype.getInputDataType=function(t){var e;return this.inputs&&!(t>=this.inputs.length||null==this.inputs[t].link)&&(t=this.inputs[t].link,t=this.graph.links[t])?(e=this.graph.getNodeById(t.origin_id))?(e=e.outputs[t.origin_slot])?e.type:null:t.type:null},LGraphNode.prototype.getInputDataByName=function(t,e){t=this.findInputSlot(t);return-1==t?null:this.getInputData(t,e)},LGraphNode.prototype.isInputConnected=function(t){return!!this.inputs&&t=this.inputs.length)&&(t=this.inputs[t])&&null!==t.link&&(t=this.graph.links[t.link])?this.graph.getNodeById(t.origin_id):null},LGraphNode.prototype.getInputOrProperty=function(t){if(!this.inputs||!this.inputs.length)return this.properties?this.properties[t]:null;for(var e=0,i=this.inputs.length;e=this.outputs.length?null:this.outputs[t]._data},LGraphNode.prototype.getOutputInfo=function(t){return this.outputs&&t=this.outputs.length)return null;var e=this.outputs[t];if(!e.links||0==e.links.length)return null;for(var i=[],n=0;nt&&this.pos[1]-s-ie)return!0;return!1},LGraphNode.prototype.getSlotInPosition=function(t,e){var i=new Float32Array(2);if(this.inputs)for(var n=0,s=this.inputs.length;n=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;if(!(e=e&&e.constructor===Number?this.graph.getNodeById(e):e))throw"target node is null";if(e==this)return null;if(i.constructor===String){if(-1==(i=e.findInputSlot(i)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+i),null}else if(i===LiteGraph.EVENT){if(!LiteGraph.do_add_triggers_slots)return null;e.changeMode(LiteGraph.ON_TRIGGER),i=e.findInputSlot("onTrigger")}else if(!e.inputs||i>=e.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),null;var n,s=!1,o=e.inputs[i],r=this.outputs[t];return this.outputs[t]?!1!==(i=e.onBeforeConnectInput?e.onBeforeConnectInput(i):i)&&null!==i&&LiteGraph.isValidConnection(r.type,o.type)?e.onConnectInput&&!1===e.onConnectInput(i,r.type,r,this,t)||this.onConnectOutput&&!1===this.onConnectOutput(t,o.type,o,e,i)?null:(e.inputs[i]&&null!=e.inputs[i].link&&(this.graph.beforeChange(),e.disconnectInput(i,{doProcessChange:!1}),s=!0),null!==r.links&&r.links.length&&r.type===LiteGraph.EVENT&&!LiteGraph.allow_multi_output_for_events&&(this.graph.beforeChange(),this.disconnectOutput(t,!1,{doProcessChange:!1}),s=!0),n=new LLink(LiteGraph.use_uuids?LiteGraph.uuidv4():++this.graph.last_link_id,o.type||r.type,this.id,t,e.id,i),this.graph.links[n.id]=n,null==r.links&&(r.links=[]),r.links.push(n.id),e.inputs[i].link=n.id,this.graph&&this.graph._version++,this.onConnectionsChange&&this.onConnectionsChange(LiteGraph.OUTPUT,t,!0,n,r),e.onConnectionsChange&&e.onConnectionsChange(LiteGraph.INPUT,i,!0,n,o),this.graph&&this.graph.onNodeConnectionChange&&(this.graph.onNodeConnectionChange(LiteGraph.INPUT,e,i,this,t),this.graph.onNodeConnectionChange(LiteGraph.OUTPUT,this,t,e,i)),this.setDirtyCanvas(!1,!0),this.graph.afterChange(),this.graph.connectionChange(this,n),n):(this.setDirtyCanvas(!1,!0),s&&this.graph.connectionChange(this,null),null):null},LGraphNode.prototype.disconnectOutput=function(t,e){if(t.constructor===String){if(-1==(t=this.findOutputSlot(t)))return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+t),!1}else if(!this.outputs||t>=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var i=this.outputs[t];if(!i||!i.links||0==i.links.length)return!1;if(e){if(!(e=e.constructor===Number?this.graph.getNodeById(e):e))throw"Target Node not found";for(var n=0,s=i.links.length;n=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var e=this.inputs[t];if(!e)return!1;var i=this.inputs[t].link;if(null!=i){this.inputs[t].link=null;var n=this.graph.links[i];if(n){var s=this.graph.getNodeById(n.origin_id);if(!s)return!1;var o=s.outputs[n.origin_slot];if(!o||!o.links||0==o.links.length)return!1;for(var r=0,a=o.links.length;rLGraphNode.MAX_CONSOLE&&this.console.shift(),this.graph.onNodeTrace&&this.graph.onNodeTrace(this,t)},LGraphNode.prototype.setDirtyCanvas=function(t,e){this.graph&&this.graph.sendActionToCanvas("setDirty",[t,e])},LGraphNode.prototype.loadImage=function(t){var e=new Image,i=(e.src=LiteGraph.node_images_path+t,e.ready=!1,this);return e.onload=function(){this.ready=!0,i.setDirtyCanvas(!0)},e},LGraphNode.prototype.captureInput=function(t){if(this.graph&&this.graph.list_of_graphcanvas)for(var e=this.graph.list_of_graphcanvas,i=0;i=this.viewport[0]&&i=this.viewport[1]&&nthis.max_scale&&(t=this.max_scale),t!=this.scale&&this.element&&(i=this.element.getBoundingClientRect())&&(e=e||[.5*i.width,.5*i.height],i=this.convertCanvasToOffset(e),this.scale=t,Math.abs(this.scale-1)<.01&&(this.scale=1),e=[(t=this.convertCanvasToOffset(e))[0]-i[0],t[1]-i[1]],this.offset[0]+=e[0],this.offset[1]+=e[1],this.onredraw)&&this.onredraw(this)},DragAndScale.prototype.changeDeltaScale=function(t,e){this.changeScale(this.scale*t,e)},DragAndScale.prototype.reset=function(){this.scale=1,this.offset[0]=0,this.offset[1]=0},global.LGraphCanvas=LiteGraph.LGraphCanvas=LGraphCanvas,LGraphCanvas.DEFAULT_BACKGROUND_IMAGE="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=",LGraphCanvas.link_type_colors={"-1":LiteGraph.EVENT_LINK_COLOR,number:"#AAA",node:"#DCA"},LGraphCanvas.gradients={},LGraphCanvas.prototype.clear=function(){this.frame=0,this.last_draw_time=0,this.render_time=0,this.fps=0,this.dragging_rectangle=null,this.selected_nodes={},this.selected_group=null,this.visible_nodes=[],this.node_dragged=null,this.node_over=null,this.node_capturing_input=null,this.connecting_node=null,this.highlighted_links={},this.dragging_canvas=!1,this.dirty_canvas=!0,this.dirty_bgcanvas=!0,this.dirty_area=null,this.node_in_panel=null,this.node_widget=null,this.last_mouse=[0,0],this.last_mouseclick=0,this.pointer_is_down=!1,this.pointer_is_double=!1,this.visible_area.set([0,0,0,0]),this.onClear&&this.onClear()},LGraphCanvas.prototype.setGraph=function(t,e){this.graph!=t&&(e||this.clear(),!t&&this.graph?this.graph.detachCanvas(this):(t.attachCanvas(this),this._graph_stack&&(this._graph_stack=null),this.setDirty(!0,!0)))},LGraphCanvas.prototype.getTopGraph=function(){return this._graph_stack.length?this._graph_stack[0]:this.graph},LGraphCanvas.prototype.openSubgraph=function(t){if(!t)throw"graph cannot be null";if(this.graph==t)throw"graph cannot be the same";this.clear(),this.graph&&(this._graph_stack||(this._graph_stack=[]),this._graph_stack.push(this.graph)),t.attachCanvas(this),this.checkPanels(),this.setDirty(!0,!0)},LGraphCanvas.prototype.closeSubgraph=function(){var t,e;this._graph_stack&&0!=this._graph_stack.length&&(t=this.graph._subgraph_node,e=this._graph_stack.pop(),this.selected_nodes={},this.highlighted_links={},e.attachCanvas(this),this.setDirty(!0,!0),t&&(this.centerOnNode(t),this.selectNodes([t])),this.ds.offset=[0,0],this.ds.scale=1)},LGraphCanvas.prototype.getCurrentGraph=function(){return this.graph},LGraphCanvas.prototype.setCanvas=function(t,e){if(t&&t.constructor===String&&!(t=document.getElementById(t)))throw"Error creating LiteGraph canvas: Canvas not found";if(t!==this.canvas&&(t||!this.canvas||e||this.unbindEvents(),this.canvas=t,this.ds.element=t)){if(t.className+=" lgraphcanvas",t.data=this,t.tabindex="1",this.bgcanvas=null,this.bgcanvas||(this.bgcanvas=document.createElement("canvas"),this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height),null==t.getContext){if("canvas"!=t.localName)throw"Element supplied for LGraphCanvas must be a element, you passed a "+t.localName;throw"This browser doesn't support Canvas"}null==(this.ctx=t.getContext("2d"))&&(t.webgl_enabled||console.warn("This canvas seems to be WebGL, enabling WebGL renderer"),this.enableWebGL()),e||this.bindEvents()}},LGraphCanvas.prototype._doNothing=function(t){return t.preventDefault(),!1},LGraphCanvas.prototype._doReturnTrue=function(t){return t.preventDefault(),!0},LGraphCanvas.prototype.bindEvents=function(){var t,e;this._events_binded?console.warn("LGraphCanvas: events already binded"):(t=this.canvas,e=this.getCanvasWindow().document,this._mousedown_callback=this.processMouseDown.bind(this),this._mousewheel_callback=this.processMouseWheel.bind(this),this._mousemove_callback=this.processMouseMove.bind(this),this._mouseup_callback=this.processMouseUp.bind(this),LiteGraph.pointerListenerAdd(t,"down",this._mousedown_callback,!0),t.addEventListener("mousewheel",this._mousewheel_callback,!1),LiteGraph.pointerListenerAdd(t,"up",this._mouseup_callback,!0),LiteGraph.pointerListenerAdd(t,"move",this._mousemove_callback),t.addEventListener("contextmenu",this._doNothing),t.addEventListener("DOMMouseScroll",this._mousewheel_callback,!1),this._key_callback=this.processKey.bind(this),t.setAttribute("tabindex",1),t.addEventListener("keydown",this._key_callback,!0),e.addEventListener("keyup",this._key_callback,!0),this._ondrop_callback=this.processDrop.bind(this),t.addEventListener("dragover",this._doNothing,!1),t.addEventListener("dragend",this._doNothing,!1),t.addEventListener("drop",this._ondrop_callback,!1),t.addEventListener("dragenter",this._doReturnTrue,!1),this._events_binded=!0)},LGraphCanvas.prototype.unbindEvents=function(){var t;this._events_binded?(t=this.getCanvasWindow().document,LiteGraph.pointerListenerRemove(this.canvas,"move",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"up",this._mousedown_callback),LiteGraph.pointerListenerRemove(this.canvas,"down",this._mousedown_callback),this.canvas.removeEventListener("mousewheel",this._mousewheel_callback),this.canvas.removeEventListener("DOMMouseScroll",this._mousewheel_callback),this.canvas.removeEventListener("keydown",this._key_callback),t.removeEventListener("keyup",this._key_callback),this.canvas.removeEventListener("contextmenu",this._doNothing),this.canvas.removeEventListener("drop",this._ondrop_callback),this.canvas.removeEventListener("dragenter",this._doReturnTrue),this._mousedown_callback=null,this._mousewheel_callback=null,this._key_callback=null,this._ondrop_callback=null,this._events_binded=!1):console.warn("LGraphCanvas: no events binded")},LGraphCanvas.getFileExtension=function(t){var e=t.indexOf("?"),e=(t=-1!=e?t.substr(0,e):t).lastIndexOf(".");return-1==e?"":t.substr(e+1).toLowerCase()},LGraphCanvas.prototype.enableWebGL=function(){if("undefined"==typeof GL)throw"litegl.js must be included to use a WebGL canvas";if("undefined"==typeof enableWebGLCanvas)throw"webglCanvas.js must be included to use this feature";this.gl=this.ctx=enableWebGLCanvas(this.canvas),this.ctx.webgl=!0,this.bgcanvas=this.canvas,this.bgctx=this.gl,this.canvas.webgl_enabled=!0},LGraphCanvas.prototype.setDirty=function(t,e){t&&(this.dirty_canvas=!0),e&&(this.dirty_bgcanvas=!0)},LGraphCanvas.prototype.getCanvasWindow=function(){var t;return this.canvas?(t=this.canvas.ownerDocument).defaultView||t.parentWindow:window},LGraphCanvas.prototype.startRendering=function(){function e(){this.pause_rendering||this.draw();var t=this.getCanvasWindow();this.is_rendering&&t.requestAnimationFrame(e.bind(this))}this.is_rendering||(this.is_rendering=!0,e.call(this))},LGraphCanvas.prototype.stopRendering=function(){this.is_rendering=!1},LGraphCanvas.prototype.blockClick=function(){this.block_click=!0,this.last_mouseclick=0},LGraphCanvas.prototype.processMouseDown=function(t){if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){this.adjustMouseEvent(t);var e=this.getCanvasWindow(),i=(e.document,LGraphCanvas.active_canvas=this),n=t.clientX,s=t.clientY,n=(this.ds.viewport=this.viewport,!this.viewport||(this.viewport,n>=this.viewport[0]&&n=this.viewport[1]&&s_[0]+4||t.canvasY<_[1]-4||t.canvasY>_[1]+4)){this.showLinkMenu(g,t),this.over_link_center=null;break}}this.selected_group=this.graph.getGroupOnPos(t.canvasX,t.canvasY),this.selected_group_resizing=!1,this.selected_group&&!this.read_only&&(t.ctrlKey&&(this.dragging_rectangle=null),distance([t.canvasX,t.canvasY],[this.selected_group.pos[0]+this.selected_group.size[0],this.selected_group.pos[1]+this.selected_group.size[1]])*this.ds.scale<10?this.selected_group_resizing=!0:this.selected_group.recomputeInsideNodes()),a&&!this.read_only&&this.allow_searchbox&&(this.showSearchBox(t),t.preventDefault(),t.stopPropagation()),n=!0}}else{if(this.live_mode||o.flags.pinned||this.bringToFront(o),this.allow_interaction&&!this.connecting_node&&!o.flags.collapsed&&!this.live_mode)if(!r&&!1!==o.resizable&&isInsideRectangle(t.canvasX,t.canvasY,o.pos[0]+o.size[0]-5,o.pos[1]+o.size[1]-5,10,10))this.graph.beforeChange(),this.resizing_node=o,this.canvas.style.cursor="se-resize",r=!0;else{if(o.outputs)for(var d=0,f=o.outputs.length;do.size[0]-LiteGraph.NODE_TITLE_HEIGHT&&s[1]<0&&(i=this,setTimeout(function(){i.openSubgraph(o.subgraph)},10)),this.live_mode&&(u=n=!0)),u?o.is_selected||this.processNodeSelected(o,t):(this.allow_dragnodes&&(this.graph.beforeChange(),this.node_dragged=o),this.processNodeSelected(o,t)),this.dirty_canvas=!0)}!r&&n&&this.allow_dragcanvas&&(this.dragging_canvas=!0)}return this.last_mouse[0]=t.clientX,this.last_mouse[1]=t.clientY,this.last_mouseclick=LiteGraph.getTime(),this.last_mouse_dragging=!0,this.graph.change(),e.document.activeElement&&("input"==e.document.activeElement.nodeName.toLowerCase()||"textarea"==e.document.activeElement.nodeName.toLowerCase())||t.preventDefault(),t.stopPropagation(),this.onMouseDown&&this.onMouseDown(t),!1}}}},LGraphCanvas.prototype.processMouseMove=function(t){if(this.autoresize&&this.resize(),this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){(LGraphCanvas.active_canvas=this).adjustMouseEvent(t);var e=[t.clientX,t.clientY],i=(this.mouse[0]=e[0],this.mouse[1]=e[1],[e[0]-this.last_mouse[0],e[1]-this.last_mouse[1]]);if(this.last_mouse=e,this.graph_mouse[0]=t.canvasX,this.graph_mouse[1]=t.canvasY,!this.block_click){t.dragging=this.last_mouse_dragging,this.node_widget&&(this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t,this.node_widget[1]),this.dirty_canvas=!0);var n=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle)this.dragging_rectangle[2]=t.canvasX-this.dragging_rectangle[0],this.dragging_rectangle[3]=t.canvasY-this.dragging_rectangle[1],this.dirty_canvas=!0;else if(this.selected_group&&!this.read_only)this.selected_group_resizing?this.selected_group.size=[t.canvasX-this.selected_group.pos[0],t.canvasY-this.selected_group.pos[1]]:(e=i[0]/this.ds.scale,a=i[1]/this.ds.scale,this.selected_group.move(e,a,t.ctrlKey),this.selected_group._nodes.length&&(this.dirty_canvas=!0)),this.dirty_bgcanvas=!0;else if(this.dragging_canvas)this.ds.offset[0]+=i[0]/this.ds.scale,this.ds.offset[1]+=i[1]/this.ds.scale,this.dirty_canvas=!0,this.dirty_bgcanvas=!0;else if((this.allow_interaction||n&&n.flags.allow_interaction)&&!this.read_only){this.connecting_node&&(this.dirty_canvas=!0);for(var s,o,r,a,h=0,p=this.graph._nodes.length;hc[0]+4||t.canvasYc[1]+4)){l=u;break}}l!=this.over_link_center&&(this.over_link_center=l,this.dirty_canvas=!0),this.canvas&&(this.canvas.style.cursor="")}if(this.node_capturing_input&&this.node_capturing_input!=n&&this.node_capturing_input.onMouseMove&&this.node_capturing_input.onMouseMove(t,[t.canvasX-this.node_capturing_input.pos[0],t.canvasY-this.node_capturing_input.pos[1]],this),this.node_dragged&&!this.live_mode){for(var h in this.selected_nodes){var d=this.selected_nodes[h];d.pos[0]+=i[0]/this.ds.scale,d.pos[1]+=i[1]/this.ds.scale,d.is_selected||this.processNodeSelected(d,t)}this.dirty_canvas=!0,this.dirty_bgcanvas=!0}this.resizing_node&&!this.live_mode&&(e=[t.canvasX-this.resizing_node.pos[0],t.canvasY-this.resizing_node.pos[1]],a=this.resizing_node.computeSize(),e[0]=Math.max(a[0],e[0]),e[1]=Math.max(a[1],e[1]),this.resizing_node.setSize(e),this.canvas.style.cursor="se-resize",this.dirty_canvas=!0,this.dirty_bgcanvas=!0)}}return t.preventDefault(),!1}},LGraphCanvas.prototype.processMouseUp=function(t){var e=void 0===t.isPrimary||t.isPrimary;if(!e)return!1;if(this.set_canvas_dirty_on_mouse_event&&(this.dirty_canvas=!0),this.graph){var i=this.getCanvasWindow().document,i=((LGraphCanvas.active_canvas=this).options.skip_events||(LiteGraph.pointerListenerRemove(i,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerAdd(this.canvas,"move",this._mousemove_callback,!0),LiteGraph.pointerListenerRemove(i,"up",this._mouseup_callback,!0)),this.adjustMouseEvent(t),LiteGraph.getTime());if(t.click_time=i-this.last_mouseclick,this.last_mouse_dragging=!1,this.last_click_position=null,this.block_click&&(this.block_click=!1),1==t.which){this.node_widget&&this.processNodeWidgets(this.node_widget[0],this.graph_mouse,t),this.node_widget=null,this.selected_group&&(i=this.selected_group.pos[0]-Math.round(this.selected_group.pos[0]),r=this.selected_group.pos[1]-Math.round(this.selected_group.pos[1]),this.selected_group.move(i,r,t.ctrlKey),this.selected_group.pos[0]=Math.round(this.selected_group.pos[0]),this.selected_group.pos[1]=Math.round(this.selected_group.pos[1]),this.selected_group._nodes.length&&(this.dirty_canvas=!0),this.selected_group=null),this.selected_group_resizing=!1;var n,i=this.graph.getNodeOnPos(t.canvasX,t.canvasY,this.visible_nodes);if(this.dragging_rectangle){if(this.graph){var s=this.graph._nodes,o=new Float32Array(4),r=Math.abs(this.dragging_rectangle[2]),a=Math.abs(this.dragging_rectangle[3]),h=this.dragging_rectangle[2]<0?this.dragging_rectangle[0]-r:this.dragging_rectangle[0],p=this.dragging_rectangle[3]<0?this.dragging_rectangle[1]-a:this.dragging_rectangle[1];if(this.dragging_rectangle[0]=h,this.dragging_rectangle[1]=p,this.dragging_rectangle[2]=r,this.dragging_rectangle[3]=a,!i||10=this.viewport[0]&&i=this.viewport[1]&&ni.nodes[o].pos[0]&&(n[0]=i.nodes[o].pos[0],s[0]=o),n[1]>i.nodes[o].pos[1]&&(n[1]=i.nodes[o].pos[1],s[1]=o)):(n=[i.nodes[o].pos[0],i.nodes[o].pos[1]],s=[o,o]);for(var r=[],o=0;o=this.viewport[0]&&e=this.viewport[1]&&i> ";e.fillText(n+i.getTitle(),.5*t.width,40),e.restore()}var o,i=!1;this.onRenderBackground&&(i=this.onRenderBackground(t,e)),this.viewport||(e.restore(),e.setTransform(1,0,0,1,0,0)),this.visible_links.length=0,this.graph&&(e.save(),this.ds.toCanvasContext(e),this.ds.scale<1.5&&!i&&this.clear_background_color&&(e.fillStyle=this.clear_background_color,e.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2],this.visible_area[3])),this.background_image&&.5t[2]&&(t[2]=e),it[3]&&(t[3]=i)}function isInsideBounding(t,e){return!(t[0]e[1][0]||t[1]>e[1][1])}function overlapBounding(t,e){var i=t[0]+t[2];return!(e[0]+e[2]e[1]+e[3]||ip.width-l.width-10&&(n=p.width-l.width-10),p.height)&&u>p.height-l.height-10&&(u=p.height-l.height-10),s.style.left=n+"px",s.style.top=u+"px",i.scale&&(s.style.transform="scale("+i.scale+")")}function CurveEditor(t){this.points=t,this.selected=-1,this.nearest=-1,this.size=null,this.must_update=!0,this.margin=5}function clamp(t,e,i){return te[1]?0:Math.PI,t.save(),t.translate(d[0],d[1]),t.rotate(O),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore(),t.save(),t.translate(s[0],s[1]),t.rotate(C),t.beginPath(),t.moveTo(-5,-3),t.lineTo(0,7),t.lineTo(5,-3),t.fill(),t.restore()),t.beginPath(),t.arc(w[0],w[1],5,0,2*Math.PI),t.fill()),o){t.fillStyle=r;for(g=0;g<5;++g){var E=(.001*LiteGraph.getTime()+.2*g)%1,w=this.computeConnectionPoint(e,i,E,a,h);t.beginPath(),t.arc(w[0],w[1],5,0,2*Math.PI),t.fill()}}},LGraphCanvas.prototype.computeConnectionPoint=function(t,e,i,n,s){n=n||LiteGraph.RIGHT,s=s||LiteGraph.LEFT;var o=distance(t,e),r=t,a=[t[0],t[1]],h=[e[0],e[1]],t=e;switch(n){case LiteGraph.LEFT:a[0]+=-.25*o;break;case LiteGraph.RIGHT:a[0]+=.25*o;break;case LiteGraph.UP:a[1]+=-.25*o;break;case LiteGraph.DOWN:a[1]+=.25*o}switch(s){case LiteGraph.LEFT:h[0]+=-.25*o;break;case LiteGraph.RIGHT:h[0]+=.25*o;break;case LiteGraph.UP:h[1]+=-.25*o;break;case LiteGraph.DOWN:h[1]+=.25*o}e=(1-i)*(1-i)*(1-i),n=(1-i)*(1-i)*3*i,s=3*(1-i)*(i*i),i*=i*i;return[e*r[0]+n*a[0]+s*h[0]+i*t[0],e*r[1]+n*a[1]+s*h[1]+i*t[1]]},LGraphCanvas.prototype.drawExecutionOrder=function(t){t.shadowColor="transparent",t.globalAlpha=.25,t.textAlign="center",t.strokeStyle="white",t.globalAlpha=.75;for(var e=this.visible_nodes,i=0;iw.last_y+widget_height||void 0===w.last_y)){var old_value=w.value;switch(w.type){case"button":event.type===LiteGraph.pointerevents_method+"down"&&(w.callback&&setTimeout(function(){w.callback(w,that,node,pos,event)},20),w.clicked=!0,this.dirty_canvas=!0);break;case"slider":var old_value=w.value,nvalue=clamp((x-15)/(widget_width-30),0,1);w.options.read_only||(w.value=w.options.min+(w.options.max-w.options.min)*nvalue,old_value!=w.value&&setTimeout(function(){inner_value_change(w,w.value)},20),this.dirty_canvas=!0);break;case"number":case"combo":var old_value=w.value,values,values_list,delta,index,text_values,menu,delta;function inner_clicked(t,e,i){return values!=values_list&&(t=text_values.indexOf(t)),this.value=t,inner_value_change(this,t),!(that.dirty_canvas=!0)}event.type==LiteGraph.pointerevents_method+"move"&&"number"==w.type?(deltaX&&(w.value+=.1*deltaX*(w.options.step||1)),null!=w.options.min&&w.valuew.options.max&&(w.value=w.options.max)):event.type==LiteGraph.pointerevents_method+"down"?(values=w.options.values,values&&values.constructor===Function&&(values=w.options.values(w,node)),values_list=null,"number"!=w.type&&(values_list=values.constructor===Array?values:Object.keys(values)),delta=x<40?-1:widget_width-40w.options.max&&(w.value=w.options.max)):delta?(index=-1,this.last_mouseclick=0,index=values.constructor===Object?values_list.indexOf(String(w.value))+delta:values_list.indexOf(w.value)+delta,index>=values_list.length&&(index=values_list.length-1),index<0&&(index=0),values.constructor===Array?w.value=values[index]:w.value=index):(text_values=values!=values_list?Object.values(values):values,menu=new LiteGraph.ContextMenu(text_values,{scale:Math.max(1,this.ds.scale),event:event,className:"dark",callback:inner_clicked.bind(w)},ref_window))):event.type==LiteGraph.pointerevents_method+"up"&&"number"==w.type&&(delta=x<40?-1:widget_width-40i.pos[0]+i.size[0])&&(i=o),(null===n||a+p>n.pos[1]+n.size[1])&&(n=o),(null===s||r"+(p.label||s)+""+t+"",value:s})}if(h.length)return new LiteGraph.ContextMenu(h,{event:i,callback:function(t,e,i,n){var s;o&&(s=this.getBoundingClientRect(),r.showEditPropertyValue(o,t.value,{position:[s.left,s.top]}))},parentMenu:n,allow_html:!0,node:o},a),!1}},LGraphCanvas.decodeHTML=function(t){var e=document.createElement("div");return e.innerText=t,e.innerHTML},LGraphCanvas.onMenuResizeNode=function(t,e,i,n,s){if(s){var o=function(t){t.size=t.computeSize(),t.onResize&&t.onResize(t.size)},r=LGraphCanvas.active_canvas;if(!r.selected_nodes||Object.keys(r.selected_nodes).length<=1)o(s);else for(var a in r.selected_nodes)o(r.selected_nodes[a]);s.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.showLinkMenu=function(n,t){var s=this,o=s.graph.getNodeById(n.origin_id),r=s.graph.getNodeById(n.target_id),a=!1,h=(o&&o.outputs&&o.outputs[n.origin_slot]&&(a=o.outputs[n.origin_slot].type),!1),p=(r&&r.outputs&&r.outputs[n.target_slot]&&(h=r.inputs[n.target_slot].type),new LiteGraph.ContextMenu(["Add Node",null,"Delete",null],{event:t,title:null!=n.data?n.data.constructor.name:null,callback:function(t,e,i){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,i,p,function(t){t.inputs&&t.inputs.length&&t.outputs&&t.outputs.length&&o.connectByType(n.origin_slot,t,a)&&(t.connectByType(n.target_slot,r,h),t.pos[0]-=.5*t.size[0])});break;case"Delete":s.graph.removeLink(n.id)}}}));return!1},LGraphCanvas.prototype.createDefaultNodeForSlot=function(t){var t=t||{},e=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,position:[],nodeType:null,posAdd:[0,0],posSizeFix:[0,0]},t),i=e.nodeFrom&&null!==e.slotFrom,t=!i&&e.nodeTo&&null!==e.slotTo;if(i||t)if(e.nodeType){var n=i?e.nodeFrom:e.nodeTo,s=i?e.slotFrom:e.slotTo,o=!1;switch(typeof s){case"string":o=i?n.findOutputSlot(s,!1):n.findInputSlot(s,!1),s=(i?n.outputs:n.inputs)[s];break;case"object":o=i?n.findOutputSlot(s.name):n.findInputSlot(s.name);break;case"number":o=s,s=(i?n.outputs:n.inputs)[s];break;default:return console.warn("Cant get slot information "+s),!1}!1!==s&&!1!==o||console.warn("createDefaultNodeForSlot bad slotX "+s+" "+o);var r=s.type==LiteGraph.EVENT?"_event_":s.type,a=i?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(a&&a[r]){if(s.link,nodeNewType=!1,"object"==typeof a[r]||"array"==typeof a[r]){for(var h in a[r])if(e.nodeType==a[r][h]||"AUTO"==e.nodeType){nodeNewType=a[r][h];break}}else e.nodeType!=a[r]&&"AUTO"!=e.nodeType||(nodeNewType=a[r]);if(nodeNewType){var p=!1,l=("object"==typeof nodeNewType&&nodeNewType.node&&(nodeNewType=(p=nodeNewType).node),LiteGraph.createNode(nodeNewType));if(l){if(p){if(p.properties)for(var u in p.properties)l.addProperty(u,p.properties[u]);if(p.inputs)for(var u in l.inputs=[],p.inputs)l.addOutput(p.inputs[u][0],p.inputs[u][1]);if(p.outputs)for(var u in l.outputs=[],p.outputs)l.addOutput(p.outputs[u][0],p.outputs[u][1]);p.title&&(l.title=p.title),p.json&&l.configure(p.json)}return this.graph.add(l),l.pos=[e.position[0]+e.posAdd[0]+(e.posSizeFix[0]?e.posSizeFix[0]*l.size[0]:0),e.position[1]+e.posAdd[1]+(e.posSizeFix[1]?e.posSizeFix[1]*l.size[1]:0)],i?e.nodeFrom.connectByType(o,l,r):e.nodeTo.connectByTypeOutput(o,l,r),!0}console.log("failed creating "+nodeNewType)}}}else console.warn("No type to createDefaultNodeForSlot");else console.warn("No data passed to createDefaultNodeForSlot "+e.nodeFrom+" "+e.slotFrom+" "+e.nodeTo+" "+e.slotTo);return!1},LGraphCanvas.prototype.showConnectionMenu=function(t){var t=t||{},n=Object.assign({nodeFrom:null,slotFrom:null,nodeTo:null,slotTo:null,e:null},t),s=this,o=n.nodeFrom&&n.slotFrom,t=!o&&n.nodeTo&&n.slotTo;if(o||t){var e=o?n.nodeFrom:n.nodeTo,r=o?n.slotFrom:n.slotTo,a=!1;switch(typeof r){case"string":a=o?e.findOutputSlot(r,!1):e.findInputSlot(r,!1),r=(o?e.outputs:e.inputs)[r];break;case"object":a=o?e.findOutputSlot(r.name):e.findInputSlot(r.name);break;case"number":a=r,r=(o?e.outputs:e.inputs)[r];break;default:return console.warn("Cant get slot information "+r),!1}var i=["Add Node",null],h=(s.allow_searchbox&&(i.push("Search"),i.push(null)),r.type==LiteGraph.EVENT?"_event_":r.type),p=o?LiteGraph.slot_types_default_out:LiteGraph.slot_types_default_in;if(p&&p[h])if("object"==typeof p[h]||"array"==typeof p[h])for(var l in p[h])i.push(p[h][l]);else i.push(p[h]);var u=new LiteGraph.ContextMenu(i,{event:n.e,title:(r&&""!=r.name?r.name+(h?" | ":""):"")+(r&&h?h:""),callback:function(t,e,i){switch(t){case"Add Node":LGraphCanvas.onMenuAdd(null,null,i,u,function(t){o?n.nodeFrom.connectByType(a,t,h):n.nodeTo.connectByTypeOutput(a,t,h)});break;case"Search":o?s.showSearchBox(i,{node_from:n.nodeFrom,slot_from:r,type_filter_in:h}):s.showSearchBox(i,{node_to:n.nodeTo,slot_from:r,type_filter_out:h});break;default:s.createDefaultNodeForSlot(Object.assign(n,{position:[n.e.canvasX,n.e.canvasY],nodeType:t}))}}})}else console.warn("No data passed to showConnectionMenu");return!1},LGraphCanvas.onShowPropertyEditor=function(e,t,i,n,s){var o=e.property||"title",r=s[o],a=document.createElement("div");a.is_modified=!1,a.className="graphdialog",a.innerHTML="",a.close=function(){a.parentNode&&a.parentNode.removeChild(a)};a.querySelector(".name").innerText=o;var h=a.querySelector(".value");h&&(h.value=r,h.addEventListener("blur",function(t){this.focus()}),h.addEventListener("keydown",function(t){if(a.is_modified=!0,27==t.keyCode)a.close();else if(13==t.keyCode)d();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}));var r=LGraphCanvas.active_canvas.canvas,p=r.getBoundingClientRect(),l=-20,u=-20;p&&(l-=p.left,u-=p.top),event?(a.style.left=event.clientX+l+"px",a.style.top=event.clientY+u+"px"):(a.style.left=.5*r.width+l+"px",a.style.top=.5*r.height+u+"px");a.querySelector("button").addEventListener("click",d),r.parentNode.appendChild(a),h&&h.focus();var c=null;function d(){var t;h&&(t=h.value,"Number"==e.type?t=Number(t):"Boolean"==e.type&&(t=Boolean(t)),s[o]=t,a.parentNode&&a.parentNode.removeChild(a),s.setDirtyCanvas(!0,!0))}a.addEventListener("mouseleave",function(t){LiteGraph.dialog_close_on_mouse_leave&&!a.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(c=setTimeout(a.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),a.addEventListener("mouseenter",function(t){LiteGraph.dialog_close_on_mouse_leave&&c&&clearTimeout(c)})},LGraphCanvas.prototype.prompt=function(t,e,i,n,s){var o=this,r=(t=t||"",document.createElement("div"));r.is_modified=!1,r.className="graphdialog rounded",r.innerHTML=s?" ":" ",r.close=function(){o.prompt_box=null,r.parentNode&&r.parentNode.removeChild(r)};var s=LGraphCanvas.active_canvas.canvas,a=(s.parentNode.appendChild(r),1LGraphCanvas.search_limit)break}}var p=null;if(Array.prototype.filter)p=Object.keys(LiteGraph.registered_node_types).filter(l);else for(var e in p=[],LiteGraph.registered_node_types)l(e)&&p.push(e);for(e=0;eLGraphCanvas.search_limit));e++);if(c.show_general_after_typefiltered&&(o.value||r.value)){for(var e in filtered_extra=[],LiteGraph.registered_node_types)l(e,{inTypeOverride:!(!o||!o.value)&&"*",outTypeOverride:!(!r||!r.value)&&"*"})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}if((o.value||r.value)&&0==_.childNodes.length&&c.show_general_if_none_on_typefilter){for(var e in filtered_extra=[],LiteGraph.registered_node_types)l(e,{skipFilter:!0})&&filtered_extra.push(e);for(e=0;eLGraphCanvas.search_limit));e++);}function l(t,e){var e=e||{},e=Object.assign({skipFilter:!1,inTypeOverride:!1,outTypeOverride:!1},e),i=LiteGraph.registered_node_types[t];if(a&&i.filter!=a)return!1;if((!c.show_all_if_empty||s)&&-1===t.toLowerCase().indexOf(s))return!1;if(c.do_type_filter&&!e.skipFilter){i=t,t=o.value;if(!1!==e.inTypeOverride&&(t=e.inTypeOverride),o&&t&&LiteGraph.registered_slot_in_types[t]&&LiteGraph.registered_slot_in_types[t].nodes){var n=LiteGraph.registered_slot_in_types[t].nodes.includes(i);if(!1===n)return!1}t=r.value;if(!1!==e.outTypeOverride&&(t=e.outTypeOverride),r&&t&&LiteGraph.registered_slot_out_types[t]&&LiteGraph.registered_slot_out_types[t].nodes){n=LiteGraph.registered_slot_out_types[t].nodes.includes(i);if(!1===n)return!1}}return!0}}function u(t,e){var i=document.createElement("div");f=f||t,i.innerText=t,i.dataset.type=escape(t),i.className="litegraph lite-search-item",e&&(i.className+=" "+e),i.addEventListener("click",function(t){T(unescape(this.dataset.type))}),_.appendChild(i)}}return h.style.left=L+"px",h.style.top=G+"px",o.layerY>s.height-200&&(_.style.maxHeight=s.height-o.layerY-20+"px"),y.focus(),c.show_all_on_open&&C(),h},LGraphCanvas.prototype.showEditPropertyValue=function(e,i,n){if(e&&void 0!==e.properties[i]){n=n||{};var s=e.getPropertyInfo(i),o=s.type,t="";if("string"==o||"number"==o||"array"==o||"object"==o)t="";else if("enum"!=o&&"combo"!=o||!s.values){if("boolean"!=o&&"toggle"!=o)return void console.warn("unknown type: "+o);t=""}else{for(var r in t=""}var h=this.createDialog(""+(s.label||i)+""+t+"",n),p=!1;return"enum"!=o&&"combo"!=o||!s.values?"boolean"==o||"toggle"==o?(p=h.querySelector("input"))&&p.addEventListener("click",function(t){h.modified(),u(!!p.checked)}):(p=h.querySelector("input"))&&(p.addEventListener("blur",function(t){this.focus()}),a=void 0!==e.properties[i]?e.properties[i]:"","string"!==o&&(a=JSON.stringify(a)),p.value=a,p.addEventListener("keydown",function(t){if(27==t.keyCode)h.close();else if(13==t.keyCode)l();else if(13!=t.keyCode)return void h.modified();t.preventDefault(),t.stopPropagation()})):(p=h.querySelector("select")).addEventListener("change",function(t){h.modified(),u(t.target.value)}),p&&p.focus(),h.querySelector("button").addEventListener("click",l),h}function l(){u(p.value)}function u(t){s&&s.values&&s.values.constructor===Object&&null!=s.values[t]&&(t=s.values[t]),"number"==typeof e.properties[i]&&(t=Number(t)),"array"!=o&&"object"!=o||(t=JSON.parse(t)),e.properties[i]=t,e.graph&&e.graph._version++,e.onPropertyChanged&&e.onPropertyChanged(i,t),n.onclose&&n.onclose(),h.close(),e.setDirtyCanvas(!0,!0)}},LGraphCanvas.prototype.createDialog=function(t,e){e=Object.assign({checkForInput:!1,closeOnLeave:!0,closeOnLeave_checkModified:!0},e||{});var i=document.createElement("div"),t=(i.className="graphdialog",i.innerHTML=t,i.is_modified=!1,this.canvas.getBoundingClientRect()),n=-20,s=-20,o=(t&&(n-=t.left,s-=t.top),e.position?(n+=e.position[0],s+=e.position[1]):e.event?(n+=e.event.clientX,s+=e.event.clientY):(n+=.5*this.canvas.width,s+=.5*this.canvas.height),i.style.left=n+"px",i.style.top=s+"px",this.canvas.parentNode.appendChild(i),e.checkForInput&&(t=[],t=i.querySelectorAll("input"))&&t.forEach(function(t){t.addEventListener("keydown",function(t){if(i.modified(),27==t.keyCode)i.close();else if(13!=t.keyCode)return;t.preventDefault(),t.stopPropagation()}),t.focus()}),i.modified=function(){i.is_modified=!0},i.close=function(){i.parentNode&&i.parentNode.removeChild(i)},null),r=!1,n=(i.addEventListener("mouseleave",function(t){r||(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&!i.is_modified&&LiteGraph.dialog_close_on_mouse_leave&&(o=setTimeout(i.close,LiteGraph.dialog_close_on_mouse_leave_delay))}),i.addEventListener("mouseenter",function(t){(e.closeOnLeave||LiteGraph.dialog_close_on_mouse_leave)&&o&&clearTimeout(o)}),i.querySelectorAll("select"));return n&&n.forEach(function(t){t.addEventListener("click",function(t){r++}),t.addEventListener("blur",function(t){r=0}),t.addEventListener("change",function(t){r=-1})}),i},LGraphCanvas.prototype.createPanel=function(t,e){var p=(e=e||{}).window||window,l=document.createElement("div");return l.className="litegraph dialog",l.innerHTML="
",l.header=l.querySelector(".dialog-header"),e.width&&(l.style.width=e.width+(e.width.constructor===Number?"px":"")),e.height&&(l.style.height=e.height+(e.height.constructor===Number?"px":"")),e.closable&&((e=document.createElement("span")).innerHTML="✕",e.classList.add("close"),e.addEventListener("click",function(){l.close()}),l.header.appendChild(e)),l.title_element=l.querySelector(".dialog-title"),l.title_element.innerText=t,l.content=l.querySelector(".dialog-content"),l.alt_content=l.querySelector(".dialog-alt-content"),l.footer=l.querySelector(".dialog-footer"),l.close=function(){l.onClose&&"function"==typeof l.onClose&&l.onClose(),l.parentNode&&l.parentNode.removeChild(l),this.parentNode&&this.parentNode.removeChild(this)},l.toggleAltContent=function(t){var e;t=void 0!==t?(e=t?"block":"none",t?"none":"block"):(e="block"!=l.alt_content.style.display?"block":"none","block"!=l.alt_content.style.display?"none":"block"),l.alt_content.style.display=e,l.content.style.display=t},l.toggleFooterVisibility=function(t){t=void 0!==t?t?"block":"none":"block"!=l.footer.style.display?"block":"none",l.footer.style.display=t},l.clear=function(){this.content.innerHTML=""},l.addHTML=function(t,e,i){var n=document.createElement("div");return e&&(n.className=e),n.innerHTML=t,(i?l.footer:l.content).appendChild(n),n},l.addButton=function(t,e,i){var n=document.createElement("button");return n.innerText=t,n.options=i,n.classList.add("btn"),n.addEventListener("click",e),l.footer.appendChild(n),n},l.addSeparator=function(){var t=document.createElement("div");t.className="separator",l.content.appendChild(t)},l.addWidget=function(e,t,i,o,n){o=o||{};var s=String(i),r=("number"==(e=e.toLowerCase())&&(s=i.toFixed(3)),document.createElement("div")),a=(r.className="property",r.innerHTML="",r.querySelector(".property_name").innerText=o.label||t,r.querySelector(".property_value"));function h(t,e){o.callback&&o.callback(t,e,o),n&&n(t,e,o)}return a.innerText=s,r.dataset.property=t,r.dataset.type=o.type||e,r.options=o,r.value=i,"code"==e?r.addEventListener("click",function(t){l.inner_showCodePad(this.dataset.property)}):"boolean"==e?(r.classList.add("boolean"),i&&r.classList.add("bool-on"),r.addEventListener("click",function(){var t=this.dataset.property;this.value=!this.value,this.classList.toggle("bool-on"),this.querySelector(".property_value").innerText=this.value?"true":"false",h(t,this.value)})):"string"==e||"number"==e?(a.setAttribute("contenteditable",!0),a.addEventListener("keydown",function(t){"Enter"!=t.code||"string"==e&&t.shiftKey||(t.preventDefault(),this.blur())}),a.addEventListener("blur",function(){var t=this.innerText;h(this.parentNode.dataset.property,t="number"==this.parentNode.dataset.type?Number(t):t)})):"enum"!=e&&"combo"!=e||(s=LGraphCanvas.getPropertyPrintableValue(i,o.values),a.innerText=s,a.addEventListener("click",function(t){var e=o.values||[],n=this.parentNode.dataset.property,s=this;new LiteGraph.ContextMenu(e,{event:t,className:"dark",callback:function(t,e,i){return s.innerText=t,h(n,t),!1}},p)})),l.content.appendChild(r),r},l.onOpen&&"function"==typeof l.onOpen&&l.onOpen(),l},LGraphCanvas.getPropertyPrintableValue=function(t,e){if(!e)return String(t);if(e.constructor===Array)return String(t);if(e.constructor===Object){var i,n="";for(i in e)if(e[i]==t){n=i;break}return String(t)+" ("+n+")"}},LGraphCanvas.prototype.closePanels=function(){var t=document.querySelector("#node-panel");t&&t.close(),(t=document.querySelector("#option-panel"))&&t.close()},LGraphCanvas.prototype.showShowGraphOptionsPanel=function(t,e,i,n){if(this.constructor&&"HTMLDivElement"==this.constructor.name){if(!(e&&e.event&&e.event.target&&e.event.target.lgraphcanvas))return void console.warn("Canvas not found");var s=e.event.target.lgraphcanvas}else s=this;s.closePanels();e=s.getCanvasWindow();function o(t,e,i){i&&i.key&&(t=i.key),i.values&&(e=Object.values(i.values).indexOf(e)),s[t]=e}panel=s.createPanel("Options",{closable:!0,window:e,onOpen:function(){s.OPTIONPANEL_IS_OPEN=!0},onClose:function(){s.OPTIONPANEL_IS_OPEN=!1,s.options_panel=null}}),(s.options_panel=panel).id="option-panel",panel.classList.add("settings"),panel.content.innerHTML="";var r,a=LiteGraph.availableCanvasOptions;for(r in a.sort(),a){var h=a[r];panel.addWidget("boolean",h,s[h],{key:h,on:"True",off:"False"},o)}s.links_render_mode,panel.addWidget("combo","Render mode",LiteGraph.LINK_RENDER_MODES[s.links_render_mode],{key:"links_render_mode",values:LiteGraph.LINK_RENDER_MODES},o),panel.addSeparator(),panel.footer.innerHTML="",s.canvas.parentNode.appendChild(panel)},LGraphCanvas.prototype.showShowNodePanel=function(o){this.SELECTED_NODE=o,this.closePanels();var t=this.getCanvasWindow(),r=this,a=this.createPanel(o.title||"",{closable:!0,window:t,onOpen:function(){r.NODEPANEL_IS_OPEN=!0},onClose:function(){r.NODEPANEL_IS_OPEN=!1,r.node_panel=null}});function s(){a.content.innerHTML="",a.addHTML(""+o.type+""+(o.constructor.desc||"")+""),a.addHTML("

Properties

");function t(t,e){switch(r.graph.beforeChange(o),t){case"Title":o.title=e;break;case"Mode":var i=Object.values(LiteGraph.NODE_MODES).indexOf(e);0<=i&&LiteGraph.NODE_MODES[i]?o.changeMode(i):console.warn("unexpected mode: "+e);break;case"Color":LGraphCanvas.node_colors[e]?(o.color=LGraphCanvas.node_colors[e].color,o.bgcolor=LGraphCanvas.node_colors[e].bgcolor):console.warn("unexpected color: "+e);break;default:o.setProperty(t,e)}r.graph.afterChange(),r.dirty_canvas=!0}a.addWidget("string","Title",o.title,{},t),a.addWidget("combo","Mode",LiteGraph.NODE_MODES[o.mode],{values:LiteGraph.NODE_MODES},t);var e,i="";for(e in void 0!==o.color&&(i=Object.keys(LGraphCanvas.node_colors).filter(function(t){return LGraphCanvas.node_colors[t].color==o.color})),a.addWidget("combo","Color",i,{values:Object.keys(LGraphCanvas.node_colors)},t),o.properties){var n=o.properties[e],s=o.getPropertyInfo(e);s.type;o.onAddPropertyToPanel&&o.onAddPropertyToPanel(e,a)||a.addWidget(s.widget||s.type,e,n,s,t)}a.addSeparator(),o.onShowCustomPanelInfo&&o.onShowCustomPanelInfo(a),a.footer.innerHTML="",a.addButton("Delete",function(){o.block_delete||(o.graph.remove(o),a.close())}).classList.add("delete")}(r.node_panel=a).id="node-panel",a.node=o,a.classList.add("settings"),a.inner_showCodePad=function(e){a.classList.remove("settings"),a.classList.add("centered"),a.alt_content.innerHTML="";function i(){a.toggleAltContent(!1),a.toggleFooterVisibility(!0),n.parentNode.removeChild(n),a.classList.add("settings"),a.classList.remove("centered"),s()}var n=a.alt_content.querySelector("textarea"),t=(n.value=o.properties[e],n.addEventListener("keydown",function(t){"Enter"==t.code&&t.ctrlKey&&(o.setProperty(e,n.value),i())}),a.toggleAltContent(!0),a.toggleFooterVisibility(!1),n.style.height="calc(100% - 40px)",a.addButton("Assign",function(){o.setProperty(e,n.value),i()})),t=(a.alt_content.appendChild(t),a.addButton("Close",i));t.style.float="right",a.alt_content.appendChild(t)},s(),this.canvas.parentNode.appendChild(a)},LGraphCanvas.prototype.showSubgraphPropertiesDialog=function(s){console.log("showing subgraph properties dialog");var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),n=(t&&t.close(),this.createPanel("Subgraph Inputs",{closable:!0,width:500}));function o(){if(n.clear(),s.inputs)for(var t=0;t","subgraph_property")).dataset.name=i.name,e.dataset.slot=t,e.querySelector(".name").innerText=i.name,e.querySelector(".type").innerText=i.type,e.querySelector("button").addEventListener("click",function(t){s.removeInput(Number(this.parentNode.dataset.slot)),o()}))}}n.node=s,n.classList.add("subgraph_dialog");return n.addHTML(" + NameType","subgraph_property extra",!0).querySelector("button").addEventListener("click",function(t){var e=this.parentNode,i=e.querySelector(".name").value,n=e.querySelector(".type").value;i&&-1==s.findInputSlot(i)&&(s.addInput(i,n),e.querySelector(".name").value="",e.querySelector(".type").value="",o())}),o(),this.canvas.parentNode.appendChild(n),n},LGraphCanvas.prototype.showSubgraphPropertiesDialogRight=function(n){var t=this.canvas.parentNode.querySelector(".subgraph_dialog"),s=(t&&t.close(),this.createPanel("Subgraph Outputs",{closable:!0,width:500}));function o(){if(s.clear(),n.outputs)for(var t=0;t","subgraph_property")).dataset.name=i.name,e.dataset.slot=t,e.querySelector(".name").innerText=i.name,e.querySelector(".type").innerText=i.type,e.querySelector("button").addEventListener("click",function(t){n.removeOutput(Number(this.parentNode.dataset.slot)),o()}))}}s.node=n,s.classList.add("subgraph_dialog");t=s.addHTML(" + NameType","subgraph_property extra",!0);function e(){var t=this.parentNode,e=t.querySelector(".name").value,i=t.querySelector(".type").value;e&&-1==n.findOutputSlot(e)&&(n.addOutput(e,i),t.querySelector(".name").value="",t.querySelector(".type").value="",o())}return t.querySelector(".name").addEventListener("keydown",function(t){13==t.keyCode&&e.apply(this)}),t.querySelector("button").addEventListener("click",function(t){e.apply(this)}),o(),this.canvas.parentNode.appendChild(s),s},LGraphCanvas.prototype.checkPanels=function(){if(this.canvas)for(var t=this.canvas.parentNode.querySelectorAll(".litegraph.dialog"),e=0;eNo color"}),LGraphCanvas.node_colors){var a=LGraphCanvas.node_colors[s],t={value:s,content:""+s+""};r.push(t)}return new LiteGraph.ContextMenu(r,{event:i,callback:function(t){if(o){var e=t.value?LGraphCanvas.node_colors[t.value]:null,i=function(t){e?t.constructor===LiteGraph.LGraphGroup?t.color=e.groupcolor:(t.color=e.color,t.bgcolor=e.bgcolor):(delete t.color,delete t.bgcolor)},n=LGraphCanvas.active_canvas;if(!n.selected_nodes||Object.keys(n.selected_nodes).length<=1)i(o);else for(var s in n.selected_nodes)i(n.selected_nodes[s]);o.setDirtyCanvas(!0,!0)}},parentMenu:n,node:o}),!1},LGraphCanvas.onMenuNodeShapes=function(t,e,i,n,s){if(s)return new LiteGraph.ContextMenu(LiteGraph.VALID_SHAPES,{event:i,callback:function(e){if(s){s.graph.beforeChange();var t=function(t){t.shape=e},i=LGraphCanvas.active_canvas;if(!i.selected_nodes||Object.keys(i.selected_nodes).length<=1)t(s);else for(var n in i.selected_nodes)t(i.selected_nodes[n]);s.graph.afterChange(),s.setDirtyCanvas(!0)}},parentMenu:n,node:s}),!1;throw"no node passed"},LGraphCanvas.onMenuNodeRemove=function(t,e,i,n,s){if(!s)throw"no node passed";function o(t){!1!==t.removable&&r.remove(t)}var r=s.graph,a=(r.beforeChange(),LGraphCanvas.active_canvas);if(!a.selected_nodes||Object.keys(a.selected_nodes).length<=1)o(s);else for(var h in a.selected_nodes)o(a.selected_nodes[h]);r.afterChange(),s.setDirtyCanvas(!0,!0)},LGraphCanvas.onMenuNodeToSubgraph=function(t,e,i,n,s){var o,r,a=s.graph,h=LGraphCanvas.active_canvas;h&&((o=Object.values(h.selected_nodes||{})).length||(o=[s]),(r=LiteGraph.createNode("graph/subgraph")).pos=s.pos.concat(),a.add(r),r.buildFromNodes(o),h.deselectAllNodes(),s.setDirtyCanvas(!0,!0))},LGraphCanvas.onMenuNodeClone=function(t,e,i,n,s){s.graph.beforeChange();function o(t){var e;!1!==t.clonable&&(e=t.clone())&&(e.pos=[t.pos[0]+5,t.pos[1]+5],t.graph.add(e),r[e.id]=e)}var r={},a=LGraphCanvas.active_canvas;if(!a.selected_nodes||Object.keys(a.selected_nodes).length<=1)o(s);else for(var h in a.selected_nodes)o(a.selected_nodes[h]);Object.keys(r).length&&a.selectNodes(r),s.graph.afterChange(),s.setDirtyCanvas(!0,!0)},LGraphCanvas.node_colors={red:{color:"#322",bgcolor:"#533",groupcolor:"#A88"},brown:{color:"#332922",bgcolor:"#593930",groupcolor:"#b06634"},green:{color:"#232",bgcolor:"#353",groupcolor:"#8A8"},blue:{color:"#223",bgcolor:"#335",groupcolor:"#88A"},pale_blue:{color:"#2a363b",bgcolor:"#3f5159",groupcolor:"#3f789e"},cyan:{color:"#233",bgcolor:"#355",groupcolor:"#8AA"},purple:{color:"#323",bgcolor:"#535",groupcolor:"#a1309b"},yellow:{color:"#432",bgcolor:"#653",groupcolor:"#b58b2a"},black:{color:"#222",bgcolor:"#000",groupcolor:"#444"}},LGraphCanvas.prototype.getCanvasMenuOptions=function(){var t,e=null;return this.getMenuOptions?e=this.getMenuOptions():(e=[{content:"Add Node",has_submenu:!0,callback:LGraphCanvas.onMenuAdd},{content:"Add Group",callback:LGraphCanvas.onGroupAdd}],1Name",e),(r=o.querySelector("input"))&&s&&(r.value=s.label||""),a=function(){h.graph.beforeChange(),r.value&&(s&&(s.label=r.value),p.setDirty(!0)),o.close(),h.graph.afterChange()},o.querySelector("button").addEventListener("click",a),r.addEventListener("keydown",function(t){if(o.is_modified=!0,27==t.keyCode)o.close();else if(13==t.keyCode)a();else if(13!=t.keyCode&&"textarea"!=t.target.localName)return;t.preventDefault(),t.stopPropagation()}),r.focus()))},extra:h},o=(h&&(s.title=h.type),null);h&&(o=h.getSlotInPosition(t.canvasX,t.canvasY),LGraphCanvas.active_node=h),o?(n=[],h.getSlotMenuOptions?n=h.getSlotMenuOptions(o):(o&&o.output&&o.output.links&&o.output.links.length&&n.push({content:"Disconnect Links",slot:o}),(e=o.input||o.output).removable&&n.push(e.locked?"Cannot remove":{content:"Remove Slot",slot:o}),e.nameLocked||n.push({content:"Rename Slot",slot:o})),s.title=(o.input||o.output).type||"*",o.input&&o.input.type==LiteGraph.ACTION&&(s.title="Action"),o.output&&o.output.type==LiteGraph.EVENT&&(s.title="Event")):h?n=this.getNodeMenuOptions(h):(n=this.getCanvasMenuOptions(),(e=this.graph.getGroupOnPos(t.canvasX,t.canvasY))&&n.push(null,{content:"Edit Group",has_submenu:!0,submenu:{title:"Group",extra:e,options:this.getGroupMenuOptions(e)}})),n&&new LiteGraph.ContextMenu(n,s,i)},LiteGraph.compareObjects=compareObjects,LiteGraph.distance=distance,LiteGraph.colorToString=colorToString,LiteGraph.isInsideRectangle=isInsideRectangle,LiteGraph.growBounding=growBounding,LiteGraph.isInsideBounding=isInsideBounding,LiteGraph.overlapBounding=overlapBounding,LiteGraph.hex2num=hex2num,LiteGraph.num2hex=num2hex,ContextMenu.prototype.addItem=function(t,e,n){var s=this,i=(n=n||{},document.createElement("div")),o=!(i.className="litemenu-entry submenu");function r(t){var e=this.value,i=!0;if((s.current_submenu&&s.current_submenu.close(t),n.callback&&!0===n.callback.call(this,e,n,t,s,n.node)&&(i=!1),e)&&(e.callback&&!n.ignore_item_callbacks&&!0!==e.disabled&&!0===e.callback.call(this,e,n,t,s,n.extra)&&(i=!1),e.submenu)){if(!e.submenu.options)throw"ContextMenu submenu needs options";new s.constructor(e.submenu.options,{callback:e.submenu.callback,event:t,parentMenu:s,ignore_item_callbacks:e.submenu.ignore_item_callbacks,title:e.submenu.title,extra:e.submenu.extra,autoopen:n.autoopen});i=!1}i&&!s.lock&&s.close()}return null===e?i.classList.add("separator"):(i.innerHTML=e&&e.title?e.title:t,(i.value=e)&&(e.disabled&&(o=!0,i.classList.add("disabled")),e.submenu||e.has_submenu)&&i.classList.add("has_submenu"),"function"==typeof e?(i.dataset.value=t,i.onclick_callback=e):i.dataset.value=e,e.className&&(i.className+=" "+e.className)),this.root.appendChild(i),o||i.addEventListener("click",r),!o&&n.autoopen&&LiteGraph.pointerListenerAdd(i,"enter",function(t){var e=this.value;e&&e.has_submenu&&r.call(this,t)}),i},ContextMenu.prototype.close=function(t,e){this.root.parentNode&&this.root.parentNode.removeChild(this.root),this.parentMenu&&!e&&(this.parentMenu.lock=!1,this.parentMenu.current_submenu=null,void 0===t?this.parentMenu.close():t&&!ContextMenu.isCursorOverElement(t,this.parentMenu.root)&&ContextMenu.trigger(this.parentMenu.root,LiteGraph.pointerevents_method+"leave",t)),this.current_submenu&&this.current_submenu.close(t,!0),this.root.closing_timer&&clearTimeout(this.root.closing_timer)},ContextMenu.trigger=function(t,e,i,n){var s=document.createEvent("CustomEvent");return s.initCustomEvent(e,!0,!0,i),s.srcElement=n,t.dispatchEvent?t.dispatchEvent(s):t.__events&&t.__events.dispatchEvent(s),s},ContextMenu.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this},ContextMenu.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event},ContextMenu.isCursorOverElement=function(t,e){var i=t.clientX,t=t.clientY,e=e.getBoundingClientRect();return!!e&&t>e.top&&te.left&&ithis.size[0]+10||t[1]<-10||t[1]>this.size[1]+10)?(r.splice(i,1),this.selected=-1):(o[0]=e?0==i?0:1:clamp(n,0,1),o[1]=1-clamp(s,0,1),r.sort(function(t,e){return t[0]-e[0]}),this.selected=r.indexOf(o),this.must_update=!0)))},CurveEditor.prototype.onMouseUp=function(t,e){return!(this.selected=-1)},CurveEditor.prototype.getCloserPoint=function(t,e){var i=this.points;if(!i)return-1;e=e||30;for(var n=this.size[0]-2*this.margin,s=this.size[1]-2*this.margin,o=i.length,r=[0,0],a=1e6,h=-1,p=0;p{var c=t.LiteGraph;function e(){this.addOutput("in ms","number"),this.addOutput("in sec","number")}function i(){this.size=[140,80],this.properties={enabled:!0},this.enabled=!0,this.subgraph=new c.LGraph,(this.subgraph._subgraph_node=this).subgraph._is_subgraph=!0,this.subgraph.onTrigger=this.onSubgraphTrigger.bind(this),this.subgraph.onInputAdded=this.onSubgraphNewInput.bind(this),this.subgraph.onInputRenamed=this.onSubgraphRenamedInput.bind(this),this.subgraph.onInputTypeChanged=this.onSubgraphTypeChangeInput.bind(this),this.subgraph.onInputRemoved=this.onSubgraphRemovedInput.bind(this),this.subgraph.onOutputAdded=this.onSubgraphNewOutput.bind(this),this.subgraph.onOutputRenamed=this.onSubgraphRenamedOutput.bind(this),this.subgraph.onOutputTypeChanged=this.onSubgraphTypeChangeOutput.bind(this),this.subgraph.onOutputRemoved=this.onSubgraphRemovedOutput.bind(this)}function n(){this.addOutput("","number"),this.name_in_graph="",this.properties={name:"",type:"number",value:0};var e=this;this.name_widget=this.addWidget("text","Name",this.properties.name,function(t){t&&e.setProperty("name",t)}),this.type_widget=this.addWidget("text","Type",this.properties.type,function(t){e.setProperty("type",t)}),this.value_widget=this.addWidget("number","Value",this.properties.value,function(t){e.setProperty("value",t)}),this.widgets_up=!0,this.size=[180,90]}function s(){this.addInput("",""),this.name_in_graph="",this.properties={name:"",type:""};this.name_widget=this.addWidget("text","Name",this.properties.name,"name"),this.type_widget=this.addWidget("text","Type",this.properties.type,"type"),this.widgets_up=!0,this.size=[180,60]}function o(){this.addOutput("value","number"),this.addProperty("value",1),this.widget=this.addWidget("number","value",1,"value"),this.widgets_up=!0,this.size=[180,30]}function r(){this.addOutput("bool","boolean"),this.addProperty("value",!0),this.widget=this.addWidget("toggle","value",!0,"value"),this.serialize_widgets=!0,this.widgets_up=!0,this.size=[140,30]}function a(){this.addOutput("string","string"),this.addProperty("value",""),this.widget=this.addWidget("text","value","","value"),this.widgets_up=!0,this.size=[180,30]}function h(){this.addOutput("obj","object"),this.size=[120,30],this._object={}}function p(){this.addInput("url","string"),this.addOutput("file","string"),this.addProperty("url",""),this.addProperty("type","text"),this.widget=this.addWidget("text","url","","url"),this._data=null}function l(){this.addInput("parse",c.ACTION),this.addInput("json","string"),this.addOutput("done",c.EVENT),this.addOutput("object","object"),this.widget=this.addWidget("button","parse","",this.parse.bind(this)),this._str=null,this._obj=null}function u(){this.addOutput("data","object"),this.addProperty("value",""),this.widget=this.addWidget("text","json","","value"),this.widgets_up=!0,this.size=[140,30],this._value=null}function d(){this._value=[],this.addInput("json",""),this.addOutput("arrayOut","array"),this.addOutput("length","number"),this.addProperty("value","[]"),this.widget=this.addWidget("text","array",this.properties.value,"value"),this.widgets_up=!0,this.size=[140,50]}function g(){this.addInput("arr","array"),this.addInput("value",""),this.addOutput("arr","array"),this.properties={index:0},this.widget=this.addWidget("number","i",this.properties.index,"index",{precision:0,step:10,min:0})}function _(){this.addInput("array","array,table,string"),this.addInput("index","number"),this.addOutput("value",""),this.addProperty("index",0)}function f(){this.addInput("table","table"),this.addInput("row","number"),this.addInput("col","number"),this.addOutput("value",""),this.addProperty("row",0),this.addProperty("column",0)}function v(){this.addInput("obj","object"),this.addOutput("property",0),this.addProperty("value",0),this.widget=this.addWidget("text","prop.","",this.setValue.bind(this)),this.widgets_up=!0,this.size=[140,30],this._value=null}function y(){this.addInput("obj",""),this.addOutput("keys","array"),this.size=[140,30]}function b(){this.addInput("obj",""),this.addInput("value",""),this.addOutput("obj",""),this.properties={property:""},this.name_widget=this.addWidget("text","prop.",this.properties.property,"property")}function m(){this.addInput("A","object"),this.addInput("B","object"),this.addOutput("out","object"),this._result={};var t=this;this.addWidget("button","clear","",function(){t._result={}}),this.size=this.computeSize()}function L(){this.size=[60,30],this.addInput("in"),this.addOutput("out"),this.properties={varname:"myname",container:L.LITEGRAPH},this.value=null}function G(){this.size=[60,30],this.addInput("data",0),this.addInput("download",c.ACTION),this.properties={filename:"data.json"},this.value=null;var e=this;this.addWidget("button","Download","",function(t){e.value&&e.downloadAsFile()})}function T(){this.size=[60,30],this.addInput("value",0,{label:""}),this.value=0}function O(){this.addInput("in",0),this.addOutput("out",0),this.size=[40,30]}function C(){this.mode=c.ON_EVENT,this.size=[80,30],this.addProperty("msg",""),this.addInput("log",c.EVENT),this.addInput("msg",0)}function w(){this.mode=c.ON_EVENT,this.addProperty("msg",""),this.addInput("",c.EVENT);this.widget=this.addWidget("text","Text","","msg"),this.widgets_up=!0,this.size=[200,30]}function E(){this.size=[60,30],this.addProperty("onExecute","return A;"),this.addInput("A",0),this.addInput("B",0),this.addOutput("out",0),this._func=null,this.data={}}function N(){this.addInput("A",0),this.addInput("B",0),this.addOutput("true","boolean"),this.addOutput("false","boolean"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP","==","enum",{values:N.values}),this.addWidget("combo","Op.",this.properties.OP,{property:"OP",values:N.values}),this.size=[80,60]}e.title="Time",e.desc="Time",e.prototype.onExecute=function(){this.setOutputData(0,1e3*this.graph.globaltime),this.setOutputData(1,this.graph.globaltime)},c.registerNodeType("basic/time",e),i.title="Subgraph",i.desc="Graph inside a node",i.title_color="#334",i.prototype.onGetInputs=function(){return[["enabled","boolean"]]},i.prototype.onDblClick=function(t,e,i){var n=this;setTimeout(function(){i.openSubgraph(n.subgraph)},10)},i.prototype.onAction=function(t,e){this.subgraph.onAction(t,e)},i.prototype.onExecute=function(){if(this.enabled=this.getInputOrProperty("enabled"),this.enabled){if(this.inputs)for(var t=0;tn&&(e[0]e.linkIDs[t]))}for(var u of t.nodes)"graph/subgraph"===u.type&&(u=reassignGraphUUIDs(u.subgraph),e.nodeIDs.assign(u.nodeIDs),e.linkIDs.assign(u.linkIDs))},i.prototype.clone=function(){var t,e=c.createNode(this.type),i=this.serialize();return c.use_uuids&&(t=c.cloneObject(i.subgraph),this.reassignSubgraphUUIDs(t),i.subgraph=t),delete i.id,delete i.inputs,delete i.outputs,e.configure(i),e},i.prototype.buildFromNodes=function(t){for(var e={},i=0,n=0;n{var n=t.LiteGraph;function e(){this.size=[60,30],this.addInput("event",n.ACTION)}function i(){this.size=[60,30],this.addInput("if",""),this.addOutput("true",n.EVENT),this.addOutput("change",n.EVENT),this.addOutput("false",n.EVENT),this.properties={only_on_change:!0},this.prev=0}function s(){var t=this;this.addInput("",n.ACTION),this.addInput("",n.ACTION),this.addInput("",n.ACTION),this.addOutput("",n.EVENT),this.addOutput("",n.EVENT),this.addOutput("",n.EVENT),this.addWidget("button","+",null,function(){t.addInput("",n.ACTION),t.addOutput("",n.EVENT)}),this.size=[90,70],this.flags={horizontal:!0,render_box:!1}}function o(){var t=this;this.addInput("",n.ACTION),this.addInput("",n.ACTION),this.addOutput("",n.EVENT),this.addWidget("button","+",null,function(){t.addInput("",n.ACTION),t.size[0]=90}),this.size=[90,70],this.ready=[]}function r(){var t=this;this.properties={index:0},this.addInput("index","number"),this.addInput("step",n.ACTION),this.addInput("reset",n.ACTION),this.addOutput("index","number"),this.addOutput("",n.EVENT),this.addOutput("",n.EVENT),this.addOutput("",n.EVENT,{removable:!0}),this.addWidget("button","+",null,function(){t.addOutput("",n.EVENT,{removable:!0})}),this.size=[120,120],this.flags={render_box:!1}}function a(){this.size=[60,30],this.addInput("event",n.ACTION),this.addOutput("event",n.EVENT),this.properties={equal_to:"",has_property:"",property_equal_to:""}}function h(){this.addInput("in",n.ACTION),this.addInput("cond","boolean"),this.addOutput("true",n.EVENT),this.addOutput("false",n.EVENT),this.size=[120,60],this._value=!1}function p(){this.addInput("inc",n.ACTION),this.addInput("dec",n.ACTION),this.addInput("reset",n.ACTION),this.addOutput("change",n.EVENT),this.addOutput("num","number"),this.addProperty("doCountExecution",!1,"boolean",{name:"Count Executions"}),this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"),this.num=0}function l(){this.size=[60,30],this.addProperty("time_in_ms",1e3),this.addInput("event",n.ACTION),this.addOutput("on_time",n.EVENT),this._pending=[]}function u(){this.addProperty("interval",1e3),this.addProperty("event","tick"),this.addOutput("on_tick",n.EVENT),this.time=0,this.last_interval=1e3,this.triggered=!1}function c(){this.addInput("go",n.ACTION),this.addInput("green",n.ACTION),this.addInput("red",n.ACTION),this.addOutput("continue",n.EVENT),this.addOutput("blocked",n.EVENT),this.addOutput("is_green","boolean"),this._ready=!1,this.properties={};var t=this;this.addWidget("button","reset","",function(){t._ready=!1})}function d(){this.addInput("in",n.ACTION),this.addInput("reset",n.ACTION),this.addOutput("out",n.EVENT),this._once=!1,this.properties={};var t=this;this.addWidget("button","reset","",function(){t._once=!1})}function g(){this.addInput("data",0),this.addInput("assign",n.ACTION),this.addOutput("data",0),this._last_value=null,this.properties={data:null,serialize:!0};var t=this;this.addWidget("button","store","",function(){t.properties.data=t._last_value})}e.title="Log Event",e.desc="Log event in console",e.prototype.onAction=function(t,e,i){console.log(t,e)},n.registerNodeType("events/log",e),i.title="TriggerEvent",i.desc="Triggers event if input evaluates to true",i.prototype.onExecute=function(t,e){var i=this.getInputData(0),n=i!=this.prev,s=(n=0===this.prev?!1:n)&&this.properties.only_on_change||!n&&!this.properties.only_on_change;i&&s&&this.triggerSlot(0,t,null,e),!i&&s&&this.triggerSlot(2,t,null,e),n&&this.triggerSlot(1,t,null,e),this.prev=i},n.registerNodeType("events/trigger",i),s.title="Sequence",s.desc="Triggers a sequence of events when an event arrives",s.prototype.getTitle=function(){return""},s.prototype.onAction=function(t,e,i){if(this.outputs){i=i||{};for(var n=0;n{var e=t.LiteGraph;function r(){this.addOutput("left_x_axis","number"),this.addOutput("left_y_axis","number"),this.addOutput("button_pressed",e.EVENT),this.properties={gamepad_index:0,threshold:.1},this._left_axis=new Float32Array(2),this._right_axis=new Float32Array(2),this._triggers=new Float32Array(2),this._previous_buttons=new Uint8Array(17),this._current_buttons=new Uint8Array(17)}r.title="Gamepad",r.desc="gets the input of the gamepad",r.CENTER=0,r.LEFT=1,r.RIGHT=2,r.UP=4,r.DOWN=8,r.zero=new Float32Array(2),r.buttons=["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs","home"],r.prototype.onExecute=function(){var t=this.getGamepad(),e=this.properties.threshold||0;if(t&&(this._left_axis[0]=Math.abs(t.xbox.axes.lx)>e?t.xbox.axes.lx:0,this._left_axis[1]=Math.abs(t.xbox.axes.ly)>e?t.xbox.axes.ly:0,this._right_axis[0]=Math.abs(t.xbox.axes.rx)>e?t.xbox.axes.rx:0,this._right_axis[1]=Math.abs(t.xbox.axes.ry)>e?t.xbox.axes.ry:0,this._triggers[0]=Math.abs(t.xbox.axes.ltrigger)>e?t.xbox.axes.ltrigger:0,this._triggers[1]=Math.abs(t.xbox.axes.rtrigger)>e?t.xbox.axes.rtrigger:0),this.outputs)for(var i=0;i{var n=t.LiteGraph;function e(){this.addInput("in",0),this.addOutput("out",0),this.size=[80,30]}function i(){this.addInput("in"),this.addOutput("out"),this.size=[80,30]}function s(){this.addInput("in"),this.addOutput("out")}function o(){this.addInput("in","number",{locked:!0}),this.addOutput("out","number",{locked:!0}),this.addOutput("clamped","number",{locked:!0}),this.addProperty("in",0),this.addProperty("in_min",0),this.addProperty("in_max",1),this.addProperty("out_min",0),this.addProperty("out_max",1),this.size=[120,50]}function r(){this.addOutput("value","number"),this.addProperty("min",0),this.addProperty("max",1),this.size=[80,30]}function p(){this.addInput("in","number"),this.addOutput("out","number"),this.addProperty("min",0),this.addProperty("max",1),this.addProperty("smooth",!0),this.addProperty("seed",0),this.addProperty("octaves",1),this.addProperty("persistence",.8),this.addProperty("speed",1),this.size=[90,30]}function a(){this.addOutput("out","number"),this.addProperty("min_time",1),this.addProperty("max_time",2),this.addProperty("duration",.2),this.size=[90,30],this._remaining_time=0,this._blink_time=0}function h(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.addProperty("min",0),this.addProperty("max",1)}function l(){this.properties={f:.5},this.addInput("A","number"),this.addInput("B","number"),this.addOutput("out","number")}function u(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function c(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function d(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30]}function g(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.properties={A:0,B:1}}function _(){this.addInput("in","number",{label:""}),this.addOutput("out","number",{label:""}),this.size=[80,30],this.addProperty("factor",1)}function f(){this.addInput("v","boolean"),this.addInput("A"),this.addInput("B"),this.addOutput("out")}function v(){this.addInput("in","number"),this.addOutput("out","number"),this.size=[80,30],this.addProperty("samples",10),this._values=new Float32Array(10),this._current=0}function y(){this.addInput("in","number"),this.addOutput("out","number"),this.addProperty("factor",.1),this.size=[80,30],this._value=null}function b(){this.addInput("A","number,array,object"),this.addInput("B","number"),this.addOutput("=","number"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP","+","enum",{values:b.values}),this._func=b.funcs[this.properties.OP],this._result=[]}function m(){this.addInput("A","number"),this.addInput("B","number"),this.addOutput("A==B","boolean"),this.addOutput("A!=B","boolean"),this.addProperty("A",0),this.addProperty("B",0)}function L(){this.addInput("A","number"),this.addInput("B","number"),this.addOutput("true","boolean"),this.addOutput("false","boolean"),this.addProperty("A",1),this.addProperty("B",1),this.addProperty("OP",">","enum",{values:L.values}),this.addWidget("combo","Cond.",this.properties.OP,{property:"OP",values:L.values}),this.size=[80,60]}function G(){this.addInput("in",0),this.addInput("cond","boolean"),this.addOutput("true",0),this.addOutput("false",0),this.size=[80,60]}function T(){this.addInput("inc","number"),this.addOutput("total","number"),this.addProperty("increment",1),this.addProperty("value",0)}function O(){this.addInput("v","number"),this.addOutput("sin","number"),this.addProperty("amplitude",1),this.addProperty("offset",0),this.bgImageUrl="nodes/imgs/icon-sin.png"}function C(){this.addInput("x","number"),this.addInput("y","number"),this.addOutput("","number"),this.properties={x:1,y:1,formula:"x+y"},this.code_widget=this.addWidget("text","F(x,y)",this.properties.formula,function(t,e,i){i.properties.formula=t}),this.addWidget("toggle","allow",n.allow_scripts,function(t){n.allow_scripts=t}),this._func=null}function w(){this.addInput("vec2","vec2"),this.addOutput("x","number"),this.addOutput("y","number")}function E(){this.addInputs([["x","number"],["y","number"]]),this.addOutput("vec2","vec2"),this.properties={x:0,y:0},this._data=new Float32Array(2)}function N(){this.addInput("vec3","vec3"),this.addOutput("x","number"),this.addOutput("y","number"),this.addOutput("z","number")}function x(){this.addInputs([["x","number"],["y","number"],["z","number"]]),this.addOutput("vec3","vec3"),this.properties={x:0,y:0,z:0},this._data=new Float32Array(3)}function k(){this.addInput("vec4","vec4"),this.addOutput("x","number"),this.addOutput("y","number"),this.addOutput("z","number"),this.addOutput("w","number")}function I(){this.addInputs([["x","number"],["y","number"],["z","number"],["w","number"]]),this.addOutput("vec4","vec4"),this.properties={x:0,y:0,z:0,w:0},this._data=new Float32Array(4)}e.title="Converter",e.desc="type A to type B",e.prototype.onExecute=function(){var t=this.getInputData(0);if(null!=t&&this.outputs)for(var e=0;ee&&(this._current=0),0),n=0;nB":s=e=B":s=e<=t}this.setOutputData(i,s)}}},m.prototype.onGetOutputs=function(){return[["A==B","boolean"],["A!=B","boolean"],["A>B","boolean"],["A=B","boolean"],["A<=B","boolean"]]},n.registerNodeType("math/compare",m),n.registerSearchboxExtra("math/compare","==",{outputs:[["A==B","boolean"]],title:"A==B"}),n.registerSearchboxExtra("math/compare","!=",{outputs:[["A!=B","boolean"]],title:"A!=B"}),n.registerSearchboxExtra("math/compare",">",{outputs:[["A>B","boolean"]],title:"A>B"}),n.registerSearchboxExtra("math/compare","<",{outputs:[["A=",{outputs:[["A>=B","boolean"]],title:"A>=B"}),n.registerSearchboxExtra("math/compare","<=",{outputs:[["A<=B","boolean"]],title:"A<=B"}),L["@OP"]={type:"enum",title:"operation",values:L.values=[">","<","==","!=","<=",">=","||","&&"]},L.title="Condition",L.desc="evaluates condition between A and B",L.prototype.getTitle=function(){return"A "+this.properties.OP+" B"},L.prototype.onExecute=function(){var t=this.getInputData(0),e=(void 0===t?t=this.properties.A:this.properties.A=t,this.getInputData(1)),i=(void 0===e?e=this.properties.B:this.properties.B=e,!0);switch(this.properties.OP){case">":i=e=":i=e<=t;break;case"||":i=t||e;break;case"&&":i=t&&e}this.setOutputData(0,i),this.setOutputData(1,!i)},n.registerNodeType("math/condition",L),G.title="Branch",G.desc="If condition is true, outputs IN in true, otherwise in false",G.prototype.onExecute=function(){var t=this.getInputData(0);this.getInputData(1)?(this.setOutputData(0,t),this.setOutputData(1,null)):(this.setOutputData(0,null),this.setOutputData(1,t))},n.registerNodeType("math/branch",G),T.title="Accumulate",T.desc="Increments a value every time",T.prototype.onExecute=function(){null===this.properties.value&&(this.properties.value=0);var t=this.getInputData(0);this.properties.value+=null!==t?t:this.properties.increment,this.setOutputData(0,this.properties.value)},n.registerNodeType("math/accumulate",T),O.title="Trigonometry",O.desc="Sin Cos Tan",O.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=0),this.properties.amplitude),i=this.findInputSlot("amplitude"),n=(-1!=i&&(e=this.getInputData(i)),this.properties.offset);-1!=(i=this.findInputSlot("offset"))&&(n=this.getInputData(i));for(var s,o=0,r=this.outputs.length;oXY",w.desc="vector 2 to components",w.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]))},n.registerNodeType("math3d/vec2-to-xy",w),E.title="XY->Vec2",E.desc="components to vector2",E.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this._data);i[0]=t,i[1]=e,this.setOutputData(0,i)},n.registerNodeType("math3d/xy-to-vec2",E),N.title="Vec3->XYZ",N.desc="vector 3 to components",N.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]),this.setOutputData(2,t[2]))},n.registerNodeType("math3d/vec3-to-xyz",N),x.title="XYZ->Vec3",x.desc="components to vector3",x.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this.getInputData(2)),n=(null==i&&(i=this.properties.z),this._data);n[0]=t,n[1]=e,n[2]=i,this.setOutputData(0,n)},n.registerNodeType("math3d/xyz-to-vec3",x),k.title="Vec4->XYZW",k.desc="vector 4 to components",k.prototype.onExecute=function(){var t=this.getInputData(0);null!=t&&(this.setOutputData(0,t[0]),this.setOutputData(1,t[1]),this.setOutputData(2,t[2]),this.setOutputData(3,t[3]))},n.registerNodeType("math3d/vec4-to-xyzw",k),I.title="XYZW->Vec4",I.desc="components to vector4",I.prototype.onExecute=function(){var t=this.getInputData(0),e=(null==t&&(t=this.properties.x),this.getInputData(1)),i=(null==e&&(e=this.properties.y),this.getInputData(2)),n=(null==i&&(i=this.properties.z),this.getInputData(3)),s=(null==n&&(n=this.properties.w),this._data);s[0]=t,s[1]=e,s[2]=i,s[3]=n,this.setOutputData(0,s)},n.registerNodeType("math3d/xyzw-to-vec4",I)})(this),(t=>{function e(){this.addInput("","string"),this.addOutput("table","table"),this.addOutput("rows","number"),this.addProperty("value",""),this.addProperty("separator",","),this._table=null}(t=t.LiteGraph).wrapFunctionAsNode("string/toString",function(t){if(t&&t.constructor===Object)try{return JSON.stringify(t)}catch(t){}return String(t)},[""],"string"),t.wrapFunctionAsNode("string/compare",function(t,e){return t==e},["string","string"],"boolean"),t.wrapFunctionAsNode("string/concatenate",function(t,e){return void 0===t?e:void 0===e?t:t+e},["string","string"],"string"),t.wrapFunctionAsNode("string/contains",function(t,e){return void 0!==t&&void 0!==e&&-1!=t.indexOf(e)},["string","string"],"boolean"),t.wrapFunctionAsNode("string/toUpperCase",function(t){return null!=t&&t.constructor===String?t.toUpperCase():t},["string"],"string"),t.wrapFunctionAsNode("string/split",function(t,e){if(null==e&&(e=this.properties.separator),null==t)return[];if(t.constructor===String)return t.split(e||" ");if(t.constructor!==Array)return null;for(var i=[],n=0;n{var i=t.LiteGraph;function e(){this.addInput("sel","number"),this.addInput("A"),this.addInput("B"),this.addInput("C"),this.addInput("D"),this.addOutput("out"),this.selected=0}function n(){this.properties={sequence:"A,B,C"},this.addInput("index","number"),this.addInput("seq"),this.addOutput("out"),this.index=0,this.values=this.properties.sequence.split(",")}function s(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function o(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function r(){this.properties={},this.addInput("in","boolean"),this.addOutput("out","boolean")}function a(){this.properties={},this.addInput("a","boolean"),this.addInput("b","boolean"),this.addOutput("out","boolean")}function h(){this.properties={},this.addInput("onTrigger",i.ACTION),this.addInput("condition","boolean"),this.addOutput("true",i.EVENT),this.addOutput("false",i.EVENT),this.mode=i.ON_TRIGGER}e.title="Selector",e.desc="selects an output",e.prototype.onDrawBackground=function(t){var e;this.flags.collapsed||(t.fillStyle="#AFB",e=(this.selected+1)*i.NODE_SLOT_HEIGHT+6,t.beginPath(),t.moveTo(50,e),t.lineTo(50,e+i.NODE_SLOT_HEIGHT),t.lineTo(34,e+.5*i.NODE_SLOT_HEIGHT),t.fill())},e.prototype.onExecute=function(){var t=this.getInputData(0),t=(null!=t&&t.constructor===Number||(t=0),this.selected=t=Math.round(t)%(this.inputs.length-1),this.getInputData(t+1));void 0!==t&&this.setOutputData(0,t)},e.prototype.onGetInputs=function(){return[["E",0],["F",0],["G",0],["H",0]]},i.registerNodeType("logic/selector",e),n.title="Sequence",n.desc="select one element from a sequence from a string",n.prototype.onPropertyChanged=function(t,e){"sequence"==t&&(this.values=e.split(","))},n.prototype.onExecute=function(){var t=this.getInputData(1),t=(t&&t!=this.current_sequence&&(this.values=t.split(","),this.current_sequence=t),this.getInputData(0));null==t&&(t=0),this.index=t=Math.round(t)%this.values.length,this.setOutputData(0,this.values[t])},i.registerNodeType("logic/sequence",n),s.title="AND",s.desc="Return true if all inputs are true",s.prototype.onExecute=function(){var t,e=!0;for(t in this.inputs)if(!this.getInputData(t)){e=!1;break}this.setOutputData(0,e)},s.prototype.onGetInputs=function(){return[["and","boolean"]]},i.registerNodeType("logic/AND",s),o.title="OR",o.desc="Return true if at least one input is true",o.prototype.onExecute=function(){var t,e=!1;for(t in this.inputs)if(this.getInputData(t)){e=!0;break}this.setOutputData(0,e)},o.prototype.onGetInputs=function(){return[["or","boolean"]]},i.registerNodeType("logic/OR",o),r.title="NOT",r.desc="Return the logical negation",r.prototype.onExecute=function(){var t=!this.getInputData(0);this.setOutputData(0,t)},i.registerNodeType("logic/NOT",r),a.title="bool == bool",a.desc="Compare for logical equality",a.prototype.onExecute=function(){var t,e=null,i=!0;for(t in this.inputs)if(null===e)e=this.getInputData(t);else if(e!=this.getInputData(t)){i=!1;break}this.setOutputData(0,i)},a.prototype.onGetInputs=function(){return[["bool","boolean"]]},i.registerNodeType("logic/CompareBool",a),h.title="Branch",h.desc="Branch execution on condition",h.prototype.onExecute=function(t,e){this.getInputData(1)?this.triggerSlot(0):this.triggerSlot(1)},i.registerNodeType("logic/IF",h)})(this),(t=>{var o=t.LiteGraph;function e(){this.size=[60,20],this.addInput("send",o.ACTION),this.addOutput("received",o.EVENT),this.addInput("in",0),this.addOutput("out",0),this.properties={url:"",room:"lgraph",only_send_changes:!0},this._ws=null,this._last_sent_data=[],this._last_received_data=[]}function i(){this.room_widget=this.addWidget("text","Room","lgraph",this.setRoom.bind(this)),this.addWidget("button","Reconnect",null,this.connectSocket.bind(this)),this.addInput("send",o.ACTION),this.addOutput("received",o.EVENT),this.addInput("in",0),this.addOutput("out",0),this.properties={url:"tamats.com:55000",room:"lgraph",only_send_changes:!0},this._server=null,this.connectSocket(),this._last_sent_data=[],this._last_received_data=[],"undefined"==typeof SillyClient&&console.warn("remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js")}function n(){this.addInput("request",o.ACTION),this.addInput("url","string"),this.addProperty("url",""),this.addOutput("ready",o.EVENT),this.addOutput("data","string"),this.addWidget("button","Fetch",null,this.fetch.bind(this)),this._data=null,this._fetching=null}e.title="WebSocket",e.desc="Send data through a websocket",e.prototype.onPropertyChanged=function(t,e){"url"==t&&this.connectSocket()},e.prototype.onExecute=function(){if(!this._ws&&this.properties.url&&this.connectSocket(),this._ws&&this._ws.readyState==WebSocket.OPEN){for(var t=this.properties.room,e=this.properties.only_send_changes,i=1;i{if(t.ok)return this.boxcolor="#0F0",t.text();this.boxcolor="#F00",e.trigger("error")}).then(t=>{e._data=t,e._fetching=null,e.trigger("ready")}))},n.prototype.onAction=function(t){"request"==t&&this.fetch()},n.prototype.onExecute=function(){this.setOutputData(1,this._data)},n.prototype.onGetOutputs=function(){return[["error",o.EVENT]]},o.registerNodeType("network/httprequest",n)})(this); \ No newline at end of file diff --git a/gruntfile.js b/gruntfile.js index 7deb42905..b0aca9d8d 100755 --- a/gruntfile.js +++ b/gruntfile.js @@ -1,47 +1,75 @@ module.exports = function (grunt) { grunt.initConfig({ pkg: grunt.file.readJSON('package.json'), - projectFiles: ['src/litegraph.js', + + // File lists for different builds + fullFiles: ['src/litegraph.js', 'src/nodes/base.js', 'src/nodes/events.js', 'src/nodes/interface.js', 'src/nodes/input.js', 'src/nodes/math.js', + 'src/nodes/math3d.js', + 'src/nodes/strings.js', 'src/nodes/logic.js', - 'src/nodes/image.js', + 'src/nodes/graphics.js', 'src/nodes/gltextures.js', + 'src/nodes/glshaders.js', + 'src/nodes/geometry.js', 'src/nodes/glfx.js', 'src/nodes/midi.js', 'src/nodes/audio.js', 'src/nodes/network.js' ], + + miniFiles: ['src/litegraph.js', + 'src/nodes/base.js', + 'src/nodes/events.js', + 'src/nodes/input.js', + 'src/nodes/math.js', + 'src/nodes/strings.js', + 'src/nodes/logic.js', + 'src/nodes/network.js' + ], + + coreFiles: ['src/litegraph.js'], + concat: { - build: { - src: '<%= projectFiles %>', + full: { + src: '<%= fullFiles %>', dest: 'build/litegraph.js' + }, + mini: { + src: '<%= miniFiles %>', + dest: 'build/litegraph_mini.js' + }, + core: { + src: '<%= coreFiles %>', + dest: 'build/litegraph.core.js' } }, - closureCompiler: { - - options: { - compilerFile: 'node_modules/google-closure-compiler/compiler.jar', - compilerOpts: { - formatting: 'pretty_print', - warning_level: 'default' - }, - d32: false, // will use 'java -client -d32 -jar compiler.jar' - TieredCompilation: false// will use 'java -server -XX:+TieredCompilation -jar compiler.jar', - // ,output_wrapper: '"var LiteGraph = (function(){%output% return LiteGraph;}).call(this);"' //* Make container for all - }, - targetName: { - src: '<%= projectFiles %>', + + uglify: { + full: { + src: 'build/litegraph.js', dest: 'build/litegraph.min.js' + }, + mini: { + src: 'build/litegraph_mini.js', + dest: 'build/litegraph_mini.min.js' + }, + core: { + src: 'build/litegraph.core.js', + dest: 'build/litegraph.core.min.js' } } }) grunt.loadNpmTasks('grunt-contrib-concat') - grunt.loadNpmTasks('grunt-closure-tools') + grunt.loadNpmTasks('grunt-contrib-uglify') - grunt.registerTask('build', ['concat:build', 'closureCompiler']) + grunt.registerTask('build', ['concat', 'uglify']) + grunt.registerTask('build-full', ['concat:full', 'uglify:full']) + grunt.registerTask('build-mini', ['concat:mini', 'uglify:mini']) + grunt.registerTask('build-core', ['concat:core', 'uglify:core']) } diff --git a/package.json b/package.json index c72830ea9..ca920f207 100755 --- a/package.json +++ b/package.json @@ -43,6 +43,8 @@ "grunt-cli": "^1.2.0", "grunt-closure-tools": "^1.0.0", "grunt-contrib-concat": "^2.1.0", + "grunt-contrib-uglify": "^5.2.2", + "grunt-contrib-uglify-es": "^3.3.0", "jest": "^28.1.3", "jest-cli": "^28.1.3", "nodemon": "^2.0.22", diff --git a/src/litegraph.js b/src/litegraph.js index 359f6e1d0..0f72f51f8 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -2433,7 +2433,9 @@ + unsafe_execution: not allowed for safe execution + skip_repeated_outputs: when adding new outputs, it wont show if there is one already connected + resizable: if set to false it wont be resizable with the mouse - + horizontal: slots are distributed horizontally + + horizontal: slots are distributed horizontally (applies to both inputs and outputs) + + inputs_horizontal: input slots are distributed horizontally (overrides horizontal for inputs, set to null to use horizontal) + + outputs_horizontal: output slots are distributed horizontally (overrides horizontal for outputs, set to null to use horizontal) + widgets_start_y: widgets start at y distance from the top of the node flags object: @@ -4767,9 +4769,17 @@ var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5; + // Check for separate input/output orientation, fallback to general horizontal property + var is_horizontal = false; + if (is_input) { + is_horizontal = this.inputs_horizontal !== undefined && this.inputs_horizontal !== null ? this.inputs_horizontal : this.horizontal; + } else { + is_horizontal = this.outputs_horizontal !== undefined && this.outputs_horizontal !== null ? this.outputs_horizontal : this.horizontal; + } + if (this.flags.collapsed) { var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH; - if (this.horizontal) { + if (is_horizontal) { out[0] = this.pos[0] + w * 0.5; if (is_input) { out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT; @@ -4814,7 +4824,7 @@ } //horizontal distributed slots - if (this.horizontal) { + if (is_horizontal) { out[0] = this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots); if (is_input) { @@ -7057,7 +7067,8 @@ LGraphNode.prototype.executeAction = function(action) var input = node.inputs[i]; var link_pos = node.getConnectionPos(true, i); var is_inside = false; - if (node.horizontal) { + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : node.horizontal; + if (inputs_horizontal) { is_inside = isInsideRectangle( canvasx, canvasy, @@ -7103,7 +7114,8 @@ LGraphNode.prototype.executeAction = function(action) var output = node.outputs[i]; var link_pos = node.getConnectionPos(false, i); var is_inside = false; - if (node.horizontal) { + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : node.horizontal; + if (outputs_horizontal) { is_inside = isInsideRectangle( canvasx, canvasy, @@ -7952,10 +7964,15 @@ LGraphNode.prototype.executeAction = function(action) var connDir = connInOrOut.dir; if(connDir == null) { - if (this.connecting_output) - connDir = this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; - else - connDir = this.connecting_node.horizontal ? LiteGraph.UP : LiteGraph.LEFT; + if (this.connecting_output) { + var outputs_horizontal = this.connecting_node.outputs_horizontal !== undefined && this.connecting_node.outputs_horizontal !== null ? + this.connecting_node.outputs_horizontal : this.connecting_node.horizontal; + connDir = outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT; + } else { + var inputs_horizontal = this.connecting_node.inputs_horizontal !== undefined && this.connecting_node.inputs_horizontal !== null ? + this.connecting_node.inputs_horizontal : this.connecting_node.horizontal; + connDir = inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT; + } } var connShape = connInOrOut.shape; @@ -8587,6 +8604,8 @@ LGraphNode.prototype.executeAction = function(action) var size = temp_vec2; temp_vec2.set(node.size); var horizontal = node.horizontal; // || node.flags.horizontal; + var inputs_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + var outputs_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; if (node.flags.collapsed) { ctx.font = this.inner_text_font; @@ -8643,7 +8662,7 @@ LGraphNode.prototype.executeAction = function(action) } //connection slots - ctx.textAlign = horizontal ? "center" : "left"; + ctx.textAlign = inputs_horizontal ? "center" : "left"; ctx.font = this.inner_text_font; var render_text = !low_quality; @@ -8700,7 +8719,7 @@ LGraphNode.prototype.executeAction = function(action) slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE ) { - if (horizontal) { + if (inputs_horizontal) { ctx.rect( pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, @@ -8744,7 +8763,7 @@ LGraphNode.prototype.executeAction = function(action) var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.UP) { + if (inputs_horizontal || slot.dir == LiteGraph.UP) { ctx.fillText(text, pos[0], pos[1] - 10); } else { ctx.fillText(text, pos[0] + 10, pos[1] + 5); @@ -8756,7 +8775,7 @@ LGraphNode.prototype.executeAction = function(action) //output connection slots - ctx.textAlign = horizontal ? "center" : "right"; + ctx.textAlign = outputs_horizontal ? "center" : "right"; ctx.strokeStyle = "black"; if (node.outputs) { for (var i = 0; i < node.outputs.length; i++) { @@ -8799,7 +8818,7 @@ LGraphNode.prototype.executeAction = function(action) slot_type === LiteGraph.EVENT || slot_shape === LiteGraph.BOX_SHAPE ) { - if (horizontal) { + if (outputs_horizontal) { ctx.rect( pos[0] - 5 + 0.5, pos[1] - 8 + 0.5, @@ -8851,7 +8870,7 @@ LGraphNode.prototype.executeAction = function(action) var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; - if (horizontal || slot.dir == LiteGraph.DOWN) { + if (outputs_horizontal || slot.dir == LiteGraph.DOWN) { ctx.fillText(text, pos[0], pos[1] - 8); } else { ctx.fillText(text, pos[0] - 10, pos[1] + 5); @@ -8866,7 +8885,7 @@ LGraphNode.prototype.executeAction = function(action) if (node.widgets) { var widgets_y = max_y; - if (horizontal || node.widgets_up) { + if (inputs_horizontal || outputs_horizontal || node.widgets_up) { widgets_y = 2; } if( node.widgets_start_y != null ) @@ -8909,7 +8928,9 @@ LGraphNode.prototype.executeAction = function(action) if (input_slot) { var x = 0; var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { + // For collapsed nodes, use inputs_horizontal if available, otherwise fall back to general horizontal + var input_is_horizontal = node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? node.inputs_horizontal : horizontal; + if (input_is_horizontal) { x = node._collapsed_width * 0.5; y = -LiteGraph.NODE_TITLE_HEIGHT; } @@ -8934,7 +8955,9 @@ LGraphNode.prototype.executeAction = function(action) if (output_slot) { var x = node._collapsed_width; var y = LiteGraph.NODE_TITLE_HEIGHT * -0.5; //center - if (horizontal) { + // For collapsed nodes, use outputs_horizontal if available, otherwise fall back to general horizontal + var output_is_horizontal = node.outputs_horizontal !== undefined && node.outputs_horizontal !== null ? node.outputs_horizontal : horizontal; + if (output_is_horizontal) { x = node._collapsed_width * 0.5; y = 0; } @@ -9438,10 +9461,14 @@ LGraphNode.prototype.executeAction = function(action) } var start_dir = start_slot.dir || - (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT); + (start_node.outputs_horizontal !== undefined && start_node.outputs_horizontal !== null ? + (start_node.outputs_horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT) : + (start_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT)); var end_dir = end_slot.dir || - (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT); + (node.inputs_horizontal !== undefined && node.inputs_horizontal !== null ? + (node.inputs_horizontal ? LiteGraph.UP : LiteGraph.LEFT) : + (node.horizontal ? LiteGraph.UP : LiteGraph.LEFT)); this.renderLink( ctx, @@ -9523,6 +9550,31 @@ LGraphNode.prototype.executeAction = function(action) var dist = distance(a, b); + // Auto-detect when to use straight lines for better visual appearance + var should_use_straight = false; + var dx = Math.abs(a[0] - b[0]); + var dy = Math.abs(a[1] - b[1]); + + // Use straight lines when connecting opposite directions and nodes are reasonably aligned + if (this.links_render_mode == LiteGraph.SPLINE_LINK) { + // Vertical to vertical connections (like mixed nodes with vertical outputs to vertical inputs) + if ((start_dir == LiteGraph.RIGHT && end_dir == LiteGraph.LEFT) || + (start_dir == LiteGraph.LEFT && end_dir == LiteGraph.RIGHT)) { + // If nodes are roughly vertically aligned (small horizontal distance relative to vertical) + if (dx < 50 && dy > 20) { + should_use_straight = true; + } + } + // Horizontal to horizontal connections + else if ((start_dir == LiteGraph.DOWN && end_dir == LiteGraph.UP) || + (start_dir == LiteGraph.UP && end_dir == LiteGraph.DOWN)) { + // If nodes are roughly horizontally aligned (small vertical distance relative to horizontal) + if (dy < 50 && dx > 20) { + should_use_straight = true; + } + } + } + if (this.render_connections_border && this.ds.scale > 0.6) { ctx.lineWidth = this.connections_width + 4; } @@ -9539,46 +9591,82 @@ LGraphNode.prototype.executeAction = function(action) if (this.links_render_mode == LiteGraph.SPLINE_LINK) { ctx.moveTo(a[0], a[1] + offsety); - var start_offset_x = 0; - var start_offset_y = 0; - var end_offset_x = 0; - var end_offset_y = 0; - switch (start_dir) { - case LiteGraph.LEFT: - start_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - start_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - start_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - start_offset_y = dist * 0.25; - break; - } - switch (end_dir) { - case LiteGraph.LEFT: - end_offset_x = dist * -0.25; - break; - case LiteGraph.RIGHT: - end_offset_x = dist * 0.25; - break; - case LiteGraph.UP: - end_offset_y = dist * -0.25; - break; - case LiteGraph.DOWN: - end_offset_y = dist * 0.25; - break; + + if (should_use_straight) { + // Use straight line logic for better visual alignment + var start_x = a[0]; + var start_y = a[1] + offsety; + var end_x = b[0]; + var end_y = b[1] + offsety; + + if (start_dir == LiteGraph.RIGHT) { + start_x += 10; + } else if (start_dir == LiteGraph.LEFT) { + start_x -= 10; + } else if (start_dir == LiteGraph.DOWN) { + start_y += 10; + } else if (start_dir == LiteGraph.UP) { + start_y -= 10; + } + + if (end_dir == LiteGraph.LEFT) { + end_x -= 10; + } else if (end_dir == LiteGraph.RIGHT) { + end_x += 10; + } else if (end_dir == LiteGraph.UP) { + end_y -= 10; + } else if (end_dir == LiteGraph.DOWN) { + end_y += 10; + } + + ctx.lineTo(start_x, start_y); + ctx.lineTo((start_x + end_x) * 0.5, start_y); + ctx.lineTo((start_x + end_x) * 0.5, end_y); + ctx.lineTo(end_x, end_y); + ctx.lineTo(b[0], b[1] + offsety); + } else { + // Original spline logic + var start_offset_x = 0; + var start_offset_y = 0; + var end_offset_x = 0; + var end_offset_y = 0; + switch (start_dir) { + case LiteGraph.LEFT: + start_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + start_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + start_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + start_offset_y = dist * 0.25; + break; + } + switch (end_dir) { + case LiteGraph.LEFT: + end_offset_x = dist * -0.25; + break; + case LiteGraph.RIGHT: + end_offset_x = dist * 0.25; + break; + case LiteGraph.UP: + end_offset_y = dist * -0.25; + break; + case LiteGraph.DOWN: + end_offset_y = dist * 0.25; + break; + } + ctx.bezierCurveTo( + a[0] + start_offset_x, + a[1] + start_offset_y + offsety, + b[0] + end_offset_x, + b[1] + end_offset_y + offsety, + b[0], + b[1] + offsety + ); } - ctx.bezierCurveTo( - a[0] + start_offset_x, - a[1] + start_offset_y + offsety, - b[0] + end_offset_x, - b[1] + end_offset_y + offsety, - b[0], - b[1] + offsety - ); } else if (this.links_render_mode == LiteGraph.LINEAR_LINK) { ctx.moveTo(a[0], a[1] + offsety); var start_offset_x = 0;