diff --git a/build/litegraph.js b/build/litegraph.js index 0866faae7..adb3bdfff 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -1219,8 +1219,8 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) supported callbacks: + onAdded: when added to graph + onRemoved: when removed from graph - + onStart: when starts playing - + onStop: when stops playing + + 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 @@ -1360,6 +1360,8 @@ LGraphNode.prototype.configure = function(info) } } + if( this.onConfigured ) + this.onConfigured( info ); } /** @@ -1538,6 +1540,24 @@ LGraphNode.prototype.getInputInfo = function(slot) 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) + return null; + return this.graph.getNodeById( input.link.origin_id ); +} + /** * tells you info about an output connection (which node, type, etc) @@ -1964,7 +1984,7 @@ LGraphNode.prototype.findOutputSlot = function(name) * @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 {boolean} if it was connected succesfully */ -LGraphNode.prototype.connect = function( slot, node, target_slot ) +LGraphNode.prototype.connect = function( slot, target_node, target_slot ) { target_slot = target_slot || 0; @@ -1986,20 +2006,19 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) return false; } - if(node && node.constructor === Number) - node = this.graph.getNodeById( node ); - if(!node) + if(target_node && target_node.constructor === Number) + target_node = this.graph.getNodeById( target_node ); + if(!target_node) throw("Node not found"); //avoid loopback - if(node == this) + if(target_node == this) return false; - //if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode"); //you can specify the slot by name if(target_slot.constructor === String) { - target_slot = node.findInputSlot(target_slot); + target_slot = target_node.findInputSlot( target_slot ); if(target_slot == -1) { if(LiteGraph.debug) @@ -2012,13 +2031,13 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) //search for first slot with event? /* //create input for trigger - var input = node.addInput("onTrigger", LiteGraph.EVENT ); - target_slot = node.inputs.length - 1; //last one is the one created - node.mode = LiteGraph.ON_TRIGGER; + var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); + target_slot = target_node.inputs.length - 1; //last one is the one created + target_node.mode = LiteGraph.ON_TRIGGER; */ return false; } - else if( !node.inputs || target_slot >= node.inputs.length ) + else if( !target_node.inputs || target_slot >= target_node.inputs.length ) { if(LiteGraph.debug) console.log("Connect: Error, slot number not found"); @@ -2026,8 +2045,8 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) } //if there is something already plugged there, disconnect - if(node.inputs[ target_slot ].link != null ) - node.disconnectInput( target_slot ); + if(target_node.inputs[ target_slot ].link != null ) + target_node.disconnectInput( target_slot ); //why here?? this.setDirtyCanvas(false,true); @@ -2036,36 +2055,36 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) var output = this.outputs[slot]; //allows nodes to block connection - if(node.onConnectInput) - if( node.onConnectInput( target_slot, output.type, output ) === false) + if(target_node.onConnectInput) + if( target_node.onConnectInput( target_slot, output.type, output ) === false) return false; - var input = node.inputs[target_slot]; + var input = target_node.inputs[target_slot]; if( LiteGraph.isValidConnection( output.type, input.type) ) { - var link = { + var link_info = { id: this.graph.last_link_id++, origin_id: this.id, origin_slot: slot, - target_id: node.id, + target_id: target_node.id, target_slot: target_slot }; //add to graph links list - this.graph.links[ link.id ] = link; + this.graph.links[ link_info.id ] = link_info; //connect in output if( output.links == null ) output.links = []; - output.links.push( link.id ); + output.links.push( link_info.id ); //connect in input - node.inputs[target_slot].link = link.id; + target_node.inputs[target_slot].link = link_info.id; if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot ); - if(node.onConnectionsChange) - node.onConnectionsChange( LiteGraph.OUTPUT, target_slot ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info ); //link_info has been created now, so its updated + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info ); } this.setDirtyCanvas(false,true); @@ -2081,7 +2100,7 @@ LGraphNode.prototype.connect = function( slot, node, target_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 succesfully */ -LGraphNode.prototype.disconnectOutput = function(slot, target_node) +LGraphNode.prototype.disconnectOutput = function( slot, target_node ) { if( slot.constructor === String ) { @@ -2124,6 +2143,10 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) output.links.splice(i,1); //remove here target_node.inputs[ link_info.target_slot ].link = null; //remove there delete this.graph.links[ link_id ]; //remove the link from the links pool + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info ); break; } } @@ -2139,10 +2162,15 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) if(target_node) target_node.inputs[ link_info.target_slot ].link = null; //remove other side link delete this.graph.links[ link_id ]; //remove the link from the links pool + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info ); } output.links = null; } + this.setDirtyCanvas(false,true); this.graph.connectionChange( this ); return true; @@ -2154,7 +2182,7 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) * @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 succesfully */ -LGraphNode.prototype.disconnectInput = function(slot) +LGraphNode.prototype.disconnectInput = function( slot ) { //seek for the output slot if( slot.constructor === String ) @@ -2185,15 +2213,15 @@ LGraphNode.prototype.disconnectInput = function(slot) var link_info = this.graph.links[ link_id ]; if( link_info ) { - var node = this.graph.getNodeById( link_info.origin_id ); - if(!node) + var target_node = this.graph.getNodeById( link_info.origin_id ); + if(!target_node) return false; - var output = node.outputs[ link_info.origin_slot ]; + var output = target_node.outputs[ link_info.origin_slot ]; if(!output || !output.links || output.links.length == 0) return false; - //check outputs + //search in the inputs list for this link for(var i = 0, l = output.links.length; i < l; i++) { var link_id = output.links[i]; @@ -2205,10 +2233,10 @@ LGraphNode.prototype.disconnectInput = function(slot) } } - if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT ); - if(node.onConnectionsChange) - node.onConnectionsChange( LiteGraph.INPUT); + if( this.onConnectionsChange ) + this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info ); + if( target_node.onConnectionsChange ) + target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info ); } this.setDirtyCanvas(false,true); @@ -2879,7 +2907,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); var skip_dragging = false; - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); if(e.which == 1) //left button mouse { @@ -3013,7 +3041,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) } else if (e.which == 3) //right button { - this.processContextualMenu(n,e); + this.processContextMenu(n,e); } //TODO @@ -4587,7 +4615,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) if(values[i]) entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e) { @@ -4597,7 +4625,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); + LiteGraph.createContextMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); return false; } @@ -4654,7 +4682,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e, prev) { @@ -4711,7 +4739,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked( v, e, prev ) { @@ -4731,7 +4759,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) var entries = []; for(var i in value) entries.push({content: i, value: value[i]}); - LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); return false; } else @@ -4758,7 +4786,7 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); function inner_clicked( v, e, prev ) { @@ -4786,7 +4814,19 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options var info = null; if(node.getPropertyInfo) info = node.getPropertyInfo(property); - if(info.type) + if(node.properties_info) + { + for(var i = 0; i < node.properties_info.length; ++i) + { + if( node.properties_info[i].name == property ) + { + info = node.properties_info[i]; + break; + } + } + } + + if(info !== undefined && info !== null && info.type ) type = info.type; var input_html = ""; @@ -4803,6 +4843,10 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options } input_html += ""; } + else if(type == "boolean") + { + input_html = ""; + } var dialog = document.createElement("div"); @@ -4813,21 +4857,35 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options { var input = dialog.querySelector("select"); input.addEventListener("change", function(e){ - var index = e.target.value; - setValue( e.options[e.selectedIndex].value ); + setValue( e.target.value ); + //var index = e.target.value; + //setValue( e.options[e.selectedIndex].value ); }); } + else if(type == "boolean") + { + var input = dialog.querySelector("input"); + if(input) + { + input.addEventListener("click", function(e){ + setValue( !!input.checked ); + }); + } + } else { var input = dialog.querySelector("input"); - input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; - input.addEventListener("keydown", function(e){ - if(e.keyCode != 13) - return; - inner(); - e.preventDefault(); - e.stopPropagation(); - }); + if(input) + { + input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } } var rect = this.canvas.getClientRects()[0]; @@ -4864,9 +4922,11 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options function setValue(value) { if(typeof( node.properties[ property ] ) == "number") - node.properties[ property ] = Number(value); - else - node.properties[ property ] = value; + value = Number(value); + node.properties[ property ] = value; + + if(node.onPropertyChanged) + node.onPropertyChanged( property, value ); dialog.parentNode.removeChild( dialog ); node.setDirtyCanvas(true,true); } @@ -4885,7 +4945,7 @@ LGraphCanvas.onMenuNodePin = function(node) LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) { - LiteGraph.createContextualMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); function inner_clicked(v) { @@ -4913,7 +4973,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) var value = {value:i, content:""+i+""}; values.push(value); } - LiteGraph.createContextualMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); function inner_clicked(v) { @@ -4932,7 +4992,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) LGraphCanvas.onMenuNodeShapes = function(node,e) { - LiteGraph.createContextualMenu(["box","round"], {event: e, callback: inner_clicked}); + LiteGraph.createContextMenu(["box","round"], {event: e, callback: inner_clicked}); function inner_clicked(v) { @@ -5047,7 +5107,7 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) return options; } -LGraphCanvas.prototype.processContextualMenu = function(node, event) +LGraphCanvas.prototype.processContextMenu = function(node, event) { var that = this; var win = this.getCanvasWindow(); @@ -5075,7 +5135,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) if(!menu_info) return; - var menu = LiteGraph.createContextualMenu( menu_info, options, win); + var menu = LiteGraph.createContextMenu( menu_info, options, win); function inner_option_clicked(v,e) { @@ -5221,7 +5281,7 @@ function num2hex(triplet) { /* LiteGraph GUI elements *************************************/ -LiteGraph.createContextualMenu = function(values,options, ref_window) +LiteGraph.createContextMenu = function(values,options, ref_window) { options = options || {}; this.options = options; @@ -5230,10 +5290,10 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) ref_window = ref_window || window; if (!options.from) - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); else { //closing submenus - var menus = document.querySelectorAll(".graphcontextualmenu"); + var menus = document.querySelectorAll(".graphcontextmenu"); for (var key in menus) { if (menus[key].previousSibling == options.from) menus[key].closeMenu(); @@ -5241,7 +5301,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } var root = ref_window.document.createElement("div"); - root.className = "graphcontextualmenu graphmenubar-panel"; + root.className = "graphcontextmenu graphmenubar-panel"; this.root = root; var style = root.style; @@ -5255,12 +5315,13 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) style.padding = "2px"; style.borderBottom = "2px solid #AAF"; style.backgroundColor = "#444"; + style.zIndex = 10; //title if(options.title) { var element = document.createElement("div"); - element.className = "graphcontextualmenu-title"; + element.className = "graphcontextmenu-title"; element.innerHTML = options.title; root.appendChild(element); } @@ -5368,7 +5429,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } if(close) - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); //root.closeMenu(); } @@ -5387,11 +5448,11 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) return root; } -LiteGraph.closeAllContextualMenus = function( ref_window ) +LiteGraph.closeAllContextMenus = function( ref_window ) { ref_window = ref_window || window; - var elements = ref_window.document.querySelectorAll(".graphcontextualmenu"); + var elements = ref_window.document.querySelectorAll(".graphcontextmenu"); if(!elements.length) return; var result = []; @@ -5456,6 +5517,25 @@ if( !window["requestAnimationFrame"] ) (function(){ +//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() { @@ -6105,12 +6185,12 @@ LiteGraph.registerNodeType("events/delay", DelayEvent ); WidgetKnob.prototype.onWidget = function(e,widget) { if(widget.name=="increase") - this.onPropertyChange("size", this.properties.size + 10); + this.onPropertyChanged("size", this.properties.size + 10); else if(widget.name=="decrease") - this.onPropertyChange("size", this.properties.size - 10); + this.onPropertyChanged("size", this.properties.size - 10); } - WidgetKnob.prototype.onPropertyChange = function(name,value) + WidgetKnob.prototype.onPropertyChanged = function(name,value) { if(name=="wcolor") this.properties[name] = value; @@ -6243,7 +6323,7 @@ LiteGraph.registerNodeType("events/delay", DelayEvent ); //this.oldmouse = null; } - WidgetHSlider.prototype.onPropertyChange = function(name,value) + WidgetHSlider.prototype.onPropertyChanged = function(name,value) { if(name=="wcolor") this.properties[name] = value; @@ -6496,7 +6576,7 @@ LiteGraph.registerNodeType("events/delay", DelayEvent ); } }, - onPropertyChange: function(name,value) + onPropertyChanged: function(name,value) { this.properties[name] = value; return true; @@ -6595,7 +6675,7 @@ LiteGraph.registerNodeType("events/delay", DelayEvent ); } } - WidgetText.prototype.onPropertyChange = function(name,value) + WidgetText.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; this.str = typeof(value) == 'number' ? value.toFixed(3) : value; @@ -7908,7 +7988,7 @@ GraphicsImage.prototype.onExecute = function() this.img.dirty = false; } -GraphicsImage.prototype.onPropertyChange = function(name,value) +GraphicsImage.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; if (name == "url" && value != "") @@ -8267,7 +8347,7 @@ ImageCrop.prototype.onExecute = function() this.setOutputData(0,null); } -ImageCrop.prototype.onPropertyChange = function(name,value) +ImageCrop.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; @@ -8390,7 +8470,7 @@ ImageVideo.prototype.loadVideo = function(url) //document.body.appendChild(this.video); } -ImageVideo.prototype.onPropertyChange = function(name,value) +ImageVideo.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; if (name == "url" && value != "") @@ -9061,6 +9141,8 @@ if(typeof(LiteGraph) != "undefined") this.boxcolor = "#FF0000"; return; } + this.boxcolor = "#FF0000"; + this._shader_code = (uvcode + "|" + pixelcode); shader = this._shader; } @@ -9106,8 +9188,10 @@ if(typeof(LiteGraph) != "undefined") void main() {\n\ vec2 uv = v_coord;\n\ UV_CODE;\n\ - vec3 color = texture2D(u_texture, uv).rgb;\n\ - vec3 colorB = texture2D(u_textureB, uv).rgb;\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\ @@ -10413,13 +10497,13 @@ if(typeof(LiteGraph) != "undefined") } this._waiting_confirmation = true; + var that = this; // Not showing vendor prefixes. navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); - var that = this; function onFailSoHard(e) { - trace('Webcam rejected', e); + console.log('Webcam rejected', e); that._webcam_stream = false; that.box_color = "red"; }; @@ -10509,7 +10593,84 @@ if(typeof(LiteGraph) != "undefined") LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam ); - //Cubemap reader + 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 }; + + if(!LGraphTextureMatte._shader) + LGraphTextureMatte._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMatte.pixel_shader ); + } + + 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; + + 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 ); + + //*********************************** + //Cubemap reader (to pass a cubemap to a node that requires cubemaps and no images) function LGraphCubemap() { this.addOutput("Cubemap","Cubemap"); @@ -10634,7 +10795,7 @@ if(typeof(LiteGraph) != "undefined") gl.disable( gl.DEPTH_TEST ); var mesh = Mesh.getScreenQuad(); var shader = LGraphFXLens._shader; - var camera = LS.Renderer._current_camera; + //var camera = LS.Renderer._current_camera; this._tex.drawTo( function() { tex.bind(0); @@ -10948,7 +11109,11 @@ if(typeof(LiteGraph) != "undefined") gl.disable( gl.BLEND ); gl.disable( gl.DEPTH_TEST ); var mesh = Mesh.getScreenQuad(); - var camera = LS.Renderer._current_camera; + var camera = window.LS ? LS.Renderer._current_camera : null; + if(camera) + camera_planes = [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far]; + else + camera_planes = [1,100]; var noise = null; if(fx == "noise") @@ -10959,7 +11124,7 @@ if(typeof(LiteGraph) != "undefined") if(fx == "noise") noise.bind(1); - shader.uniforms({u_texture:0, u_noise:1, u_size: [tex.width, tex.height], u_rand:[ Math.random(), Math.random() ], u_value1: value1, u_value2: value2, u_camera_planes: [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far] }) + shader.uniforms({u_texture:0, u_noise:1, u_size: [tex.width, tex.height], u_rand:[ Math.random(), Math.random() ], u_value1: value1, u_value2: value2, u_camera_planes: camera_planes }) .draw(mesh); }); @@ -11737,4 +11902,650 @@ function now() { return window.performance.now() } +})( window ); +//not tested nor finished + +(function( global ) +{ + +function LGAudio() +{ + this.properties = { + src: "demodata/audio.wav", + gain: 0.5, + loop: true + }; + + this._loading_audio = false; + this._audio_buffer = null; + this._audionodes = []; + + this.addOutput( "out", "audio" ); + this.addInput( "gain", "number" ); + + //init context + var context = LGAudio.getAudioContext(); + + //create gain node + this.audionode = context.createGain(); + this.audionode.graphnode = this; + this.audionode.gain.value = this.properties.gain; + + //debug + if(this.properties.src) + this.loadSound( this.properties.src ); +} + +LGAudio.getAudioContext = function() +{ + if(!this._audio_context) + { + window.AudioContext = window.AudioContext || window.webkitAudioContext; + if(!window.AudioContext) + { + console.error("AudioContext not supported by browser"); + return null; + } + this._audio_context = new AudioContext(); + this._audio_context.onmessage = function(msg) { console.log("msg",msg);}; + this._audio_context.onended = function(msg) { console.log("ended",msg);}; + this._audio_context.oncomplete = function(msg) { console.log("complete",msg);}; + } + + //in case it crashes + if(this._audio_context.state == "suspended") + this._audio_context.resume(); + return this._audio_context; +} + +LGAudio.connect = function( audionodeA, audionodeB ) +{ + audionodeA.connect( audionodeB ); + + /* + if(!nodeA.outputs) + nodeA.outputs = []; + nodeA.outputs.push( nodeB ); + if(!nodeB.inputs) + nodeB.inputs = []; + nodeB.inputs.push( nodeA ); + */ +} + +LGAudio.disconnect = function( audionodeA, audionodeB ) +{ + audionodeA.disconnect( audionodeB ); + + /* + if(nodeA.outputs) + { + var index = nodeA.outputs.indexOf( nodeB ); + if(index != -1) + nodeA.outputs.splice(index,1); + } + if(nodeB.inputs) + { + var index = nodeB.inputs.indexOf( nodeA ); + if(index != -1) + nodeB.inputs.splice(index,1); + } + */ +} + +LGAudio.prototype.onAdded = function(graph) +{ + if(graph.status === LGraph.STATUS_RUNNING) + this.onStart(); +} + +LGAudio.prototype.onStart = function() +{ + if(!this._audio_buffer) + return; + + this.playBuffer( this._audio_buffer ); +} + +LGAudio.prototype.onStop = function() +{ + this.stopAllSounds(); +} + +LGAudio.prototype.onRemoved = function() +{ + this.stopAllSounds(); +} + +LGAudio.prototype.stopAllSounds = function() +{ + //iterate and stop + for(var i = 0; i < this._audionodes.length; ++i ) + { + this._audionodes[i].stop(); + //this._audionodes[i].disconnect( this.audionode ); + } + this._audionodes.length = 0; +} + +LGAudio.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if( v !== undefined ) + this.audionode.gain.value = v; +} + +LGAudio.prototype.onAction = function(event) +{ + if(this._audio_buffer) + { + if(event == "Play") + this.playBuffer(this._audio_buffer); + else if(event == "Stop") + this.stopAllSounds(); + } +} + +LGAudio.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "src" ) + this.loadSound( value ); + else if(name == "gain") + this.audionode.gain.value = value; +} + +LGAudio.prototype.playBuffer = function( buffer ) +{ + var that = this; + var context = LGAudio.getAudioContext(); + + //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones) + var audionode = context.createBufferSource(); //create a AudioBufferSourceNode + audionode.graphnode = this; + audionode.buffer = buffer; + audionode.loop = this.properties.loop; + this._audionodes.push( audionode ); + audionode.connect( this.audionode ); //connect to gain + this._audionodes.push( audionode ); + + audionode.onended = function() + { + console.log("ended!"); + that.trigger("ended"); + //remove + var index = that._audionodes.indexOf( audionode ); + if(index != -1) + that._audionodes.splice(index,1); + } + + audionode.start(); + return audionode; +} + +LGAudio.prototype.onConnectionsChange = function( connection, slot, connected, link_info ) +{ + //only process the outputs events + if(connection != LiteGraph.OUTPUT) + return; + + var target_node = null; + if( link_info ) + target_node = this.graph.getNodeById( link_info.target_id ); + + if( !target_node ) + return; + + if( connected ) + { + if(target_node.connectAudioToSlot) + target_node.connectAudioToSlot( this.audionode, link_info.target_slot ); + else + LGAudio.connect( this.audionode, target_node.audionode ); + } + else + { + if(target_node.disconnectAudioFromSlot) + target_node.disconnectAudioFromSlot( this.audionode, link_info.target_slot ); + else + LGAudio.disconnect( this.audionode, target_node.audionode ); + } +} + +LGAudio.prototype.loadSound = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._audio_buffer = null; + this._loading_audio = false; + + if(!url) + return; + + //load new sample + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + this._loading_audio = true; + this._request = request; + + var context = LGAudio.getAudioContext(); + + // Decode asynchronously + request.onload = function() { + context.decodeAudioData( request.response, function(buffer) { + that._audio_buffer = buffer; + that._loading_audio = false; + //if is playing, then play it + if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) + that.onStart(); + }, onError); + } + request.send(); + + function onError(err) + { + console.log("Audio loading sample error:",err); + } +} + +LGAudio.prototype.onGetInputs = function() +{ + return [["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]]; +} + +LGAudio.prototype.onGetOutputs = function() +{ + return [["ended",LiteGraph.EVENT]]; +} + + +LGAudio.title = "Source"; +LGAudio.desc = "Plays audio"; +LiteGraph.registerNodeType("audio/source", LGAudio); +global.LGAudio = LGAudio; + +//***************************************************** + +function LGAudioAnalyser() +{ + this.properties = { + fftSize: 2048, + minDecibels: -100, + maxDecibels: -10, + smoothingTimeConstant: 0.5 + }; + + var context = LGAudio.getAudioContext(); + + this.audionode = context.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","FFT"); + //this.addOutput("time","freq"); + + this._freq_bin = null; + this._time_bin = null; +} + +LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) +{ + this.audionode[ name ] = value; +} + +LGAudioAnalyser.prototype.onExecute = function() +{ + if(this.isOutputConnected(0)) + { + //send FFT + var bufferLength = this.audionode.frequencyBinCount; + if( !this._freq_bin || this._freq_bin.length != bufferLength ) + this._freq_bin = new Uint8Array( bufferLength ); + this.audionode.getByteFrequencyData( this._freq_bin ); + this.setOutputData(0,this._freq_bin); + } + + //properties + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v !== undefined) + this.audionode[ input.name ].value = v; + } + + //time domain + //this.audionode.getFloatTimeDomainData( dataArray ); +} + +LGAudioAnalyser.prototype.onGetInputs = function() +{ + return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; +} + + +LGAudioAnalyser.title = "Analyser"; +LGAudioAnalyser.desc = "Audio Analyser"; +LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); + + + +//***************************************************** + +//this function helps creating wrappers to existing classes +function createAudioNodeWrapper( class_object ) +{ + class_object.prototype.onPropertyChanged = function(name, value) + { + if( this.audionode[ name ] === undefined ) + return; + + if( this.audionode[ name ].value !== undefined ) + this.audionode[ name ].value = value; + else + this.audionode[ name ] = value; + } + + class_object.prototype.onConnectionsChange = function( connection, slot, connected, link_info ) + { + //only process the outputs events + if(connection != LiteGraph.OUTPUT) + return; + + var target_node = null; + if( link_info ) + target_node = this.graph.getNodeById( link_info.target_id ); + if( !target_node ) + return; + + if( connected ) + { + if(target_node.connectAudioToSlot) + target_node.connectAudioToSlot( this.audionode, link_info.target_slot ); + else + LGAudio.connect( this.audionode, target_node.audionode ); + } + else + { + if(target_node.disconnectAudioFromSlot) + target_node.disconnectAudioFromSlot( this.audionode, link_info.target_slot ); + else + LGAudio.disconnect( this.audionode, target_node.audionode ); + } + } +} + + +//***************************************************** + +function LGAudioGain() +{ + //default + this.properties = { + gain: 1 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + this.addInput("in","audio"); + this.addInput("gain","number"); + this.addOutput("out","audio"); +} + +LGAudioGain.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +createAudioNodeWrapper( LGAudioGain ); + +LGAudioGain.title = "Gain"; +LGAudioGain.desc = "Audio gain"; +LiteGraph.registerNodeType("audio/gain", LGAudioGain); + + +function LGAudioMixer() +{ + //default + this.properties = { + gain1: 0.5, + gain2: 0.5 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + + this.audionode1 = LGAudio.getAudioContext().createGain(); + this.audionode1.gain.value = this.properties.gain1; + this.audionode2 = LGAudio.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"); +} + +LGAudioMixer.prototype.connectAudioToSlot = function( audionode, slot ) +{ + if(slot == 0) + LGAudio.connect( audionode, this.audionode1 ); + else if(slot == 2) + LGAudio.connect( audionode, this.audionode2 ); +} + +LGAudioMixer.prototype.disconnectAudioFromSlot = function( audionode, slot ) +{ + if(slot == 0) + LGAudio.disconnect( audionode, this.audionode1 ); + else if(slot == 2) + LGAudio.disconnect( audionode, this.audionode2 ); +} + +LGAudioMixer.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.type == "audio") + continue; + + var v = this.getInputData(i); + if(v === undefined) + continue; + + if(i == 1) + this.audionode1.gain.value = v; + else if(i == 3) + this.audionode2.gain.value = v; + } +} + +createAudioNodeWrapper( LGAudioMixer ); + +LGAudioMixer.title = "Mixer"; +LGAudioMixer.desc = "Audio mixer"; +LiteGraph.registerNodeType("audio/mixer", LGAudioMixer); + + +function LGAudioDelay() +{ + //default + this.properties = { + time: 5 + }; + + this.audionode = LGAudio.getAudioContext().createDelay( this.properties.time ); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +createAudioNodeWrapper( LGAudioDelay ); + +LGAudioDelay.prototype.onPropertyChanged = function( name, value ) +{ + if(name == "time") + { + if(value > 500) + value = 500; + if(value < 0) + value = 0; + + var input_node = this.getInputNode(0); + var output_nodes = this.getOutputNodes(0); + + if(input_node) + input_node.audionode.disconnect( this.audionode ); + if(output_nodes) + { + for(var i = 0; i < output_nodes.length; ++i) + this.audionode.disconnect( output_nodes[i].audionode ); + } + + this.audionode = LGAudio.getAudioContext().createDelay( value ); + + if(input_node) + input_node.audionode.connect( this.audionode ); + if(output_nodes) + { + for(var i = 0; i < output_nodes.length; ++i) + this.audionode.connect( output_nodes[i].audionode ); + } + } +} + +LGAudioDelay.title = "Delay"; +LGAudioDelay.desc = "Audio delay"; +LiteGraph.registerNodeType("audio/delay", LGAudioDelay); + + +function LGAudioBiquadFilter() +{ + //default + this.properties = { + frequency: 350, + detune: 0, + Q: 1 + }; + this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]}); + + //create node + this.audionode = LGAudio.getAudioContext().createBiquadFilter(); + + //slots + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudioBiquadFilter.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioBiquadFilter.prototype.onGetInputs = function() +{ + return [["frequency","number"],["detune","number"],["Q","number"]]; +} + +createAudioNodeWrapper( LGAudioBiquadFilter ); + +LGAudioBiquadFilter.title = "BiquadFilter"; +LGAudioBiquadFilter.desc = "Audio filter"; +LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); + + +//***************************************************** + +function LGAudioDestination() +{ + this.audionode = LGAudio.getAudioContext().destination; + this.addInput("in","audio"); +} + + +LGAudioDestination.title = "Destination"; +LGAudioDestination.desc = "Audio output"; +LiteGraph.registerNodeType("audio/destination", LGAudioDestination); + + + +//EXTRA + + +function LGAudioVisualization() +{ + this.addInput("freqs","FFT"); + this.size = [300,200]; + this._last_buffer = null; +} + +LGAudioVisualization.prototype.onExecute = function() +{ + this._last_buffer = this.getInputData(0); +} + +LGAudioVisualization.prototype.onDrawForeground = function(ctx) +{ + if(!this._last_buffer) + return; + + var buffer = this._last_buffer; + + var delta = buffer.length / this.size[0]; + var h = this.size[1]; + + ctx.fillStyle = "black"; + ctx.fillRect(0,0,this.size[0],this.size[1]); + ctx.strokeStyle = "white"; + ctx.beginPath(); + var x = 0; + for(var i = 0; i < buffer.length; i+= delta) + { + ctx.moveTo(x,h); + ctx.lineTo(x,h - (buffer[i|0]/255) * h); + x++; + } + ctx.stroke(); +} + +LGAudioVisualization.title = "Visualization"; +LGAudioVisualization.desc = "Audio Visualization"; +LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); + + + })( window ); diff --git a/build/litegraph.min.js b/build/litegraph.min.js index 86e6b8f7c..e82e74e4b 100755 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -29,14 +29,14 @@ LGraph.prototype.connectionChange=function(a){this.updateExecutionOrder();if(thi LGraph.prototype.setDirtyCanvas=function(a,b){this.sendActionToCanvas("setDirty",[a,b])};LGraph.prototype.serialize=function(){for(var a=[],b=0,c=this._nodes.length;b!a.length||(this._pos[0]=a[0],this._pos[1]=a[1])},get:function(){return this._pos},enumerable:!0});this.id=-1;this.type=null;this.inputs=[];this.outputs=[];this.connections=[];this.properties={};this.properties_info=[];this.data=null;this.flags={}}; -LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)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]=LiteGraph.cloneObject(a[b],this[b]):this[b]=a[b]);if(this.onConnectionsChange)this.onConnectionsChange();for(var d in this.inputs)c=this.inputs[d],c.link&&c.link.length&&(a=c.link, -"object"==typeof a&&(c.link=a[0],this.graph.links[a[0]]={id:a[0],origin_id:a[1],origin_slot:a[2],target_id:a[3],target_slot:a[4]}));for(d in this.outputs)if(c=this.outputs[d],c.links&&0!=c.links.length)for(b in c.links)a=c.links[b],"object"==typeof a&&(c.links[b]=a[0])}; +LGraphNode.prototype.configure=function(a){for(var b in a)if("console"!=b)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]=LiteGraph.cloneObject(a[b],this[b]):this[b]=a[b]);if(this.onConnectionsChange)this.onConnectionsChange();for(var d in this.inputs){var e=this.inputs[d];e.link&&e.link.length&&(c=e.link, +"object"==typeof c&&(e.link=c[0],this.graph.links[c[0]]={id:c[0],origin_id:c[1],origin_slot:c[2],target_id:c[3],target_slot:c[4]}))}for(d in this.outputs)if(e=this.outputs[d],e.links&&0!=e.links.length)for(b in e.links)c=e.links[b],"object"==typeof c&&(e.links[b]=c[0]);if(this.onConfigured)this.onConfigured(a)}; LGraphNode.prototype.serialize=function(){var a={id:this.id,title:this.title,type:this.type,pos:this.pos,size:this.size,data:this.data,flags:LiteGraph.cloneObject(this.flags),inputs:this.inputs,outputs:this.outputs,mode:this.mode};this.properties&&(a.properties=LiteGraph.cloneObject(this.properties));a.type||(a.type=this.constructor.type);this.color&&(a.color=this.color);this.bgcolor&&(a.bgcolor=this.bgcolor);this.boxcolor&&(a.boxcolor=this.boxcolor);this.shape&&(a.shape=this.shape);if(this.onSerialize)this.onSerialize(a); return a};LGraphNode.prototype.clone=function(){var a=LiteGraph.createNode(this.type),b=LiteGraph.cloneObject(this.serialize());if(b.inputs)for(var c=0;c=this.inputs.length||null==this.inputs[a].link)){var c=this.graph.links[this.inputs[a].link];if(!b)return c.data;var d=this.graph.getNodeById(c.origin_id);if(!d)return c.data;if(d.updateOutputData)d.updateOutputData(c.origin_slot);else if(d.onExecute)d.onExecute();return c.data}};LGraphNode.prototype.isInputConnected=function(a){return this.inputs?a=this.inputs.length?null:(a=this.inputs[a])&&a.link?this.graph.getNodeById(a.link.origin_id):null};LGraphNode.prototype.getOutputInfo=function(a){return this.outputs?a=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;b&&b.constructor===Number&&(b=this.graph.getNodeById(b));if(!b)throw"Node not found";if(b==this)return!1;if(c.constructor===String){if(c=b.findInputSlot(c),-1==c)return LiteGraph.debug&& console.log("Connect: Error, no slot of name "+c),!1}else{if(c===LiteGraph.EVENT)return!1;if(!b.inputs||c>=b.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1}null!=b.inputs[c].link&&b.disconnectInput(c);this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);var d=this.outputs[a];if(b.onConnectInput&&!1===b.onConnectInput(c,d.type,d))return!1;if(LiteGraph.isValidConnection(d.type,b.inputs[c].type)){var e={id:this.graph.last_link_id++,origin_id:this.id, -origin_slot:a,target_id:b.id,target_slot:c};this.graph.links[e.id]=e;null==d.links&&(d.links=[]);d.links.push(e.id);b.inputs[c].link=e.id;if(this.onConnectionsChange)this.onConnectionsChange(LiteGraph.OUTPUT,a);if(b.onConnectionsChange)b.onConnectionsChange(LiteGraph.OUTPUT,c)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0}; +origin_slot:a,target_id:b.id,target_slot:c};this.graph.links[e.id]=e;null==d.links&&(d.links=[]);d.links.push(e.id);b.inputs[c].link=e.id;if(this.onConnectionsChange)this.onConnectionsChange(LiteGraph.OUTPUT,a,!0,e);if(b.onConnectionsChange)b.onConnectionsChange(LiteGraph.INPUT,c,!0,e)}this.setDirtyCanvas(!1,!0);this.graph.connectionChange(this);return!0}; LGraphNode.prototype.disconnectOutput=function(a,b){if(a.constructor===String){if(a=this.findOutputSlot(a),-1==a)return LiteGraph.debug&&console.log("Connect: Error, no slot of name "+a),!1}else if(!this.outputs||a>=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;var c=this.outputs[a];if(!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< -e;d++){var f=c.links[d],g=this.graph.links[f];if(g.target_id==b.id){c.links.splice(d,1);b.inputs[g.target_slot].link=null;delete this.graph.links[f];break}}}else{d=0;for(e=c.links.length;d=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;if(!this.inputs[a])return!1;var b=this.inputs[a].link;this.inputs[a].link=null;if(b=this.graph.links[b]){a=this.graph.getNodeById(b.origin_id);if(!a)return!1;var c=a.outputs[b.origin_slot];if(!c|| -!c.links||0==c.links.length)return!1;for(var d=0,e=c.links.length;d=this.inputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;if(!this.inputs[a])return!1;var b=this.inputs[a].link;this.inputs[a].link=null;if(b=this.graph.links[b]){var c=this.graph.getNodeById(b.origin_id);if(!c)return!1;var d=c.outputs[b.origin_slot]; +if(!d||!d.links||0==d.links.length)return!1;for(var e=0,f=d.links.length;eb&&this.inputs[b].pos?[this.pos[0]+this.inputs[b].pos[0],this.pos[1]+this.inputs[b].pos[1]]:!a&&this.outputs.length>b&&this.outputs[b].pos?[this.pos[0]+this.outputs[b].pos[0],this.pos[1]+this.outputs[b].pos[1]]: a?[this.pos[0],this.pos[1]+10+b*LiteGraph.NODE_SLOT_HEIGHT]:[this.pos[0]+this.size[0]+1,this.pos[1]+10+b*LiteGraph.NODE_SLOT_HEIGHT]};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)}; LGraphNode.prototype.trace=function(a){this.console||(this.console=[]);this.console.push(a);this.console.length>LGraphNode.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};LGraphNode.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.sendActionToCanvas("setDirty",[a,b])};LGraphNode.prototype.loadImage=function(a){var b=new Image;b.src=LiteGraph.node_images_path+a;b.ready=!1;var c=this;b.onload=function(){this.ready=!0;c.setDirtyCanvas(!0)};return b}; @@ -75,11 +76,11 @@ this._ondrop_callback),this.canvas.removeEventListener("dragenter",this._doRetur LGraphCanvas.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()};LGraphCanvas.prototype.enableWebGL=function(){if(void 0===typeof GL)throw"litegl.js must be included to use a WebGL canvas";if(void 0===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}; LGraphCanvas.prototype.setDirty=function(a,b){a&&(this.dirty_canvas=!0);b&&(this.dirty_bgcanvas=!0)};LGraphCanvas.prototype.getCanvasWindow=function(){if(!this.canvas)return window;var a=this.canvas.ownerDocument;return a.defaultView||a.parentWindow};LGraphCanvas.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))}; LGraphCanvas.prototype.stopRendering=function(){this.is_rendering=!1}; -LGraphCanvas.prototype.processMouseDown=function(a){if(this.graph){this.adjustMouseEvent(a);var b=this.getCanvasWindow();this.canvas.removeEventListener("mousemove",this._mousemove_callback);b.document.addEventListener("mousemove",this._mousemove_callback,!0);b.document.addEventListener("mouseup",this._mouseup_callback,!0);var c=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes);LiteGraph.closeAllContextualMenus(b);if(1==a.which){if(!(a.shiftKey||c&&this.selected_nodes[c.id])){var d= -[],e;for(e in this.selected_nodes)this.selected_nodes[e]!=c&&d.push(this.selected_nodes[e]);for(e in d)this.processNodeDeselected(d[e])}d=!1;if(c){this.live_mode||c.flags.pinned||this.bringToFront(c);var f=!1;if(!this.connecting_node&&!c.flags.collapsed&&!this.live_mode){if(c.outputs){e=0;for(var g=c.outputs.length;eLiteGraph.getTime()-this.last_mouseclick&&this.selected_nodes[c.id]){if(c.onDblClick)c.onDblClick(a);this.processNodeDblClicked(c);e=!0}c.onMouseDown&&c.onMouseDown(a,[a.canvasX-c.pos[0],a.canvasY-c.pos[1]])?e=!0:this.live_mode&&(e=d=!0);e||(this.allow_dragnodes&&(this.node_dragged=c),this.selected_nodes[c.id]||this.processNodeSelected(c,a));this.dirty_canvas=!0}}else d=!0;d&&this.allow_dragcanvas&&(this.dragging_canvas= -!0)}else 2!=a.which&&3==a.which&&this.processContextualMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=LiteGraph.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];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}}; +!0)}else 2!=a.which&&3==a.which&&this.processContextMenu(c,a);this.last_mouse[0]=a.localX;this.last_mouse[1]=a.localY;this.last_mouseclick=LiteGraph.getTime();this.canvas_mouse=[a.canvasX,a.canvasY];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}}; LGraphCanvas.prototype.processMouseMove=function(a){this.autoresize&&this.resize();if(this.graph){this.adjustMouseEvent(a);var b=[a.localX,a.localY],c=[b[0]-this.last_mouse[0],b[1]-this.last_mouse[1]];this.last_mouse=b;this.canvas_mouse=[a.canvasX,a.canvasY];if(this.dragging_canvas)this.offset[0]+=c[0]/this.scale,this.offset[1]+=c[1]/this.scale,this.dirty_bgcanvas=this.dirty_canvas=!0;else{this.connecting_node&&(this.dirty_canvas=!0);for(var b=this.graph.getNodeOnPos(a.canvasX,a.canvasY,this.visible_nodes), d=0,e=this.graph._nodes.length;dc&&0.01>b.editor_alpha&&(clearInterval(d),1>c&&(b.live_mode=!0));1"+h+""+(void 0!==a.properties[h]?a.properties[h]:" ")+"",value:h});if(g.length)return LiteGraph.createContextualMenu(g,{event:b,callback:d,from:c},f),!1}}; -LGraphCanvas.prototype.showEditPropertyValue=function(a,b,c){function d(){e(q.value)}function e(c){a.properties[b]="number"==typeof a.properties[b]?Number(c):c;l.parentNode.removeChild(l);a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var f="string";null!==a.properties[b]&&(f=typeof a.properties[b]);var g=null;a.getPropertyInfo&&(g=a.getPropertyInfo(b));g.type&&(f=g.type);var h="";if("string"==f||"number"==f)h="";else if("enum"==f&&g.values){var h= -""}var l=document.createElement("div");l.className="graphdialog";l.innerHTML=""+b+""+h+"";if("enum"==f&&g.values){var q=l.querySelector("select");q.addEventListener("change",function(a){e(a.options[a.selectedIndex].value)})}else q=l.querySelector("input"), -q.value=void 0!==a.properties[b]?a.properties[b]:"",q.addEventListener("keydown",function(a){13==a.keyCode&&(d(),a.preventDefault(),a.stopPropagation())});f=this.canvas.getClientRects()[0];h=g=-20;f&&(g-=f.left,h-=f.top);c.event?(l.style.left=c.event.pageX+g+"px",l.style.top=c.event.pageY+h+"px"):(l.style.left=0.5*this.canvas.width+g+"px",l.style.top=0.5*this.canvas.height+h+"px");l.querySelector("button").addEventListener("click",d);this.canvas.parentNode.appendChild(l)}}; -LGraphCanvas.onMenuNodeCollapse=function(a){a.flags.collapsed=!a.flags.collapsed;a.setDirtyCanvas(!0,!0)};LGraphCanvas.onMenuNodePin=function(a){a.pin()};LGraphCanvas.onMenuNodeMode=function(a,b,c){LiteGraph.createContextualMenu(["Always","On Event","Never"],{event:b,callback:function(b){if(a)switch(b){case "On Event":a.mode=LiteGraph.ON_EVENT;break;case "Never":a.mode=LiteGraph.NEVER;break;default:a.mode=LiteGraph.ALWAYS}},from:c});return!1}; -LGraphCanvas.onMenuNodeColors=function(a,b,c){var d=[],e;for(e in LGraphCanvas.node_colors){var f=LGraphCanvas.node_colors[e];d.push({value:e,content:""+e+""})}LiteGraph.createContextualMenu(d,{event:b,callback:function(b){a&&(b=LGraphCanvas.node_colors[b.value])&&(a.color=b.color,a.bgcolor=b.bgcolor,a.setDirtyCanvas(!0))},from:c});return!1}; -LGraphCanvas.onMenuNodeShapes=function(a,b){LiteGraph.createContextualMenu(["box","round"],{event:b,callback:function(b){a&&(a.shape=b,a.setDirtyCanvas(!0))}});return!1};LGraphCanvas.onMenuNodeRemove=function(a){!1!=a.removable&&(a.graph.remove(a),a.setDirtyCanvas(!0,!0))};LGraphCanvas.onMenuNodeClone=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),a.setDirtyCanvas(!0,!0))}}; +LGraphCanvas.onMenuAdd=function(a,b,c,d,e){function f(a,b){var c=LiteGraph.createNode(a.value);c&&(c.pos=d.convertEventToCanvas(e),d.graph.add(c))}var g=d.getCanvasWindow();a=LiteGraph.getNodeTypesCategories();var h={},k;for(k in a)a[k]&&(h[k]={value:a[k],content:a[k],is_menu:!0});var n=LiteGraph.createContextMenu(h,{event:b,callback:function(a,b){var c=LiteGraph.getNodeTypesInCategory(a.value),d=[],h;for(h in c)d.push({content:c[h].title,value:c[h].type});LiteGraph.createContextMenu(d,{event:b,callback:f, +from:n},g);return!1},from:c},g);return!1};LGraphCanvas.onMenuCollapseAll=function(){};LGraphCanvas.onMenuNodeEdit=function(){}; +LGraphCanvas.showMenuNodeInputs=function(a,b,c){function d(b,c,d){a&&(b.callback&&b.callback.call(e,a,b,c,d),b.value&&a.addInput(b.value[0],b.value[1],b.value[2]))}if(a){var e=this,f=this.getCanvasWindow(),g=a.optional_inputs;a.onGetInputs&&(g=a.onGetInputs());var h=[];if(g)for(var k in g){var n=g[k],l=n[0];n[2]&&n[2].label&&(l=n[2].label);h.push({content:l,value:n})}this.onMenuNodeInputs&&(h=this.onMenuNodeInputs(h));if(h.length)return LiteGraph.createContextMenu(h,{event:b,callback:d,from:c},f), +!1}}; +LGraphCanvas.showMenuNodeOutputs=function(a,b,c){function d(b,g,h){if(a&&(b.callback&&b.callback.call(e,a,b,g,h),b.value))if(h=b.value[1],!h||h.constructor!==Object&&h.constructor!==Array)a.addOutput(b.value[0],b.value[1],b.value[2]);else{b=[];for(var k in h)b.push({content:k,value:h[k]});LiteGraph.createContextMenu(b,{event:g,callback:d,from:c});return!1}}if(a){var e=this,f=this.getCanvasWindow(),g=a.optional_outputs;a.onGetOutputs&&(g=a.onGetOutputs());var h=[];if(g)for(var k in g){var n=g[k];if(!n)h.push(null); +else if(-1==a.findOutputSlot(n[0])){var l=n[0];n[2]&&n[2].label&&(l=n[2].label);l={content:l,value:n};n[1]==LiteGraph.EVENT&&(l.className="event");h.push(l)}}this.onMenuNodeOutputs&&(h=this.onMenuNodeOutputs(h));if(h.length)return LiteGraph.createContextMenu(h,{event:b,callback:d,from:c},f),!1}}; +LGraphCanvas.onShowMenuNodeProperties=function(a,b,c){function d(b,c,d){a&&e.showEditPropertyValue(a,b.value,{event:c})}if(a&&a.properties){var e=this,f=this.getCanvasWindow(),g=[],h;for(h in a.properties)g.push({content:""+h+""+(void 0!==a.properties[h]?a.properties[h]:" ")+"",value:h});if(g.length)return LiteGraph.createContextMenu(g,{event:b,callback:d,from:c},f),!1}}; +LGraphCanvas.prototype.showEditPropertyValue=function(a,b,c){function d(){e(q.value)}function e(c){"number"==typeof a.properties[b]&&(c=Number(c));a.properties[b]=c;if(a.onPropertyChanged)a.onPropertyChanged(b,c);l.parentNode.removeChild(l);a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var f="string";null!==a.properties[b]&&(f=typeof a.properties[b]);var g=null;a.getPropertyInfo&&(g=a.getPropertyInfo(b));if(a.properties_info)for(var h=0;h";else if("enum"==f&&g.values){k=""}else"boolean"==f&&(k="");var l=document.createElement("div");l.className="graphdialog";l.innerHTML=""+b+""+k+"";if("enum"==f&&g.values){var q=l.querySelector("select");q.addEventListener("change",function(a){e(a.target.value)})}else if("boolean"==f)(q=l.querySelector("input"))&&q.addEventListener("click",function(a){e(!!q.checked)});else if(q=l.querySelector("input"))q.value=void 0!==a.properties[b]?a.properties[b]:"",q.addEventListener("keydown",function(a){13== +a.keyCode&&(d(),a.preventDefault(),a.stopPropagation())});f=this.canvas.getClientRects()[0];h=g=-20;f&&(g-=f.left,h-=f.top);c.event?(l.style.left=c.event.pageX+g+"px",l.style.top=c.event.pageY+h+"px"):(l.style.left=0.5*this.canvas.width+g+"px",l.style.top=0.5*this.canvas.height+h+"px");l.querySelector("button").addEventListener("click",d);this.canvas.parentNode.appendChild(l)}};LGraphCanvas.onMenuNodeCollapse=function(a){a.flags.collapsed=!a.flags.collapsed;a.setDirtyCanvas(!0,!0)}; +LGraphCanvas.onMenuNodePin=function(a){a.pin()};LGraphCanvas.onMenuNodeMode=function(a,b,c){LiteGraph.createContextMenu(["Always","On Event","Never"],{event:b,callback:function(b){if(a)switch(b){case "On Event":a.mode=LiteGraph.ON_EVENT;break;case "Never":a.mode=LiteGraph.NEVER;break;default:a.mode=LiteGraph.ALWAYS}},from:c});return!1}; +LGraphCanvas.onMenuNodeColors=function(a,b,c){var d=[],e;for(e in LGraphCanvas.node_colors){var f=LGraphCanvas.node_colors[e];d.push({value:e,content:""+e+""})}LiteGraph.createContextMenu(d,{event:b,callback:function(b){a&&(b=LGraphCanvas.node_colors[b.value])&&(a.color=b.color,a.bgcolor=b.bgcolor,a.setDirtyCanvas(!0))},from:c});return!1}; +LGraphCanvas.onMenuNodeShapes=function(a,b){LiteGraph.createContextMenu(["box","round"],{event:b,callback:function(b){a&&(a.shape=b,a.setDirtyCanvas(!0))}});return!1};LGraphCanvas.onMenuNodeRemove=function(a){!1!=a.removable&&(a.graph.remove(a),a.setDirtyCanvas(!0,!0))};LGraphCanvas.onMenuNodeClone=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),a.setDirtyCanvas(!0,!0))}}; LGraphCanvas.node_colors={red:{color:"#FAA",bgcolor:"#A44"},green:{color:"#AFA",bgcolor:"#4A4"},blue:{color:"#AAF",bgcolor:"#44A"},white:{color:"#FFF",bgcolor:"#AAA"}}; LGraphCanvas.prototype.getCanvasMenuOptions=function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",is_menu:!0,callback:LGraphCanvas.onMenuAdd}],this._graph_stack&&0a&&db?!0:!1} function growBounding(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)}function isInsideBounding(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0}function overlapBounding(a,b){return a[0]>b[2]||a[1]>b[3]||a[2]f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b} function num2hex(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} -LiteGraph.createContextualMenu=function(a,b,c){function d(a){var d=!0;b.callback&&(a=b.callback.call(g,this.data,a),void 0!==a&&(d=a));d&&LiteGraph.closeAllContextualMenus(c)}this.options=b=b||{};c=c||window;if(b.from){var e=document.querySelectorAll(".graphcontextualmenu"),f;for(f in e)e[f].previousSibling==b.from&&e[f].closeMenu()}else LiteGraph.closeAllContextualMenus(c);var g=c.document.createElement("div");g.className="graphcontextualmenu graphmenubar-panel";this.root=g;e=g.style;e.minWidth= -"100px";e.minHeight="20px";e.position="fixed";e.top="100px";e.left="100px";e.color="#AAF";e.padding="2px";e.borderBottom="2px solid #AAF";e.backgroundColor="#444";b.title&&(e=document.createElement("div"),e.className="graphcontextualmenu-title",e.innerHTML=b.title,g.appendChild(e));g.addEventListener("contextmenu",function(a){a.preventDefault();return!1});for(var h in a)f=a[h],e=c.document.createElement("div"),e.className="graphmenu-entry",null==f?e.className+=" separator":(f.is_menu&&(e.className+= -" submenu"),f.disabled&&(e.className+=" disabled"),f.className&&(e.className+=" "+f.className),e.style.cursor="pointer",e.dataset.value="string"==typeof f?f:f.value,e.data=f,e.innerHTML="string"==typeof f?a.constructor==Array?a[h]:h:f.content?f.content:h,e.addEventListener("click",d)),g.appendChild(e);g.addEventListener("mouseover",function(a){this.mouse_inside=!0});g.addEventListener("mouseout",function(a){for(a=a.relatedTarget||a.toElement;a!=this&&a!=c.document;)a=a.parentNode;a!=this&&(this.mouse_inside= +LiteGraph.createContextMenu=function(a,b,c){function d(a){var d=!0;b.callback&&(a=b.callback.call(g,this.data,a),void 0!==a&&(d=a));d&&LiteGraph.closeAllContextMenus(c)}this.options=b=b||{};c=c||window;if(b.from){var e=document.querySelectorAll(".graphcontextmenu"),f;for(f in e)e[f].previousSibling==b.from&&e[f].closeMenu()}else LiteGraph.closeAllContextMenus(c);var g=c.document.createElement("div");g.className="graphcontextmenu graphmenubar-panel";this.root=g;e=g.style;e.minWidth="100px";e.minHeight= +"20px";e.position="fixed";e.top="100px";e.left="100px";e.color="#AAF";e.padding="2px";e.borderBottom="2px solid #AAF";e.backgroundColor="#444";e.zIndex=10;b.title&&(e=document.createElement("div"),e.className="graphcontextmenu-title",e.innerHTML=b.title,g.appendChild(e));g.addEventListener("contextmenu",function(a){a.preventDefault();return!1});for(var h in a)f=a[h],e=c.document.createElement("div"),e.className="graphmenu-entry",null==f?e.className+=" separator":(f.is_menu&&(e.className+=" submenu"), +f.disabled&&(e.className+=" disabled"),f.className&&(e.className+=" "+f.className),e.style.cursor="pointer",e.dataset.value="string"==typeof f?f:f.value,e.data=f,e.innerHTML="string"==typeof f?a.constructor==Array?a[h]:h:f.content?f.content:h,e.addEventListener("click",d)),g.appendChild(e);g.addEventListener("mouseover",function(a){this.mouse_inside=!0});g.addEventListener("mouseout",function(a){for(a=a.relatedTarget||a.toElement;a!=this&&a!=c.document;)a=a.parentNode;a!=this&&(this.mouse_inside= !1,this.block_close||this.closeMenu())});c.document.body.appendChild(g);a=g.getClientRects()[0];b.from&&(b.from.block_close=!0);f=b.left||0;h=b.top||0;b.event&&(f=b.event.pageX-10,h=b.event.pageY-10,b.left&&(f=b.left),e=c.document.body.getClientRects()[0],b.from&&(f=b.from.getClientRects()[0],f=f.left+f.width),f>e.width-a.width-10&&(f=e.width-a.width-10),h>e.height-a.height-10&&(h=e.height-a.height-10));g.style.left=f+"px";g.style.top=h+"px";g.closeMenu=function(){b.from&&(b.from.block_close=!1,b.from.mouse_inside|| -b.from.closeMenu());this.parentNode&&c.document.body.removeChild(this)};return g};LiteGraph.closeAllContextualMenus=function(a){a=a||window;a=a.document.querySelectorAll(".graphcontextualmenu");if(a.length){for(var b=[],c=0;ca.canvasY-this.pos[1]||distance([a.canvasX,a.canvasY],[this.pos[0]+this.center[0],this.pos[1]+this.center[1]])>this.radius)return!1;this.oldmouse=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];this.captureInput(!0);return!0}};b.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b-0.01*(a[1]-this.oldmouse[1]);1b&&(b=0);this.value=b;this.properties.value= -this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};b.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};b.prototype.onMouseLeave=function(a){};b.prototype.onWidget=function(a,b){if("increase"==b.name)this.onPropertyChange("size",this.properties.size+10);else if("decrease"==b.name)this.onPropertyChange("size",this.properties.size-10)};b.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]= +this.properties.min+(this.properties.max-this.properties.min)*this.value;this.oldmouse=a;this.setDirtyCanvas(!0)}};b.prototype.onMouseUp=function(a){this.oldmouse&&(this.oldmouse=null,this.captureInput(!1))};b.prototype.onMouseLeave=function(a){};b.prototype.onWidget=function(a,b){if("increase"==b.name)this.onPropertyChanged("size",this.properties.size+10);else if("decrease"==b.name)this.onPropertyChanged("size",this.properties.size-10)};b.prototype.onPropertyChanged=function(a,b){if("wcolor"==a)this.properties[a]= b;else if("size"==a)b=parseInt(b),this.properties[a]=b,this.size=[b+4,b+24],this.setDirtyCanvas(!0,!0);else if("min"==a||"max"==a||"value"==a)this.properties[a]=parseFloat(b);else return!1;return!0};LiteGraph.registerNodeType("widget/knob",b);c.title="H.Slider";c.desc="Linear slider controller";c.prototype.onInit=function(){this.value=0.5;this.imgfg=this.loadImage("imgs/slider_fg.png")};c.prototype.onDrawVectorial=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.strokeStyle=this.mouseOver? "#FFF":"#AAA",a.fillStyle="#000",a.beginPath(),a.rect(2,0,this.size[0]-4,20),a.stroke(),a.fillStyle=this.properties.wcolor,a.beginPath(),a.rect(2+(this.size[0]-4-20)*this.value,0,20,20),a.fill())};c.prototype.onDrawImage=function(a){this.imgfg&&this.imgfg.width&&(a.lineWidth=1,a.fillStyle="#000",a.fillRect(2,9,this.size[0]-4,2),a.strokeStyle="#333",a.beginPath(),a.moveTo(2,9),a.lineTo(this.size[0]-4,9),a.stroke(),a.strokeStyle="#AAA",a.beginPath(),a.moveTo(2,11),a.lineTo(this.size[0]-4,11),a.stroke(), a.drawImage(this.imgfg,2+(this.size[0]-4)*this.value-0.5*this.imgfg.width,0.5*-this.imgfg.height+10))};c.prototype.onDrawForeground=function(a){this.onDrawImage(a)};c.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=colorToString([this.value,this.value,this.value])};c.prototype.onMouseDown=function(a){if(0>a.canvasY-this.pos[1])return!1;this.oldmouse=[a.canvasX-this.pos[0], -a.canvasY-this.pos[1]];this.captureInput(!0);return!0};c.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b+(a[0]-this.oldmouse[0])/this.size[0];1b&&(b=0);this.value=b;this.oldmouse=a;this.setDirtyCanvas(!0)}};c.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};c.prototype.onMouseLeave=function(a){};c.prototype.onPropertyChange=function(a,b){if("wcolor"==a)this.properties[a]=b;else return!1;return!0}; +a.canvasY-this.pos[1]];this.captureInput(!0);return!0};c.prototype.onMouseMove=function(a){if(this.oldmouse){a=[a.canvasX-this.pos[0],a.canvasY-this.pos[1]];var b=this.value,b=b+(a[0]-this.oldmouse[0])/this.size[0];1b&&(b=0);this.value=b;this.oldmouse=a;this.setDirtyCanvas(!0)}};c.prototype.onMouseUp=function(a){this.oldmouse=null;this.captureInput(!1)};c.prototype.onMouseLeave=function(a){};c.prototype.onPropertyChanged=function(a,b){if("wcolor"==a)this.properties[a]=b;else return!1;return!0}; LiteGraph.registerNodeType("widget/hslider",c);d.title="Progress";d.desc="Shows data in linear progress";d.prototype.onExecute=function(){var a=this.getInputData(0);void 0!=a&&(this.properties.value=a)};d.prototype.onDrawForeground=function(a){a.lineWidth=1;a.fillStyle=this.properties.wcolor;var b=(this.properties.value-this.properties.min)/(this.properties.max-this.properties.min),b=Math.min(1,b),b=Math.max(0,b);a.fillRect(2,2,(this.size[0]-4)*b,this.size[1]-4)};LiteGraph.registerNodeType("widget/progress", d);e.title="Text";e.desc="Shows the input value";e.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}];e.prototype.onDrawForeground=function(a){a.fillStyle=this.properties.color;var b=this.properties.value;this.properties.glowSize?(a.shadowColor=this.properties.color,a.shadowOffsetX=0,a.shadowOffsetY=0,a.shadowBlur=this.properties.glowSize):a.shadowColor="transparent";var c=this.properties.fontsize; a.textAlign=this.properties.align;a.font=c.toString()+"px "+this.properties.font;this.str="number"==typeof b?b.toFixed(this.properties.decimals):b;if("string"==typeof this.str){var b=this.str.split("\\n"),d;for(d in b)a.fillText(b[d],"left"==this.properties.align?15:this.size[0]-15,-0.15*c+c*(parseInt(d)+1))}a.shadowColor="transparent";this.last_ctx=a;a.textAlign="left"};e.prototype.onExecute=function(){var a=this.getInputData(0);this.properties.value=null!=a?a:"";this.setDirtyCanvas(!0)};e.prototype.resize= function(){if(this.last_ctx){var a=this.str.split("\\n");this.last_ctx.font=this.properties.fontsize+"px "+this.properties.font;var b=0,c;for(c in a){var d=this.last_ctx.measureText(a[c]).width;bb&&(b=0);if(0!=a.length){var c=[0,0,0];if(0==b)c=a[0];else if(1==b)c=a[a.length- 1];else{var d=(a.length-1)*b,b=a[Math.floor(d)],a=a[Math.floor(d)+1],d=d-Math.floor(d);c[0]=b[0]*(1-d)+a[0]*d;c[1]=b[1]*(1-d)+a[1]*d;c[2]=b[2]*(1-d)+a[2]*d}for(var e in c)c[e]/=255;this.boxcolor=colorToString(c);this.setOutputData(0,c)}};LiteGraph.registerNodeType("color/palette",b);c.title="Frame";c.desc="Frame viewerew";c.widgets=[{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}];c.prototype.onDrawBackground=function(a){this.frame&&a.drawImage(this.frame, 0,0,this.size[0],this.size[1])};c.prototype.onExecute=function(){this.frame=this.getInputData(0);this.setDirtyCanvas(!0)};c.prototype.onWidget=function(a,b){if("resize"==b.name&&this.frame){var c=this.frame.width,d=this.frame.height;c||null==this.frame.videoWidth||(c=this.frame.videoWidth,d=this.frame.videoHeight);c&&d&&(this.size=[c,d]);this.setDirtyCanvas(!0,!0)}else"view"==b.name&&this.show()};c.prototype.show=function(){showElement&&this.frame&&showElement(this.frame)};LiteGraph.registerNodeType("graphics/frame", c);d.title="Image fade";d.desc="Fades between images";d.widgets=[{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}];d.prototype.onAdded=function(){this.createCanvas();var a=this.canvas.getContext("2d");a.fillStyle="#000";a.fillRect(0,0,this.properties.width,this.properties.height)};d.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};d.prototype.onExecute= function(){var a=this.canvas.getContext("2d");this.canvas.width=this.canvas.width;var b=this.getInputData(0);null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);b=this.getInputData(2);null==b?b=this.properties.fade:this.properties.fade=b;a.globalAlpha=b;b=this.getInputData(1);null!=b&&a.drawImage(b,0,0,this.canvas.width,this.canvas.height);a.globalAlpha=1;this.setOutputData(0,this.canvas);this.setDirtyCanvas(!0)};LiteGraph.registerNodeType("graphics/imagefade",d);e.title="Crop";e.desc= "Crop Image";e.prototype.onAdded=function(){this.createCanvas()};e.prototype.createCanvas=function(){this.canvas=document.createElement("canvas");this.canvas.width=this.properties.width;this.canvas.height=this.properties.height};e.prototype.onExecute=function(){var a=this.getInputData(0);a&&(a.width?(this.canvas.getContext("2d").drawImage(a,-this.properties.x,-this.properties.y,a.width*this.properties.scale,a.height*this.properties.scale),this.setOutputData(0,this.canvas)):this.setOutputData(0,null))}; -e.prototype.onPropertyChange=function(a,b){this.properties[a]=b;"scale"==a?(this.properties[a]=parseFloat(b),0==this.properties[a]&&(this.trace("Error in scale"),this.properties[a]=1)):this.properties[a]=parseInt(b);this.createCanvas();return!0};LiteGraph.registerNodeType("graphics/cropImage",d);f.title="Video";f.desc="Video playback";f.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", +e.prototype.onPropertyChanged=function(a,b){this.properties[a]=b;"scale"==a?(this.properties[a]=parseFloat(b),0==this.properties[a]&&(this.trace("Error in scale"),this.properties[a]=1)):this.properties[a]=parseInt(b);this.createCanvas();return!0};LiteGraph.registerNodeType("graphics/cropImage",d);f.title="Video";f.desc="Video playback";f.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"}];f.prototype.onExecute=function(){if(this.properties.url&&(this.properties.url!=this._video_url&&this.loadVideo(this.properties.url),this._video&&0!=this._video.width)){var a=this.getInputData(0);a&&0<=a&&1>=a&&(this._video.currentTime=a*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)}};f.prototype.onStart=function(){this.play()}; f.prototype.onStop=function(){this.stop()};f.prototype.loadVideo=function(a){this._video_url=a;this._video=document.createElement("video");this._video.src=a;this._video.type="type=video/mp4";this._video.muted=!0;this._video.autoplay=!0;var b=this;this._video.addEventListener("loadedmetadata",function(a){b.trace("Duration: "+this.duration+" seconds");b.trace("Size: "+this.videoWidth+","+this.videoHeight);b.setDirtyCanvas(!0);this.width=this.videoWidth;this.height=this.videoHeight});this._video.addEventListener("progress", function(a){});this._video.addEventListener("error",function(a){console.log("Error loading video: "+this.src);b.trace("Error loading video: "+this.src);if(this.error)switch(this.error.code){case this.error.MEDIA_ERR_ABORTED:b.trace("You stopped the video.");break;case this.error.MEDIA_ERR_NETWORK:b.trace("Network error - please try again later.");break;case this.error.MEDIA_ERR_DECODE:b.trace("Video is broken..");break;case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED:b.trace("Sorry, your browser can't play this video.")}}); -this._video.addEventListener("ended",function(a){b.trace("Ended.");this.play()})};f.prototype.onPropertyChange=function(a,b){this.properties[a]=b;"url"==a&&""!=b&&this.loadVideo(b);return!0};f.prototype.play=function(){this._video&&this._video.play()};f.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};f.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};f.prototype.pause=function(){this._video&&(this.trace("Video paused"), +this._video.addEventListener("ended",function(a){b.trace("Ended.");this.play()})};f.prototype.onPropertyChanged=function(a,b){this.properties[a]=b;"url"==a&&""!=b&&this.loadVideo(b);return!0};f.prototype.play=function(){this._video&&this._video.play()};f.prototype.playPause=function(){this._video&&(this._video.paused?this.play():this.pause())};f.prototype.stop=function(){this._video&&(this._video.pause(),this._video.currentTime=0)};f.prototype.pause=function(){this._video&&(this.trace("Video paused"), this._video.pause())};f.prototype.onWidget=function(a,b){};LiteGraph.registerNodeType("graphics/video",f);g.title="Webcam";g.desc="Webcam image";g.prototype.openStream=function(){function a(c){console.log("Webcam rejected",c);b._webcam_stream=!1;b.box_color="red"}navigator.getUserMedia=navigator.getUserMedia||navigator.webkitGetUserMedia||navigator.mozGetUserMedia||navigator.msGetUserMedia;window.URL=window.URL||window.webkitURL;if(navigator.getUserMedia){this._waiting_confirmation=!0;navigator.getUserMedia({video:!0}, this.streamReady.bind(this),a);var b=this}};g.prototype.onRemoved=function(){this._webcam_stream&&(this._webcam_stream.stop(),this._video=this._webcam_stream=null)};g.prototype.streamReady=function(a){this._webcam_stream=a;var b=this._video;b||(b=document.createElement("video"),b.autoplay=!0,b.src=window.URL.createObjectURL(a),this._video=b,b.onloadedmetadata=function(a){console.log(a)})};g.prototype.onExecute=function(){null!=this._webcam_stream||this._waiting_confirmation||this.openStream();this._video&& this._video.videoWidth&&(this._video.width=this._video.videoWidth,this._video.height=this._video.videoHeight,this.setOutputData(0,this._video))};g.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Frame":"Show Frame",callback:function(){b.properties.show=!b.properties.show}}]};g.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||!this.properties.show||!this._video||(a.save(),a.drawImage(this._video,0,0,this.size[0],this.size[1]), @@ -268,8 +271,8 @@ function(){this.addInput("Texture","Texture");this.addOutput("","Texture");this. LGraphTextureOperation.widgets_info={uvcode:{widget:"textarea",height:100},pixelcode:{widget:"textarea",height:100},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphTextureOperation.title="Operation";LGraphTextureOperation.desc="Texture shader operation";LGraphTextureOperation.prototype.getExtraMenuOptions=function(a){var b=this;return[{content:b.properties.show?"Hide Texture":"Show Texture",callback:function(){b.properties.show=!b.properties.show}}]};LGraphTextureOperation.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())};LGraphTextureOperation.prototype.onExecute=function(){var a=this.getInputData(0);if(this.isOutputConnected(0))if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else{var b=this.getInputData(1);if(this.properties.uvcode||this.properties.pixelcode){var c=512,d=512;a?(c=a.width,d=a.height):b&& (c=b.width,d=b.height);this._tex=a||this._tex?LGraphTexture.getTargetTexture(a||this._tex,this._tex,this.properties.precision):new GL.Texture(c,d,{type:this.precision===LGraphTexture.LOW?gl.UNSIGNED_BYTE:gl.HIGH_PRECISION_FORMAT,format:gl.RGBA,filter:gl.LINEAR});var e="";this.properties.uvcode&&(e="uv = "+this.properties.uvcode,-1!=this.properties.uvcode.indexOf(";")&&(e=this.properties.uvcode));var f="";this.properties.pixelcode&&(f="result = "+this.properties.pixelcode,-1!=this.properties.pixelcode.indexOf(";")&& -(f=this.properties.pixelcode));var g=this._shader;if(!g||this._shader_code!=e+"|"+f){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:e,PIXEL_CODE:f}),this.boxcolor="#00FF00"}catch(h){console.log("Error compiling shader: ",h);this.boxcolor="#FF0000";return}this._shader_code=e+"|"+f;g=this._shader}if(g){this.boxcolor="green";var k=this.getInputData(2);null!=k?this.properties.value=k:k=parseFloat(this.properties.value);var n=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 e=Mesh.getScreenQuad();g.uniforms({u_texture:0,u_textureB:1,value:k,texSize:[c,d],time:n}).draw(e)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform vec2 texSize;\n\t\t\tuniform float time;\n\t\t\tuniform float value;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tUV_CODE;\n\t\t\t\tvec3 color = texture2D(u_texture, uv).rgb;\n\t\t\t\tvec3 colorB = texture2D(u_textureB, uv).rgb;\n\t\t\t\tvec3 result = color;\n\t\t\t\tfloat alpha = 1.0;\n\t\t\t\tPIXEL_CODE;\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t\t}\n\t\t\t"; +(f=this.properties.pixelcode));var g=this._shader;if(!g||this._shader_code!=e+"|"+f){try{this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureOperation.pixel_shader,{UV_CODE:e,PIXEL_CODE:f}),this.boxcolor="#00FF00"}catch(h){console.log("Error compiling shader: ",h);this.boxcolor="#FF0000";return}this.boxcolor="#FF0000";this._shader_code=e+"|"+f;g=this._shader}if(g){this.boxcolor="green";var k=this.getInputData(2);null!=k?this.properties.value=k:k=parseFloat(this.properties.value);var n= +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 e=Mesh.getScreenQuad();g.uniforms({u_texture:0,u_textureB:1,value:k,texSize:[c,d],time:n}).draw(e)});this.setOutputData(0,this._tex)}else this.boxcolor="red"}}};LGraphTextureOperation.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_textureB;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform vec2 texSize;\n\t\t\tuniform float time;\n\t\t\tuniform float value;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 uv = v_coord;\n\t\t\t\tUV_CODE;\n\t\t\t\tvec4 color4 = texture2D(u_texture, uv);\n\t\t\t\tvec3 color = color4.rgb;\n\t\t\t\tvec4 color4B = texture2D(u_textureB, uv);\n\t\t\t\tvec3 colorB = color4B.rgb;\n\t\t\t\tvec3 result = color;\n\t\t\t\tfloat alpha = 1.0;\n\t\t\t\tPIXEL_CODE;\n\t\t\t\tgl_FragColor = vec4(result, alpha);\n\t\t\t}\n\t\t\t"; LiteGraph.registerNodeType("texture/operation",LGraphTextureOperation);var LGraphTextureShader=function(){this.addOutput("Texture","Texture");this.properties={code:"",width:512,height:512};this.properties.code="\nvoid main() {\n vec2 uv = coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"};LGraphTextureShader.title="Shader";LGraphTextureShader.desc="Texture shader";LGraphTextureShader.widgets_info={code:{type:"code"},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}}; LGraphTextureShader.prototype.onExecute=function(){if(this.isOutputConnected(0)){if(this._shader_code!=this.properties.code)if(this._shader_code=this.properties.code,this._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphTextureShader.pixel_shader+this.properties.code))this.boxcolor="green";else{this.boxcolor="red";return}this._tex&&this._tex.width==this.properties.width&&this._tex.height==this.properties.height||(this._tex=new GL.Texture(this.properties.width,this.properties.height,{format:gl.RGBA, filter:gl.LINEAR}));var a=this._tex,b=this._shader,c=this.graph.getTime();a.drawTo(function(){b.uniforms({texSize:[a.width,a.height],time:c}).draw(Mesh.getScreenQuad())});this.setOutputData(0,this._tex)}};LGraphTextureShader.pixel_shader="precision highp float;\n\t\t\t\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform float time;\n\t\t\t";LiteGraph.registerNodeType("texture/shader",LGraphTextureShader);var LGraphTextureScaleOffset=function(){this.addInput("in","Texture");this.addInput("scale","vec2");this.addInput("offset", @@ -318,12 +321,15 @@ LiteGraph.registerNodeType("texture/depth_range",LGraphTextureDepthRange);var LG "Blur a texture";LGraphTextureBlur.max_iterations=20;LGraphTextureBlur.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}),this._final_texture=new GL.Texture(a.width,a.height,{type:a.type,format:gl.RGBA,filter:gl.LINEAR}));b=this.properties.iterations;this.isInputConnected(1)&&(b= this.getInputData(1),this.properties.iterations=b);b=Math.min(Math.floor(b),LGraphTextureBlur.max_iterations);if(0==b)this.setOutputData(0,a);else{var c=this.properties.intensity;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.intensity=c);var d=LiteGraph.camera_aspect;d||void 0===window.gl||(d=gl.canvas.height/gl.canvas.width);d||(d=1);for(var d=this.properties.preserve_aspect?d:1,e=this.properties.scale||[1,1],f=0;f=this.size[1]||!this._video||(a.save(),a.webgl?this._temp_texture&&a.drawImage(this._temp_texture,0,0,this.size[0],this.size[1]):(a.translate(0,this.size[1]),a.scale(1,-1),a.drawImage(this._video,0,0,this.size[0],this.size[1])),a.restore())};LGraphTextureWebcam.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,c=this._temp_texture;c&&c.width==a&&c.height==b||(this._temp_texture=new GL.Texture(a,b,{format:gl.RGB,filter:gl.LINEAR}));this._temp_texture.uploadImage(this._video);this.properties.texture_name&&(LGraphTexture.getTexturesContainer()[this.properties.texture_name]=this._temp_texture);this.setOutputData(0,this._temp_texture)}};LiteGraph.registerNodeType("texture/webcam",LGraphTextureWebcam);var LGraphCubemap=function(){this.addOutput("Cubemap", -"Cubemap");this.properties={name:""};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphCubemap.prototype.onDropFile=function(a,b,c){a?(this._drop_texture="string"==typeof a?GL.Texture.fromURL(a):GL.Texture.fromDDSInMemory(a),this.properties.name=b):(this._drop_texture=null,this.properties.name="")};LGraphCubemap.prototype.onExecute=function(){if(this._drop_texture)this.setOutputData(0,this._drop_texture);else if(this.properties.name){var a=LGraphTexture.getTexture(this.properties.name); -a&&(this._last_tex=a,this.setOutputData(0,a))}};LGraphCubemap.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||a.webgl&&(gl.meshes.cube||(gl.meshes.cube=GL.Mesh.cube({size:1})))};LiteGraph.registerNodeType("texture/cubemap",LGraphCubemap)} +this._video.videoWidth){var a=this._video.videoWidth,b=this._video.videoHeight,c=this._temp_texture;c&&c.width==a&&c.height==b||(this._temp_texture=new GL.Texture(a,b,{format:gl.RGB,filter:gl.LINEAR}));this._temp_texture.uploadImage(this._video);this.properties.texture_name&&(LGraphTexture.getTexturesContainer()[this.properties.texture_name]=this._temp_texture);this.setOutputData(0,this._temp_texture)}};LiteGraph.registerNodeType("texture/webcam",LGraphTextureWebcam);var LGraphTextureMatte=function(){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._shader||(LGraphTextureMatte._shader=new GL.Shader(GL.Shader.SCREEN_VERTEX_SHADER,LGraphTextureMatte.pixel_shader))};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)){var a=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else if(a){this._tex=LGraphTexture.getTargetTexture(a,this._tex,this.properties.precision);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);this._uniforms||(this._uniforms={u_texture:0,u_key_color:this.properties.key_color,u_threshold:1,u_slope:1});var b=this._uniforms,c=Mesh.getScreenQuad(),d=LGraphTextureMatte._shader;b.u_key_color=this.properties.key_color; +b.u_threshold=this.properties.threshold;b.u_slope=this.properties.slope;this._tex.drawTo(function(){a.bind(0);d.uniforms(b).draw(c)});this.setOutputData(0,this._tex)}}};LGraphTextureMatte.pixel_shader="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform vec3 u_key_color;\n\t\t\tuniform float u_threshold;\n\t\t\tuniform float u_slope;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec3 color = texture2D( u_texture, v_coord ).xyz;\n\t\t\t\tfloat diff = length( normalize(color) - normalize(u_key_color) );\n\t\t\t\tfloat edge = u_threshold * (1.0 - u_slope);\n\t\t\t\tfloat alpha = smoothstep( edge, u_threshold, diff);\n\t\t\t\tgl_FragColor = vec4( color, alpha );\n\t\t\t}"; +LiteGraph.registerNodeType("texture/matte",LGraphTextureMatte);var LGraphCubemap=function(){this.addOutput("Cubemap","Cubemap");this.properties={name:""};this.size=[LGraphTexture.image_preview_size,LGraphTexture.image_preview_size]};LGraphCubemap.prototype.onDropFile=function(a,b,c){a?(this._drop_texture="string"==typeof a?GL.Texture.fromURL(a):GL.Texture.fromDDSInMemory(a),this.properties.name=b):(this._drop_texture=null,this.properties.name="")};LGraphCubemap.prototype.onExecute=function(){if(this._drop_texture)this.setOutputData(0, +this._drop_texture);else if(this.properties.name){var a=LGraphTexture.getTexture(this.properties.name);a&&(this._last_tex=a,this.setOutputData(0,a))}};LGraphCubemap.prototype.onDrawBackground=function(a){this.flags.collapsed||20>=this.size[1]||a.webgl&&(gl.meshes.cube||(gl.meshes.cube=GL.Mesh.cube({size:1})))};LiteGraph.registerNodeType("texture/cubemap",LGraphCubemap)} if("undefined"!=typeof LiteGraph){var LGraphFXLens=function(){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:LGraphTexture.DEFAULT};LGraphFXLens._shader||(LGraphFXLens._shader=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,LGraphFXLens.pixel_shader))};LGraphFXLens.title="Lens";LGraphFXLens.desc="Camera Lens distortion"; LGraphFXLens.widgets_info={precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphFXLens.prototype.onExecute=function(){var a=this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else if(a){this._tex=LGraphTexture.getTargetTexture(a,this._tex,this.properties.precision);var b=this.properties.aberration;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.aberration=b);var c=this.properties.distortion;this.isInputConnected(2)&& (c=this.getInputData(2),this.properties.distortion=c);var d=this.properties.blur;this.isInputConnected(3)&&(d=this.getInputData(3),this.properties.blur=d);gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var e=Mesh.getScreenQuad(),f=LGraphFXLens._shader;this._tex.drawTo(function(){a.bind(0);f.uniforms({u_texture:0,u_aberration:b,u_distortion:c,u_blur:d}).draw(e)});this.setOutputData(0,this._tex)}};LGraphFXLens.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"; @@ -337,8 +343,8 @@ a.height]}).draw(k,gl.POINTS)});this.setOutputData(0,this._temp_texture)}}else t LGraphFXBokeh._second_pixel_shader="precision highp float;\n\t\t\tvarying vec4 v_color;\n\t\t\tuniform sampler2D u_shape;\n\t\t\tuniform float u_alpha;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D( u_shape, gl_PointCoord );\n\t\t\t\tcolor *= v_color * u_alpha;\n\t\t\t\tgl_FragColor = color;\n\t\t\t}\n";LiteGraph.registerNodeType("fx/bokeh",LGraphFXBokeh);window.LGraphFXBokeh=LGraphFXBokeh;var LGraphFXGeneric=function(){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:LGraphTexture.DEFAULT}};LGraphFXGeneric.title="FX";LGraphFXGeneric.desc="applies an FX from a list";LGraphFXGeneric.widgets_info={fx:{widget:"combo",values:["halftone","pixelate","lowpalette","noise","gamma"]},precision:{widget:"combo",values:LGraphTexture.MODE_VALUES}};LGraphFXGeneric.shaders={};LGraphFXGeneric.prototype.onExecute=function(){if(this.isOutputConnected(0)){var a= this.getInputData(0);if(this.properties.precision===LGraphTexture.PASS_THROUGH)this.setOutputData(0,a);else if(a){this._tex=LGraphTexture.getTargetTexture(a,this._tex,this.properties.precision);var b=this.properties.value1;this.isInputConnected(1)&&(b=this.getInputData(1),this.properties.value1=b);var c=this.properties.value2;this.isInputConnected(2)&&(c=this.getInputData(2),this.properties.value2=c);var d=this.properties.fx,e=LGraphFXGeneric.shaders[d];if(!e){var f=LGraphFXGeneric["pixel_shader_"+ -d];if(!f)return;e=LGraphFXGeneric.shaders[d]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,f)}gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var g=Mesh.getScreenQuad(),h=null;"noise"==d&&(h=LGraphTexture.getNoiseTexture());this._tex.drawTo(function(){a.bind(0);"noise"==d&&h.bind(1);e.uniforms({u_texture:0,u_noise:1,u_size:[a.width,a.height],u_rand:[Math.random(),Math.random()],u_value1:b,u_value2:c,u_camera_planes:[LS.Renderer._current_camera.near,LS.Renderer._current_camera.far]}).draw(g)});this.setOutputData(0, -this._tex)}}};LGraphFXGeneric.pixel_shader_halftone="precision 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 vec2 u_size;\n\t\t\tuniform float u_value1;\n\t\t\tuniform float u_value2;\n\t\t\t\n\t\t\tfloat pattern() {\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\n\t\t\t\tvec2 point = vec2(\n\t\t\t\t c * tex.x - s * tex.y ,\n\t\t\t\t s * tex.x + c * tex.y \n\t\t\t\t) * u_value2;\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\n\t\t\t}\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\t\t\t}\n"; +d];if(!f)return;e=LGraphFXGeneric.shaders[d]=new GL.Shader(Shader.SCREEN_VERTEX_SHADER,f)}gl.disable(gl.BLEND);gl.disable(gl.DEPTH_TEST);var g=Mesh.getScreenQuad();camera_planes=window.LS&&LS.Renderer._current_camera?[LS.Renderer._current_camera.near,LS.Renderer._current_camera.far]:[1,100];var h=null;"noise"==d&&(h=LGraphTexture.getNoiseTexture());this._tex.drawTo(function(){a.bind(0);"noise"==d&&h.bind(1);e.uniforms({u_texture:0,u_noise:1,u_size:[a.width,a.height],u_rand:[Math.random(),Math.random()], +u_value1:b,u_value2:c,u_camera_planes:camera_planes}).draw(g)});this.setOutputData(0,this._tex)}}};LGraphFXGeneric.pixel_shader_halftone="precision 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 vec2 u_size;\n\t\t\tuniform float u_value1;\n\t\t\tuniform float u_value2;\n\t\t\t\n\t\t\tfloat pattern() {\n\t\t\t\tfloat s = sin(u_value1 * 3.1415), c = cos(u_value1 * 3.1415);\n\t\t\t\tvec2 tex = v_coord * u_size.xy;\n\t\t\t\tvec2 point = vec2(\n\t\t\t\t c * tex.x - s * tex.y ,\n\t\t\t\t s * tex.x + c * tex.y \n\t\t\t\t) * u_value2;\n\t\t\t\treturn (sin(point.x) * sin(point.y)) * 4.0;\n\t\t\t}\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\t\t\t\tfloat average = (color.r + color.g + color.b) / 3.0;\n\t\t\t\tgl_FragColor = vec4(vec3(average * 10.0 - 5.0 + pattern()), color.a);\n\t\t\t}\n"; LGraphFXGeneric.pixel_shader_pixelate="precision 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 vec2 u_size;\n\t\t\tuniform float u_value1;\n\t\t\tuniform float u_value2;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec2 coord = vec2( floor(v_coord.x * u_value1) / u_value1, floor(v_coord.y * u_value2) / u_value2 );\n\t\t\t\tvec4 color = texture2D(u_texture, coord);\n\t\t\t\tgl_FragColor = color;\n\t\t\t}\n";LGraphFXGeneric.pixel_shader_lowpalette= "precision 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 vec2 u_size;\n\t\t\tuniform float u_value1;\n\t\t\tuniform float u_value2;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\t\t\t\tgl_FragColor = floor(color * u_value1) / u_value1;\n\t\t\t}\n";LGraphFXGeneric.pixel_shader_noise="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform sampler2D u_noise;\n\t\t\tuniform vec2 u_size;\n\t\t\tuniform float u_value1;\n\t\t\tuniform float u_value2;\n\t\t\tuniform vec2 u_rand;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\t\t\t\tvec3 noise = texture2D(u_noise, v_coord * vec2(u_size.x / 512.0, u_size.y / 512.0) + u_rand).xyz - vec3(0.5);\n\t\t\t\tgl_FragColor = vec4( color.xyz + noise * u_value1, color.a );\n\t\t\t}\n"; LGraphFXGeneric.pixel_shader_gamma="precision highp float;\n\t\t\tvarying vec2 v_coord;\n\t\t\tuniform sampler2D u_texture;\n\t\t\tuniform float u_value1;\n\t\t\t\n\t\t\tvoid main() {\n\t\t\t\tvec4 color = texture2D(u_texture, v_coord);\n\t\t\t\tfloat gamma = 1.0 / u_value1;\n\t\t\t\tgl_FragColor = vec4( pow( color.xyz, vec3(gamma) ), color.a );\n\t\t\t}\n";LiteGraph.registerNodeType("fx/generic",LGraphFXGeneric);window.LGraphFXGeneric=LGraphFXGeneric;var LGraphFXVigneting=function(){this.addInput("Tex.", @@ -364,3 +370,21 @@ function(){return[["on_midi",LiteGraph.EVENT]]};LiteGraph.registerNodeType("midi g);h.title="MIDIEvent";h.desc="Create a MIDI Event";h.prototype.onAction=function(a,c){"assign"==a?(this.properties.channel=c.channel,this.properties.cmd=c.cmd,this.properties.value1=c.data[1],this.properties.value2=c.data[2]):(c=new b,c.channel=this.properties.channel,this.properties.cmd&&this.properties.cmd.constructor===String?c.setCommandFromString(this.properties.cmd):c.cmd=this.properties.cmd,c.data[0]=c.cmd|c.channel,c.data[1]=Number(this.properties.value1),c.data[2]=Number(this.properties.value2), this.trigger("on_midi",c))};h.prototype.onExecute=function(){var a=this.properties;if(this.outputs)for(var c=0;cc&&(c=0);var d=this.getInputNode(0),e=this.getOutputNodes(0);d&&d.audionode.disconnect(this.audionode);if(e)for(var f=0;f + diff --git a/doc/api.js b/doc/api.js index 18d6f246a..548229da2 100644 --- a/doc/api.js +++ b/doc/api.js @@ -7,6 +7,7 @@ YUI.add("yuidoc-meta", function(Y) { "LiteGraph" ], "modules": [], - "allModules": [] + "allModules": [], + "elements": [] } }; }); \ No newline at end of file diff --git a/doc/assets/css/logo.png b/doc/assets/css/logo.png index 609b336c7..c82444af9 100644 Binary files a/doc/assets/css/logo.png and b/doc/assets/css/logo.png differ diff --git a/doc/assets/css/main.css b/doc/assets/css/main.css index d745d4438..cdfe209e8 100644 --- a/doc/assets/css/main.css +++ b/doc/assets/css/main.css @@ -98,7 +98,7 @@ code, kbd, pre, samp { line-height: 1.35; } -p code, p kbd, p samp { +p code, p kbd, p samp, li code { background: #FCFBFA; border: 1px solid #EFEEED; padding: 0 3px; diff --git a/doc/assets/favicon.ico b/doc/assets/favicon.ico new file mode 100644 index 000000000..414ac4fb9 Binary files /dev/null and b/doc/assets/favicon.ico differ diff --git a/doc/assets/favicon.png b/doc/assets/favicon.png deleted file mode 100644 index 5a95ddab6..000000000 Binary files a/doc/assets/favicon.png and /dev/null differ diff --git a/doc/assets/js/api-filter.js b/doc/assets/js/api-filter.js index 37aefbab9..d442e5430 100644 --- a/doc/assets/js/api-filter.js +++ b/doc/assets/js/api-filter.js @@ -14,6 +14,10 @@ Y.APIFilter = Y.Base.create('apiFilter', Y.Base, [Y.AutoCompleteBase], { } }); + if (this.get('queryType') === 'elements') { + name = '<' + name + '>'; + } + return name; } @@ -24,7 +28,7 @@ Y.APIFilter = Y.Base.create('apiFilter', Y.Base, [Y.AutoCompleteBase], { value: 'phraseMatch' }, - // May be set to "classes" or "modules". + // May be set to "classes", "elements" or "modules". queryType: { value: 'classes' }, diff --git a/doc/assets/js/api-list.js b/doc/assets/js/api-list.js index 88905b52e..e8f650d5d 100644 --- a/doc/assets/js/api-list.js +++ b/doc/assets/js/api-list.js @@ -6,6 +6,7 @@ var Lang = Y.Lang, APIList = Y.namespace('APIList'), classesNode = Y.one('#api-classes'), + elementsNode = Y.one('#api-elements'), inputNode = Y.one('#api-filter'), modulesNode = Y.one('#api-modules'), tabviewNode = Y.one('#api-tabview'), @@ -97,7 +98,9 @@ tabview.get('panelNode').all('a').each(function (link) { // -- Private Functions -------------------------------------------------------- function getFilterResultNode() { - return filter.get('queryType') === 'classes' ? classesNode : modulesNode; + var queryType = filter.get('queryType'); + return queryType === 'classes' ? classesNode + : queryType === 'elements' ? elementsNode : modulesNode; } // -- Event Handlers ----------------------------------------------------------- @@ -105,7 +108,7 @@ function onFilterResults(e) { var frag = Y.one(Y.config.doc.createDocumentFragment()), resultNode = getFilterResultNode(), typePlural = filter.get('queryType'), - typeSingular = typePlural === 'classes' ? 'class' : 'module'; + typeSingular = typePlural === 'classes' ? 'class' : typePlural === 'elements' ? 'element' : 'module'; if (e.results.length) { YArray.each(e.results, function (result) { @@ -181,6 +184,7 @@ function onTabSelectionChange(e) { }; switch (name) { + case 'elements':// fallthru case 'classes': // fallthru case 'modules': filter.setAttrs({ diff --git a/doc/assets/js/apidocs.js b/doc/assets/js/apidocs.js index c64bb4632..af9ac3220 100644 --- a/doc/assets/js/apidocs.js +++ b/doc/assets/js/apidocs.js @@ -43,6 +43,12 @@ pjax = new Y.Pjax({ callbacks: defaultRoute }, + // -- /elements/* ------------------------------------------------------- + { + path : '/elements/:element.html*', + callbacks: defaultRoute + }, + // -- /classes/* ------------------------------------------------------- { path : '/classes/:class.html*', @@ -167,7 +173,7 @@ pjax.initLineNumbers = function () { }; pjax.initRoot = function () { - var terminators = /^(?:classes|files|modules)$/, + var terminators = /^(?:classes|files|elements|modules)$/, parts = pjax._getPathRoot().split('/'), root = [], i, len, part; @@ -284,7 +290,7 @@ pjax.handleClasses = function (req, res, next) { var status = res.ioResponse.status; // Handles success and local filesystem XHRs. - if (!status || (status >= 200 && status < 300)) { + if (res.ioResponse.readyState === 4 && (!status || (status >= 200 && status < 300))) { pjax.initClassTabView(); } @@ -295,7 +301,7 @@ pjax.handleFiles = function (req, res, next) { var status = res.ioResponse.status; // Handles success and local filesystem XHRs. - if (!status || (status >= 200 && status < 300)) { + if (res.ioResponse.readyState === 4 && (!status || (status >= 200 && status < 300))) { pjax.initLineNumbers(); } diff --git a/doc/classes/LGraph.html b/doc/classes/LGraph.html index 2471a5fe1..1aaed1369 100644 --- a/doc/classes/LGraph.html +++ b/doc/classes/LGraph.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,399 +25,254 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-

LGraph Class

+

LGraph Class

- - -
- Defined in: ../src/litegraph.js:234 + Defined in: ../src/litegraph.js:274
- - -
-

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.

-

Constructor

-
+

LGraph

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:234 - + ../src/litegraph.js:274

- -
- - - +
-
-

Item Index

-

Methods

- - - -
-

Methods

- -
+

add

-
(
    -
  • - node -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:596 - + ../src/litegraph.js:660

- -
@@ -427,88 +280,53 @@
-

Parameters:

    -
  • - node LGraphNode - -

    the instance of the node

    -
  • -
- - - +
- - -
+

attachCanvas

-
(
    -
  • - graph_canvas -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:303 - + ../src/litegraph.js:343

- -
@@ -516,77 +334,46 @@
-

Parameters:

    -
  • - graph_canvas GraphCanvas - -
    -
  • -
- - - +
- - -
+

clear

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:258 - + ../src/litegraph.js:298

- -
@@ -594,64 +381,36 @@
- - - +
- - -
+

configure

-
(
    -
  • - str -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1077 - + ../src/litegraph.js:1154

- -
@@ -659,88 +418,53 @@
-

Parameters:

    -
  • - str String - -

    configure a graph from a JSON string

    -
  • -
- - - +
- - -
+

detachCanvas

-
(
    -
  • - graph_canvas -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:322 - + ../src/litegraph.js:362

- -
@@ -748,91 +472,55 @@
-

Parameters:

    -
  • - graph_canvas GraphCanvas - -
    -
  • -
- - - +
- - -
+

findNodesByClass

-
(
    -
  • - classObject -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:723 - + ../src/litegraph.js:790

- -
@@ -840,106 +528,65 @@
-

Parameters:

    -
  • - classObject Class - -

    the class itself (not an string)

    -
  • -
- -

Returns:

- - Array: -

a list with all the nodes of this type

-
- - +
- - -
+

findNodesByName

-
(
    -
  • - name -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:756 - + ../src/litegraph.js:823

- -
@@ -947,106 +594,65 @@
-

Parameters:

    -
  • - name String - -

    the name of the node to search

    -
  • -
- -

Returns:

- - Array: -

a list with all the nodes with this name

-
- - +
- - -
+

findNodesByType

-
(
    -
  • - type -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:739 - + ../src/litegraph.js:806

- -
@@ -1054,96 +660,59 @@
-

Parameters:

    -
  • - type String - -

    the name of the node type

    -
  • -
- -

Returns:

- - Array: -

a list with all the nodes of this type

-
- - +
- - -
+

getElapsedTime

- () - - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:549 - + ../src/litegraph.js:605

- -
@@ -1152,72 +721,42 @@ if the nodes are using graphical actions

- -

Returns:

- - Number: -

number of milliseconds it took the last cycle

-
- - +
- - -
+

getFixedTime

- () - - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:538 - + ../src/litegraph.js:594

- -
@@ -1225,78 +764,45 @@ if the nodes are using graphical actions

- -

Returns:

- - Number: -

number of milliseconds the graph has been running

-
- - +
- - -
+

getNodeById

-
(
    -
  • - id -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:711 - + ../src/litegraph.js:778

- -
@@ -1304,103 +810,61 @@ if the nodes are using graphical actions

-

Parameters:

    -
  • - id String - -
    -
  • -
- - - +
- - -
+

getNodeOnPos

-
(
    -
  • - x -
  • -
  • - y -
  • -
  • - nodes_list -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:772 - + ../src/litegraph.js:839

- -
@@ -1408,128 +872,81 @@ if the nodes are using graphical actions

-

Parameters:

    -
  • - x Number - -

    the x coordinate in canvas space

    -
  • -
  • - y Number - -

    the y coordinate in canvas space

    -
  • -
  • - nodes_list Array - -

    a list with all the nodes to search from, by default is all the nodes in the graph

    -
  • -
- -

Returns:

- - Array: -

a list with all the nodes that intersect this coordinate

-
- - +
- - -
+

getTime

- () - - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:527 - + ../src/litegraph.js:583

- -
@@ -1537,68 +954,39 @@ if the nodes are using graphical actions

- -

Returns:

- - Number: -

number of milliseconds the graph has been running

-
- - +
- - -
+

isLive

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1011 - + ../src/litegraph.js:1081

- -
@@ -1606,64 +994,36 @@ if the nodes are using graphical actions

- - - +
- - -
+

remove

-
(
    -
  • - node -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:645 - + ../src/litegraph.js:709

- -
@@ -1671,88 +1031,53 @@ if the nodes are using graphical actions

-

Parameters:

    -
  • - node LGraphNode - -

    the instance of the node

    -
  • -
- - - +
- - -
+

runStep

-
(
    -
  • - num -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:385 - + ../src/litegraph.js:429

- -
@@ -1760,94 +1085,56 @@ if the nodes are using graphical actions

-

Parameters:

    -
  • - num Number - -

    number of steps to run, default is 1

    -
  • -
- - - +
- - -
+

sendEventToAllNodes

-
(
    -
  • - eventname -
  • -
  • - params -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:561 - + ../src/litegraph.js:617

- -
@@ -1855,98 +1142,61 @@ if the nodes are using graphical actions

-

Parameters:

    -
  • - eventname String - -

    the name of the event (function to be called)

    -
  • -
  • - params Array - -

    parameters in array format

    -
  • -
- - - +
- - -
+

serialize

- () - - Object - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1044 - + ../src/litegraph.js:1118

- -
@@ -1954,84 +1204,48 @@ if the nodes are using graphical actions

- -

Returns:

- - Object: -

value of the node

-
- - +
- - -
+

setInputData

-
(
    -
  • - name -
  • -
  • - value -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:959 - + ../src/litegraph.js:1026

- -
@@ -2040,108 +1254,67 @@ can be easily accesed from the outside of the graph

-

Parameters:

    -
  • - name String - -

    the name of the node

    -
  • -
  • - value - -

    value to assign to this node

    -
  • -
- - - +
- - -
+

setInputData

-
(
    -
  • - name -
  • -
)
- - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:974 - + ../src/litegraph.js:1041

- -
@@ -2149,102 +1322,62 @@ can be easily accesed from the outside of the graph

-

Parameters:

    -
  • - name String - -

    the name of the node

    -
  • -
- -

Returns:

- - : -

value of the node

-
- - +
- - -
+

start

-
(
    -
  • - interval -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:336 - + ../src/litegraph.js:380

- -
@@ -2252,78 +1385,47 @@ can be easily accesed from the outside of the graph

-

Parameters:

    -
  • - interval Number - -

    amount of milliseconds between executions, default is 1

    -
  • -
- - - +
- - -
+

stop execution

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:363 - + ../src/litegraph.js:407

- -
@@ -2331,54 +1433,30 @@ can be easily accesed from the outside of the graph

- - - +
- - -
+

updateExecutionOrder

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:429 - + ../src/litegraph.js:485

- -
@@ -2387,25 +1465,16 @@ nodes with only inputs.

- - - +
- -
- - - -
-
diff --git a/doc/classes/LGraphCanvas.html b/doc/classes/LGraphCanvas.html index 0b5bc9851..d88e8a1e9 100644 --- a/doc/classes/LGraphCanvas.html +++ b/doc/classes/LGraphCanvas.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,340 +25,239 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-

LGraphCanvas Class

+

LGraphCanvas Class

- - - - - -
-

marks as dirty the canvas, this way it will be rendered again

-

Constructor

-
+

LGraphCanvas

-
(
    -
  • - canvas -
  • -
  • - graph -
  • - +
  • + options +
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2551 - + ../src/litegraph.js:2819

- -
-

Parameters:

    -
  • - canvas HTMLCanvas - -

    the canvas where you want to render (it accepts a selector in string format or the canvas itself)

    -
  • -
  • - graph LGraph - -

    [optional]

    -
  • - +
  • + options + Object + + +
    +

    [optional] { skip_rendering, autoresize }

    + +
    + +
- - - +
-
-

Item Index

-

Methods

- - - -
-

Methods

- -
+

clear

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2192 - + ../src/litegraph.js:2481

- -
@@ -368,64 +265,36 @@
- - - +
- - -
+

closeSubgraph

-
(
    -
  • - assigns -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2306 - + ../src/litegraph.js:2574

- -
@@ -433,82 +302,50 @@
-

Parameters:

    -
  • - assigns LGraph - -

    a graph

    -
  • -
- - - +
- - -
+

getCanvasWindow

- () - - Window - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2567 - + ../src/litegraph.js:2835

- -
@@ -516,78 +353,45 @@
- -

Returns:

- - Window: -

returns the window where the canvas is attached (the DOM root node)

-
- - +
- - -
+

openSubgraph

-
(
    -
  • - graph -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2279 - + ../src/litegraph.js:2547

- -
@@ -595,87 +399,52 @@
-

Parameters:

- - - +
- - -
+

setCanvas

-
(
    -
  • - assigns -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2321 - + ../src/litegraph.js:2589

- -
@@ -683,88 +452,53 @@
-

Parameters:

    -
  • - assigns Canvas - -

    a canvas

    -
  • -
- - - +
- - -
+

setGraph

-
(
    -
  • - graph -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2248 - + ../src/litegraph.js:2516

- -
@@ -772,77 +506,46 @@
-

Parameters:

- - - +
- - -
+

startRendering

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2579 - + ../src/litegraph.js:2849

- -
@@ -850,54 +553,30 @@
- - - +
- - -
+

stopRendering

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2602 - + ../src/litegraph.js:2872

- -
@@ -905,25 +584,16 @@
- - - +
- -
- - - -
-
diff --git a/doc/classes/LGraphNode.html b/doc/classes/LGraphNode.html index f11cc8f66..70e0aec49 100644 --- a/doc/classes/LGraphNode.html +++ b/doc/classes/LGraphNode.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,409 +25,270 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-

LGraphNode Class

+

LGraphNode Class

- - - - - -
-

Base Class for all the node type classes

-

Item Index

- - - - -
-

Methods

- -
+

addConnection

-
(
    -
  • - name -
  • -
  • - type -
  • -
  • - pos -
  • -
  • - direction -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1600 - + ../src/litegraph.js:1811

- -
@@ -437,147 +296,91 @@
-

Parameters:

    -
  • - name String - -
    -
  • -
  • - type String - -

    string defining the input type ("vec3","number",...)

    -
  • -
  • - pos x,y - -

    position of the connection inside the node

    -
  • -
  • - direction String - -

    if is input or output

    -
  • -
- - - +
- - -
+

addInput

-
(
    -
  • - name -
  • -
  • - type -
  • -
  • - extra_info -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1538 - + ../src/litegraph.js:1748

- -
@@ -585,119 +388,74 @@
-

Parameters:

    -
  • - name String - -
    -
  • -
  • - type String - -

    string defining the input type ("vec3","number",...), it its a generic one use 0

    -
  • -
  • - extra_info Object - -

    this can be used to have special properties of an input (label, color, position, etc)

    -
  • -
- - - +
- - -
+

addInputs

-
(
    -
  • - array -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1561 - + ../src/litegraph.js:1772

- -
@@ -705,100 +463,59 @@
-

Parameters:

    -
  • - array Array - -

    of triplets like [[name,type,extra_info],[...]]

    -
  • -
- - - +
- - -
+

addOutput

-
(
    -
  • - name -
  • -
  • - type -
  • -
  • - extra_info -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1478 - + ../src/litegraph.js:1686

- -
@@ -806,119 +523,74 @@
-

Parameters:

    -
  • - name String - -
    -
  • -
  • - type String - -

    string defining the output type ("vec3","number",...)

    -
  • -
  • - extra_info Object - -

    this can be used to have special properties of an output (label, special color, position, etc)

    -
  • -
- - - +
- - -
+

addOutputs

-
(
    -
  • - array -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1499 - + ../src/litegraph.js:1709

- -
@@ -926,78 +598,141 @@
-

Parameters:

    -
  • - array Array - -

    of triplets like [[name,type,extra_info],[...]]

    -
  • -
- - - +
+
+

addProperty

- -
-

collapse

+
+ (
    +
  • + name +
  • +
  • + default_value +
  • +
  • + type +
  • +
  • + extra_info +
  • +
) +
- - () - - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2121 - + ../src/litegraph.js:1660 +

+ + + +
+ +
+

add a new property to this node

+ +
+ +
+

Parameters:

+ +
    +
  • + name + String + + +
    + +
    + +
  • +
  • + default_value + + + +
    + +
    + +
  • +
  • + type + String + + +
    +

    string defining the output type ("vec3","number",...)

    + +
    + +
  • +
  • + extra_info + Object + + +
    +

    this can be used to have special properties of the property (like values, etc)

    + +
    + +
  • +
+
+ + + +
+
+

collapse

+ + () + + + + + + + + +
+

+ Defined in + ../src/litegraph.js:2380

- -
@@ -1005,68 +740,39 @@
- - - +
- - -
+

computeSize

-
(
    -
  • - minHeight -
  • -
)
- - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1613 - + ../src/litegraph.js:1832

- -
@@ -1074,91 +780,55 @@
-

Parameters:

    -
  • - minHeight Number - -
    -
  • -
- -

Returns:

- - Number: -

the total size

-
- - +
- - -
+

configure

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1217 - + ../src/litegraph.js:1297

- -
@@ -1166,80 +836,45 @@
- - - +
- - -
+

connect

-
(
    -
  • - slot -
  • -
  • - node -
  • -
  • - target_slot -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1759 - + ../src/litegraph.js:1978

- -
@@ -1247,138 +882,87 @@
-

Parameters:

    -
  • - slot Number_or_string - -

    (could be the number of the slot or the string with the name of the slot)

    -
  • -
  • - node LGraphNode - -

    the target node

    -
  • -
  • - target_slot Number_or_string - -
    -

    the input slot of the target node (could be the number of the slot or the string with the name of the 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)

    -
  • -
- -

Returns:

- - Boolean: -

if it was connected succesfully

-
- - +
- - -
+

disconnectInput

-
(
    -
  • - slot -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1927 - + ../src/litegraph.js:2178

- -
@@ -1386,112 +970,68 @@
-

Parameters:

    -
  • - slot Number_or_string - -

    (could be the number of the slot or the string with the name of the slot)

    -
  • -
- -

Returns:

- - Boolean: -

if it was disconnected succesfully

-
- - +
- - -
+

disconnectOutput

-
(
    -
  • - slot -
  • -
  • - target_node -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1855 - + ../src/litegraph.js:2095

- -
@@ -1499,122 +1039,76 @@
-

Parameters:

    -
  • - slot Number_or_string - -

    (could be the number of the slot or the string with the name of the slot)

    -
  • -
  • - target_node LGraphNode - -

    the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]

    -
  • -
- -

Returns:

- - Boolean: -

if it was disconnected succesfully

-
- - +
- - -
+

findInputSlot

-
(
    -
  • - name -
  • -
)
- - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1729 - + ../src/litegraph.js:1948

- -
@@ -1622,106 +1116,65 @@
-

Parameters:

    -
  • - name String - -

    the name of the slot

    -
  • -
- -

Returns:

- - Number: -

the slot (-1 if not found)

-
- - +
- - -
+

findOutputSlot

-
(
    -
  • - name -
  • -
)
- - Number - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1744 - + ../src/litegraph.js:1963

- -
@@ -1729,96 +1182,59 @@
-

Parameters:

    -
  • - name String - -

    the name of the slot

    -
  • -
- -

Returns:

- - Number: -

the slot (-1 if not found)

-
- - +
- - -
+

getBounding

- () - - Float32Array4 - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1664 - + ../src/litegraph.js:1883

- -
@@ -1826,88 +1242,51 @@
- -

Returns:

- - Float32Array4: -

the total size

-
- - +
- - -
+

getConnectionPos

-
(
    -
  • - is_input -
  • -
  • - slot -
  • -
)
- - x,y - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1989 - + ../src/litegraph.js:2246

- -
@@ -1915,122 +1294,76 @@
-

Parameters:

    -
  • - is_input Boolean - -

    true if if a input slot, false if it is an output

    -
  • -
  • - slot Number_or_string - -

    (could be the number of the slot or the string with the name of the slot)

    -
  • -
- -

Returns:

- - x,y: -

the position

-
- - +
- - -
+

getInputData

-
(
    -
  • - slot -
  • -
)
- - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1376 - + ../src/litegraph.js:1482

- -
@@ -2038,105 +1371,64 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - : -

data or if it is not connected returns undefined

-
- - +
- - -
+

getInputInfo

-
(
    -
  • - slot -
  • -
)
- - Object - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1404 - + ../src/litegraph.js:1527

- -
@@ -2144,105 +1436,129 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - Object: -

object or null

-
- - +
+
+

getInputNode

- -
-

getOutputInfo

- -
(
    -
  • - slot -
  • -
)
- - - Object + LGraphNode - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1420 - + ../src/litegraph.js:1542 +

+ + + +
+ +
+

returns the node connected in the input slot

+ +
+ +
+

Parameters:

+ +
    +
  • + slot + Number + + +
    + +
    + +
  • +
+
+ +
+

Returns:

+ +
+ LGraphNode: +

node or null

+ +
+
+ + +
+
+

getOutputInfo

+ +
+ (
    +
  • + slot +
  • +
) +
+ + + Object + + + + + + + + +
+

+ Defined in + ../src/litegraph.js:1561

- -
@@ -2250,105 +1566,64 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - Object: -

object or null

-
- - +
- - -
+

getOutputNodes

-
(
    -
  • - slot -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1449 - + ../src/litegraph.js:1590

- -
@@ -2356,109 +1631,65 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - Array: - -
- - +
- - -
+

getSlotInPosition

-
(
    -
  • - x -
  • -
  • - y -
  • -
)
- - Object - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1698 - + ../src/litegraph.js:1917

- -
@@ -2466,106 +1697,65 @@
-

Parameters:

    -
  • - x Number - -
    -
  • -
  • - y Number - -
    -
  • -
- -

Returns:

- - Object: -

if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }

-
- - +
- - -
+

getTitle

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1342 - + ../src/litegraph.js:1448

- -
@@ -2573,68 +1763,39 @@
- - - +
- - -
+

isInputConnected

-
(
    -
  • - slot -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1391 - + ../src/litegraph.js:1514

- -
@@ -2642,103 +1803,62 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - Boolean: - -
- - +
- - -
+

isOutputConnected

-
(
    -
  • - slot -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1436 - + ../src/litegraph.js:1577

- -
@@ -2746,109 +1866,65 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- -

Returns:

- - Boolean: - -
- - +
- - -
+

isPointInsideNode

-
(
    -
  • - x -
  • -
  • - y -
  • -
)
- - Boolean - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1674 - + ../src/litegraph.js:1893

- -
@@ -2856,104 +1932,63 @@
-

Parameters:

    -
  • - x Number - -
    -
  • -
  • - y Number - -
    -
  • -
- -

Returns:

- - Boolean: - -
- - +
- - -
+

pin

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:2134 - + ../src/litegraph.js:2393

- -
@@ -2961,64 +1996,36 @@
- - - +
- - -
+

removeInput

-
(
    -
  • - slot -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1586 - + ../src/litegraph.js:1797

- -
@@ -3026,87 +2033,52 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- - - +
- - -
+

removeOutput

-
(
    -
  • - slot -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1524 - + ../src/litegraph.js:1734

- -
@@ -3114,77 +2086,46 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
- - - +
- - -
+

serialize

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1276 - + ../src/litegraph.js:1366

- -
@@ -3192,70 +2133,39 @@
- - - +
- - -
+

setOutputData

-
(
    -
  • - slot -
  • -
  • - data -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1355 - + ../src/litegraph.js:1461

- -
@@ -3263,92 +2173,56 @@
-

Parameters:

    -
  • - slot Number - -
    -
  • -
  • - data - -
    -
  • -
- - - +
- - -
+

toString

- () - - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:1330 - + ../src/litegraph.js:1436

- -
@@ -3356,25 +2230,83 @@
- - - +
+
+

trigger

- +
+ (
    +
  • + event +
  • +
  • + param +
  • +
) +
+ + + + + + + + +
+

+ Defined in + ../src/litegraph.js:1610 +

+ + + +
+ +
+

Triggers an event in this node, this will trigger any output with the same name

+ +
+ +
+

Parameters:

+ +
    +
  • + event + String + + +
    +

    name ( "on_play", ... ) if action is equivalent to false then the event is send to all

    + +
    + +
  • +
  • + param + + + +
    + +
    + +
  • +
+
+ + + +
- - - -
-
diff --git a/doc/classes/LiteGraph.html b/doc/classes/LiteGraph.html index 23ec6c4fd..19745a275 100644 --- a/doc/classes/LiteGraph.html +++ b/doc/classes/LiteGraph.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,285 +25,246 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-

LiteGraph Class

+

LiteGraph Class

- - -
Defined in: ../src/litegraph.js:6
- - -
-

The Global Scope. It contains all the registered node classes.

-

Constructor

-
+

LiteGraph

- () - - - - - - - -
- - -

- Defined in - - - - ../src/litegraph.js:6 -

- -
- - - +
-
-

Item Index

- - - - -
-

Methods

- -
-

createNode

+
+

addNodeMethod

-
(
    -
  • - - type - + func
  • - -
  • - - name - -
  • - -
  • - - options - -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:71 - + ../src/litegraph.js:86 +

+ + + +
+ +
+

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)

+ +
+ +
+

Parameters:

+ +
    +
  • + func + Function + + +
    + +
    + +
  • +
+
+ + + +
+
+

createNode

+ +
+ (
    +
  • + type +
  • +
  • + name +
  • +
  • + options +
  • +
) +
+ + + + + + + + +
+

+ Defined in + ../src/litegraph.js:99

- -
@@ -313,124 +272,78 @@
-

Parameters:

    -
  • - type String - -

    full name of the node class. p.e. "math/sin"

    -
  • -
  • - name String - -

    a name to distinguish from other nodes

    -
  • -
  • - options Object - -

    to set options

    -
  • -
- - - +
- - -
+

getNodeType

-
(
    -
  • - type -
  • -
)
- - Class - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:112 - + ../src/litegraph.js:142

- -
@@ -438,106 +351,65 @@
-

Parameters:

    -
  • - type String - -

    full name of the node class. p.e. "math/sin"

    -
  • -
- -

Returns:

- - Class: -

the node class

-
- - +
- - -
+

getNodeType

-
(
    -
  • - category -
  • -
)
- - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:125 - + ../src/litegraph.js:155

- -
@@ -545,96 +417,59 @@
-

Parameters:

    -
  • - category String - -

    category name

    -
  • -
- -

Returns:

- - Array: -

array with all the node classes

-
- - +
- - -
+

getNodeTypesCategories

- () - - Array - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:147 - + ../src/litegraph.js:177

- -
@@ -642,84 +477,48 @@
- -

Returns:

- - Array: -

array with all the names of the categories

-
- - +
- - -
+

registerNodeType

-
(
    -
  • - type -
  • -
  • - base_class -
  • -
)
- - - - - - - -
- - -

- Defined in - - - - - ../src/litegraph.js:38 - + ../src/litegraph.js:49

- -
@@ -727,65 +526,44 @@
-

Parameters:

    -
  • - type String - -

    name of the node and path

    -
  • -
  • - base_class Class - -

    class containing the structure of a node

    -
  • -
- - - +
- -
- - - -
-
diff --git a/doc/data.json b/doc/data.json index 3f421e7dd..43458c66e 100644 --- a/doc/data.json +++ b/doc/data.json @@ -38,7 +38,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 234, + "line": 274, "description": "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.", "is_constructor": 1 }, @@ -51,7 +51,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 1166, + "line": 1244, "description": "Base Class for all the node type classes", "params": [ { @@ -70,7 +70,7 @@ "plugin_for": [], "extension_for": [], "file": "../src/litegraph.js", - "line": 2551, + "line": 2819, "description": "marks as dirty the canvas, this way it will be rendered again", "is_constructor": 1, "params": [ @@ -83,15 +83,21 @@ "name": "graph", "description": "[optional]", "type": "LGraph" + }, + { + "name": "options", + "description": "[optional] { skip_rendering, autoresize }", + "type": "Object" } ], "itemtype": "method" } }, + "elements": {}, "classitems": [ { "file": "../src/litegraph.js", - "line": 38, + "line": 49, "description": "Register a node class so it can be listed when the user wants to create a new one", "itemtype": "method", "name": "registerNodeType", @@ -111,7 +117,22 @@ }, { "file": "../src/litegraph.js", - "line": 71, + "line": 86, + "description": "Adds this method to all nodetypes, existing and to be created\n(You can add it to LGraphNode.prototype but then existing node types wont have it)", + "itemtype": "method", + "name": "addNodeMethod", + "params": [ + { + "name": "func", + "description": "", + "type": "Function" + } + ], + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 99, "description": "Create a node of a given type with a name. The node is not attached to any graph yet.", "itemtype": "method", "name": "createNode", @@ -136,7 +157,7 @@ }, { "file": "../src/litegraph.js", - "line": 112, + "line": 142, "description": "Returns a registered node type with a given name", "itemtype": "method", "name": "getNodeType", @@ -155,7 +176,7 @@ }, { "file": "../src/litegraph.js", - "line": 125, + "line": 155, "description": "Returns a list of node types matching one category", "itemtype": "method", "name": "getNodeType", @@ -174,7 +195,7 @@ }, { "file": "../src/litegraph.js", - "line": 147, + "line": 177, "description": "Returns a list with all the node type categories", "itemtype": "method", "name": "getNodeTypesCategories", @@ -186,7 +207,7 @@ }, { "file": "../src/litegraph.js", - "line": 258, + "line": 298, "description": "Removes all nodes from this graph", "itemtype": "method", "name": "clear", @@ -194,7 +215,7 @@ }, { "file": "../src/litegraph.js", - "line": 303, + "line": 343, "description": "Attach Canvas to this graph", "itemtype": "method", "name": "attachCanvas", @@ -209,7 +230,7 @@ }, { "file": "../src/litegraph.js", - "line": 322, + "line": 362, "description": "Detach Canvas from this graph", "itemtype": "method", "name": "detachCanvas", @@ -224,7 +245,7 @@ }, { "file": "../src/litegraph.js", - "line": 336, + "line": 380, "description": "Starts running this graph every interval milliseconds.", "itemtype": "method", "name": "start", @@ -239,7 +260,7 @@ }, { "file": "../src/litegraph.js", - "line": 363, + "line": 407, "description": "Stops the execution loop of the graph", "itemtype": "method", "name": "stop execution", @@ -247,7 +268,7 @@ }, { "file": "../src/litegraph.js", - "line": 385, + "line": 429, "description": "Run N steps (cycles) of the graph", "itemtype": "method", "name": "runStep", @@ -262,7 +283,7 @@ }, { "file": "../src/litegraph.js", - "line": 429, + "line": 485, "description": "Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than\nnodes with only inputs.", "itemtype": "method", "name": "updateExecutionOrder", @@ -270,7 +291,7 @@ }, { "file": "../src/litegraph.js", - "line": 527, + "line": 583, "description": "Returns the amount of time the graph has been running in milliseconds", "itemtype": "method", "name": "getTime", @@ -282,7 +303,7 @@ }, { "file": "../src/litegraph.js", - "line": 538, + "line": 594, "description": "Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant", "itemtype": "method", "name": "getFixedTime", @@ -294,7 +315,7 @@ }, { "file": "../src/litegraph.js", - "line": 549, + "line": 605, "description": "Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct\nif the nodes are using graphical actions", "itemtype": "method", "name": "getElapsedTime", @@ -306,7 +327,7 @@ }, { "file": "../src/litegraph.js", - "line": 561, + "line": 617, "description": "Sends an event to all the nodes, useful to trigger stuff", "itemtype": "method", "name": "sendEventToAllNodes", @@ -326,7 +347,7 @@ }, { "file": "../src/litegraph.js", - "line": 596, + "line": 660, "description": "Adds a new node instasnce to this graph", "itemtype": "method", "name": "add", @@ -341,7 +362,7 @@ }, { "file": "../src/litegraph.js", - "line": 645, + "line": 709, "description": "Removes a node from the graph", "itemtype": "method", "name": "remove", @@ -356,7 +377,7 @@ }, { "file": "../src/litegraph.js", - "line": 711, + "line": 778, "description": "Returns a node by its id.", "itemtype": "method", "name": "getNodeById", @@ -371,7 +392,7 @@ }, { "file": "../src/litegraph.js", - "line": 723, + "line": 790, "description": "Returns a list of nodes that matches a class", "itemtype": "method", "name": "findNodesByClass", @@ -390,7 +411,7 @@ }, { "file": "../src/litegraph.js", - "line": 739, + "line": 806, "description": "Returns a list of nodes that matches a type", "itemtype": "method", "name": "findNodesByType", @@ -409,7 +430,7 @@ }, { "file": "../src/litegraph.js", - "line": 756, + "line": 823, "description": "Returns a list of nodes that matches a name", "itemtype": "method", "name": "findNodesByName", @@ -428,7 +449,7 @@ }, { "file": "../src/litegraph.js", - "line": 772, + "line": 839, "description": "Returns the top-most node in this position of the canvas", "itemtype": "method", "name": "getNodeOnPos", @@ -457,7 +478,7 @@ }, { "file": "../src/litegraph.js", - "line": 959, + "line": 1026, "description": "Assigns a value to all the nodes that matches this name. This is used to create global variables of the node that\ncan be easily accesed from the outside of the graph", "itemtype": "method", "name": "setInputData", @@ -477,7 +498,7 @@ }, { "file": "../src/litegraph.js", - "line": 974, + "line": 1041, "description": "Returns the value of the first node with this name. This is used to access global variables of the graph from the outside", "itemtype": "method", "name": "setInputData", @@ -496,7 +517,7 @@ }, { "file": "../src/litegraph.js", - "line": 1011, + "line": 1081, "description": "returns if the graph is in live mode", "itemtype": "method", "name": "isLive", @@ -504,7 +525,7 @@ }, { "file": "../src/litegraph.js", - "line": 1044, + "line": 1118, "description": "Creates a Object containing all the info about this graph, it can be serialized", "itemtype": "method", "name": "serialize", @@ -516,7 +537,7 @@ }, { "file": "../src/litegraph.js", - "line": 1077, + "line": 1154, "description": "Configure a graph from a JSON string", "itemtype": "method", "name": "configure", @@ -531,7 +552,7 @@ }, { "file": "../src/litegraph.js", - "line": 1217, + "line": 1297, "description": "configure a node from an object containing the serialized info", "itemtype": "method", "name": "configure", @@ -539,7 +560,7 @@ }, { "file": "../src/litegraph.js", - "line": 1276, + "line": 1366, "description": "serialize the content", "itemtype": "method", "name": "serialize", @@ -547,7 +568,7 @@ }, { "file": "../src/litegraph.js", - "line": 1330, + "line": 1436, "description": "serialize and stringify", "itemtype": "method", "name": "toString", @@ -555,7 +576,7 @@ }, { "file": "../src/litegraph.js", - "line": 1342, + "line": 1448, "description": "get the title string", "itemtype": "method", "name": "getTitle", @@ -563,7 +584,7 @@ }, { "file": "../src/litegraph.js", - "line": 1355, + "line": 1461, "description": "sets the output data", "itemtype": "method", "name": "setOutputData", @@ -583,7 +604,7 @@ }, { "file": "../src/litegraph.js", - "line": 1376, + "line": 1482, "description": "retrieves the input data (data traveling through the connection) from one slot", "itemtype": "method", "name": "getInputData", @@ -602,7 +623,7 @@ }, { "file": "../src/litegraph.js", - "line": 1391, + "line": 1514, "description": "tells you if there is a connection in one input slot", "itemtype": "method", "name": "isInputConnected", @@ -621,7 +642,7 @@ }, { "file": "../src/litegraph.js", - "line": 1404, + "line": 1527, "description": "tells you info about an input connection (which node, type, etc)", "itemtype": "method", "name": "getInputInfo", @@ -640,7 +661,26 @@ }, { "file": "../src/litegraph.js", - "line": 1420, + "line": 1542, + "description": "returns the node connected in the input slot", + "itemtype": "method", + "name": "getInputNode", + "params": [ + { + "name": "slot", + "description": "", + "type": "Number" + } + ], + "return": { + "description": "node or null", + "type": "LGraphNode" + }, + "class": "LGraphNode" + }, + { + "file": "../src/litegraph.js", + "line": 1561, "description": "tells you info about an output connection (which node, type, etc)", "itemtype": "method", "name": "getOutputInfo", @@ -659,7 +699,7 @@ }, { "file": "../src/litegraph.js", - "line": 1436, + "line": 1577, "description": "tells you if there is a connection in one output slot", "itemtype": "method", "name": "isOutputConnected", @@ -678,7 +718,7 @@ }, { "file": "../src/litegraph.js", - "line": 1449, + "line": 1590, "description": "retrieves all the nodes connected to this output slot", "itemtype": "method", "name": "getOutputNodes", @@ -697,7 +737,57 @@ }, { "file": "../src/litegraph.js", - "line": 1478, + "line": 1610, + "description": "Triggers an event in this node, this will trigger any output with the same name", + "itemtype": "method", + "name": "trigger", + "params": [ + { + "name": "event", + "description": "name ( \"on_play\", ... ) if action is equivalent to false then the event is send to all", + "type": "String" + }, + { + "name": "param", + "description": "", + "type": "*" + } + ], + "class": "LGraphNode" + }, + { + "file": "../src/litegraph.js", + "line": 1660, + "description": "add a new property to this node", + "itemtype": "method", + "name": "addProperty", + "params": [ + { + "name": "name", + "description": "", + "type": "String" + }, + { + "name": "default_value", + "description": "", + "type": "*" + }, + { + "name": "type", + "description": "string defining the output type (\"vec3\",\"number\",...)", + "type": "String" + }, + { + "name": "extra_info", + "description": "this can be used to have special properties of the property (like values, etc)", + "type": "Object" + } + ], + "class": "LGraphNode" + }, + { + "file": "../src/litegraph.js", + "line": 1686, "description": "add a new output slot to use in this node", "itemtype": "method", "name": "addOutput", @@ -722,7 +812,7 @@ }, { "file": "../src/litegraph.js", - "line": 1499, + "line": 1709, "description": "add a new output slot to use in this node", "itemtype": "method", "name": "addOutputs", @@ -737,7 +827,7 @@ }, { "file": "../src/litegraph.js", - "line": 1524, + "line": 1734, "description": "remove an existing output slot", "itemtype": "method", "name": "removeOutput", @@ -752,7 +842,7 @@ }, { "file": "../src/litegraph.js", - "line": 1538, + "line": 1748, "description": "add a new input slot to use in this node", "itemtype": "method", "name": "addInput", @@ -777,7 +867,7 @@ }, { "file": "../src/litegraph.js", - "line": 1561, + "line": 1772, "description": "add several new input slots in this node", "itemtype": "method", "name": "addInputs", @@ -792,7 +882,7 @@ }, { "file": "../src/litegraph.js", - "line": 1586, + "line": 1797, "description": "remove an existing input slot", "itemtype": "method", "name": "removeInput", @@ -807,7 +897,7 @@ }, { "file": "../src/litegraph.js", - "line": 1600, + "line": 1811, "description": "add an special connection to this node (used for special kinds of graphs)", "itemtype": "method", "name": "addConnection", @@ -837,7 +927,7 @@ }, { "file": "../src/litegraph.js", - "line": 1613, + "line": 1832, "description": "computes the size of a node according to its inputs and output slots", "itemtype": "method", "name": "computeSize", @@ -856,7 +946,7 @@ }, { "file": "../src/litegraph.js", - "line": 1664, + "line": 1883, "description": "returns the bounding of the object, used for rendering purposes", "itemtype": "method", "name": "getBounding", @@ -868,7 +958,7 @@ }, { "file": "../src/litegraph.js", - "line": 1674, + "line": 1893, "description": "checks if a point is inside the shape of a node", "itemtype": "method", "name": "isPointInsideNode", @@ -892,7 +982,7 @@ }, { "file": "../src/litegraph.js", - "line": 1698, + "line": 1917, "description": "checks if a point is inside a node slot, and returns info about which slot", "itemtype": "method", "name": "getSlotInPosition", @@ -916,7 +1006,7 @@ }, { "file": "../src/litegraph.js", - "line": 1729, + "line": 1948, "description": "returns the input slot with a given name (used for dynamic slots), -1 if not found", "itemtype": "method", "name": "findInputSlot", @@ -935,7 +1025,7 @@ }, { "file": "../src/litegraph.js", - "line": 1744, + "line": 1963, "description": "returns the output slot with a given name (used for dynamic slots), -1 if not found", "itemtype": "method", "name": "findOutputSlot", @@ -954,7 +1044,7 @@ }, { "file": "../src/litegraph.js", - "line": 1759, + "line": 1978, "description": "connect this node output to the input of another node", "itemtype": "method", "name": "connect", @@ -971,7 +1061,7 @@ }, { "name": "target_slot", - "description": "the input slot of the target node (could be the number of the slot or the string with the name of the slot)", + "description": "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)", "type": "Number_or_string" } ], @@ -983,7 +1073,7 @@ }, { "file": "../src/litegraph.js", - "line": 1855, + "line": 2095, "description": "disconnect one output to an specific node", "itemtype": "method", "name": "disconnectOutput", @@ -1007,7 +1097,7 @@ }, { "file": "../src/litegraph.js", - "line": 1927, + "line": 2178, "description": "disconnect one input", "itemtype": "method", "name": "disconnectInput", @@ -1026,7 +1116,7 @@ }, { "file": "../src/litegraph.js", - "line": 1989, + "line": 2246, "description": "returns the center of a connection point in canvas coords", "itemtype": "method", "name": "getConnectionPos", @@ -1050,7 +1140,7 @@ }, { "file": "../src/litegraph.js", - "line": 2121, + "line": 2380, "description": "Collapse the node to make it smaller on the canvas", "itemtype": "method", "name": "collapse", @@ -1058,7 +1148,7 @@ }, { "file": "../src/litegraph.js", - "line": 2134, + "line": 2393, "description": "Forces the node to do not move or realign on Z", "itemtype": "method", "name": "pin", @@ -1066,7 +1156,7 @@ }, { "file": "../src/litegraph.js", - "line": 2192, + "line": 2481, "description": "clears all the data inside", "itemtype": "method", "name": "clear", @@ -1074,7 +1164,7 @@ }, { "file": "../src/litegraph.js", - "line": 2248, + "line": 2516, "description": "assigns a graph, you can reasign graphs to the same canvas", "itemtype": "method", "name": "setGraph", @@ -1089,7 +1179,7 @@ }, { "file": "../src/litegraph.js", - "line": 2279, + "line": 2547, "description": "opens a graph contained inside a node in the current graph", "itemtype": "method", "name": "openSubgraph", @@ -1104,7 +1194,7 @@ }, { "file": "../src/litegraph.js", - "line": 2306, + "line": 2574, "description": "closes a subgraph contained inside a node", "itemtype": "method", "name": "closeSubgraph", @@ -1119,7 +1209,7 @@ }, { "file": "../src/litegraph.js", - "line": 2321, + "line": 2589, "description": "assigns a canvas", "itemtype": "method", "name": "setCanvas", @@ -1134,7 +1224,7 @@ }, { "file": "../src/litegraph.js", - "line": 2567, + "line": 2835, "description": "Used to attach the canvas in a popup", "itemtype": "method", "name": "getCanvasWindow", @@ -1146,7 +1236,7 @@ }, { "file": "../src/litegraph.js", - "line": 2579, + "line": 2849, "description": "starts rendering the content of the canvas when needed", "itemtype": "method", "name": "startRendering", @@ -1154,7 +1244,7 @@ }, { "file": "../src/litegraph.js", - "line": 2602, + "line": 2872, "description": "stops rendering the content of the canvas (to save resources)", "itemtype": "method", "name": "stopRendering", diff --git a/doc/elements/index.html b/doc/elements/index.html new file mode 100644 index 000000000..487fe15b2 --- /dev/null +++ b/doc/elements/index.html @@ -0,0 +1,10 @@ + + + + Redirector + + + + Click here to redirect + + diff --git a/doc/files/.._src_litegraph.js.html b/doc/files/.._src_litegraph.js.html index c432c2c8c..fc3bd0b78 100644 --- a/doc/files/.._src_litegraph.js.html +++ b/doc/files/.._src_litegraph.js.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,68 +25,61 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-

File: ../src/litegraph.js

+

File: ../src/litegraph.js

@@ -122,12 +113,23 @@ var LiteGraph = {
 	DEFAULT_POSITION: [100,100],//default node position
 	node_images_path: "",
 
+	//enums
+	INPUT: 1, 
+	OUTPUT: 2, 
+
+	EVENT: -1, //for outputs
+	ACTION: -1, //for inputs
+
+	ALWAYS: 0,
+	ON_EVENT: 1,
+	NEVER: 2,
+
 	proxy: null, //used to redirect calls
 
 	debug: false,
 	throw_errors: true,
-	registered_node_types: {},
-	Nodes: {},
+	registered_node_types: {}, //nodetypes by string
+	Nodes: {}, //node types by classname
 
 	/**
 	* Register a node class so it can be listed when the user wants to create a new one
@@ -160,6 +162,23 @@ var LiteGraph = {
 		this.registered_node_types[ type ] = base_class;
 		if(base_class.constructor.name)
 			this.Nodes[ base_class.constructor.name ] = base_class;
+
+		//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");
+	},
+
+	/**
+	* 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)
+			this.registered_node_types[i].prototype[name] = func;
 	},
 
 	/**
@@ -170,7 +189,7 @@ var LiteGraph = {
 	* @param {Object} options to set options
 	*/
 
-	createNode: function(type, title, options)
+	createNode: function( type, title, options )
 	{
 		var base_class = this.registered_node_types[type];
 		if (!base_class)
@@ -189,9 +208,11 @@ var LiteGraph = {
 		node.type = type;
 		if(!node.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();
 		if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
+		if(!node.mode) node.mode = LiteGraph.ALWAYS;
 
 		//extra options
 		if(options)
@@ -308,6 +329,16 @@ var LiteGraph = {
 		for(var i in r)
 			target[i] = r[i];
 		return target;
+	},
+
+	isValidConnection: function( type_a, type_b )
+	{
+		if( !type_a ||  //generic output
+			!type_b || //generic input
+			type_a == type_a || //same type (is valid for triggers)
+			(type_a !== LiteGraph.EVENT && type_b !== LiteGraph.EVENT && type_a.toLowerCase() == type_b.toLowerCase()) ) //same type
+			return true;
+		return false;
 	}
 };
 
@@ -421,8 +452,12 @@ LGraph.prototype.attachCanvas = function(graphcanvas)
 
 LGraph.prototype.detachCanvas = function(graphcanvas)
 {
-	var pos = this.list_of_graphcanvas.indexOf(graphcanvas);
-	if(pos == -1) return;
+	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);
 }
@@ -489,11 +524,22 @@ LGraph.prototype.runStep = function(num)
 	var start = LiteGraph.getTime();
 	this.globaltime = 0.001 * (start - this.starttime);
 
+	var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;
+	if(!nodes)
+		return;
+
 	try
 	{
+		//iterations
 		for(var i = 0; i < num; i++)
 		{
-			this.sendEventToAllNodes("onExecute");
+			for( var j = 0, l = nodes.length; j < l; ++j )
+			{
+				var node = nodes[j];
+				if( node.mode == LiteGraph.ALWAYS && node.onExecute )
+					node.onExecute();
+			}
+
 			this.fixedtime += this.fixedtime_lapse;
 			if( this.onExecuteStep )
 				this.onExecuteStep();
@@ -514,7 +560,8 @@ LGraph.prototype.runStep = function(num)
 	}
 
 	var elapsed = LiteGraph.getTime() - start;
-	if (elapsed == 0) elapsed = 1;
+	if (elapsed == 0)
+		elapsed = 1;
 	this.elapsed_time = 0.001 * elapsed;
 	this.globaltime += 0.001 * elapsed;
 	this.iteration += 1;
@@ -541,14 +588,14 @@ LGraph.prototype.computeExecutionOrder = function()
 	var remaining_links = {}; //to a
 	
 	//search for the nodes without inputs (starting nodes)
-	for (var i in this._nodes)
+	for (var i = 0, l = this._nodes.length; i < l; ++i)
 	{
 		var n = this._nodes[i];
 		M[n.id] = n; //add to pending nodes
 
 		var num = 0; //num of input connections
 		if(n.inputs)
-			for(var j = 0, l = n.inputs.length; j < l; j++)
+			for(var j = 0, l2 = n.inputs.length; j < l2; j++)
 				if(n.inputs[j] && n.inputs[j].link != null)
 					num += 1;
 
@@ -605,13 +652,13 @@ LGraph.prototype.computeExecutionOrder = function()
 	
 	//the remaining ones (loops)
 	for(var i in M)
-		L.push(M[i]);
+		L.push( M[i] );
 		
-	if(L.length != this._nodes.length && LiteGraph.debug)
-		console.log("something went wrong, nodes missing");
+	if( L.length != this._nodes.length && LiteGraph.debug )
+		console.warn("something went wrong, nodes missing");
 
 	//save order number in the node
-	for(var i in L)
+	for(var i = 0; i < L.length; ++i)
 		L[i].order = i;
 	
 	return L;
@@ -659,19 +706,27 @@ LGraph.prototype.getElapsedTime = function()
 * @param {Array} params parameters in array format
 */
 
-LGraph.prototype.sendEventToAllNodes = function(eventname, params)
+LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode )
 {
-	var M = this._nodes_in_order ? this._nodes_in_order : this._nodes;
-	for(var j in M)
-		if(M[j][eventname])
+	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[eventname] && node.mode == mode )
 		{
 			if(params === undefined)
-				M[j][eventname]();
+				node[eventname]();
 			else if(params && params.constructor === Array)
-				M[j][eventname].apply(M[j], params);
+				node[eventname].apply( node, params );
 			else
-				M[j][eventname](params);
+				node[eventname](params);
 		}
+	}
 }
 
 LGraph.prototype.sendActionToCanvas = function(action, params)
@@ -679,7 +734,7 @@ LGraph.prototype.sendActionToCanvas = function(action, params)
 	if(!this.list_of_graphcanvas) 
 		return;
 
-	for(var i in this.list_of_graphcanvas)
+	for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
 	{
 		var c = this.list_of_graphcanvas[i];
 		if( c[action] )
@@ -717,7 +772,7 @@ LGraph.prototype.add = function(node, skip_compute_order)
 	*/
 
 	if(node.onAdded)
-		node.onAdded();
+		node.onAdded( this );
 
 	if(this.config.align_to_grid)
 		node.alignToGrid();
@@ -768,7 +823,7 @@ LGraph.prototype.remove = function(node)
 				node.disconnectOutput(i);
 		}
 
-	node.id = -1;
+	//node.id = -1; //why?
 
 	//callback
 	if(node.onRemoved)
@@ -777,13 +832,16 @@ LGraph.prototype.remove = function(node)
 	node.graph = null;
 
 	//remove from canvas render
-	for(var i in this.list_of_graphcanvas)
+	if(this.list_of_graphcanvas)
 	{
-		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;
+		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
@@ -824,7 +882,7 @@ LGraph.prototype.getNodeById = function(id)
 LGraph.prototype.findNodesByClass = function(classObject)
 {
 	var r = [];
-	for(var i in this._nodes)
+	for(var i = 0, l = this._nodes.length; i < l; ++i)
 		if(this._nodes[i].constructor === classObject)
 			r.push(this._nodes[i]);
 	return r;
@@ -841,7 +899,7 @@ LGraph.prototype.findNodesByType = function(type)
 {
 	var type = type.toLowerCase();
 	var r = [];
-	for(var i in this._nodes)
+	for(var i = 0, l = this._nodes.length; i < l; ++i)
 		if(this._nodes[i].type.toLowerCase() == type )
 			r.push(this._nodes[i]);
 	return r;
@@ -857,7 +915,7 @@ LGraph.prototype.findNodesByType = function(type)
 LGraph.prototype.findNodesByTitle = function(title)
 {
 	var result = [];
-	for (var i in this._nodes)
+	for(var i = 0, l = this._nodes.length; i < l; ++i)
 		if(this._nodes[i].title == title)
 			result.push(this._nodes[i]);
 	return result;
@@ -1060,9 +1118,9 @@ LGraph.prototype.removeGlobalOutput = function(name)
 
 LGraph.prototype.setInputData = function(name,value)
 {
-	var m = this.findNodesByName(name);
-	for(var i in m)
-		m[i].setValue(value);
+	var nodes = this.findNodesByName( name );
+	for(var i = 0, l = nodes.length; i < l; ++i)
+		nodes[i].setValue(value);
 }
 
 /**
@@ -1084,22 +1142,25 @@ LGraph.prototype.getOutputData = function(name)
 
 LGraph.prototype.triggerInput = function(name,value)
 {
-	var m = this.findNodesByName(name);
-	for(var i in m)
-		m[i].onTrigger(value);
+	var nodes = this.findNodesByName(name);
+	for(var i = 0; i < nodes.length; ++i)
+		nodes[i].onTrigger(value);
 }
 
 LGraph.prototype.setCallback = function(name,func)
 {
-	var m = this.findNodesByName(name);
-	for(var i in m)
-		m[i].setTrigger(func);
+	var nodes = this.findNodesByName(name);
+	for(var i = 0; i < nodes.length; ++i)
+		nodes[i].setTrigger(func);
 }
 
 
-LGraph.prototype.onConnectionChange = function()
+LGraph.prototype.connectionChange = function( node )
 {
 	this.updateExecutionOrder();
+	if( this.onConnectionChange )
+		this.onConnectionChange( node );
+	this.sendActionToCanvas("onConnectionChange");
 }
 
 /**
@@ -1109,10 +1170,14 @@ LGraph.prototype.onConnectionChange = function()
 
 LGraph.prototype.isLive = function()
 {
-	for(var i in this.list_of_graphcanvas)
+	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;
+		if(c.live_mode)
+			return true;
 	}
 	return false;
 }
@@ -1143,13 +1208,16 @@ LGraph.prototype.setDirtyCanvas = function(fg,bg)
 LGraph.prototype.serialize = function()
 {
 	var nodes_info = [];
-	for (var i in this._nodes)
+	for(var i = 0, l = this._nodes.length; i < l; ++i)
 		nodes_info.push( this._nodes[i].serialize() );
 
 	//remove data from links, we dont want to store it
-	for (var i in this.links)
-		this.links[i].data = null;
-
+	for(var i in this.links) //links is an OBJECT
+	{
+		var link = this.links[i];
+		link.data = null;
+		delete link._last_time;
+	}
 
 	var data = {
 //		graph: this.graph,
@@ -1188,7 +1256,7 @@ LGraph.prototype.configure = function(data, keep_old)
 
 	//create nodes
 	this._nodes = [];
-	for (var i in 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 );
@@ -1235,8 +1303,8 @@ LGraph.prototype.onNodeTrace = function(node, msg, color)
 	supported callbacks: 
 		+ onAdded: when added to graph
 		+ onRemoved: when removed from graph
-		+ onStart:	when starts playing
-		+ onStop:	when stops playing
+		+ 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
@@ -1245,7 +1313,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color)
 		+ onMouseEnter
 		+ onMouseLeave
 		+ onExecute: execute the node
-		+ onPropertyChange: when a property is changed in the panel (return true to skip default behaviour)
+		+ 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
 		+ onDblClick
@@ -1255,6 +1323,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color)
 		+ 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)
 */
 
 /**
@@ -1300,7 +1369,9 @@ LGraphNode.prototype._ctor = function( title )
 	this.connections = [];
 
 	//local data
-	this.properties = {};
+	this.properties = {}; //for the values
+	this.properties_info = []; //for the info
+
 	this.data = null; //persistent local data
 	this.flags = {
 		//skip_title_render: true,
@@ -1316,13 +1387,18 @@ LGraphNode.prototype.configure = function(info)
 {
 	for (var j in info)
 	{
-		if(j == "console") continue;
+		if(j == "console")
+			continue;
 
 		if(j == "properties")
 		{
 			//i dont 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;
 		}
 
@@ -1339,6 +1415,9 @@ LGraphNode.prototype.configure = function(info)
 			this[j] = info[j];
 	}
 
+	if(this.onConnectionsChange)
+		this.onConnectionsChange();
+
 	//FOR LEGACY, PLEASE REMOVE ON NEXT VERSION
 	for(var i in this.inputs)
 	{
@@ -1365,6 +1444,8 @@ LGraphNode.prototype.configure = function(info)
 		}
 	}
 
+	if( this.onConfigured )
+		this.onConfigured( info );
 }
 
 /**
@@ -1383,7 +1464,8 @@ LGraphNode.prototype.serialize = function()
 		data: this.data,
 		flags: LiteGraph.cloneObject(this.flags),
 		inputs: this.inputs,
-		outputs: this.outputs
+		outputs: this.outputs,
+		mode: this.mode
 	};
 
 	if(this.properties)
@@ -1413,8 +1495,23 @@ LGraphNode.prototype.clone = function()
 {
 	var node = LiteGraph.createNode(this.type);
 
-	var data = this.serialize();
+	//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"];
+	//remove links
 	node.configure(data);
 
 	return node;
@@ -1473,13 +1570,30 @@ LGraphNode.prototype.setOutputData = function(slot,data)
 * @param {number} slot
 * @return {*} data or if it is not connected returns undefined
 */
-LGraphNode.prototype.getInputData = function(slot)
+LGraphNode.prototype.getInputData = function( slot, force_update )
 {
 	if(!this.inputs) 
 		return; //undefined;
-	if(slot < this.inputs.length && this.inputs[slot].link != null)
-		return this.graph.links[ this.inputs[slot].link ].data;
-	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(!force_update)
+		return link.data;
+
+	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;
 }
 
 /**
@@ -1510,6 +1624,24 @@ LGraphNode.prototype.getInputInfo = function(slot)
 	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)
+		return null;
+	return this.graph.getNodeById( input.link.origin_id );
+}
+
 
 /**
 * tells you info about an output connection (which node, type, etc)
@@ -1560,13 +1692,80 @@ LGraphNode.prototype.getOutputNodes = function(slot)
 	return null;
 }
 
-LGraphNode.prototype.triggerOutput = function(slot,param)
+/**
+* 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 )
 {
-	var n = this.getOutputNode(slot);
-	if(n && n.onTrigger)
-		n.onTrigger(param);
+	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.type !== LiteGraph.EVENT || (action && output.name != action) )
+			continue;
+
+		var links = output.links;
+		if(!links || !links.length)
+			continue;
+
+		//for every link attached here
+		for(var k = 0; k < links.length; ++k)
+		{
+			var link_info = this.graph.links[ links[k] ];
+			if(!link_info) //not connected
+				continue;
+			var node = this.graph.getNodeById( link_info.target_id );
+			if(!node) //node not found?
+				continue;
+
+			//used to mark events in graph
+			link_info._last_time = LiteGraph.getTime();
+
+			var target_connection = node.inputs[ link_info.target_slot ];
+
+			if(node.onAction)
+				node.onAction( target_connection.name, param );
+			else if(node.mode === LiteGraph.ON_TRIGGER)
+			{
+				if(node.onExecute)
+					node.onExecute(param);
+			}
+		}
+	}
 }
 
+/**
+* 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
 
 /**
@@ -1578,16 +1777,18 @@ LGraphNode.prototype.triggerOutput = function(slot,param)
 */
 LGraphNode.prototype.addOutput = function(name,type,extra_info)
 {
-	var o = {name:name,type:type,links:null};
+	var o = { name: name, type: type, links: null };
 	if(extra_info)
 		for(var i in extra_info)
 			o[i] = extra_info[i];
 
-	if(!this.outputs) this.outputs = [];
+	if(!this.outputs)
+		this.outputs = [];
 	this.outputs.push(o);
 	if(this.onOutputAdded)
 		this.onOutputAdded(o);
 	this.size = this.computeSize();
+	return o;
 }
 
 /**
@@ -1597,7 +1798,7 @@ LGraphNode.prototype.addOutput = function(name,type,extra_info)
 */
 LGraphNode.prototype.addOutputs = function(array)
 {
-	for(var i in array)
+	for(var i = 0; i < array.length; ++i)
 	{
 		var info = array[i];
 		var o = {name:info[0],type:info[1],link:null};
@@ -1650,6 +1851,7 @@ LGraphNode.prototype.addInput = function(name,type,extra_info)
 	this.size = this.computeSize();
 	if(this.onInputAdded)
 		this.onInputAdded(o);
+	return o;
 }
 
 /**
@@ -1659,7 +1861,7 @@ LGraphNode.prototype.addInput = function(name,type,extra_info)
 */
 LGraphNode.prototype.addInputs = function(array)
 {
-	for(var i in array)
+	for(var i = 0; i < array.length; ++i)
 	{
 		var info = array[i];
 		var o = {name:info[0], type:info[1], link:null};
@@ -1701,7 +1903,15 @@ LGraphNode.prototype.removeInput = function(slot)
 */
 LGraphNode.prototype.addConnection = function(name,type,pos,direction)
 {
-	this.connections.push( {name:name,type:type,pos:pos,direction:direction,links:null});
+	var o = { 
+		name: name,
+		type: type,
+		pos: pos,
+		direction: direction,
+		links: null
+	};
+	this.connections.push( o );
+	return o;
 }
 
 /**
@@ -1710,10 +1920,10 @@ LGraphNode.prototype.addConnection = function(name,type,pos,direction)
 * @param {number} minHeight
 * @return {number} the total size
 */
-LGraphNode.prototype.computeSize = function(minHeight)
+LGraphNode.prototype.computeSize = function( minHeight, out )
 {
 	var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
-	var size = new Float32Array([0,0]);
+	var size = out || new Float32Array([0,0]);
 	rows = Math.max(rows, 1);
 	size[1] = rows * 14 + 6;
 
@@ -1855,10 +2065,10 @@ LGraphNode.prototype.findOutputSlot = function(name)
 * @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)
+* @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 {boolean} if it was connected succesfully
 */
-LGraphNode.prototype.connect = function(slot, node, target_slot)
+LGraphNode.prototype.connect = function( slot, target_node, target_slot )
 {
 	target_slot = target_slot || 0;
 
@@ -1880,19 +2090,19 @@ LGraphNode.prototype.connect = function(slot, node, target_slot)
 		return false;
 	}
 
-	if(node && node.constructor === Number)
-		node = this.graph.getNodeById( node );
-	if(!node)
+	if(target_node && target_node.constructor === Number)
+		target_node = this.graph.getNodeById( target_node );
+	if(!target_node)
 		throw("Node not found");
 
 	//avoid loopback
-	if(node == this)
+	if(target_node == this)
 		return false; 
-	//if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode");
 
+	//you can specify the slot by name
 	if(target_slot.constructor === String)
 	{
-		target_slot = node.findInputSlot(target_slot);
+		target_slot = target_node.findInputSlot( target_slot );
 		if(target_slot == -1)
 		{
 			if(LiteGraph.debug)
@@ -1900,7 +2110,18 @@ LGraphNode.prototype.connect = function(slot, node, target_slot)
 			return false;
 		}
 	}
-	else if(!node.inputs || target_slot >= node.inputs.length) 
+	else if( target_slot === LiteGraph.EVENT )
+	{
+		//search for first slot with event?
+		/*
+		//create input for trigger
+		var input = target_node.addInput("onTrigger", LiteGraph.EVENT );
+		target_slot = target_node.inputs.length - 1; //last one is the one created
+		target_node.mode = LiteGraph.ON_TRIGGER;
+		*/
+		return false;
+	}
+	else if( !target_node.inputs || target_slot >= target_node.inputs.length ) 
 	{
 		if(LiteGraph.debug)
 			console.log("Connect: Error, slot number not found");
@@ -1908,41 +2129,51 @@ LGraphNode.prototype.connect = function(slot, node, target_slot)
 	}
 
 	//if there is something already plugged there, disconnect
-	if(target_slot != -1 && node.inputs[target_slot].link != null)
-		node.disconnectInput(target_slot);
+	if(target_node.inputs[ target_slot ].link != null )
+		target_node.disconnectInput( target_slot );
 
+	//why here??
 	this.setDirtyCanvas(false,true);
-	this.graph.onConnectionChange();
+	this.graph.connectionChange( this );
 		
-	//special case: -1 means node-connection, used for triggers
 	var output = this.outputs[slot];
 
-	//allows nodes to block connection even if all test passes
-	if(node.onConnectInput)
-		if( node.onConnectInput( target_slot, output.type, output ) === false)
+	//allows nodes to block connection 
+	if(target_node.onConnectInput)
+		if( target_node.onConnectInput( target_slot, output.type, output ) === false)
 			return false;
 
-	if(target_slot == -1)
+	var input = target_node.inputs[target_slot];
+
+	if( LiteGraph.isValidConnection( output.type, input.type) )
 	{
+		var link_info = { 
+			id: this.graph.last_link_id++, 
+			origin_id: this.id, 
+			origin_slot: slot, 
+			target_id: target_node.id, 
+			target_slot: 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({id:node.id, slot: -1});
-	}
-	else if( !output.type ||  //generic output
-			!node.inputs[target_slot].type || //generic input
-			output.type.toLowerCase() == node.inputs[target_slot].type.toLowerCase() ) //same type
-	{
-		//info: link structure => [ 0:link_id, 1:start_node_id, 2:start_slot, 3:end_node_id, 4:end_slot ]
-		//var link = [ this.graph.last_link_id++, this.id, slot, node.id, target_slot ];
-		var link = { id: this.graph.last_link_id++, origin_id: this.id, origin_slot: slot, target_id: node.id, target_slot: target_slot };
-		this.graph.links[ link.id ] = link;
-
-		//connect
-		if( output.links == null )	output.links = [];
-		output.links.push( link.id );
-		node.inputs[target_slot].link = link.id;
+		output.links.push( link_info.id );
+		//connect in input
+		target_node.inputs[target_slot].link = link_info.id;
 
+		if(this.onConnectionsChange)
+			this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info ); //link_info has been created now, so its updated
+		if(target_node.onConnectionsChange)
+			target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info );
 	}
+
+	this.setDirtyCanvas(false,true);
+	this.graph.connectionChange( this );
+
 	return true;
 }
 
@@ -1953,7 +2184,7 @@ LGraphNode.prototype.connect = function(slot, node, target_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 succesfully
 */
-LGraphNode.prototype.disconnectOutput = function(slot, target_node)
+LGraphNode.prototype.disconnectOutput = function( slot, target_node )
 {
 	if( slot.constructor === String )
 	{
@@ -1977,6 +2208,7 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node)
 	if(!output.links || output.links.length == 0)
 		return false;
 
+	//one of the links
 	if(target_node)
 	{
 		if(target_node.constructor === Number)
@@ -1995,11 +2227,15 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node)
 				output.links.splice(i,1); //remove here
 				target_node.inputs[ link_info.target_slot ].link = null; //remove there
 				delete this.graph.links[ link_id ]; //remove the link from the links pool
+				if(target_node.onConnectionsChange)
+					target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok
+				if(this.onConnectionsChange)
+					this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info );
 				break;
 			}
 		}
 	}
-	else
+	else //all the links
 	{
 		for(var i = 0, l = output.links.length; i < l; i++)
 		{
@@ -2009,12 +2245,18 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node)
 			var target_node = this.graph.getNodeById( link_info.target_id );
 			if(target_node)
 				target_node.inputs[ link_info.target_slot ].link = null; //remove other side link
+			delete this.graph.links[ link_id ]; //remove the link from the links pool
+			if(target_node.onConnectionsChange)
+				target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok
+			if(this.onConnectionsChange)
+				this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info );
 		}
 		output.links = null;
 	}
 
+
 	this.setDirtyCanvas(false,true);
-	this.graph.onConnectionChange();
+	this.graph.connectionChange( this );
 	return true;
 }
 
@@ -2024,7 +2266,7 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node)
 * @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 succesfully
 */
-LGraphNode.prototype.disconnectInput = function(slot)
+LGraphNode.prototype.disconnectInput = function( slot )
 {
 	//seek for the output slot
 	if( slot.constructor === String )
@@ -2047,6 +2289,7 @@ LGraphNode.prototype.disconnectInput = function(slot)
 	var input = this.inputs[slot];
 	if(!input)
 		return false;
+
 	var link_id = this.inputs[slot].link;
 	this.inputs[slot].link = null;
 
@@ -2054,15 +2297,15 @@ LGraphNode.prototype.disconnectInput = function(slot)
 	var link_info = this.graph.links[ link_id ];
 	if( link_info )
 	{
-		var node = this.graph.getNodeById( link_info.origin_id );
-		if(!node)
+		var target_node = this.graph.getNodeById( link_info.origin_id );
+		if(!target_node)
 			return false;
 
-		var output = node.outputs[ link_info.origin_slot ];
+		var output = target_node.outputs[ link_info.origin_slot ];
 		if(!output || !output.links || output.links.length == 0) 
 			return false;
 
-		//check outputs
+		//search in the inputs list for this link
 		for(var i = 0, l = output.links.length; i < l; i++)
 		{
 			var link_id = output.links[i];
@@ -2073,10 +2316,15 @@ LGraphNode.prototype.disconnectInput = function(slot)
 				break;
 			}
 		}
+
+		if( this.onConnectionsChange )
+			this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info );
+		if( target_node.onConnectionsChange )
+			target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info );
 	}
 
 	this.setDirtyCanvas(false,true);
-	this.graph.onConnectionChange();
+	this.graph.connectionChange( this );
 	return true;
 }
 
@@ -2156,6 +2404,7 @@ LGraphNode.prototype.loadImage = function(url)
 }
 
 //safe LGraphNode action execution (not sure if safe)
+/*
 LGraphNode.prototype.executeAction = function(action)
 {
 	if(action == "") return false;
@@ -2191,6 +2440,7 @@ LGraphNode.prototype.executeAction = function(action)
 
 	return true;
 }
+*/
 
 /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
 LGraphNode.prototype.captureInput = function(v)
@@ -2200,7 +2450,7 @@ LGraphNode.prototype.captureInput = function(v)
 
 	var list = this.graph.list_of_graphcanvas;
 
-	for(var i in list)
+	for(var i = 0; i < list.length; ++i)
 	{
 		var c = list[i];
 		//releasing somebody elses capture?!
@@ -2252,14 +2502,18 @@ LGraphNode.prototype.localToScreen = function(x,y, graphcanvas)
 
 /**
 * The Global Scope. It contains all the registered node classes.
+* 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 itself)
 * @param {LGraph} graph [optional]
+* @param {Object} options [optional] { skip_rendering, autoresize }
 */
-function LGraphCanvas( canvas, graph, skip_render )
+function LGraphCanvas( canvas, graph, options )
 {
+	options = options || {};
+
 	//if(graph === undefined)
 	//	throw ("No graph assigned");
 
@@ -2269,6 +2523,30 @@ function LGraphCanvas( canvas, graph, skip_render )
 	this.max_zoom = 10;
 	this.min_zoom = 0.1;
 
+	this.title_text_font = "bold 14px Arial";
+	this.inner_text_font = "normal 12px Arial";
+	this.default_link_color = "#AAC";
+
+	this.highquality_render = true;
+	this.editor_alpha = 1; //used for transition
+	this.pause_rendering = false;
+	this.render_shadows = true;
+	this.clear_background = true;
+
+	this.render_only_selected = true;
+	this.live_mode = false;
+	this.show_info = true;
+	this.allow_dragcanvas = true;
+	this.allow_dragnodes = true;
+
+	this.always_render_background = false; 
+	this.render_connections_shadows = false; //too much cpu
+	this.render_connections_border = true;
+	this.render_curved_connections = true;
+	this.render_connection_arrows = true;
+
+	this.connections_width = 4;
+
 	//link canvas and graph
 	if(graph)
 		graph.attachCanvas(this);
@@ -2276,11 +2554,13 @@ function LGraphCanvas( canvas, graph, skip_render )
 	this.setCanvas( canvas );
 	this.clear();
 
-	if(!skip_render)
+	if(!options.skip_render)
 		this.startRendering();
+
+	this.autoresize = options.autoresize;
 }
 
-LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"};
+LGraphCanvas.link_type_colors = {"-1":"#F85",'number':"#AAC","node":"#DCA"};
 
 
 /**
@@ -2304,18 +2584,6 @@ LGraphCanvas.prototype.clear = function()
 	this.node_capturing_input = null;
 	this.connecting_node = null;
 
-	this.highquality_render = true;
-	this.editor_alpha = 1; //used for transition
-	this.pause_rendering = false;
-	this.render_shadows = true;
-	this.clear_background = true;
-
-	this.render_only_selected = true;
-	this.live_mode = false;
-	this.show_info = true;
-	this.allow_dragcanvas = true;
-	this.allow_dragnodes = true;
-
 	this.dirty_canvas = true;
 	this.dirty_bgcanvas = true;
 	this.dirty_area = null;
@@ -2325,17 +2593,8 @@ LGraphCanvas.prototype.clear = function()
 	this.last_mouse = [0,0];
 	this.last_mouseclick = 0;
 
-	this.title_text_font = "bold 14px Arial";
-	this.inner_text_font = "normal 12px Arial";
-
-	this.render_connections_shadows = false; //too much cpu
-	this.render_connections_border = true;
-	this.render_curved_connections = true;
-	this.render_connection_arrows = true;
-
-	this.connections_width = 4;
-
-	if(this.onClear) this.onClear();
+	if(this.onClear)
+		this.onClear();
 	//this.UIinit();
 }
 
@@ -2666,6 +2925,8 @@ LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas)
 */
 LGraphCanvas.prototype.getCanvasWindow = function()
 {
+	if(!this.canvas)
+		return window;
 	var doc = this.canvas.ownerDocument;
 	return doc.defaultView || doc.parentWindow;
 }
@@ -2729,19 +2990,24 @@ LGraphCanvas.prototype.processMouseDown = function(e)
 
 	var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
 	var skip_dragging = false;
+    
+    LiteGraph.closeAllContextMenus( ref_window );
 
 	if(e.which == 1) //left button mouse
 	{
-		//another node selected
 		if(!e.shiftKey) //REFACTOR: integrate with function
 		{
-			var todeselect = [];
-			for(var i in this.selected_nodes)
-				if (this.selected_nodes[i] != n)
-						todeselect.push(this.selected_nodes[i]);
-			//two passes to avoid problems modifying the container
-			for(var i in todeselect)
-				this.processNodeDeselected(todeselect[i]);
+            //no node or another node selected
+            if (!n || !this.selected_nodes[n.id]) {
+
+                var todeselect = [];
+                for (var i in this.selected_nodes)
+                    if (this.selected_nodes[i] != n)
+                        todeselect.push(this.selected_nodes[i]);
+                //two passes to avoid problems modifying the container
+                for (var i in todeselect)
+                    this.processNodeDeselected(todeselect[i]);
+            }
 		}
 		var clicking_canvas_bg = false;
 
@@ -2825,7 +3091,7 @@ LGraphCanvas.prototype.processMouseDown = function(e)
 
 				//if do not capture mouse
 
-				if( n.onMouseDown && n.onMouseDown(e) )
+				if( n.onMouseDown && n.onMouseDown(e, [e.canvasX - n.pos[0], e.canvasY - n.pos[1]] ) )
 					block_drag_node = true;
 				else if(this.live_mode)
 				{
@@ -2859,7 +3125,7 @@ LGraphCanvas.prototype.processMouseDown = function(e)
 	}
 	else if (e.which == 3) //right button
 	{
-		this.processContextualMenu(n,e);
+		this.processContextMenu(n,e);
 	}
 
 	//TODO
@@ -2882,12 +3148,20 @@ LGraphCanvas.prototype.processMouseDown = function(e)
 	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;
 }
 
 LGraphCanvas.prototype.processMouseMove = function(e)
 {
-	if(!this.graph) return;
+	if(this.autoresize)
+		this.resize();
+
+	if(!this.graph)
+		return;
 
 	this.adjustMouseEvent(e);
 	var mouse = [e.localX, e.localY];
@@ -2911,7 +3185,7 @@ LGraphCanvas.prototype.processMouseMove = function(e)
 		var n = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes);
 
 		//remove mouseover flag
-		for(var i in this.graph._nodes)
+		for(var i = 0, l = this.graph._nodes.length; i < l; ++i)
 		{
 			if(this.graph._nodes[i].mouseOver && n != this.graph._nodes[i])
 			{
@@ -2940,19 +3214,27 @@ LGraphCanvas.prototype.processMouseMove = function(e)
 
 			if(n.onMouseMove) n.onMouseMove(e);
 
-			//ontop of input
+			//on top of input
 			if(this.connecting_node)
 			{
-				var pos = this._highlight_input || [0,0];
-				var slot = this.isOverNodeInput(n, e.canvasX, e.canvasY, pos);
-				if(slot != -1 && n.inputs[slot])
-				{	
-					var slot_type = n.inputs[slot].type;
-					if( !this.connecting_output.type || !slot_type || slot_type.toLowerCase() == this.connecting_output.type.toLowerCase() )
-						this._highlight_input = pos;
+				var pos = this._highlight_input || [0,0]; //to store the output of isOverNodeInput
+				
+				if( this.isOverNodeBox( n, e.canvasX, e.canvasY ) )
+				{
+					//mouse on top of the corner box, dont know what to do
 				}
 				else
-					this._highlight_input = null;
+				{
+					var slot = this.isOverNodeInput( n, e.canvasX, e.canvasY, pos );
+					if(slot != -1 && n.inputs[slot])
+					{	
+						var slot_type = n.inputs[slot].type;
+						if( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) )
+							this._highlight_input = pos;
+					}
+					else
+						this._highlight_input = null;
+				}
 			}
 
 			//Search for corner
@@ -3044,15 +3326,14 @@ LGraphCanvas.prototype.processMouseUp = function(e)
 			this.dirty_canvas = true;
 			this.dirty_bgcanvas = true;
 
-			var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes);
+			var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes );
 
 			//node below mouse
 			if(node)
 			{
-			
-				if(this.connecting_output.type == 'node')
+				if( this.connecting_output.type == LiteGraph.EVENT && this.isOverNodeBox( node, e.canvasX, e.canvasY ) )
 				{
-					this.connecting_node.connect(this.connecting_slot, node, -1);
+					this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
 				}
 				else
 				{
@@ -3065,9 +3346,12 @@ LGraphCanvas.prototype.processMouseUp = function(e)
 					else
 					{ //not on top of an input
 						var input = node.getInputInfo(0);
-						//simple connect
-						if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing
-							this.connecting_node.connect(this.connecting_slot, node, 0);
+						//auto connect
+						if(this.connecting_output.type == LiteGraph.EVENT)
+							this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT );
+						else
+							if(input && !input.link && input.type == this.connecting_output.type) //toLowerCase missing
+								this.connecting_node.connect(this.connecting_slot, node, 0);
 					}
 				}
 			}
@@ -3100,9 +3384,9 @@ LGraphCanvas.prototype.processMouseUp = function(e)
 			this.dragging_canvas = false;
 
 			if( this.node_over && this.node_over.onMouseUp )
-				this.node_over.onMouseUp(e);
+				this.node_over.onMouseUp(e, [e.canvasX - this.node_over.pos[0], e.canvasY - this.node_over.pos[1]] );
 			if( this.node_capturing_input && this.node_capturing_input.onMouseUp )
-				this.node_capturing_input.onMouseUp(e);
+				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
@@ -3160,7 +3444,15 @@ LGraphCanvas.prototype.processMouseWheel = function(e)
 	return false; // prevent default
 }
 
-LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos)
+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;
+}
+
+LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_pos )
 {
 	if(node.inputs)
 		for(var i = 0, l = node.inputs.length; i < l; ++i)
@@ -3169,7 +3461,11 @@ LGraphCanvas.prototype.isOverNodeInput = function(node, canvasx, canvasy, slot_p
 			var link_pos = node.getConnectionPos(true,i);
 			if( isInsideRectangle(canvasx, canvasy, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
 			{
-				if(slot_pos) { slot_pos[0] = link_pos[0]; slot_pos[1] = link_pos[1] };
+				if(slot_pos)
+				{ 
+					slot_pos[0] = link_pos[0];
+					slot_pos[1] = link_pos[1];
+				}
 				return i;
 			}
 		}
@@ -3316,11 +3612,9 @@ LGraphCanvas.prototype.processNodeDeselected = function(n)
 	delete this.selected_nodes[n.id];
 
 	if(this.onNodeDeselected)
-		this.onNodeDeselected();
+		this.onNodeDeselected(n);
 
 	this.dirty_canvas = true;
-
-	//this.showNodePanel(null);
 }
 
 LGraphCanvas.prototype.processNodeDblClicked = function(n)
@@ -3350,7 +3644,7 @@ LGraphCanvas.prototype.selectNode = function(node)
 
 LGraphCanvas.prototype.selectAllNodes = function()
 {
-	for(var i in this.graph._nodes)
+	for(var i = 0; i < this.graph._nodes.length; ++i)
 	{
 		var n = this.graph._nodes[i];
 		if(!n.selected && n.onSelected)
@@ -3472,7 +3766,7 @@ LGraphCanvas.prototype.sendToBack = function(n)
 LGraphCanvas.prototype.computeVisibleNodes = function()
 {
 	var visible_nodes = [];
-	for (var i in this.graph._nodes)
+	for(var i = 0, l = this.graph._nodes.length; i < l; ++i)
 	{
 		var n = this.graph._nodes[i];
 
@@ -3490,6 +3784,9 @@ LGraphCanvas.prototype.computeVisibleNodes = function()
 
 LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
 {
+	if(!this.canvas)
+		return;
+
 	//fps counting
 	var now = LiteGraph.getTime();
 	this.render_time = (now - this.last_draw_time)*0.001;
@@ -3502,7 +3799,7 @@ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
 		this.visible_area = new Float32Array([start[0],start[1],end[0],end[1]]);
 	}
 
-	if(this.dirty_bgcanvas || force_bgcanvas)
+	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)
@@ -3569,7 +3866,7 @@ LGraphCanvas.prototype.drawFrontCanvas = function()
 		var visible_nodes = this.computeVisibleNodes();
 		this.visible_nodes = visible_nodes;
 
-		for (var i in visible_nodes)
+		for (var i = 0; i < visible_nodes.length; ++i)
 		{
 			var node = visible_nodes[i];
 
@@ -3594,11 +3891,22 @@ LGraphCanvas.prototype.drawFrontCanvas = function()
 		if(this.connecting_pos != null)
 		{
 			ctx.lineWidth = this.connections_width;
-			var link_color = this.connecting_output.type == 'node' ? "#F85" : "#AFA";
+			var link_color = null;
+			switch( this.connecting_output.type )
+			{
+				case LiteGraph.EVENT: link_color = "#F85"; break;
+				default:
+					link_color = "#AFA";
+			}
 			this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]], link_color );
 
 			ctx.beginPath();
-			ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);
+
+			if( this.connecting_output.type === LiteGraph.EVENT )
+				ctx.rect( (this.connecting_pos[0] - 6) + 0.5, (this.connecting_pos[1] - 5) + 0.5,14,10);
+			else
+				ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);
+
 			/*
 			if( this.connecting_output.round)
 				ctx.arc( this.connecting_pos[0], this.connecting_pos[1],4,0,Math.PI*2);
@@ -3655,6 +3963,13 @@ LGraphCanvas.prototype.renderInfo = function( ctx, x, y )
 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;
@@ -3693,7 +4008,7 @@ LGraphCanvas.prototype.drawBackCanvas = function()
 			}
 
 			var pattern = null;
-			if(this._bg_img != this._pattern_img && this._bg_img.width > 0)
+			if(this._pattern == null && this._bg_img.width > 0)
 			{
 				pattern = ctx.createPattern( this._bg_img, 'repeat' );
 				this._pattern_img = this._bg_img;
@@ -3854,6 +4169,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx )
 
 	var render_text = this.scale > 0.6;
 
+	var out_slot = this.connecting_output;
+
 	//render inputs and outputs
 	if(!node.flags.collapsed)
 	{
@@ -3864,7 +4181,8 @@ LGraphCanvas.prototype.drawNode = function(node, ctx )
 				var slot = node.inputs[i];
 
 				ctx.globalAlpha = editor_alpha;
-				if (this.connecting_node != null && this.connecting_output.type && node.inputs[i].type && this.connecting_output.type.toLowerCase() != node.inputs[i].type.toLowerCase() )
+				//change opacity of incompatible slots
+				if ( this.connecting_node && LiteGraph.isValidConnection( slot.type && out_slot.type ) )
 					ctx.globalAlpha = 0.4 * editor_alpha;
 
 				ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA";
@@ -3875,10 +4193,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx )
 
 				ctx.beginPath();
 
-				if (1 || slot.round)
+				if (slot.type === LiteGraph.EVENT)
+					ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10);
+				else
 					ctx.arc(pos[0],pos[1],4,0,Math.PI*2);
-				//else
-				//	ctx.rect((pos[0] - 6) + 0.5, (pos[1] - 5) + 0.5,14,10);
 
 				ctx.fill();
 
@@ -3915,10 +4233,10 @@ LGraphCanvas.prototype.drawNode = function(node, ctx )
 				ctx.beginPath();
 				//ctx.rect( node.size[0] - 14,i*14,10,10);
 
-				if (1 || slot.round)
-					ctx.arc(pos[0],pos[1],4,0,Math.PI*2);
-				//else
-				//	ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10);
+				if (slot.type === LiteGraph.EVENT)
+					ctx.rect((pos[0] - 6) + 0.5,(pos[1] - 5) + 0.5,14,10);
+				else
+					ctx.arc( pos[0],pos[1],4,0, Math.PI*2 );
 
 				//trigger
 				//if(slot.node_id != null && slot.slot == -1)
@@ -4012,7 +4330,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo
 	if(node.onDrawBackground)
 		node.onDrawBackground(ctx);
 
-	//title bg
+	//title bg (remember, it is rendered ABOVE the node
 	if(!no_title)
 	{
 		ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
@@ -4033,7 +4351,7 @@ LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolo
 			//ctx.stroke();
 		}
 
-		//box
+		//title box
 		ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
 		ctx.beginPath();
 		if (shape == "round")
@@ -4108,10 +4426,11 @@ LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor)
 	}
 }
 
-LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"];
-
+//OPTIMIZE THIS: precatch connections position instead of recomputing them every time
 LGraphCanvas.prototype.drawConnections = function(ctx)
 {
+	var now = LiteGraph.getTime();
+
 	//draw connections
 	ctx.lineWidth = this.connections_width;
 
@@ -4119,19 +4438,20 @@ LGraphCanvas.prototype.drawConnections = function(ctx)
 	ctx.strokeStyle = "#AAA";
 	ctx.globalAlpha = this.editor_alpha;
 	//for every node
-	for (var n in this.graph._nodes)
+	for (var n = 0, l = this.graph._nodes.length; n < l; ++n)
 	{
 		var node = this.graph._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)
-			for(var i in node.inputs)
+			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;
+				if(!link)
+					continue;
 
 				var start_node = this.graph.getNodeById( link.origin_id );
 				if(start_node == null) continue;
@@ -4143,16 +4463,22 @@ LGraphCanvas.prototype.drawConnections = function(ctx)
 				else
 					start_node_slotpos = start_node.getConnectionPos(false, start_node_slot);
 
-				var color = LGraphCanvas.link_type_colors[node.inputs[i].type];
-				if(color == null)
-					color = LGraphCanvas.link_colors[node.id % LGraphCanvas.link_colors.length];
+				var color = LGraphCanvas.link_type_colors[ node.inputs[i].type ] || this.default_link_color;
+
 				this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i), color );
+
+				if(link && link._last_time && now - link._last_time < 1000 )
+				{
+					var f = 2.0 - (now - link._last_time) * 0.002;
+					var color = "rgba(255,255,255, " + f.toFixed(2) + ")";
+					this.renderLink( ctx, start_node_slotpos, node.getConnectionPos(true,i) , color, true, f );
+				}
 			}
 	}
 	ctx.globalAlpha = 1;
 }
 
-LGraphCanvas.prototype.renderLink = function(ctx,a,b,color)
+LGraphCanvas.prototype.renderLink = function(ctx,a,b,color, skip_border, flow )
 {
 	if(!this.highquality_render)
 	{
@@ -4185,7 +4511,7 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color)
 		ctx.lineTo(b[0]-10,b[1]);
 	}
 
-	if(this.render_connections_border && this.scale > 0.6)
+	if(this.render_connections_border && this.scale > 0.6 && !skip_border)
 	{
 		ctx.strokeStyle = "rgba(0,0,0,0.5)";
 		ctx.stroke();
@@ -4195,12 +4521,17 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color)
 	ctx.fillStyle = ctx.strokeStyle = color;
 	ctx.stroke();
 
+	//no symbols
+	if(!this.render_connection_arrows || this.scale < 0.6)
+		return;
+
 	//render arrow
 	if(this.render_connection_arrows && this.scale > 0.6)
 	{
-		//get two points in the bezier curve
 		var pos = this.computeConnectionPoint(a,b,0.5);
 		var pos2 = this.computeConnectionPoint(a,b,0.51);
+
+		//get two points in the bezier curve
 		var angle = 0;
 		if(this.render_curved_connections)
 			angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]);
@@ -4217,6 +4548,18 @@ LGraphCanvas.prototype.renderLink = function(ctx,a,b,color)
 		ctx.fill();
 		ctx.restore();
 	}
+
+	if(flow)
+	{
+		for(var i = 0; i < 5; ++i)
+		{
+			var f = (LiteGraph.getTime() * 0.001 + (i * 0.2)) % 1;
+			var pos = this.computeConnectionPoint(a,b,f);
+			ctx.beginPath();
+			ctx.arc(pos[0],pos[1],5,0,2*Math.PI);
+			ctx.fill();
+		}
+	}
 }
 
 LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t)
@@ -4348,7 +4691,7 @@ LGraphCanvas.prototype.touchHandler = function(event)
 
 LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event )
 {
-	var window = canvas.getCanvasWindow();
+	var ref_window = canvas.getCanvasWindow();
 
 	var values = LiteGraph.getNodeTypesCategories();
 	var entries = {};
@@ -4356,7 +4699,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event )
 		if(values[i])
 			entries[ i ] = { value: values[i], content: values[i]  , is_menu: true };
 
-	var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, window);
+	var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window);
 
 	function inner_clicked(v, e)
 	{
@@ -4366,7 +4709,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event )
 		for(var i in node_types)
 			values.push( { content: node_types[i].title, value: node_types[i].type });
 
-		LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, window);
+		LiteGraph.createContextMenu(values, {event: e, callback: inner_create, from: menu}, ref_window);
 		return false;
 	}
 
@@ -4394,16 +4737,20 @@ LGraphCanvas.onMenuNodeEdit = function()
 
 }
 
-LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu)
+LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu)
 {
-	if(!node) return;
+	if(!node)
+		return;
+
+	var that = this;
+	var ref_window = this.getCanvasWindow();
 
 	var options = node.optional_inputs;
 	if(node.onGetInputs)
 		options = node.onGetInputs();
+
+	var entries = [];
 	if(options)
-	{
-		var entries = [];
 		for (var i in options)
 		{
 			var entry = options[i];
@@ -4412,55 +4759,91 @@ LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu)
 				label = entry[2].label;
 			entries.push({content: label, value: entry});
 		}
-		var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});
-	}
 
-	function inner_clicked(v)
+	if(this.onMenuNodeInputs)
+		entries = this.onMenuNodeInputs( entries );
+
+	if(!entries.length)
+		return;
+
+	var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window);
+
+	function inner_clicked(v, e, prev)
 	{
-		if(!node) return;
-		node.addInput(v.value[0],v.value[1], v.value[2]);
+		if(!node)
+			return;
+
+		if(v.callback)
+			v.callback.call(that, node, v, e, prev);
+
+		if(v.value)
+			node.addInput(v.value[0],v.value[1], v.value[2]);
 	}
 
 	return false;
 }
 
-LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu)
+LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu)
 {
-	if(!node) return;
+	if(!node)
+		return;
+
+	var that = this;
+	var ref_window = this.getCanvasWindow();
 
 	var options = node.optional_outputs;
 	if(node.onGetOutputs)
 		options = node.onGetOutputs();
+
+	var entries = [];
 	if(options)
-	{
-		var entries = [];
 		for (var i in options)
 		{
 			var entry = options[i];
+			if(!entry) //separator?
+			{
+				entries.push(null);
+				continue;
+			}
+
 			if(node.findOutputSlot(entry[0]) != -1)
 				continue; //skip the ones already on
 			var label = entry[0];
 			if(entry[2] && entry[2].label)
 				label = entry[2].label;
-			entries.push({content: label, value: entry});
+			var data = {content: label, value: entry};
+			if(entry[1] == LiteGraph.EVENT)
+				data.className = "event";
+			entries.push(data);
 		}
-		if(entries.length)
-			var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});
-	}
 
-	function inner_clicked(v)
+	if(this.onMenuNodeOutputs)
+		entries = this.onMenuNodeOutputs( entries );
+
+	if(!entries.length)
+		return;
+
+	var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, 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
+		if(value && (value.constructor === Object || value.constructor === Array)) //submenu why?
 		{
 			var entries = [];
 			for(var i in value)
 				entries.push({content: i, value: value[i]});
-			LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});		
+			LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});		
 			return false;
 		}
 		else
@@ -4470,6 +4853,169 @@ LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu)
 	return false;
 }
 
+LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu)
+{
+	if(!node || !node.properties)
+		return;
+
+	var that = this;
+	var ref_window = this.getCanvasWindow();
+
+	var entries = [];
+		for (var i in node.properties)
+		{
+			var value = node.properties[i] !== undefined ? node.properties[i] : " ";
+			entries.push({content: "<span class='property_name'>" + i + "</span>" + "<span class='property_value'>" + value + "</span>", value: i});
+		}
+	if(!entries.length)
+		return;
+
+	var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window);
+
+	function inner_clicked( v, e, prev )
+	{
+		if(!node)
+			return;
+		that.showEditPropertyValue( node, v.value, { event: e });
+	}
+
+	return false;
+}
+
+LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options )
+{
+	if(!node || node.properties[ property ] === undefined )
+		return;
+
+	options = options || {};
+	var that = this;
+
+	var type = "string";
+	
+	if(node.properties[ property ] !== null)
+		type = typeof(node.properties[ property ]);
+
+	var info = null;
+	if(node.getPropertyInfo)
+		info = node.getPropertyInfo(property);
+	if(node.properties_info)
+	{
+		for(var i = 0; i < node.properties_info.length; ++i)
+		{
+			if( node.properties_info[i].name == property )
+			{
+				info = node.properties_info[i];
+				break;
+			}
+		}
+	}
+
+	if(info !== undefined && info !== null && info.type )
+		type = info.type;
+
+	var input_html = "";
+	
+	if(type == "string" || type == "number")
+		input_html = "<input autofocus type='text' class='value'/>";
+	else if(type == "enum" && info.values)
+	{
+		input_html = "<select autofocus type='text' class='value'>";
+		for(var i in info.values)
+		{
+			var v = info.values.constructor === Array ? info.values[i] : i;
+			input_html += "<option value='"+v+"' "+(v == node.properties[property] ? "selected" : "")+">"+info.values[i]+"</option>";
+		}
+		input_html += "</select>";
+	}
+	else if(type == "boolean")
+	{
+		input_html = "<input autofocus type='checkbox' class='value' "+(node.properties[property] ? "checked" : "")+"/>";
+	}
+
+
+	var dialog = document.createElement("div");
+	dialog.className = "graphdialog";
+	dialog.innerHTML = "<span class='name'>" + property + "</span>"+input_html+"<button>OK</button>";
+
+	if(type == "enum" && info.values)
+	{
+		var input = dialog.querySelector("select");
+		input.addEventListener("change", function(e){
+			setValue( e.target.value );
+			//var index = e.target.value;
+			//setValue( e.options[e.selectedIndex].value );
+		});
+	}
+	else if(type == "boolean")
+	{
+		var input = dialog.querySelector("input");
+		if(input)
+		{
+			input.addEventListener("click", function(e){
+				setValue( !!input.checked );
+			});
+		}
+	}
+	else
+	{
+		var input = dialog.querySelector("input");
+		if(input)
+		{
+			input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : "";
+			input.addEventListener("keydown", function(e){
+				if(e.keyCode != 13)
+					return;
+				inner();
+				e.preventDefault();
+				e.stopPropagation();
+			});
+		}
+	}
+
+	var rect = this.canvas.getClientRects()[0];
+	var offsetx = -20;
+	var offsety = -20;
+	if(rect)
+	{
+		offsetx -= rect.left;
+		offsety -= rect.top;
+	}
+
+	if( options.event )
+	{
+		dialog.style.left = (options.event.pageX + offsetx) + "px";
+		dialog.style.top = (options.event.pageY + offsety)+ "px";
+	}
+	else
+	{
+		dialog.style.left = (this.canvas.width * 0.5 + offsetx) + "px";
+		dialog.style.top = (this.canvas.height * 0.5 + offsety) + "px";
+	}
+
+	var button = dialog.querySelector("button");
+	button.addEventListener("click", inner );
+
+	this.canvas.parentNode.appendChild( dialog );
+
+
+	function inner()
+	{
+		setValue( input.value );
+	}
+
+	function setValue(value)
+	{
+		if(typeof( node.properties[ property ] ) == "number")
+			value = Number(value);
+		node.properties[ property ] = value;
+
+		if(node.onPropertyChanged)
+			node.onPropertyChanged( property, value );
+		dialog.parentNode.removeChild( dialog );
+		node.setDirtyCanvas(true,true);
+	}
+}
+
 LGraphCanvas.onMenuNodeCollapse = function(node)
 {
 	node.flags.collapsed = !node.flags.collapsed;
@@ -4481,6 +5027,27 @@ LGraphCanvas.onMenuNodePin = function(node)
 	node.pin();
 }
 
+LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu)
+{
+	LiteGraph.createContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu});
+
+	function inner_clicked(v)
+	{
+		if(!node)
+			return;
+		switch(v)
+		{
+			case "On Event": node.mode = LiteGraph.ON_EVENT; break;
+			case "Never": node.mode = LiteGraph.NEVER; break;
+			case "Always": 
+			default:
+				node.mode = LiteGraph.ALWAYS; break;
+		}
+	}
+
+	return false;
+}
+
 LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu)
 {
 	var values = [];
@@ -4490,7 +5057,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu)
 		var value = {value:i, content:"<span style='display: block; color:"+color.color+"; background-color:"+color.bgcolor+"'>"+i+"</span>"};
 		values.push(value);
 	}
-	LiteGraph.createContextualMenu(values, {event: e, callback: inner_clicked, from: prev_menu});
+	LiteGraph.createContextMenu(values, {event: e, callback: inner_clicked, from: prev_menu});
 
 	function inner_clicked(v)
 	{
@@ -4509,7 +5076,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu)
 
 LGraphCanvas.onMenuNodeShapes = function(node,e)
 {
-	LiteGraph.createContextualMenu(["box","round"], {event: e, callback: inner_clicked});
+	LiteGraph.createContextMenu(["box","round"], {event: e, callback: inner_clicked});
 
 	function inner_clicked(v)
 	{
@@ -4563,12 +5130,9 @@ LGraphCanvas.prototype.getCanvasMenuOptions = function()
 
 	if(this.getExtraMenuOptions)
 	{
-		var extra = this.getExtraMenuOptions(this);
+		var extra = this.getExtraMenuOptions(this,options);
 		if(extra)
-		{
-			extra.push(null);
-			options = extra.concat( options );
-		}
+			options = options.concat( extra );
 	}
 
 	return options;
@@ -4582,9 +5146,12 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node)
 		options = node.getMenuOptions(this);
 	else
 		options = [
-			{content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeInputs },
-			{content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeOutputs },
+			{content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeInputs },
+			{content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.showMenuNodeOutputs },
 			null,
+			{content:"Properties", is_menu: true, callback: LGraphCanvas.onShowMenuNodeProperties },
+			null,
+			{content:"Mode", is_menu: true, callback: LGraphCanvas.onMenuNodeMode },
 			{content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse },
 			{content:"Pin", callback: LGraphCanvas.onMenuNodePin },
 			{content:"Colors", is_menu: true, callback: LGraphCanvas.onMenuNodeColors },
@@ -4624,7 +5191,7 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node)
 	return options;
 }
 
-LGraphCanvas.prototype.processContextualMenu = function(node, event)
+LGraphCanvas.prototype.processContextMenu = function(node, event)
 {
 	var that = this;
 	var win = this.getCanvasWindow();
@@ -4641,6 +5208,8 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event)
 	{
 		menu_info = slot.locked ? [ "Cannot remove" ] : { "Remove Slot": slot };
 		options.title = slot.input ? slot.input.type : slot.output.type;
+		if(slot.input && slot.input.type == LiteGraph.EVENT)
+			options.title = "Event";
 	}
 	else
 		menu_info = node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions();
@@ -4650,7 +5219,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event)
 	if(!menu_info)
 		return;
 
-	var menu = LiteGraph.createContextualMenu( menu_info, options, win);
+	var menu = LiteGraph.createContextMenu( menu_info, options, win);
 
 	function inner_option_clicked(v,e)
 	{
@@ -4667,7 +5236,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event)
 		}
 
 		if(v.callback)
-			return v.callback(node, e, menu, that, event );
+			return v.callback.call(that, node, e, menu, that, event );
 	}
 }
 
@@ -4796,7 +5365,7 @@ function num2hex(triplet) {
 
 /* LiteGraph GUI elements *************************************/
 
-LiteGraph.createContextualMenu = function(values,options, ref_window)
+LiteGraph.createContextMenu = function(values,options, ref_window)
 {
 	options = options || {};
 	this.options = options;
@@ -4804,11 +5373,19 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 	//allows to create graph canvas in separate window
 	ref_window = ref_window || window;
 
-	if(!options.from)
-		LiteGraph.closeAllContextualMenus();
+    if (!options.from)
+        LiteGraph.closeAllContextMenus( ref_window );
+    else {
+        //closing submenus
+        var menus = document.querySelectorAll(".graphcontextmenu");
+        for (var key in menus) {
+            if (menus[key].previousSibling == options.from)
+                menus[key].closeMenu();
+        }
+    }
 
 	var root = ref_window.document.createElement("div");
-	root.className = "graphcontextualmenu graphmenubar-panel";
+	root.className = "graphcontextmenu graphmenubar-panel";
 	this.root = root;
 	var style = root.style;
 
@@ -4822,12 +5399,13 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 	style.padding = "2px";
 	style.borderBottom = "2px solid #AAF";
 	style.backgroundColor = "#444";
+	style.zIndex = 10;
 
 	//title
 	if(options.title)
 	{
 		var element = document.createElement("div");
-		element.className = "graphcontextualmenu-title";
+		element.className = "graphcontextmenu-title";
 		element.innerHTML = options.title;
 		root.appendChild(element);
 	}
@@ -4843,7 +5421,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 
 		if(item == null)
 		{
-			element.className = "graphmenu-entry separator";
+			element.className += " separator";
 			root.appendChild(element);
 			continue;
 		}
@@ -4854,6 +5432,9 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 		if(item.disabled)
 			element.className += " disabled";
 
+		if(item.className)
+			element.className += " " + item.className;
+
 		element.style.cursor = "pointer";
 		element.dataset["value"] = typeof(item) == "string" ? item : item.value;
 		element.data = item;
@@ -4872,11 +5453,13 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 
 	root.addEventListener("mouseout", function(e) {
 		//console.log("OUT!");
-		var aux = e.toElement;
+		//check if mouse leave a inner element
+		var aux = e.relatedTarget || e.toElement;
 		while(aux != this && aux != ref_window.document)
 			aux = aux.parentNode;
 
-		if(aux == this) return;
+		if(aux == this)
+			return;
 		this.mouse_inside = false;
 		if(!this.block_close)
 			this.closeMenu();
@@ -4930,7 +5513,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 		}
 
 		if(close)
-			LiteGraph.closeAllContextualMenus();
+			LiteGraph.closeAllContextMenus( ref_window );
 			//root.closeMenu();
 	}
 
@@ -4949,9 +5532,11 @@ LiteGraph.createContextualMenu = function(values,options, ref_window)
 	return root;
 }
 
-LiteGraph.closeAllContextualMenus = function()
+LiteGraph.closeAllContextMenus = function( ref_window )
 {
-	var elements = document.querySelectorAll(".graphcontextualmenu");
+	ref_window = ref_window || window;
+
+	var elements = ref_window.document.querySelectorAll(".graphcontextmenu");
 	if(!elements.length) return;
 
 	var result = [];
@@ -5014,7 +5599,6 @@ if( !window["requestAnimationFrame"] )
 
     
-
diff --git a/doc/index.html b/doc/index.html index 0d9a3cbe7..b3482a793 100644 --- a/doc/index.html +++ b/doc/index.html @@ -6,7 +6,7 @@ - + @@ -14,9 +14,7 @@
- -

- +

API Docs for: @@ -27,68 +25,61 @@
- Show: - - - - - - - -
- - + Show: + + + + + + + +
+
-
+

Browse to a module or class using the sidebar to view its API documentation. @@ -109,7 +100,6 @@

-
diff --git a/src/litegraph.js b/src/litegraph.js index 087048b3b..918bb06db 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -1218,8 +1218,8 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) supported callbacks: + onAdded: when added to graph + onRemoved: when removed from graph - + onStart: when starts playing - + onStop: when stops playing + + 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 @@ -1359,6 +1359,8 @@ LGraphNode.prototype.configure = function(info) } } + if( this.onConfigured ) + this.onConfigured( info ); } /** @@ -1537,6 +1539,24 @@ LGraphNode.prototype.getInputInfo = function(slot) 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) + return null; + return this.graph.getNodeById( input.link.origin_id ); +} + /** * tells you info about an output connection (which node, type, etc) @@ -1963,7 +1983,7 @@ LGraphNode.prototype.findOutputSlot = function(name) * @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 {boolean} if it was connected succesfully */ -LGraphNode.prototype.connect = function( slot, node, target_slot ) +LGraphNode.prototype.connect = function( slot, target_node, target_slot ) { target_slot = target_slot || 0; @@ -1985,20 +2005,19 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) return false; } - if(node && node.constructor === Number) - node = this.graph.getNodeById( node ); - if(!node) + if(target_node && target_node.constructor === Number) + target_node = this.graph.getNodeById( target_node ); + if(!target_node) throw("Node not found"); //avoid loopback - if(node == this) + if(target_node == this) return false; - //if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode"); //you can specify the slot by name if(target_slot.constructor === String) { - target_slot = node.findInputSlot(target_slot); + target_slot = target_node.findInputSlot( target_slot ); if(target_slot == -1) { if(LiteGraph.debug) @@ -2011,13 +2030,13 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) //search for first slot with event? /* //create input for trigger - var input = node.addInput("onTrigger", LiteGraph.EVENT ); - target_slot = node.inputs.length - 1; //last one is the one created - node.mode = LiteGraph.ON_TRIGGER; + var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); + target_slot = target_node.inputs.length - 1; //last one is the one created + target_node.mode = LiteGraph.ON_TRIGGER; */ return false; } - else if( !node.inputs || target_slot >= node.inputs.length ) + else if( !target_node.inputs || target_slot >= target_node.inputs.length ) { if(LiteGraph.debug) console.log("Connect: Error, slot number not found"); @@ -2025,8 +2044,8 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) } //if there is something already plugged there, disconnect - if(node.inputs[ target_slot ].link != null ) - node.disconnectInput( target_slot ); + if(target_node.inputs[ target_slot ].link != null ) + target_node.disconnectInput( target_slot ); //why here?? this.setDirtyCanvas(false,true); @@ -2035,36 +2054,36 @@ LGraphNode.prototype.connect = function( slot, node, target_slot ) var output = this.outputs[slot]; //allows nodes to block connection - if(node.onConnectInput) - if( node.onConnectInput( target_slot, output.type, output ) === false) + if(target_node.onConnectInput) + if( target_node.onConnectInput( target_slot, output.type, output ) === false) return false; - var input = node.inputs[target_slot]; + var input = target_node.inputs[target_slot]; if( LiteGraph.isValidConnection( output.type, input.type) ) { - var link = { + var link_info = { id: this.graph.last_link_id++, origin_id: this.id, origin_slot: slot, - target_id: node.id, + target_id: target_node.id, target_slot: target_slot }; //add to graph links list - this.graph.links[ link.id ] = link; + this.graph.links[ link_info.id ] = link_info; //connect in output if( output.links == null ) output.links = []; - output.links.push( link.id ); + output.links.push( link_info.id ); //connect in input - node.inputs[target_slot].link = link.id; + target_node.inputs[target_slot].link = link_info.id; if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT, slot ); - if(node.onConnectionsChange) - node.onConnectionsChange( LiteGraph.OUTPUT, target_slot ); + this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info ); //link_info has been created now, so its updated + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info ); } this.setDirtyCanvas(false,true); @@ -2080,7 +2099,7 @@ LGraphNode.prototype.connect = function( slot, node, target_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 succesfully */ -LGraphNode.prototype.disconnectOutput = function(slot, target_node) +LGraphNode.prototype.disconnectOutput = function( slot, target_node ) { if( slot.constructor === String ) { @@ -2123,6 +2142,10 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) output.links.splice(i,1); //remove here target_node.inputs[ link_info.target_slot ].link = null; //remove there delete this.graph.links[ link_id ]; //remove the link from the links pool + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info ); break; } } @@ -2138,10 +2161,15 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) if(target_node) target_node.inputs[ link_info.target_slot ].link = null; //remove other side link delete this.graph.links[ link_id ]; //remove the link from the links pool + if(target_node.onConnectionsChange) + target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info ); //link_info hasnt been modified so its ok + if(this.onConnectionsChange) + this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info ); } output.links = null; } + this.setDirtyCanvas(false,true); this.graph.connectionChange( this ); return true; @@ -2153,7 +2181,7 @@ LGraphNode.prototype.disconnectOutput = function(slot, target_node) * @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 succesfully */ -LGraphNode.prototype.disconnectInput = function(slot) +LGraphNode.prototype.disconnectInput = function( slot ) { //seek for the output slot if( slot.constructor === String ) @@ -2184,15 +2212,15 @@ LGraphNode.prototype.disconnectInput = function(slot) var link_info = this.graph.links[ link_id ]; if( link_info ) { - var node = this.graph.getNodeById( link_info.origin_id ); - if(!node) + var target_node = this.graph.getNodeById( link_info.origin_id ); + if(!target_node) return false; - var output = node.outputs[ link_info.origin_slot ]; + var output = target_node.outputs[ link_info.origin_slot ]; if(!output || !output.links || output.links.length == 0) return false; - //check outputs + //search in the inputs list for this link for(var i = 0, l = output.links.length; i < l; i++) { var link_id = output.links[i]; @@ -2204,10 +2232,10 @@ LGraphNode.prototype.disconnectInput = function(slot) } } - if(this.onConnectionsChange) - this.onConnectionsChange( LiteGraph.OUTPUT ); - if(node.onConnectionsChange) - node.onConnectionsChange( LiteGraph.INPUT); + if( this.onConnectionsChange ) + this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info ); + if( target_node.onConnectionsChange ) + target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info ); } this.setDirtyCanvas(false,true); @@ -2878,7 +2906,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) var n = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes ); var skip_dragging = false; - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); if(e.which == 1) //left button mouse { @@ -3012,7 +3040,7 @@ LGraphCanvas.prototype.processMouseDown = function(e) } else if (e.which == 3) //right button { - this.processContextualMenu(n,e); + this.processContextMenu(n,e); } //TODO @@ -4586,7 +4614,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) if(values[i]) entries[ i ] = { value: values[i], content: values[i] , is_menu: true }; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e) { @@ -4596,7 +4624,7 @@ LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) for(var i in node_types) values.push( { content: node_types[i].title, value: node_types[i].type }); - LiteGraph.createContextualMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); + LiteGraph.createContextMenu(values, {event: e, callback: inner_create, from: menu}, ref_window); return false; } @@ -4653,7 +4681,7 @@ LGraphCanvas.showMenuNodeInputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked(v, e, prev) { @@ -4710,7 +4738,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}, ref_window); function inner_clicked( v, e, prev ) { @@ -4730,7 +4758,7 @@ LGraphCanvas.showMenuNodeOutputs = function(node, e, prev_menu) var entries = []; for(var i in value) entries.push({content: i, value: value[i]}); - LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); return false; } else @@ -4757,7 +4785,7 @@ LGraphCanvas.onShowMenuNodeProperties = function(node,e, prev_menu) if(!entries.length) return; - var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); + var menu = LiteGraph.createContextMenu(entries, {event: e, callback: inner_clicked, from: prev_menu},ref_window); function inner_clicked( v, e, prev ) { @@ -4785,7 +4813,19 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options var info = null; if(node.getPropertyInfo) info = node.getPropertyInfo(property); - if(info.type) + if(node.properties_info) + { + for(var i = 0; i < node.properties_info.length; ++i) + { + if( node.properties_info[i].name == property ) + { + info = node.properties_info[i]; + break; + } + } + } + + if(info !== undefined && info !== null && info.type ) type = info.type; var input_html = ""; @@ -4802,6 +4842,10 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options } input_html += ""; } + else if(type == "boolean") + { + input_html = ""; + } var dialog = document.createElement("div"); @@ -4812,21 +4856,35 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options { var input = dialog.querySelector("select"); input.addEventListener("change", function(e){ - var index = e.target.value; - setValue( e.options[e.selectedIndex].value ); + setValue( e.target.value ); + //var index = e.target.value; + //setValue( e.options[e.selectedIndex].value ); }); } + else if(type == "boolean") + { + var input = dialog.querySelector("input"); + if(input) + { + input.addEventListener("click", function(e){ + setValue( !!input.checked ); + }); + } + } else { var input = dialog.querySelector("input"); - input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; - input.addEventListener("keydown", function(e){ - if(e.keyCode != 13) - return; - inner(); - e.preventDefault(); - e.stopPropagation(); - }); + if(input) + { + input.value = node.properties[ property ] !== undefined ? node.properties[ property ] : ""; + input.addEventListener("keydown", function(e){ + if(e.keyCode != 13) + return; + inner(); + e.preventDefault(); + e.stopPropagation(); + }); + } } var rect = this.canvas.getClientRects()[0]; @@ -4863,9 +4921,11 @@ LGraphCanvas.prototype.showEditPropertyValue = function( node, property, options function setValue(value) { if(typeof( node.properties[ property ] ) == "number") - node.properties[ property ] = Number(value); - else - node.properties[ property ] = value; + value = Number(value); + node.properties[ property ] = value; + + if(node.onPropertyChanged) + node.onPropertyChanged( property, value ); dialog.parentNode.removeChild( dialog ); node.setDirtyCanvas(true,true); } @@ -4884,7 +4944,7 @@ LGraphCanvas.onMenuNodePin = function(node) LGraphCanvas.onMenuNodeMode = function(node, e, prev_menu) { - LiteGraph.createContextualMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(["Always","On Event","Never"], {event: e, callback: inner_clicked, from: prev_menu}); function inner_clicked(v) { @@ -4912,7 +4972,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) var value = {value:i, content:""+i+""}; values.push(value); } - LiteGraph.createContextualMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); + LiteGraph.createContextMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); function inner_clicked(v) { @@ -4931,7 +4991,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) LGraphCanvas.onMenuNodeShapes = function(node,e) { - LiteGraph.createContextualMenu(["box","round"], {event: e, callback: inner_clicked}); + LiteGraph.createContextMenu(["box","round"], {event: e, callback: inner_clicked}); function inner_clicked(v) { @@ -5046,7 +5106,7 @@ LGraphCanvas.prototype.getNodeMenuOptions = function(node) return options; } -LGraphCanvas.prototype.processContextualMenu = function(node, event) +LGraphCanvas.prototype.processContextMenu = function(node, event) { var that = this; var win = this.getCanvasWindow(); @@ -5074,7 +5134,7 @@ LGraphCanvas.prototype.processContextualMenu = function(node, event) if(!menu_info) return; - var menu = LiteGraph.createContextualMenu( menu_info, options, win); + var menu = LiteGraph.createContextMenu( menu_info, options, win); function inner_option_clicked(v,e) { @@ -5220,7 +5280,7 @@ function num2hex(triplet) { /* LiteGraph GUI elements *************************************/ -LiteGraph.createContextualMenu = function(values,options, ref_window) +LiteGraph.createContextMenu = function(values,options, ref_window) { options = options || {}; this.options = options; @@ -5229,10 +5289,10 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) ref_window = ref_window || window; if (!options.from) - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); else { //closing submenus - var menus = document.querySelectorAll(".graphcontextualmenu"); + var menus = document.querySelectorAll(".graphcontextmenu"); for (var key in menus) { if (menus[key].previousSibling == options.from) menus[key].closeMenu(); @@ -5240,7 +5300,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } var root = ref_window.document.createElement("div"); - root.className = "graphcontextualmenu graphmenubar-panel"; + root.className = "graphcontextmenu graphmenubar-panel"; this.root = root; var style = root.style; @@ -5254,12 +5314,13 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) style.padding = "2px"; style.borderBottom = "2px solid #AAF"; style.backgroundColor = "#444"; + style.zIndex = 10; //title if(options.title) { var element = document.createElement("div"); - element.className = "graphcontextualmenu-title"; + element.className = "graphcontextmenu-title"; element.innerHTML = options.title; root.appendChild(element); } @@ -5367,7 +5428,7 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) } if(close) - LiteGraph.closeAllContextualMenus( ref_window ); + LiteGraph.closeAllContextMenus( ref_window ); //root.closeMenu(); } @@ -5386,11 +5447,11 @@ LiteGraph.createContextualMenu = function(values,options, ref_window) return root; } -LiteGraph.closeAllContextualMenus = function( ref_window ) +LiteGraph.closeAllContextMenus = function( ref_window ) { ref_window = ref_window || window; - var elements = ref_window.document.querySelectorAll(".graphcontextualmenu"); + var elements = ref_window.document.querySelectorAll(".graphcontextmenu"); if(!elements.length) return; var result = []; diff --git a/src/nodes/audio.js b/src/nodes/audio.js new file mode 100644 index 000000000..947b470ae --- /dev/null +++ b/src/nodes/audio.js @@ -0,0 +1,646 @@ +//not tested nor finished + +(function( global ) +{ + +function LGAudio() +{ + this.properties = { + src: "demodata/audio.wav", + gain: 0.5, + loop: true + }; + + this._loading_audio = false; + this._audio_buffer = null; + this._audionodes = []; + + this.addOutput( "out", "audio" ); + this.addInput( "gain", "number" ); + + //init context + var context = LGAudio.getAudioContext(); + + //create gain node + this.audionode = context.createGain(); + this.audionode.graphnode = this; + this.audionode.gain.value = this.properties.gain; + + //debug + if(this.properties.src) + this.loadSound( this.properties.src ); +} + +LGAudio.getAudioContext = function() +{ + if(!this._audio_context) + { + window.AudioContext = window.AudioContext || window.webkitAudioContext; + if(!window.AudioContext) + { + console.error("AudioContext not supported by browser"); + return null; + } + this._audio_context = new AudioContext(); + this._audio_context.onmessage = function(msg) { console.log("msg",msg);}; + this._audio_context.onended = function(msg) { console.log("ended",msg);}; + this._audio_context.oncomplete = function(msg) { console.log("complete",msg);}; + } + + //in case it crashes + if(this._audio_context.state == "suspended") + this._audio_context.resume(); + return this._audio_context; +} + +LGAudio.connect = function( audionodeA, audionodeB ) +{ + audionodeA.connect( audionodeB ); + + /* + if(!nodeA.outputs) + nodeA.outputs = []; + nodeA.outputs.push( nodeB ); + if(!nodeB.inputs) + nodeB.inputs = []; + nodeB.inputs.push( nodeA ); + */ +} + +LGAudio.disconnect = function( audionodeA, audionodeB ) +{ + audionodeA.disconnect( audionodeB ); + + /* + if(nodeA.outputs) + { + var index = nodeA.outputs.indexOf( nodeB ); + if(index != -1) + nodeA.outputs.splice(index,1); + } + if(nodeB.inputs) + { + var index = nodeB.inputs.indexOf( nodeA ); + if(index != -1) + nodeB.inputs.splice(index,1); + } + */ +} + +LGAudio.prototype.onAdded = function(graph) +{ + if(graph.status === LGraph.STATUS_RUNNING) + this.onStart(); +} + +LGAudio.prototype.onStart = function() +{ + if(!this._audio_buffer) + return; + + this.playBuffer( this._audio_buffer ); +} + +LGAudio.prototype.onStop = function() +{ + this.stopAllSounds(); +} + +LGAudio.prototype.onRemoved = function() +{ + this.stopAllSounds(); +} + +LGAudio.prototype.stopAllSounds = function() +{ + //iterate and stop + for(var i = 0; i < this._audionodes.length; ++i ) + { + this._audionodes[i].stop(); + //this._audionodes[i].disconnect( this.audionode ); + } + this._audionodes.length = 0; +} + +LGAudio.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if( v !== undefined ) + this.audionode.gain.value = v; +} + +LGAudio.prototype.onAction = function(event) +{ + if(this._audio_buffer) + { + if(event == "Play") + this.playBuffer(this._audio_buffer); + else if(event == "Stop") + this.stopAllSounds(); + } +} + +LGAudio.prototype.onPropertyChanged = function( name, value ) +{ + if( name == "src" ) + this.loadSound( value ); + else if(name == "gain") + this.audionode.gain.value = value; +} + +LGAudio.prototype.playBuffer = function( buffer ) +{ + var that = this; + var context = LGAudio.getAudioContext(); + + //create a new audionode (this is mandatory, AudioAPI doesnt like to reuse old ones) + var audionode = context.createBufferSource(); //create a AudioBufferSourceNode + audionode.graphnode = this; + audionode.buffer = buffer; + audionode.loop = this.properties.loop; + this._audionodes.push( audionode ); + audionode.connect( this.audionode ); //connect to gain + this._audionodes.push( audionode ); + + audionode.onended = function() + { + console.log("ended!"); + that.trigger("ended"); + //remove + var index = that._audionodes.indexOf( audionode ); + if(index != -1) + that._audionodes.splice(index,1); + } + + audionode.start(); + return audionode; +} + +LGAudio.prototype.onConnectionsChange = function( connection, slot, connected, link_info ) +{ + //only process the outputs events + if(connection != LiteGraph.OUTPUT) + return; + + var target_node = null; + if( link_info ) + target_node = this.graph.getNodeById( link_info.target_id ); + + if( !target_node ) + return; + + if( connected ) + { + if(target_node.connectAudioToSlot) + target_node.connectAudioToSlot( this.audionode, link_info.target_slot ); + else + LGAudio.connect( this.audionode, target_node.audionode ); + } + else + { + if(target_node.disconnectAudioFromSlot) + target_node.disconnectAudioFromSlot( this.audionode, link_info.target_slot ); + else + LGAudio.disconnect( this.audionode, target_node.audionode ); + } +} + +LGAudio.prototype.loadSound = function( url ) +{ + var that = this; + + //kill previous load + if(this._request) + { + this._request.abort(); + this._request = null; + } + + this._audio_buffer = null; + this._loading_audio = false; + + if(!url) + return; + + //load new sample + var request = new XMLHttpRequest(); + request.open('GET', url, true); + request.responseType = 'arraybuffer'; + this._loading_audio = true; + this._request = request; + + var context = LGAudio.getAudioContext(); + + // Decode asynchronously + request.onload = function() { + context.decodeAudioData( request.response, function(buffer) { + that._audio_buffer = buffer; + that._loading_audio = false; + //if is playing, then play it + if(that.graph && that.graph.status === LGraph.STATUS_RUNNING) + that.onStart(); + }, onError); + } + request.send(); + + function onError(err) + { + console.log("Audio loading sample error:",err); + } +} + +LGAudio.prototype.onGetInputs = function() +{ + return [["Play",LiteGraph.ACTION],["Stop",LiteGraph.ACTION]]; +} + +LGAudio.prototype.onGetOutputs = function() +{ + return [["ended",LiteGraph.EVENT]]; +} + + +LGAudio.title = "Source"; +LGAudio.desc = "Plays audio"; +LiteGraph.registerNodeType("audio/source", LGAudio); +global.LGAudio = LGAudio; + +//***************************************************** + +function LGAudioAnalyser() +{ + this.properties = { + fftSize: 2048, + minDecibels: -100, + maxDecibels: -10, + smoothingTimeConstant: 0.5 + }; + + var context = LGAudio.getAudioContext(); + + this.audionode = context.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","FFT"); + //this.addOutput("time","freq"); + + this._freq_bin = null; + this._time_bin = null; +} + +LGAudioAnalyser.prototype.onPropertyChanged = function(name, value) +{ + this.audionode[ name ] = value; +} + +LGAudioAnalyser.prototype.onExecute = function() +{ + if(this.isOutputConnected(0)) + { + //send FFT + var bufferLength = this.audionode.frequencyBinCount; + if( !this._freq_bin || this._freq_bin.length != bufferLength ) + this._freq_bin = new Uint8Array( bufferLength ); + this.audionode.getByteFrequencyData( this._freq_bin ); + this.setOutputData(0,this._freq_bin); + } + + //properties + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v !== undefined) + this.audionode[ input.name ].value = v; + } + + //time domain + //this.audionode.getFloatTimeDomainData( dataArray ); +} + +LGAudioAnalyser.prototype.onGetInputs = function() +{ + return [["minDecibels","number"],["maxDecibels","number"],["smoothingTimeConstant","number"]]; +} + + +LGAudioAnalyser.title = "Analyser"; +LGAudioAnalyser.desc = "Audio Analyser"; +LiteGraph.registerNodeType( "audio/analyser", LGAudioAnalyser ); + + + +//***************************************************** + +//this function helps creating wrappers to existing classes +function createAudioNodeWrapper( class_object ) +{ + class_object.prototype.onPropertyChanged = function(name, value) + { + if( this.audionode[ name ] === undefined ) + return; + + if( this.audionode[ name ].value !== undefined ) + this.audionode[ name ].value = value; + else + this.audionode[ name ] = value; + } + + class_object.prototype.onConnectionsChange = function( connection, slot, connected, link_info ) + { + //only process the outputs events + if(connection != LiteGraph.OUTPUT) + return; + + var target_node = null; + if( link_info ) + target_node = this.graph.getNodeById( link_info.target_id ); + if( !target_node ) + return; + + if( connected ) + { + if(target_node.connectAudioToSlot) + target_node.connectAudioToSlot( this.audionode, link_info.target_slot ); + else + LGAudio.connect( this.audionode, target_node.audionode ); + } + else + { + if(target_node.disconnectAudioFromSlot) + target_node.disconnectAudioFromSlot( this.audionode, link_info.target_slot ); + else + LGAudio.disconnect( this.audionode, target_node.audionode ); + } + } +} + + +//***************************************************** + +function LGAudioGain() +{ + //default + this.properties = { + gain: 1 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + this.addInput("in","audio"); + this.addInput("gain","number"); + this.addOutput("out","audio"); +} + +LGAudioGain.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +createAudioNodeWrapper( LGAudioGain ); + +LGAudioGain.title = "Gain"; +LGAudioGain.desc = "Audio gain"; +LiteGraph.registerNodeType("audio/gain", LGAudioGain); + + +function LGAudioMixer() +{ + //default + this.properties = { + gain1: 0.5, + gain2: 0.5 + }; + + this.audionode = LGAudio.getAudioContext().createGain(); + + this.audionode1 = LGAudio.getAudioContext().createGain(); + this.audionode1.gain.value = this.properties.gain1; + this.audionode2 = LGAudio.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"); +} + +LGAudioMixer.prototype.connectAudioToSlot = function( audionode, slot ) +{ + if(slot == 0) + LGAudio.connect( audionode, this.audionode1 ); + else if(slot == 2) + LGAudio.connect( audionode, this.audionode2 ); +} + +LGAudioMixer.prototype.disconnectAudioFromSlot = function( audionode, slot ) +{ + if(slot == 0) + LGAudio.disconnect( audionode, this.audionode1 ); + else if(slot == 2) + LGAudio.disconnect( audionode, this.audionode2 ); +} + +LGAudioMixer.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + if(input.type == "audio") + continue; + + var v = this.getInputData(i); + if(v === undefined) + continue; + + if(i == 1) + this.audionode1.gain.value = v; + else if(i == 3) + this.audionode2.gain.value = v; + } +} + +createAudioNodeWrapper( LGAudioMixer ); + +LGAudioMixer.title = "Mixer"; +LGAudioMixer.desc = "Audio mixer"; +LiteGraph.registerNodeType("audio/mixer", LGAudioMixer); + + +function LGAudioDelay() +{ + //default + this.properties = { + time: 5 + }; + + this.audionode = LGAudio.getAudioContext().createDelay( this.properties.time ); + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +createAudioNodeWrapper( LGAudioDelay ); + +LGAudioDelay.prototype.onPropertyChanged = function( name, value ) +{ + if(name == "time") + { + if(value > 500) + value = 500; + if(value < 0) + value = 0; + + var input_node = this.getInputNode(0); + var output_nodes = this.getOutputNodes(0); + + if(input_node) + input_node.audionode.disconnect( this.audionode ); + if(output_nodes) + { + for(var i = 0; i < output_nodes.length; ++i) + this.audionode.disconnect( output_nodes[i].audionode ); + } + + this.audionode = LGAudio.getAudioContext().createDelay( value ); + + if(input_node) + input_node.audionode.connect( this.audionode ); + if(output_nodes) + { + for(var i = 0; i < output_nodes.length; ++i) + this.audionode.connect( output_nodes[i].audionode ); + } + } +} + +LGAudioDelay.title = "Delay"; +LGAudioDelay.desc = "Audio delay"; +LiteGraph.registerNodeType("audio/delay", LGAudioDelay); + + +function LGAudioBiquadFilter() +{ + //default + this.properties = { + frequency: 350, + detune: 0, + Q: 1 + }; + this.addProperty("type","lowpass","enum",{values:["lowpass","highpass","bandpass","lowshelf","highshelf","peaking","notch","allpass"]}); + + //create node + this.audionode = LGAudio.getAudioContext().createBiquadFilter(); + + //slots + this.addInput("in","audio"); + this.addOutput("out","audio"); +} + +LGAudioBiquadFilter.prototype.onExecute = function() +{ + if(!this.inputs || !this.inputs.length) + return; + + for(var i = 1; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var v = this.getInputData(i); + if(v !== undefined) + this.audionode[ input.name ].value = v; + } +} + +LGAudioBiquadFilter.prototype.onGetInputs = function() +{ + return [["frequency","number"],["detune","number"],["Q","number"]]; +} + +createAudioNodeWrapper( LGAudioBiquadFilter ); + +LGAudioBiquadFilter.title = "BiquadFilter"; +LGAudioBiquadFilter.desc = "Audio filter"; +LiteGraph.registerNodeType("audio/biquadfilter", LGAudioBiquadFilter); + + +//***************************************************** + +function LGAudioDestination() +{ + this.audionode = LGAudio.getAudioContext().destination; + this.addInput("in","audio"); +} + + +LGAudioDestination.title = "Destination"; +LGAudioDestination.desc = "Audio output"; +LiteGraph.registerNodeType("audio/destination", LGAudioDestination); + + + +//EXTRA + + +function LGAudioVisualization() +{ + this.addInput("freqs","FFT"); + this.size = [300,200]; + this._last_buffer = null; +} + +LGAudioVisualization.prototype.onExecute = function() +{ + this._last_buffer = this.getInputData(0); +} + +LGAudioVisualization.prototype.onDrawForeground = function(ctx) +{ + if(!this._last_buffer) + return; + + var buffer = this._last_buffer; + + var delta = buffer.length / this.size[0]; + var h = this.size[1]; + + ctx.fillStyle = "black"; + ctx.fillRect(0,0,this.size[0],this.size[1]); + ctx.strokeStyle = "white"; + ctx.beginPath(); + var x = 0; + for(var i = 0; i < buffer.length; i+= delta) + { + ctx.moveTo(x,h); + ctx.lineTo(x,h - (buffer[i|0]/255) * h); + x++; + } + ctx.stroke(); +} + +LGAudioVisualization.title = "Visualization"; +LGAudioVisualization.desc = "Audio Visualization"; +LiteGraph.registerNodeType("audio/visualization", LGAudioVisualization); + + + +})( window ); \ No newline at end of file diff --git a/src/nodes/base.js b/src/nodes/base.js index 8862201c6..d934e77b9 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -2,6 +2,25 @@ (function(){ +//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() { diff --git a/src/nodes/glfx.js b/src/nodes/glfx.js index 5ff74a723..b453f804d 100755 --- a/src/nodes/glfx.js +++ b/src/nodes/glfx.js @@ -61,7 +61,7 @@ if(typeof(LiteGraph) != "undefined") gl.disable( gl.DEPTH_TEST ); var mesh = Mesh.getScreenQuad(); var shader = LGraphFXLens._shader; - var camera = LS.Renderer._current_camera; + //var camera = LS.Renderer._current_camera; this._tex.drawTo( function() { tex.bind(0); @@ -375,7 +375,11 @@ if(typeof(LiteGraph) != "undefined") gl.disable( gl.BLEND ); gl.disable( gl.DEPTH_TEST ); var mesh = Mesh.getScreenQuad(); - var camera = LS.Renderer._current_camera; + var camera = window.LS ? LS.Renderer._current_camera : null; + if(camera) + camera_planes = [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far]; + else + camera_planes = [1,100]; var noise = null; if(fx == "noise") @@ -386,7 +390,7 @@ if(typeof(LiteGraph) != "undefined") if(fx == "noise") noise.bind(1); - shader.uniforms({u_texture:0, u_noise:1, u_size: [tex.width, tex.height], u_rand:[ Math.random(), Math.random() ], u_value1: value1, u_value2: value2, u_camera_planes: [LS.Renderer._current_camera.near, LS.Renderer._current_camera.far] }) + shader.uniforms({u_texture:0, u_noise:1, u_size: [tex.width, tex.height], u_rand:[ Math.random(), Math.random() ], u_value1: value1, u_value2: value2, u_camera_planes: camera_planes }) .draw(mesh); }); diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index ea607726e..afd355dd0 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -494,6 +494,8 @@ if(typeof(LiteGraph) != "undefined") this.boxcolor = "#FF0000"; return; } + this.boxcolor = "#FF0000"; + this._shader_code = (uvcode + "|" + pixelcode); shader = this._shader; } @@ -539,8 +541,10 @@ if(typeof(LiteGraph) != "undefined") void main() {\n\ vec2 uv = v_coord;\n\ UV_CODE;\n\ - vec3 color = texture2D(u_texture, uv).rgb;\n\ - vec3 colorB = texture2D(u_textureB, uv).rgb;\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\ @@ -1846,13 +1850,13 @@ if(typeof(LiteGraph) != "undefined") } this._waiting_confirmation = true; + var that = this; // Not showing vendor prefixes. navigator.getUserMedia({video: true}, this.streamReady.bind(this), onFailSoHard); - var that = this; function onFailSoHard(e) { - trace('Webcam rejected', e); + console.log('Webcam rejected', e); that._webcam_stream = false; that.box_color = "red"; }; @@ -1942,7 +1946,84 @@ if(typeof(LiteGraph) != "undefined") LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam ); - //Cubemap reader + 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 }; + + if(!LGraphTextureMatte._shader) + LGraphTextureMatte._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureMatte.pixel_shader ); + } + + 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; + + 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 ); + + //*********************************** + //Cubemap reader (to pass a cubemap to a node that requires cubemaps and no images) function LGraphCubemap() { this.addOutput("Cubemap","Cubemap"); diff --git a/src/nodes/image.js b/src/nodes/image.js index e6c1e530e..81fe0b496 100755 --- a/src/nodes/image.js +++ b/src/nodes/image.js @@ -42,7 +42,7 @@ GraphicsImage.prototype.onExecute = function() this.img.dirty = false; } -GraphicsImage.prototype.onPropertyChange = function(name,value) +GraphicsImage.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; if (name == "url" && value != "") @@ -401,7 +401,7 @@ ImageCrop.prototype.onExecute = function() this.setOutputData(0,null); } -ImageCrop.prototype.onPropertyChange = function(name,value) +ImageCrop.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; @@ -524,7 +524,7 @@ ImageVideo.prototype.loadVideo = function(url) //document.body.appendChild(this.video); } -ImageVideo.prototype.onPropertyChange = function(name,value) +ImageVideo.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; if (name == "url" && value != "") diff --git a/src/nodes/interface.js b/src/nodes/interface.js index 228edd563..8f88a0642 100755 --- a/src/nodes/interface.js +++ b/src/nodes/interface.js @@ -213,12 +213,12 @@ WidgetKnob.prototype.onWidget = function(e,widget) { if(widget.name=="increase") - this.onPropertyChange("size", this.properties.size + 10); + this.onPropertyChanged("size", this.properties.size + 10); else if(widget.name=="decrease") - this.onPropertyChange("size", this.properties.size - 10); + this.onPropertyChanged("size", this.properties.size - 10); } - WidgetKnob.prototype.onPropertyChange = function(name,value) + WidgetKnob.prototype.onPropertyChanged = function(name,value) { if(name=="wcolor") this.properties[name] = value; @@ -351,7 +351,7 @@ //this.oldmouse = null; } - WidgetHSlider.prototype.onPropertyChange = function(name,value) + WidgetHSlider.prototype.onPropertyChanged = function(name,value) { if(name=="wcolor") this.properties[name] = value; @@ -604,7 +604,7 @@ } }, - onPropertyChange: function(name,value) + onPropertyChanged: function(name,value) { this.properties[name] = value; return true; @@ -703,7 +703,7 @@ } } - WidgetText.prototype.onPropertyChange = function(name,value) + WidgetText.prototype.onPropertyChanged = function(name,value) { this.properties[name] = value; this.str = typeof(value) == 'number' ? value.toFixed(3) : value; diff --git a/utils/deploy_files.txt b/utils/deploy_files.txt index 24ca35587..1229287df 100755 --- a/utils/deploy_files.txt +++ b/utils/deploy_files.txt @@ -9,3 +9,4 @@ ../src/nodes/gltextures.js ../src/nodes/glfx.js ../src/nodes/midi.js +../src/nodes/audio.js