diff --git a/build/litegraph.js b/build/litegraph.js index 53e255dd0..35b413bc9 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -18,9 +18,10 @@ var LiteGraph = { NODE_WIDTH: 140, NODE_MIN_WIDTH: 50, NODE_COLLAPSED_RADIUS: 10, + NODE_COLLAPSED_WIDTH: 80, CANVAS_GRID_SIZE: 10, - NODE_DEFAULT_COLOR: "#888", - NODE_DEFAULT_BGCOLOR: "#333", + NODE_DEFAULT_COLOR: "#999", + NODE_DEFAULT_BGCOLOR: "#444", NODE_DEFAULT_BOXCOLOR: "#AEF", NODE_DEFAULT_SHAPE: "box", MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops @@ -1167,7 +1168,7 @@ LGraphNode.prototype.computeSize = function(minHeight) //returns the bounding of the object, used for rendering purposes LGraphNode.prototype.getBounding = function() { - return new Float32Array([this.pos[0] - 4, this.pos[1] - LGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]); + return new Float32Array([this.pos[0] - 4, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]); } //checks if a point is inside the shape of a node @@ -1176,7 +1177,8 @@ LGraphNode.prototype.isPointInsideNode = function(x,y) var margin_top = this.graph.isLive() ? 0 : 20; if(this.flags.collapsed) { - if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if( isInsideRectangle(x,y, this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_COLLAPSED_WIDTH, LiteGraph.NODE_TITLE_HEIGHT) ) return true; } else if (this.pos[0] - 4 < x && (this.pos[0] + this.size[0] + 4) > x @@ -1385,7 +1387,13 @@ LGraphNode.prototype.disconnectInput = function(slot) LGraphNode.prototype.getConnectionPos = function(is_input,slot_number) { if(this.flags.collapsed) - return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5]; + { + if(is_input) + return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + else + return [this.pos[0] + LiteGraph.NODE_COLLAPSED_WIDTH, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + //return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5]; + } if(is_input && slot_number == -1) { @@ -1402,331 +1410,6 @@ LGraphNode.prototype.getConnectionPos = function(is_input,slot_number) return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT]; } -/* Renders the LGraphNode on the canvas */ -LGraphNode.prototype.draw = function(ctx, canvasrender) -{ - var glow = false; - - var color = this.color || LiteGraph.NODE_DEFAULT_COLOR; - //if (this.selected) color = "#88F"; - - var render_title = true; - if(this.flags.skip_title_render || this.graph.isLive()) - render_title = false; - if(this.mouseOver) - render_title = true; - - //shadow and glow - if (this.mouseOver) glow = true; - - if(this.selected) - { - /* - ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 1; - */ - } - else if(canvasrender.render_shadows) - { - ctx.shadowColor = "#111"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 4; - } - else - ctx.shadowColor = "transparent"; - - //only render if it forces it to do it - if(canvasrender.live_mode) - { - if(!this.flags.collapsed) - { - ctx.shadowColor = "transparent"; - if(this.onDrawBackground) - this.onDrawBackground(ctx); - if(this.onDrawForeground) - this.onDrawForeground(ctx); - } - - return; - } - - //draw in collapsed form - if(this.flags.collapsed) - { - if(!this.onDrawCollapsed || this.onDrawCollapsed(ctx) == false) - this.drawNodeCollapsed(ctx,color,this.bgcolor); - return; - } - - //clip if required (mask) - if(this.flags.clip_area) - { - ctx.save(); - if(this.shape == null || this.shape == "box") - { - ctx.beginPath(); - ctx.rect(0,0,this.size[0], this.size[1]); - } - else if (this.shape == "round") - { - ctx.roundRect(0,0,this.size[0], this.size[1],10); - } - else if (this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2); - } - ctx.clip(); - } - - //draw shape - this.drawNodeShape(ctx,color, this.bgcolor, !render_title, this.selected ); - ctx.shadowColor = "transparent"; - - //connection slots - ctx.textAlign = "left"; - ctx.font = "12px Arial"; - - var render_text = this.graph.config.canvas_scale > 0.6; - - //input connection slots - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var slot = this.inputs[i]; - - ctx.globalAlpha = 1.0; - if (canvasrender.connecting_node != null && canvasrender.connecting_output.type != 0 && this.inputs[i].type != 0 && canvasrender.connecting_output.type != this.inputs[i].type) - ctx.globalAlpha = 0.4; - - ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; - - var pos = this.getConnectionPos(true,i); - pos[0] -= this.pos[0]; - pos[1] -= this.pos[1]; - - ctx.beginPath(); - - 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); - - ctx.fill(); - - //render name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = color; - ctx.fillText(text,pos[0] + 10,pos[1] + 5); - } - } - } - - //output connection slots - if(canvasrender.connecting_node) - ctx.globalAlpha = 0.4; - - ctx.lineWidth = 1; - - ctx.textAlign = "right"; - ctx.strokeStyle = "black"; - if(this.outputs) - for(var i = 0; i < this.outputs.length; i++) - { - var slot = this.outputs[i]; - - var pos = this.getConnectionPos(false,i); - pos[0] -= this.pos[0]; - pos[1] -= this.pos[1]; - - ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA"; - ctx.beginPath(); - //ctx.rect( this.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); - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - ctx.stroke(); - - //render output name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = color; - ctx.fillText(text, pos[0] - 10,pos[1] + 5); - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1.0; - - if(this.onDrawForeground) - this.onDrawForeground(ctx); - - if(this.flags.clip_area) - ctx.restore(); -} - -/* Renders the node shape */ -LGraphNode.prototype.drawNodeShape = function(ctx, fgcolor, bgcolor, no_title, selected ) -{ - //bg rect - ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - /* gradient test - var grad = ctx.createLinearGradient(0,0,0,this.size[1]); - grad.addColorStop(0, "#AAA"); - grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR); - grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR); - ctx.fillStyle = grad; - */ - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - - //render depending on shape - if(this.shape == null || this.shape == "box") - { - if(selected) - { - ctx.strokeStyle = "#CCC"; - ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5,this.size[0]+2, no_title ? (this.size[1]+2) : (this.size[1] + title_height+2) ); - ctx.strokeStyle = fgcolor; - } - - ctx.beginPath(); - ctx.rect(0.5,no_title ? 0.5 : -title_height + 0.5,this.size[0], no_title ? this.size[1] : this.size[1] + title_height); - } - else if (this.shape == "round") - { - ctx.roundRect(0,no_title ? 0 : -title_height,this.size[0], no_title ? this.size[1] : this.size[1] + title_height, 10); - } - else if (this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2); - } - - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.stroke(); - - //image - if (this.bgImage && this.bgImage.width) - ctx.drawImage( this.bgImage, (this.size[0] - this.bgImage.width) * 0.5 , (this.size[1] - this.bgImage.height) * 0.5); - - if(this.bgImageUrl && !this.bgImage) - this.bgImage = this.loadImage(this.bgImageUrl); - - if(this.onDrawBackground) - this.onDrawBackground(ctx); - - //title bg - if(!no_title) - { - ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - - if(this.shape == null || this.shape == "box") - { - ctx.fillRect(0,-title_height,this.size[0],title_height); - ctx.stroke(); - } - else if (this.shape == "round") - { - ctx.roundRect(0,-title_height,this.size[0], title_height,10,0); - //ctx.fillRect(0,8,this.size[0],NODE_TITLE_HEIGHT - 12); - ctx.fill(); - ctx.stroke(); - } - - //box - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - if (this.shape == "round") - ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); - else - ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6); - ctx.fill(); - - //title text - ctx.font = "bold 12px Arial"; - if(this.name != "" && this.graph.config.canvas_scale > 0.8) - { - ctx.fillStyle = "#222"; - ctx.fillText(this.name,16,13-title_height ); - } - } -} - -/* Renders the node when collapsed */ -LGraphNode.prototype.drawNodeCollapsed = function(ctx, fgcolor, bgcolor) -{ - //draw default collapsed shape - ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS; - - //circle shape - if(this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius,0,Math.PI * 2); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2); - ctx.fill(); - } - else if(this.shape == "round") //rounded box - { - ctx.beginPath(); - ctx.roundRect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.roundRect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2); - ctx.fill(); - } - else //flat box - { - ctx.beginPath(); - ctx.rect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.rect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius); - ctx.fill(); - } -} - /* Force align to grid */ LGraphNode.prototype.alignToGrid = function() { @@ -2947,14 +2630,14 @@ LGraphCanvas.prototype.drawFrontCanvas = function() for (var i in visible_nodes) { - var n = visible_nodes[i]; + var node = visible_nodes[i]; //transform coords system ctx.save(); - ctx.translate( n.pos[0], n.pos[1] ); + ctx.translate( node.pos[0], node.pos[1] ); //Draw - n.draw(ctx,this); + this.drawNode(node, ctx ); drawn_nodes += 1; //Restore @@ -3091,6 +2774,349 @@ LGraphCanvas.prototype.drawBgcanvas = function() this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas } +/* Renders the LGraphNode on the canvas */ +LGraphCanvas.prototype.drawNode = function(node, ctx ) +{ + var glow = false; + + var color = node.color || LiteGraph.NODE_DEFAULT_COLOR; + //if (this.selected) color = "#88F"; + + var render_title = true; + if(node.flags.skip_title_render || node.graph.isLive()) + render_title = false; + if(node.mouseOver) + render_title = true; + + //shadow and glow + if (node.mouseOver) glow = true; + + if(node.selected) + { + /* + ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 1; + */ + } + else if(this.render_shadows) + { + ctx.shadowColor = "#111"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 4; + } + else + ctx.shadowColor = "transparent"; + + //only render if it forces it to do it + if(this.live_mode) + { + if(!node.flags.collapsed) + { + ctx.shadowColor = "transparent"; + if(node.onDrawBackground) + node.onDrawBackground(ctx); + if(node.onDrawForeground) + node.onDrawForeground(ctx); + } + + return; + } + + //draw in collapsed form + /* + if(node.flags.collapsed) + { + if(!node.onDrawCollapsed || node.onDrawCollapsed(ctx) == false) + this.drawNodeCollapsed(node, ctx, color, node.bgcolor); + return; + } + */ + + //clip if required (mask) + var shape = node.shape || "box"; + var size = new Float32Array(node.size); + if(node.flags.collapsed) + size.set([LiteGraph.NODE_COLLAPSED_WIDTH, 0]); + + //Start cliping + if(node.flags.clip_area) + { + ctx.save(); + if(shape == "box") + { + ctx.beginPath(); + ctx.rect(0,0,size[0], size[1]); + } + else if (shape == "round") + { + ctx.roundRect(0,0,size[0], size[1],10); + } + else if (shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + ctx.clip(); + } + + //draw shape + this.drawNodeShape(node, ctx, size, color, node.bgcolor, !render_title, node.selected ); + ctx.shadowColor = "transparent"; + + //connection slots + ctx.textAlign = "left"; + ctx.font = "12px Arial"; + + var render_text = node.graph.config.canvas_scale > 0.6; + + //render inputs and outputs + if(!node.flags.collapsed) + { + //input connection slots + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + + ctx.globalAlpha = 1.0; + if (this.connecting_node != null && this.connecting_output.type != 0 && node.inputs[i].type != 0 && this.connecting_output.type != node.inputs[i].type) + ctx.globalAlpha = 0.4; + + ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; + + var pos = node.getConnectionPos(true,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.beginPath(); + + 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); + + ctx.fill(); + + //render name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text,pos[0] + 10,pos[1] + 5); + } + } + } + + //output connection slots + if(this.connecting_node) + ctx.globalAlpha = 0.4; + + ctx.lineWidth = 1; + + ctx.textAlign = "right"; + ctx.strokeStyle = "black"; + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + + var pos = node.getConnectionPos(false,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA"; + 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); + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + ctx.stroke(); + + //render output name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text, pos[0] - 10,pos[1] + 5); + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1.0; + + if(node.onDrawForeground) + node.onDrawForeground(ctx); + }//!collapsed + + if(node.flags.clip_area) + ctx.restore(); +} + +/* Renders the node shape */ +LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, no_title, selected ) +{ + //bg rect + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + /* gradient test + var grad = ctx.createLinearGradient(0,0,0,node.size[1]); + grad.addColorStop(0, "#AAA"); + grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR); + grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR); + ctx.fillStyle = grad; + */ + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + + //render depending on shape + var shape = node.shape || "box"; + if(shape == "box") + { + if(selected) + { + ctx.strokeStyle = "#CCC"; + ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) ); + ctx.strokeStyle = fgcolor; + } + + ctx.beginPath(); + ctx.rect(0,no_title ? 0.5 : -title_height + 1,size[0]+1, no_title ? size[1] : size[1] + title_height); + } + else if (node.shape == "round") + { + ctx.roundRect(0,no_title ? 0 : -title_height,size[0], no_title ? size[1] : size[1] + title_height, 10); + } + else if (node.shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + + ctx.fill(); + ctx.shadowColor = "transparent"; + + //ctx.stroke(); + + //image + if (node.bgImage && node.bgImage.width) + ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5); + + if(node.bgImageUrl && !node.bgImage) + node.bgImage = node.loadImage(node.bgImageUrl); + + if(node.onDrawBackground) + node.onDrawBackground(ctx); + + //title bg + if(!no_title) + { + ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + + if(shape == "box") + { + ctx.beginPath(); + ctx.fillRect(0,-title_height,size[0]+1,title_height); + ctx.stroke(); + } + else if (shape == "round") + { + ctx.roundRect(0,-title_height,size[0], title_height,10,0); + //ctx.fillRect(0,8,size[0],NODE_TITLE_HEIGHT - 12); + ctx.fill(); + ctx.stroke(); + } + + //box + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + if (shape == "round") + ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); + else + ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6); + ctx.fill(); + + //title text + ctx.font = "bold 12px Arial"; + if(node.name != "" && node.graph.config.canvas_scale > 0.8) + { + ctx.fillStyle = "#222"; + ctx.fillText(node.name,16,13-title_height ); + } + } +} + +/* Renders the node when collapsed */ +LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor) +{ + //draw default collapsed shape + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS; + + //circle shape + var shape = node.shape || "box"; + if(shape == "circle") + { + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius,0,Math.PI * 2); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2); + ctx.fill(); + } + else if(shape == "round") //rounded box + { + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2); + ctx.fill(); + } + else //flat box + { + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius, 2*collapsed_radius); + ctx.rect(0, 0, node.size[0], collapsed_radius * 2 ); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius); + ctx.rect(collapsed_radius*0.5, collapsed_radius*0.5, collapsed_radius, collapsed_radius); + ctx.fill(); + } +} + LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"]; LGraphCanvas.prototype.drawConnections = function(ctx) diff --git a/build/litegraph.min.js b/build/litegraph.min.js index f7c4d74f2..43d67c80d 100644 --- a/build/litegraph.min.js +++ b/build/litegraph.min.js @@ -1,8 +1,8 @@ -var LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,CANVAS_GRID_SIZE:10,NODE_DEFAULT_COLOR:"#888",NODE_DEFAULT_BGCOLOR:"#333",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",debug:!1,registered_node_types:{},graphs:[],registerNodeType:function(a,b){b.type=a;LiteGraph.debug&&console.log("Node registered: "+a);a.split("/");var c=a.lastIndexOf("/");b.category=a.substr(0, -c);if(b.prototype)for(var d in LGraphNode.prototype)b.prototype[d]||(b.prototype[d]=LGraphNode.prototype[d]);this.registered_node_types[a]=b},createNode:function(a,b,c){var d=this.registered_node_types[a];if(!d)return LiteGraph.debug&&console.log('GraphNode type "'+a+'" not registered.'),null;var e=d.prototype||d;b=b||e.title||d.title||a;var f=null;if(d.prototype)f=new d(b);else{f=new LGraphNode(b);f.inputs=[];f.outputs=[];for(var g in e)if("inputs"==g)for(var h in e[g])f.addInput(e[g][h][0],e[g][h][1], -e[g][h][2]);else if("outputs"==g)for(h in e[g])f.addOutput(e[g][h][0],e[g][h][1],e[g][h][2]);else f[g]=e[g].concat?e[g].concat():"object"==typeof e[g]?jQuery.extend({},e[g]):e[g];d.size&&(f.size=d.size.concat())}f.type=a;f.name||(f.name=b);f.flags||(f.flags={});f.size||(f.size=f.computeSize());f.pos||(f.pos=LiteGraph.DEFAULT_POSITION.concat());if(c)for(g in c)f[g]=c[g];return f},getNodeType:function(a){return this.registered_node_types[a]},getNodeTypesInCategory:function(a){var b=[],c;for(c in this.registered_node_types)""== -a?null==this.registered_node_types[c].category&&b.push(this.registered_node_types[c]):this.registered_node_types[c].category==a&&b.push(this.registered_node_types[c]);return b},getNodeTypesCategories:function(){var a={"":1},b;for(b in this.registered_node_types)this.registered_node_types[b].category&&!this.registered_node_types[b].skip_list&&(a[this.registered_node_types[b].category]=1);var c=[];for(b in a)c.push(b);return c},reloadNodes:function(a){var b=document.getElementsByTagName("script"),c= -[],d;for(d in b)c.push(b[d]);b=document.getElementsByTagName("head")[0];a=document.location.href+a;for(d in c){var e=c[d].src;if(e&&e.substr(0,a.length)==a)try{LiteGraph.debug&&console.log("Reloading: "+e);var f=document.createElement("script");f.type="text/javascript";f.src=e;b.appendChild(f);b.removeChild(c[d])}catch(g){if(LiteGraph.throw_errors)throw g;LiteGraph.debug&&console.log("Error while reloading "+e)}}for(d in LiteGraph.graphs)for(var h in LiteGraph.graphs[d].nodes)if(a=LiteGraph.graphs[d].nodes[h], +var LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",debug:!1,registered_node_types:{},graphs:[],registerNodeType:function(a,b){b.type=a;LiteGraph.debug&&console.log("Node registered: "+a);a.split("/");var c=a.lastIndexOf("/"); +b.category=a.substr(0,c);if(b.prototype)for(var d in LGraphNode.prototype)b.prototype[d]||(b.prototype[d]=LGraphNode.prototype[d]);this.registered_node_types[a]=b},createNode:function(a,b,c){var d=this.registered_node_types[a];if(!d)return LiteGraph.debug&&console.log('GraphNode type "'+a+'" not registered.'),null;var e=d.prototype||d;b=b||e.title||d.title||a;var f=null;if(d.prototype)f=new d(b);else{f=new LGraphNode(b);f.inputs=[];f.outputs=[];for(var g in e)if("inputs"==g)for(var h in e[g])f.addInput(e[g][h][0], +e[g][h][1],e[g][h][2]);else if("outputs"==g)for(h in e[g])f.addOutput(e[g][h][0],e[g][h][1],e[g][h][2]);else f[g]=e[g].concat?e[g].concat():"object"==typeof e[g]?jQuery.extend({},e[g]):e[g];d.size&&(f.size=d.size.concat())}f.type=a;f.name||(f.name=b);f.flags||(f.flags={});f.size||(f.size=f.computeSize());f.pos||(f.pos=LiteGraph.DEFAULT_POSITION.concat());if(c)for(g in c)f[g]=c[g];return f},getNodeType:function(a){return this.registered_node_types[a]},getNodeTypesInCategory:function(a){var b=[],c; +for(c in this.registered_node_types)""==a?null==this.registered_node_types[c].category&&b.push(this.registered_node_types[c]):this.registered_node_types[c].category==a&&b.push(this.registered_node_types[c]);return b},getNodeTypesCategories:function(){var a={"":1},b;for(b in this.registered_node_types)this.registered_node_types[b].category&&!this.registered_node_types[b].skip_list&&(a[this.registered_node_types[b].category]=1);var c=[];for(b in a)c.push(b);return c},reloadNodes:function(a){var b=document.getElementsByTagName("script"), +c=[],d;for(d in b)c.push(b[d]);b=document.getElementsByTagName("head")[0];a=document.location.href+a;for(d in c){var e=c[d].src;if(e&&e.substr(0,a.length)==a)try{LiteGraph.debug&&console.log("Reloading: "+e);var f=document.createElement("script");f.type="text/javascript";f.src=e;b.appendChild(f);b.removeChild(c[d])}catch(g){if(LiteGraph.throw_errors)throw g;LiteGraph.debug&&console.log("Error while reloading "+e)}}for(d in LiteGraph.graphs)for(var h in LiteGraph.graphs[d].nodes)if(a=LiteGraph.graphs[d].nodes[h], c=LiteGraph.getNodeType(n.type))for(var k in c)"function"==typeof c[k]&&(a[k]=c[k]);LiteGraph.debug&&console.log("Nodes reloaded")}};function LGraph(){LiteGraph.debug&&console.log("Graph created");this.canvas=null;LiteGraph.graphs.push(this);this.clear()}LGraph.STATUS_STOPPED=1;LGraph.STATUS_RUNNING=2; LGraph.prototype.clear=function(){this.stop();this.status=LGraph.STATUS_STOPPED;this.last_node_id=0;this.nodes=[];this.nodes_by_id={};this.last_link_id=0;this.links={};this.iteration=0;this.config={canvas_offset:[0,0],canvas_scale:1};this.fixedtime=this.runningtime=this.globaltime=0;this.elapsed_time=this.fixedtime_lapse=0.01;this.starttime=0;this.graph={};this.debug=!0;this.change();this.canvas&&this.canvas.clear()}; LGraph.prototype.start=function(a){if(this.status!=LGraph.STATUS_RUNNING){this.status=LGraph.STATUS_RUNNING;if(this.onPlayEvent)this.onPlayEvent();this.sendEventToAllNodes("onStart");this.starttime=(new Date).getTime();var b=this;this.execution_timer_id=setInterval(function(){b.runStep(1)},a||1)}}; @@ -27,28 +27,18 @@ LGraphNode.prototype.isInputConnected=function(a){return this.inputs?aa&&this.pos[1]-cb)return!0;return!1};LGraphNode.prototype.findInputSlot=function(a){if(!this.inputs)return-1;for(var b=0,c=this.inputs.length;ba&&this.pos[1]-cb)return!0;return!1}; +LGraphNode.prototype.findInputSlot=function(a){if(!this.inputs)return-1;for(var b=0,c=this.inputs.length;b=this.outputs.length)return LiteGraph.debug&&console.log("Connect: Error, slot number not found"),!1;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(!b.inputs||c>=b.inputs.length)return LiteGraph.debug&& console.log("Connect: Error, slot number not found"),!1;-1!=c&&null!=b.inputs[c].link&&b.disconnectInput(c);var d=this.outputs[a];if(-1==c)null==d.links&&(d.links=[]),d.links.push({id:b.id,slot:-1});else if(0==d.type||0==b.inputs[c].type||d.type==b.inputs[c].type)a=[this.graph.last_link_id++,this.id,a,b.id,c],null==d.links&&(d.links=[]),d.links.push(a),b.inputs[c].link=a,this.setDirtyCanvas(!1,!0),this.graph.onConnectionChange();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)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;a=this.graph.getNodeById(b[1]);if(!a)return!1;a=a.outputs[b[2]];if(!a||!a.links||0==a.links.length)return!1;for(var c= 0,d=a.links.length;cb&&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.draw=function(a,b){var c=this.color||LiteGraph.NODE_DEFAULT_COLOR,d=!0;if(this.flags.skip_title_render||this.graph.isLive())d=!1;this.mouseOver&&(d=!0);this.selected||(b.render_shadows?(a.shadowColor="#111",a.shadowOffsetX=2,a.shadowOffsetY=2,a.shadowBlur=4):a.shadowColor="transparent");if(b.live_mode){if(!this.flags.collapsed){a.shadowColor="transparent";if(this.onDrawBackground)this.onDrawBackground(a);if(this.onDrawForeground)this.onDrawForeground(a)}}else if(this.flags.collapsed)this.onDrawCollapsed&&!1!= -this.onDrawCollapsed(a)||this.drawNodeCollapsed(a,c,this.bgcolor);else{this.flags.clip_area&&(a.save(),null==this.shape||"box"==this.shape?(a.beginPath(),a.rect(0,0,this.size[0],this.size[1])):"round"==this.shape?a.roundRect(0,0,this.size[0],this.size[1],10):"circle"==this.shape&&(a.beginPath(),a.arc(0.5*this.size[0],0.5*this.size[1],0.5*this.size[0],0,2*Math.PI)),a.clip());this.drawNodeShape(a,c,this.bgcolor,!d,this.selected);a.shadowColor="transparent";a.textAlign="left";a.font="12px Arial";d=0.6< -this.graph.config.canvas_scale;if(this.inputs)for(var e=0;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.copyFromObject=function(a,b){for(var c in a)(!b||"outputs"!=c&&"inputs"!=c)&&"console"!=c&&null!=a[c]&&(this[c]=a[c].concat?a[c].concat():"object"==typeof a[c]?jQuery.extend({},a[c]):a[c])}; LGraphNode.prototype.clone=function(){var a=LiteGraph.createNode(this.type);a.size=this.size.concat();if(this.inputs)for(var b=0,c=this.inputs.length;bLGraphNode.MAX_CONSOLE&&this.console.shift();this.graph.onNodeTrace(this,a)};LGraphNode.prototype.setDirtyCanvas=function(a,b){this.graph&&this.graph.canvas&&(a&&(this.graph.canvas.dirty_canvas=!0),b&&(this.graph.canvas.dirty_bgcanvas=!0))}; 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}; @@ -91,11 +81,21 @@ LGraphCanvas.prototype.computeVisibleNodes=function(){var a=[],b;for(b in this.g LGraphCanvas.prototype.draw=function(a,b){var c=(new Date).getTime();this.render_time=0.001*(c-this.last_draw_time);this.last_draw_time=c;if(this.graph){var c=[-this.graph.config.canvas_offset[0],-this.graph.config.canvas_offset[1]],d=[c[0]+this.canvas.width/this.graph.config.canvas_scale,c[1]+this.canvas.height/this.graph.config.canvas_scale];this.visible_area=new Float32Array([c[0],c[1],d[0],d[1]])}(this.dirty_bgcanvas||b)&&this.drawBgcanvas();(this.dirty_canvas||a)&&this.drawFrontCanvas();this.fps= this.render_time?1/this.render_time:0;this.frame+=1}; LGraphCanvas.prototype.drawFrontCanvas=function(){var a=this.ctx,b=this.canvas;a.restore();a.setTransform(1,0,0,1,0,0);this.dirty_area&&(a.save(),a.beginPath(),a.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]),a.clip());a.clearRect(0,0,b.width,b.height);a.drawImage(this.bgcanvas,0,0);this.show_info&&(a.font="10px Arial",a.fillStyle="#888",this.graph?(a.fillText("T: "+this.graph.globaltime.toFixed(2)+"s",5,13),a.fillText("I: "+this.graph.iteration,5,26),a.fillText("F: "+ -this.frame,5,39),a.fillText("FPS:"+this.fps.toFixed(2),5,52)):a.fillText("No graph selected",5,13));if(this.graph){a.save();a.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale);a.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]);this.visible_nodes=b=this.computeVisibleNodes();for(var c in b){var d=b[c];a.save();a.translate(d.pos[0],d.pos[1]);d.draw(a,this);a.restore()}this.graph.config.links_ontop&&(this.live_mode||this.drawConnections(a));null!=this.connecting_pos&& -(a.lineWidth=LGraphCanvas.link_width,a.fillStyle="node"==this.connecting_output.type?"#F85":"#AFA",a.strokeStyle=a.fillStyle,this.renderLink(a,this.connecting_pos,[this.canvas_mouse[0],this.canvas_mouse[1]]),a.beginPath(),a.arc(this.connecting_pos[0],this.connecting_pos[1],4,0,2*Math.PI),a.fill(),a.fillStyle="#ffcc00",this._highlight_input&&(a.beginPath(),a.arc(this._highlight_input[0],this._highlight_input[1],6,0,2*Math.PI),a.fill()));a.restore()}this.dirty_area&&a.restore();this.dirty_canvas=!1}; +this.frame,5,39),a.fillText("FPS:"+this.fps.toFixed(2),5,52)):a.fillText("No graph selected",5,13));if(this.graph){a.save();a.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale);a.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]);this.visible_nodes=b=this.computeVisibleNodes();for(var c in b){var d=b[c];a.save();a.translate(d.pos[0],d.pos[1]);this.drawNode(d,a);a.restore()}this.graph.config.links_ontop&&(this.live_mode||this.drawConnections(a));null!= +this.connecting_pos&&(a.lineWidth=LGraphCanvas.link_width,a.fillStyle="node"==this.connecting_output.type?"#F85":"#AFA",a.strokeStyle=a.fillStyle,this.renderLink(a,this.connecting_pos,[this.canvas_mouse[0],this.canvas_mouse[1]]),a.beginPath(),a.arc(this.connecting_pos[0],this.connecting_pos[1],4,0,2*Math.PI),a.fill(),a.fillStyle="#ffcc00",this._highlight_input&&(a.beginPath(),a.arc(this._highlight_input[0],this._highlight_input[1],6,0,2*Math.PI),a.fill()));a.restore()}this.dirty_area&&a.restore(); +this.dirty_canvas=!1}; LGraphCanvas.prototype.drawBgcanvas=function(){var a=this.bgcanvas,b=this.bgctx;a.width=a.width;b.restore();b.setTransform(1,0,0,1,0,0);if(this.graph){b.save();b.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale);b.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]);if(this.background_image&&0.5= LiteGraph.MAX_NUMBER_OF_NODES) + throw("LiteGraph: max number of nodes in a graph reached"); + + //give him an id + if(node.id == null || node.id == -1) + node.id = this.last_node_id++; + + node.graph = this; + + this.nodes.push(node); + this.nodes_by_id[node.id] = node; + + /* + // rendering stuf... + if(node.bgImageUrl) + node.bgImage = node.loadImage(node.bgImageUrl); + */ + + if(node.onInit) + node.onInit(); + + if(this.config.align_to_grid) + node.alignToGrid(); + + this.updateExecutionOrder(); + + if(this.canvas) + this.canvas.dirty_canvas = true; + + this.change(); + + return node; //to chain actions +} + +/** +* Removes a node from the graph +* @method remove +* @param {LGraphNode} node the instance of the node +*/ + +LGraph.prototype.remove = function(node) +{ + if(this.nodes_by_id[node.id] == null) + return; //not found + + if(node.ignore_remove) + return; //cannot be removed + + //disconnect inputs + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + if(slot.link != null) + node.disconnectInput(i); + } + + //disconnect outputs + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + if(slot.links != null && slot.links.length) + node.disconnectOutput(i); + } + + node.id = -1; + + //callback + if(node.onDelete) + node.onDelete(); + + //remove from environment + if(this.canvas) + { + if(this.canvas.selected_nodes[node.id]) + delete this.canvas.selected_nodes[node.id]; + if(this.canvas.node_dragged == node) + this.canvas.node_dragged = null; + } + + //remove from containers + var pos = this.nodes.indexOf(node); + if(pos != -1) + this.nodes.splice(pos,1); + delete this.nodes_by_id[node.id]; + + if(this.canvas) + this.canvas.setDirty(true,true); + + this.change(); + + this.updateExecutionOrder(); +} + +/** +* Returns a node by its id. +* @method getNodeById +* @param {String} id +*/ + +LGraph.prototype.getNodeById = function(id) +{ + if(id==null) return null; + return this.nodes_by_id[id]; +} + + +/** +* Returns a list of nodes that matches a type +* @method findNodesByType +* @param {String} type the name of the node type +* @return {Array} a list with all the nodes of this type +*/ + +LGraph.prototype.findNodesByType = function(type) +{ + var r = []; + for(var i in this.nodes) + if(this.nodes[i].type == type) + r.push(this.nodes[i]); + return r; +} + +/** +* Returns a list of nodes that matches a name +* @method findNodesByName +* @param {String} name the name of the node to search +* @return {Array} a list with all the nodes with this name +*/ + +LGraph.prototype.findNodesByName = function(name) +{ + var result = []; + for (var i in this.nodes) + if(this.nodes[i].name == name) + result.push(this.nodes[i]); + return result; +} + +/** +* Returns the top-most node in this position of the canvas +* @method getNodeOnPos +* @param {number} x the x coordinate in canvas space +* @param {number} y the y coordinate in canvas space +* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph +* @return {Array} a list with all the nodes that intersect this coordinate +*/ + +LGraph.prototype.getNodeOnPos = function(x,y, nodes_list) +{ + nodes_list = nodes_list || this.nodes; + for (var i = nodes_list.length - 1; i >= 0; i--) + { + var n = nodes_list[i]; + if(n.isPointInsideNode(x,y)) + return n; + } + return null; +} + +/** +* Assigns a value to all the nodes that matches this name. This is used to create global variables of the node that +* can be easily accesed from the outside of the graph +* @method setInputData +* @param {String} name the name of the node +* @param {*} value value to assign to this node +*/ + +LGraph.prototype.setInputData = function(name,value) +{ + var m = this.findNodesByName(name); + for(var i in m) + m[i].setValue(value); +} + +/** +* Returns the value of the first node with this name. This is used to access global variables of the graph from the outside +* @method setInputData +* @param {String} name the name of the node +* @return {*} value of the node +*/ + +LGraph.prototype.getOutputData = function(name) +{ + var n = this.findNodesByName(name); + if(n.length) + return m[0].getValue(); + return null; +} + +//This feature is not finished yet, is to create graphs where nodes are not executed unless a trigger message is received + +LGraph.prototype.triggerInput = function(name,value) +{ + var m = this.findNodesByName(name); + for(var i in m) + m[i].onTrigger(value); +} + +LGraph.prototype.setCallback = function(name,func) +{ + var m = this.findNodesByName(name); + for(var i in m) + m[i].setTrigger(func); +} + +//********** + + +LGraph.prototype.onConnectionChange = function() +{ + this.updateExecutionOrder(); +} + +LGraph.prototype.isLive = function() +{ + if(!this.canvas) return false; + return this.canvas.live_mode; +} + +LGraph.prototype.change = function() +{ + if(LiteGraph.debug) + console.log("Graph changed"); + if(this.on_change) + this.on_change(this); +} + +//save and recover app state *************************************** +/** +* Creates a JSON String containing all the info about this graph +* @method serialize +* @return {String} value of the node +*/ +LGraph.prototype.serialize = function() +{ + var nodes_info = []; + for (var i in this.nodes) + nodes_info.push( this.nodes[i].objectivize() ); + + var data = { + graph: this.graph, + + iteration: this.iteration, + frame: this.frame, + last_node_id: this.last_node_id, + last_link_id: this.last_link_id, + + config: this.config, + nodes: nodes_info + }; + + return JSON.stringify(data); +} + +/** +* Configure a graph from a JSON string +* @method unserialize +* @param {String} str configure a graph from a JSON string +*/ +LGraph.prototype.unserialize = function(str, keep_old) +{ + if(!keep_old) + this.clear(); + + var data = JSON.parse(str); + var nodes = data.nodes; + + //copy all stored fields + for (var i in data) + this[i] = data[i]; + + var error = false; + + //create nodes + this.nodes = []; + for (var i in nodes) + { + var n_info = nodes[i]; //stored info + var n = LiteGraph.createNode( n_info.type, n_info.name ); + if(!n) + { + if(LiteGraph.debug) + console.log("Node not found: " + n_info.type); + error = true; + continue; + } + + n.copyFromObject(n_info); + this.add(n); + } + + //TODO: dispatch redraw + if(this.canvas) + this.canvas.draw(true,true); + + return error; +} + +LGraph.prototype.onNodeTrace = function(node, msg, color) +{ + if(this.canvas) + this.canvas.onNodeTrace(node,msg,color); +} + +// ************************************************************* +// Node CLASS ******* +// ************************************************************* + +/* flags: + + skip_title_render + + clip_area + + unsafe_execution: not allowed for safe execution + + supported callbacks: + + onInit: when added to graph + + onStart: when starts playing + + onStop: when stops playing + + onDrawForeground + + onDrawBackground + + onMouseMove + + onMouseOver + + onExecute: execute the node + + onPropertyChange: 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 + + onClick + + onDblClick + + onSerialize + + onSelected + + onDeselected +*/ + +/** +* Base Class for all the node type classes +* @class LGraphNode +* @param {String} name a name for the node +*/ + +function LGraphNode(name) +{ + this.name = name || "Unnamed"; + this.size = [LiteGraph.NODE_WIDTH,60]; + this.graph = null; + + this.pos = [10,10]; + this.id = -1; //not know till not added + this.type = null; + + //inputs available: array of inputs + this.inputs = []; + this.outputs = []; + this.connections = []; + + //local data + this.data = null; //persistent local data + this.flags = { + //skip_title_render: true, + //unsafe_execution: false, + }; +} + +//serialization ************************* + +LGraphNode.prototype.objectivize = function() +{ + var o = { + id: this.id, + name: this.name, + type: this.type, + pos: this.pos, + size: this.size, + data: this.data, + properties: jQuery.extend({}, this.properties), + flags: jQuery.extend({}, this.flags), + inputs: this.inputs, + outputs: this.outputs + }; + + if(!o.type) + o.type = this.constructor.type; + + if(this.color) + o.color = this.color; + if(this.bgcolor) + o.bgcolor = this.bgcolor; + if(this.boxcolor) + o.boxcolor = this.boxcolor; + if(this.shape) + o.shape = this.shape; + + return o; +} + +//reduced version of objectivize: NOT FINISHED +LGraphNode.prototype.reducedObjectivize = function() +{ + var o = this.objectivize(); + + var type = LiteGraph.getNodeType(o.type); + + if(type.name == o.name) + delete o["name"]; + + if(type.size && compareObjects(o.size,type.size)) + delete o["size"]; + + if(type.properties && compareObjects(o.properties, type.properties)) + delete o["properties"]; + + return o; +} + + +LGraphNode.prototype.serialize = function() +{ + if(this.onSerialize) + this.onSerialize(); + return JSON.stringify( this.reducedObjectivize() ); +} +//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph + + +// Execution ************************* + +LGraphNode.prototype.setOutputData = function(slot,data) +{ + if(!this.outputs) return; + if(slot > -1 && slot < this.outputs.length && this.outputs[slot] && this.outputs[slot].links != null) + { + for(var i = 0; i < this.outputs[slot].links.length; i++) + this.graph.links[ this.outputs[slot].links[i][0] ] = data; + } +} + +LGraphNode.prototype.getInputData = function(slot) +{ + if(!this.inputs) return null; + if(slot < this.inputs.length && this.inputs[slot].link != null) + return this.graph.links[ this.inputs[slot].link[0] ]; + return null; +} + +LGraphNode.prototype.isInputConnected = function(slot) +{ + if(!this.inputs) return null; + return (slot < this.inputs.length && this.inputs[slot].link != null); +} + +LGraphNode.prototype.getInputInfo = function(slot) +{ + if(!this.inputs) return null; + if(slot < this.inputs.length) + return this.inputs[slot]; + return null; +} + + +LGraphNode.prototype.getOutputInfo = function(slot) +{ + if(!this.outputs) return null; + if(slot < this.outputs.length) + return this.outputs[slot]; + return null; +} + +LGraphNode.prototype.isOutputConnected = function(slot) +{ + if(!this.outputs) return null; + return (slot < this.outputs.length && this.outputs[slot].links && this.outputs[slot].links.length); +} + +LGraphNode.prototype.getOutputNodes = function(slot) +{ + if(!this.outputs || this.outputs.length == 0) return null; + if(slot < this.outputs.length) + { + var output = this.outputs[slot]; + var r = []; + for(var i = 0; i < output.length; i++) + r.push( this.graph.getNodeById( output.links[i][3] )); + return r; + } + return null; +} + +LGraphNode.prototype.triggerOutput = function(slot,param) +{ + var n = this.getOutputNode(slot); + if(n && n.onTrigger) + n.onTrigger(param); +} + +//connections + +LGraphNode.prototype.addOutput = function(name,type,extra_info) +{ + 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 = []; + this.outputs.push(o); + this.size = this.computeSize(); +} + +LGraphNode.prototype.removeOutput = function(slot) +{ + this.disconnectOutput(slot); + this.outputs.splice(slot,1); + this.size = this.computeSize(); +} + +LGraphNode.prototype.addInput = function(name,type,extra_info) +{ + var o = {name:name,type:type,link:null}; + if(extra_info) + for(var i in extra_info) + o[i] = extra_info[i]; + + if(!this.inputs) this.inputs = []; + this.inputs.push(o); + this.size = this.computeSize(); +} + +LGraphNode.prototype.removeInput = function(slot) +{ + this.disconnectInput(slot); + this.inputs.splice(slot,1); + this.size = this.computeSize(); +} + +//trigger connection +LGraphNode.prototype.addConnection = function(name,type,pos,direction) +{ + this.connections.push( {name:name,type:type,pos:pos,direction:direction,links:null}); +} + + +LGraphNode.prototype.computeSize = function(minHeight) +{ + var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1); + var size = [0,0]; + size[1] = rows * 14 + 6; + if(!this.inputs || this.inputs.length == 0 || !this.outputs || this.outputs.length == 0) + size[0] = LiteGraph.NODE_WIDTH * 0.5; + else + size[0] = LiteGraph.NODE_WIDTH; + return size; +} + +//returns the bounding of the object, used for rendering purposes +LGraphNode.prototype.getBounding = function() +{ + return new Float32Array([this.pos[0] - 4, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]); +} + +//checks if a point is inside the shape of a node +LGraphNode.prototype.isPointInsideNode = function(x,y) +{ + var margin_top = this.graph.isLive() ? 0 : 20; + if(this.flags.collapsed) + { + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if( isInsideRectangle(x,y, this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_COLLAPSED_WIDTH, LiteGraph.NODE_TITLE_HEIGHT) ) + return true; + } + else if (this.pos[0] - 4 < x && (this.pos[0] + this.size[0] + 4) > x + && (this.pos[1] - margin_top) < y && (this.pos[1] + this.size[1]) > y) + return true; + return false; +} + +LGraphNode.prototype.findInputSlot = function(name) +{ + if(!this.inputs) return -1; + for(var i = 0, l = this.inputs.length; i < l; ++i) + if(name == this.inputs[i].name) + return i; + return -1; +} + +LGraphNode.prototype.findOutputSlot = function(name) +{ + if(!this.outputs) return -1; + for(var i = 0, l = this.outputs.length; i < l; ++i) + if(name == this.outputs[i].name) + return i; + return -1; +} + +//connect this node output to the input of another node +LGraphNode.prototype.connect = function(slot, node, target_slot) +{ + target_slot = target_slot || 0; + + //seek for the output slot + if( slot.constructor === String ) + { + slot = this.findOutputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.outputs || slot >= this.outputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + //avoid loopback + if(node == this) return false; + //if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode"); + + if(target_slot.constructor === String) + { + target_slot = node.findInputSlot(target_slot); + if(target_slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + target_slot); + return false; + } + } + else if(!node.inputs || target_slot >= node.inputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + //if there is something already plugged there, disconnect + if(target_slot != -1 && node.inputs[target_slot].link != null) + node.disconnectInput(target_slot); + + //special case: -1 means node-connection, used for triggers + var output = this.outputs[slot]; + if(target_slot == -1) + { + if( output.links == null ) + output.links = []; + output.links.push({id:node.id, slot: -1}); + } + else if(output.type == 0 || //generic output + node.inputs[target_slot].type == 0 || //generic input + output.type == node.inputs[target_slot].type) //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 ]; + + //connect + if( output.links == null ) output.links = []; + output.links.push(link); + node.inputs[target_slot].link = link; + + this.setDirtyCanvas(false,true); + this.graph.onConnectionChange(); + } + return true; +} + +LGraphNode.prototype.disconnectOutput = function(slot, target_node) +{ + if( slot.constructor === String ) + { + slot = this.findOutputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.outputs || slot >= this.outputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + //get output slot + var output = this.outputs[slot]; + if(!output.links || output.links.length == 0) + return false; + + if(target_node) + { + for(var i = 0, l = output.links.length; i < l; i++) + { + var link = output.links[i]; + //is the link we are searching for... + if( link[3] == target_node.id ) + { + output.links.splice(i,1); //remove here + target_node.inputs[ link[4] ].link = null; //remove there + delete this.graph.links[link[0]]; + break; + } + } + } + else + { + for(var i = 0, l = output.links.length; i < l; i++) + { + var link = output.links[i]; + var target_node = this.graph.getNodeById( link[3] ); + if(target_node) + target_node.inputs[ link[4] ].link = null; //remove other side link + } + output.links = null; + } + + this.setDirtyCanvas(false,true); + this.graph.onConnectionChange(); + return true; +} + +LGraphNode.prototype.disconnectInput = function(slot) +{ + //seek for the output slot + if( slot.constructor === String ) + { + slot = this.findInputSlot(slot); + if(slot == -1) + { + if(LiteGraph.debug) + console.log("Connect: Error, no slot of name " + slot); + return false; + } + } + else if(!this.inputs || slot >= this.inputs.length) + { + if(LiteGraph.debug) + console.log("Connect: Error, slot number not found"); + return false; + } + + var input = this.inputs[slot]; + if(!input) return false; + var link = this.inputs[slot].link; + this.inputs[slot].link = null; + + //remove other side + var node = this.graph.getNodeById( link[1] ); + if(!node) return false; + + var output = node.outputs[ link[2] ]; + if(!output || !output.links || output.links.length == 0) + return false; + + for(var i = 0, l = output.links.length; i < l; i++) + { + var link = output.links[i]; + if( link[3] == this.id ) + { + output.links.splice(i,1); + break; + } + } + + this.setDirtyCanvas(false,true); + this.graph.onConnectionChange(); + return true; +} + +//returns the center of a connection point in canvas coords +LGraphNode.prototype.getConnectionPos = function(is_input,slot_number) +{ + if(this.flags.collapsed) + { + if(is_input) + return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + else + return [this.pos[0] + LiteGraph.NODE_COLLAPSED_WIDTH, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + //return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5]; + } + + if(is_input && slot_number == -1) + { + return [this.pos[0] + 10, this.pos[1] + 10]; + } + + if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos) + return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]]; + else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos) + return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]]; + + if(!is_input) //output + return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT]; + return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT]; +} + +/* Force align to grid */ +LGraphNode.prototype.alignToGrid = function() +{ + this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE); + this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE); +} + +/* Copy all the info from one object to this node (used for serialization) */ +LGraphNode.prototype.copyFromObject = function(info, ignore_connections) +{ + var outputs = null; + var inputs = null; + var properties = null; + var local_data = null; + + for (var j in info) + { + if(ignore_connections && (j == "outputs" || j == "inputs")) + continue; + + if(j == "console") continue; + + if(info[j] == null) + continue; + else if( info[j].concat ) //array + this[j] = info[j].concat(); + else if (typeof(info[j]) == 'object') //object + this[j] = jQuery.extend({}, info[j]); + else //value + this[j] = info[j]; + } + + //redo the connections + /* + if(outputs) + this.outputs = outputs.concat(); + if(inputs) + this.inputs = inputs.concat(); + + if(local_data) + this.data = local_data; + if(properties) + { + //copy only the ones defined + for (var j in properties) + if (this.properties[j] != null) + this.properties[j] = properties[j]; + } + */ +} + +/* Creates a clone of this node */ +LGraphNode.prototype.clone = function() +{ + var node = LiteGraph.createNode(this.type); + + node.size = this.size.concat(); + if(this.inputs) + for(var i = 0, l = this.inputs.length; i < l; ++i) + { + if(node.findInputSlot( this.inputs[i].name ) == -1) + node.addInput( this.inputs[i].name, this.inputs[i].type ); + } + + if(this.outputs) + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + if(node.findOutputSlot( this.outputs[i].name ) == -1) + node.addOutput( this.outputs[i].name, this.outputs[i].type ); + } + + + return node; +} + +/* Console output */ +LGraphNode.prototype.trace = function(msg) +{ + if(!this.console) + this.console = []; + this.console.push(msg); + if(this.console.length > LGraphNode.MAX_CONSOLE) + this.console.shift(); + + this.graph.onNodeTrace(this,msg); +} + +/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ +LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background) +{ + if(!this.graph || !this.graph.canvas) + return; + + if(dirty_foreground) + this.graph.canvas.dirty_canvas = true; + if(dirty_background) + this.graph.canvas.dirty_bgcanvas = true; +} + +LGraphNode.prototype.loadImage = function(url) +{ + var img = new Image(); + img.src = LiteGraph.node_images_path + url; + img.ready = false; + + var that = this; + img.onload = function() { + this.ready = true; + that.setDirtyCanvas(true); + } + return img; +} + +//safe LGraphNode action execution (not sure if safe) +LGraphNode.prototype.executeAction = function(action) +{ + if(action == "") return false; + + if( action.indexOf(";") != -1 || action.indexOf("}") != -1) + { + this.trace("Error: Action contains unsafe characters"); + return false; + } + + var tokens = action.split("("); + var func_name = tokens[0]; + if( typeof(this[func_name]) != "function") + { + this.trace("Error: Action not found on node: " + func_name); + return false; + } + + var code = action; + + try + { + var _foo = eval; + eval = null; + (new Function("with(this) { " + code + "}")).call(this); + eval = _foo; + } + catch (err) + { + this.trace("Error executing action {" + action + "} :" + err); + return false; + } + + return true; +} + +/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */ +LGraphNode.prototype.captureInput = function(v) +{ + if(!this.graph || !this.graph.canvas) + return; + + //releasing somebody elses capture?! + if(!v && this.graph.canvas.node_capturing_input != this) + return; + + //change + this.graph.canvas.node_capturing_input = v ? this : null; + if(this.graph.debug) + console.log(this.name + ": Capturing input " + (v?"ON":"OFF")); +} + +/* Collapse the node */ +LGraphNode.prototype.collapse = function() +{ + if(!this.flags.collapsed) + this.flags.collapsed = true; + else + this.flags.collapsed = false; + this.setDirtyCanvas(true,true); +} + +/* Forces the node to do not move or realign on Z */ +LGraphNode.prototype.pin = function() +{ + if(!this.flags.pinned) + this.flags.pinned = true; + else + this.flags.pinned = false; +} + +LGraphNode.prototype.localToScreen = function(x,y) +{ + return [(x + this.pos[0]) * this.graph.config.canvas_scale + this.graph.config.canvas_offset[0], + (y + this.pos[1]) * this.graph.config.canvas_scale + this.graph.config.canvas_offset[1]]; +} + + + +//********************************************************************************* +// LGraphCanvas: LGraph renderer CLASS +//********************************************************************************* + +function LGraphCanvas(canvas, graph) +{ + if(graph === undefined) + throw ("No graph assigned"); + + if( typeof(window) != "undefined" ) + { + window.requestAnimFrame = (function(){ + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + function( callback ){ + window.setTimeout(callback, 1000 / 60); + }; + })(); + } + + //link canvas and graph + this.graph = graph; + if(graph) + graph.canvas = this; + + this.setCanvas(canvas); + this.clear(); + + this.startRendering(); +} + +LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"}; +LGraphCanvas.link_width = 2; + +LGraphCanvas.prototype.clear = function() +{ + this.frame = 0; + this.last_draw_time = 0; + this.render_time = 0; + this.fps = 0; + + this.selected_nodes = {}; + this.node_dragged = null; + this.node_over = null; + this.node_capturing_input = null; + this.connecting_node = null; + + this.highquality_render = true; + this.pause_rendering = false; + this.render_shadows = true; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.dirty_area = null; + + this.render_only_selected = true; + this.live_mode = false; + this.show_info = true; + this.allow_dragcanvas = true; + this.allow_dragnodes = true; + + this.node_in_panel = null; + + this.last_mouse = [0,0]; + this.last_mouseclick = 0; + + if(this.onClear) this.onClear(); + //this.UIinit(); +} + +LGraphCanvas.prototype.setGraph = function(graph) +{ + if(this.graph == graph) return; + + this.clear(); + if(this.graph) + this.graph.canvas = null; //remove old graph link to the canvas + this.graph = graph; + if(this.graph) + this.graph.canvas = this; + this.setDirty(true,true); +} + +LGraphCanvas.prototype.resize = function(width, height) +{ + if(this.canvas.width == width && this.canvas.height == height) + return; + + this.canvas.width = width; + this.canvas.height = height; + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + this.setDirty(true,true); +} + + +LGraphCanvas.prototype.setCanvas = function(canvas) +{ + var that = this; + + //Canvas association + if(typeof(canvas) == "string") + canvas = document.getElementById(canvas); + + if(canvas == null) + throw("Error creating LiteGraph canvas: Canvas not found"); + if(canvas == this.canvas) return; + + this.canvas = canvas; + //this.canvas.tabindex = "1000"; + this.canvas.className += " lgraphcanvas"; + this.canvas.data = this; + + //bg canvas: used for non changing stuff + this.bgcanvas = null; + if(!this.bgcanvas) + { + this.bgcanvas = document.createElement("canvas"); + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + } + + if(this.canvas.getContext == null) + { + throw("This browser doesnt support Canvas"); + } + + this.ctx = this.canvas.getContext("2d"); + this.bgctx = this.bgcanvas.getContext("2d"); + + //input: (move and up could be unbinded) + this._mousemove_callback = this.processMouseMove.bind(this); + this._mouseup_callback = this.processMouseUp.bind(this); + + this.canvas.addEventListener("mousedown", this.processMouseDown.bind(this) ); //down do not need to store the binded + this.canvas.addEventListener("mousemove", this._mousemove_callback); + + this.canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; }); + + + this.canvas.addEventListener("mousewheel", this.processMouseWheel.bind(this), false); + this.canvas.addEventListener("DOMMouseScroll", this.processMouseWheel.bind(this), false); + + //touch events + //if( 'touchstart' in document.documentElement ) + { + //alert("doo"); + this.canvas.addEventListener("touchstart", this.touchHandler, true); + this.canvas.addEventListener("touchmove", this.touchHandler, true); + this.canvas.addEventListener("touchend", this.touchHandler, true); + this.canvas.addEventListener("touchcancel", this.touchHandler, true); + } + + //this.canvas.onselectstart = function () { return false; }; + this.canvas.addEventListener("keydown", function(e) { + that.processKeyDown(e); + }); + + this.canvas.addEventListener("keyup", function(e) { + that.processKeyUp(e); + }); +} + +/* +LGraphCanvas.prototype.UIinit = function() +{ + var that = this; + $("#node-console input").change(function(e) + { + if(e.target.value == "") + return; + + var node = that.node_in_panel; + if(!node) + return; + + node.trace("] " + e.target.value, "#333"); + if(node.onConsoleCommand) + { + if(!node.onConsoleCommand(e.target.value)) + node.trace("command not found", "#A33"); + } + else if (e.target.value == "info") + { + node.trace("Special methods:"); + for(var i in node) + { + if(typeof(node[i]) == "function" && LGraphNode.prototype[i] == null && i.substr(0,2) != "on" && i[0] != "_") + node.trace(" + " + i); + } + } + else + { + try + { + eval("var _foo = function() { return ("+e.target.value+"); }"); + var result = _foo.call(node); + if(result) + node.trace(result.toString()); + delete window._foo; + } + catch(err) + { + node.trace("error: " + err, "#A33"); + } + } + + this.value = ""; + }); +} +*/ + +LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas) +{ + if(fgcanvas) + this.dirty_canvas = true; + if(bgcanvas) + this.dirty_bgcanvas = true; +} + +LGraphCanvas.prototype.startRendering = function() +{ + if(this.is_rendering) return; //already rendering + + this.is_rendering = true; + renderFrame.call(this); + + function renderFrame() + { + if(!this.pause_rendering) + this.draw(); + + if(this.is_rendering) + window.requestAnimFrame( renderFrame.bind(this) ); + } + + + /* + this.rendering_timer_id = setInterval( function() { + //trace("Frame: " + new Date().getTime() ); + that.draw(); + }, 1000/50); + */ +} + +LGraphCanvas.prototype.stopRendering = function() +{ + this.is_rendering = false; + /* + if(this.rendering_timer_id) + { + clearInterval(this.rendering_timer_id); + this.rendering_timer_id = null; + } + */ +} + +/* LiteGraphCanvas input */ + +LGraphCanvas.prototype.processMouseDown = function(e) +{ + if(!this.graph) return; + + this.adjustMouseEvent(e); + + this.canvas.removeEventListener("mousemove", this._mousemove_callback ); + document.addEventListener("mousemove", this._mousemove_callback ); + document.addEventListener("mouseup", this._mouseup_callback ); + + var n = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + var skip_dragging = false; + + 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]); + } + var clicking_canvas_bg = false; + + //when clicked on top of a node + //and it is not interactive + if(n) + { + if(!this.live_mode && !n.flags.pinned) + this.bringToFront(n); //if it wasnt selected? + var skip_action = false; + + //not dragging mouse to connect two slots + if(!this.connecting_node && !n.flags.collapsed && !this.live_mode) + { + //search for outputs + if(n.outputs) + for(var i = 0, l = n.outputs.length; i < l; ++i) + { + var output = n.outputs[i]; + var link_pos = n.getConnectionPos(false,i); + if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + { + this.connecting_node = n; + this.connecting_output = output; + this.connecting_pos = n.getConnectionPos(false,i); + this.connecting_slot = i; + + skip_action = true; + break; + } + } + + //search for inputs + if(n.inputs) + for(var i = 0, l = n.inputs.length; i < l; ++i) + { + var input = n.inputs[i]; + var link_pos = n.getConnectionPos(true,i); + if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) ) + { + if(input.link) + { + n.disconnectInput(i); + this.dirty_bgcanvas = true; + skip_action = true; + } + } + } + + //Search for corner + if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 )) + { + this.resizing_node = n; + this.canvas.style.cursor = "se-resize"; + skip_action = true; + } + } + + //it wasnt clicked on the links boxes + if(!skip_action) + { + var block_drag_node = false; + + //double clicking + var now = new Date().getTime(); + if ((now - this.last_mouseclick) < 300 && this.selected_nodes[n.id]) + { + //double click node + if( n.onDblClick) + n.onDblClick(e); + this.processNodeDblClicked(n); + block_drag_node = true; + } + + //if do not capture mouse + + if( n.onMouseDown && n.onMouseDown(e) ) + block_drag_node = true; + else if(this.live_mode) + { + clicking_canvas_bg = true; + block_drag_node = true; + } + + if(!block_drag_node) + { + if(this.allow_dragnodes) + this.node_dragged = n; + + if(!this.selected_nodes[n.id]) + this.processNodeSelected(n,e); + } + + this.dirty_canvas = true; + } + } + else + clicking_canvas_bg = true; + + if(clicking_canvas_bg && this.allow_dragcanvas) + { + this.dragging_canvas = true; + } + } + else if (e.which == 2) //middle button + { + + } + else if (e.which == 3) //right button + { + this.processContextualMenu(n,e); + } + + //TODO + //if(this.node_selected != prev_selected) + // this.onNodeSelectionChange(this.node_selected); + + this.last_mouse[0] = e.localX; + this.last_mouse[1] = e.localY; + this.last_mouseclick = new Date().getTime(); + this.canvas_mouse = [e.canvasX, e.canvasY]; + + /* + if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + //this is to ensure to defocus(blur) if a text input element is on focus + if(!document.activeElement || (document.activeElement.nodeName.toLowerCase() != "input" && document.activeElement.nodeName.toLowerCase() != "textarea")) + e.preventDefault(); + e.stopPropagation(); + return false; +} + +LGraphCanvas.prototype.processMouseMove = function(e) +{ + if(!this.graph) return; + + this.adjustMouseEvent(e); + var mouse = [e.localX, e.localY]; + var delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]]; + this.last_mouse = mouse; + this.canvas_mouse = [e.canvasX, e.canvasY]; + + if(this.dragging_canvas) + { + this.graph.config.canvas_offset[0] += delta[0] / this.graph.config.canvas_scale; + this.graph.config.canvas_offset[1] += delta[1] / this.graph.config.canvas_scale; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + else + { + if(this.connecting_node) + this.dirty_canvas = true; + + //get node over + var n = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + + //remove mouseover flag + for(var i in this.graph.nodes) + { + if(this.graph.nodes[i].mouseOver && n != this.graph.nodes[i]) + { + //mouse leave + this.graph.nodes[i].mouseOver = false; + if(this.node_over && this.node_over.onMouseLeave) + this.node_over.onMouseLeave(e); + this.node_over = null; + this.dirty_canvas = true; + } + } + + //mouse over a node + if(n) + { + //this.canvas.style.cursor = "move"; + if(!n.mouseOver) + { + //mouse enter + n.mouseOver = true; + this.node_over = n; + this.dirty_canvas = true; + + if(n.onMouseEnter) n.onMouseEnter(e); + } + + if(n.onMouseMove) n.onMouseMove(e); + + //ontop 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(slot_type == this.connecting_output.type || slot_type == "*" || this.connecting_output.type == "*") + this._highlight_input = pos; + } + else + this._highlight_input = null; + } + + //Search for corner + if( isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 )) + this.canvas.style.cursor = "se-resize"; + else + this.canvas.style.cursor = null; + } + else + this.canvas.style.cursor = null; + + if(this.node_capturing_input && this.node_capturing_input != n && this.node_capturing_input.onMouseMove) + { + this.node_capturing_input.onMouseMove(e); + } + + + if(this.node_dragged && !this.live_mode) + { + /* + this.node_dragged.pos[0] += delta[0] / this.graph.config.canvas_scale; + this.node_dragged.pos[1] += delta[1] / this.graph.config.canvas_scale; + this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]); + this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]); + */ + + for(var i in this.selected_nodes) + { + var n = this.selected_nodes[i]; + + n.pos[0] += delta[0] / this.graph.config.canvas_scale; + n.pos[1] += delta[1] / this.graph.config.canvas_scale; + n.pos[0] = Math.round(n.pos[0]); + n.pos[1] = Math.round(n.pos[1]); + } + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + + if(this.resizing_node && !this.live_mode) + { + this.resizing_node.size[0] += delta[0] / this.graph.config.canvas_scale; + this.resizing_node.size[1] += delta[1] / this.graph.config.canvas_scale; + var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0); + if(this.resizing_node.size[1] < max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4) + this.resizing_node.size[1] = max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4; + if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH) + this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH; + + this.canvas.style.cursor = "se-resize"; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + } + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + e.preventDefault(); + e.stopPropagation(); + return false; + //this is not really optimal + //this.graph.change(); +} + +LGraphCanvas.prototype.processMouseUp = function(e) +{ + if(!this.graph) return; + + document.removeEventListener("mousemove", this._mousemove_callback, true ); + this.canvas.addEventListener("mousemove", this._mousemove_callback, true); + document.removeEventListener("mouseup", this._mouseup_callback, true ); + + this.adjustMouseEvent(e); + + if (e.which == 1) //left button + { + //dragging a connection + if(this.connecting_node) + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + + var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes); + + //node below mouse + if(node) + { + + if(this.connecting_output.type == 'node') + { + this.connecting_node.connect(this.connecting_slot, node, -1); + } + else + { + //slot below mouse? connect + var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY); + if(slot != -1) + { + this.connecting_node.connect(this.connecting_slot, node, slot); + } + } + } + + this.connecting_output = null; + this.connecting_pos = null; + this.connecting_node = null; + this.connecting_slot = -1; + + }//not dragging connection + else if(this.resizing_node) + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + this.resizing_node = null; + } + else if(this.node_dragged) //node being dragged? + { + this.dirty_canvas = true; + this.dirty_bgcanvas = true; + + if(this.graph.config.align_to_grid) + this.node_dragged.alignToGrid(); + this.node_dragged = null; + } + else //no node being dragged + { + this.dirty_canvas = true; + this.dragging_canvas = false; + + if( this.node_over && this.node_over.onMouseUp ) + this.node_over.onMouseUp(e); + if( this.node_capturing_input && this.node_capturing_input.onMouseUp ) + this.node_capturing_input.onMouseUp(e); + } + } + else if (e.which == 2) //middle button + { + //trace("middle"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + else if (e.which == 3) //right button + { + //trace("right"); + this.dirty_canvas = true; + this.dragging_canvas = false; + } + + /* + if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + e.stopPropagation(); + e.preventDefault(); + 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) + { + var input = node.inputs[i]; + 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] }; + return i; + } + } + return -1; +} + +LGraphCanvas.prototype.processKeyDown = function(e) +{ + if(!this.graph) return; + var block_default = false; + + //select all Control A + if(e.keyCode == 65 && e.ctrlKey) + { + this.selectAllNodes(); + block_default = true; + } + + //delete or backspace + if(e.keyCode == 46 || e.keyCode == 8) + { + this.deleteSelectedNodes(); + } + + //collapse + //... + + //TODO + if(this.selected_nodes) + for (var i in this.selected_nodes) + if(this.selected_nodes[i].onKeyDown) + this.selected_nodes[i].onKeyDown(e); + + this.graph.change(); + + if(block_default) + { + e.preventDefault(); + return false; + } +} + +LGraphCanvas.prototype.processKeyUp = function(e) +{ + if(!this.graph) return; + //TODO + if(this.selected_nodes) + for (var i in this.selected_nodes) + if(this.selected_nodes[i].onKeyUp) + this.selected_nodes[i].onKeyUp(e); + + this.graph.change(); +} + +LGraphCanvas.prototype.processMouseWheel = function(e) +{ + if(!this.graph) return; + if(!this.allow_dragcanvas) return; + + var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60); + + this.adjustMouseEvent(e); + + var zoom = this.graph.config.canvas_scale; + + if (delta > 0) + zoom *= 1.1; + else if (delta < 0) + zoom *= 1/(1.1); + + this.setZoom( zoom, [ e.localX, e.localY ] ); + + /* + if(this.rendering_timer_id == null) + this.draw(); + */ + + this.graph.change(); + + e.preventDefault(); + return false; // prevent default +} + +LGraphCanvas.prototype.processNodeSelected = function(n,e) +{ + n.selected = true; + if (n.onSelected) + n.onSelected(); + + if(e && e.shiftKey) //add to selection + this.selected_nodes[n.id] = n; + else + { + this.selected_nodes = {}; + this.selected_nodes[ n.id ] = n; + } + + this.dirty_canvas = true; + + if(this.onNodeSelected) + this.onNodeSelected(n); + + //if(this.node_in_panel) this.showNodePanel(n); +} + +LGraphCanvas.prototype.processNodeDeselected = function(n) +{ + n.selected = false; + if(n.onDeselected) + n.onDeselected(); + + delete this.selected_nodes[n.id]; + + if(this.onNodeDeselected) + this.onNodeDeselected(); + + this.dirty_canvas = true; + + //this.showNodePanel(null); +} + +LGraphCanvas.prototype.processNodeDblClicked = function(n) +{ + if(this.onShowNodePanel) + this.onShowNodePanel(n); + + if(this.onNodeDblClicked) + this.onNodeDblClicked(n); + + this.setDirty(true); +} + +LGraphCanvas.prototype.selectNode = function(node) +{ + this.deselectAllNodes(); + + if(!node) + return; + + if(!node.selected && node.onSelected) + node.onSelected(); + node.selected = true; + this.selected_nodes[ node.id ] = node; + this.setDirty(true); +} + +LGraphCanvas.prototype.selectAllNodes = function() +{ + for(var i in this.graph.nodes) + { + var n = this.graph.nodes[i]; + if(!n.selected && n.onSelected) + n.onSelected(); + n.selected = true; + this.selected_nodes[this.graph.nodes[i].id] = n; + } + + this.setDirty(true); +} + +LGraphCanvas.prototype.deselectAllNodes = function() +{ + for(var i in this.selected_nodes) + { + var n = this.selected_nodes; + if(n.onDeselected) + n.onDeselected(); + n.selected = false; + } + this.selected_nodes = {}; + this.setDirty(true); +} + +LGraphCanvas.prototype.deleteSelectedNodes = function() +{ + for(var i in this.selected_nodes) + { + var m = this.selected_nodes[i]; + //if(m == this.node_in_panel) this.showNodePanel(null); + this.graph.remove(m); + } + this.selected_nodes = {}; + this.setDirty(true); +} + +LGraphCanvas.prototype.centerOnNode = function(node) +{ + this.graph.config.canvas_offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.graph.config.canvas_scale); + this.graph.config.canvas_offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.graph.config.canvas_scale); + this.setDirty(true,true); +} + +LGraphCanvas.prototype.adjustMouseEvent = function(e) +{ + var b = this.canvas.getBoundingClientRect(); + e.localX = e.pageX - b.left; + e.localY = e.pageY - b.top; + + e.canvasX = e.localX / this.graph.config.canvas_scale - this.graph.config.canvas_offset[0]; + e.canvasY = e.localY / this.graph.config.canvas_scale - this.graph.config.canvas_offset[1]; +} + +LGraphCanvas.prototype.setZoom = function(value, zooming_center) +{ + if(!zooming_center) + zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5]; + + var center = this.convertOffsetToCanvas( zooming_center ); + + this.graph.config.canvas_scale = value; + + if(this.graph.config.canvas_scale > 4) + this.graph.config.canvas_scale = 4; + else if(this.graph.config.canvas_scale < 0.1) + this.graph.config.canvas_scale = 0.1; + + var new_center = this.convertOffsetToCanvas( zooming_center ); + var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]]; + + this.graph.config.canvas_offset[0] += delta_offset[0]; + this.graph.config.canvas_offset[1] += delta_offset[1]; + + this.dirty_canvas = true; + this.dirty_bgcanvas = true; +} + +LGraphCanvas.prototype.convertOffsetToCanvas = function(pos) +{ + return [pos[0] / this.graph.config.canvas_scale - this.graph.config.canvas_offset[0], pos[1] / this.graph.config.canvas_scale - this.graph.config.canvas_offset[1]]; +} + +LGraphCanvas.prototype.convertCanvasToOffset = function(pos) +{ + return [(pos[0] + this.graph.config.canvas_offset[0]) * this.graph.config.canvas_scale, + (pos[1] + this.graph.config.canvas_offset[1]) * this.graph.config.canvas_scale ]; +} + +LGraphCanvas.prototype.convertEventToCanvas = function(e) +{ + var rect = this.canvas.getClientRects()[0]; + return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]); +} + +LGraphCanvas.prototype.bringToFront = function(n) +{ + var i = this.graph.nodes.indexOf(n); + if(i == -1) return; + + this.graph.nodes.splice(i,1); + this.graph.nodes.push(n); +} + +LGraphCanvas.prototype.sendToBack = function(n) +{ + var i = this.graph.nodes.indexOf(n); + if(i == -1) return; + + this.graph.nodes.splice(i,1); + this.graph.nodes.unshift(n); +} + +/* Interaction */ + + + +/* LGraphCanvas render */ + +LGraphCanvas.prototype.computeVisibleNodes = function() +{ + var visible_nodes = []; + for (var i in this.graph.nodes) + { + var n = this.graph.nodes[i]; + + //skip rendering nodes in live mode + if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground) + continue; + + if(!overlapBounding(this.visible_area, n.getBounding() )) + continue; //out of the visible area + + visible_nodes.push(n); + } + return visible_nodes; +} + +LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) +{ + //fps counting + var now = new Date().getTime(); + this.render_time = (now - this.last_draw_time)*0.001; + this.last_draw_time = now; + + if(this.graph) + { + var start = [-this.graph.config.canvas_offset[0], -this.graph.config.canvas_offset[1] ]; + var end = [start[0] + this.canvas.width / this.graph.config.canvas_scale, start[1] + this.canvas.height / this.graph.config.canvas_scale]; + this.visible_area = new Float32Array([start[0],start[1],end[0],end[1]]); + } + + if(this.dirty_bgcanvas || force_bgcanvas) + this.drawBgcanvas(); + + if(this.dirty_canvas || force_canvas) + this.drawFrontCanvas(); + + this.fps = this.render_time ? (1.0 / this.render_time) : 0; + this.frame += 1; +} + +LGraphCanvas.prototype.drawFrontCanvas = function() +{ + var ctx = this.ctx; + var canvas = this.canvas; + + //reset in case of error + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + + //clip dirty area if there is one, otherwise work in full canvas + if(this.dirty_area) + { + ctx.save(); + ctx.beginPath(); + ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]); + ctx.clip(); + } + + //clear + //canvas.width = canvas.width; + ctx.clearRect(0,0,canvas.width, canvas.height); + + //draw bg canvas + ctx.drawImage(this.bgcanvas,0,0); + + //info widget + if(this.show_info) + { + ctx.font = "10px Arial"; + ctx.fillStyle = "#888"; + if(this.graph) + { + ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 ); + ctx.fillText( "I: " + this.graph.iteration,5,13*2 ); + ctx.fillText( "F: " + this.frame,5,13*3 ); + ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 ); + } + else + ctx.fillText( "No graph selected",5,13*1 ); + } + + if(this.graph) + { + //apply transformations + ctx.save(); + ctx.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale); + ctx.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]); + + //draw nodes + var drawn_nodes = 0; + var visible_nodes = this.computeVisibleNodes(); + this.visible_nodes = visible_nodes; + + for (var i in visible_nodes) + { + var node = visible_nodes[i]; + + //transform coords system + ctx.save(); + ctx.translate( node.pos[0], node.pos[1] ); + + //Draw + this.drawNode(node, ctx ); + drawn_nodes += 1; + + //Restore + ctx.restore(); + } + + //connections ontop? + if(this.graph.config.links_ontop) + if(!this.live_mode) + this.drawConnections(ctx); + + //current connection + if(this.connecting_pos != null) + { + ctx.lineWidth = LGraphCanvas.link_width; + ctx.fillStyle = this.connecting_output.type == 'node' ? "#F85" : "#AFA"; + ctx.strokeStyle = ctx.fillStyle; + this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]] ); + + ctx.beginPath(); + 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); + else + ctx.rect( this.connecting_pos[0], this.connecting_pos[1],12,6); + */ + ctx.fill(); + + ctx.fillStyle = "#ffcc00"; + if(this._highlight_input) + { + ctx.beginPath(); + ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2); + ctx.fill(); + } + } + ctx.restore(); + } + + if(this.dirty_area) + { + ctx.restore(); + //this.dirty_area = null; + } + + this.dirty_canvas = false; +} + +LGraphCanvas.prototype.drawBgcanvas = function() +{ + var canvas = this.bgcanvas; + var ctx = this.bgctx; + + + //clear + canvas.width = canvas.width; + + //reset in case of error + ctx.restore(); + ctx.setTransform(1, 0, 0, 1, 0, 0); + + if(this.graph) + { + //apply transformations + ctx.save(); + ctx.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale); + ctx.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]); + + //render BG + if(this.background_image && this.graph.config.canvas_scale > 0.5) + { + ctx.globalAlpha = 1.0 - 0.5 / this.graph.config.canvas_scale; + ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false + if(!this._bg_img || this._bg_img.name != this.background_image) + { + this._bg_img = new Image(); + this._bg_img.name = this.background_image; + this._bg_img.src = this.background_image; + var that = this; + this._bg_img.onload = function() { + that.draw(true,true); + } + } + + var pattern = null; + if(this._bg_img != this._pattern_img && this._bg_img.width > 0) + { + pattern = ctx.createPattern( this._bg_img, 'repeat' ); + this._pattern_img = this._bg_img; + this._pattern = pattern; + } + else + pattern = this._pattern; + if(pattern) + { + ctx.fillStyle = pattern; + ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2]-this.visible_area[0],this.visible_area[3]-this.visible_area[1]); + ctx.fillStyle = "transparent"; + } + + ctx.globalAlpha = 1.0; + } + + //DEBUG: show clipping area + //ctx.fillStyle = "red"; + //ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - this.visible_area[0] - 20, this.visible_area[3] - this.visible_area[1] - 20); + + //bg + ctx.strokeStyle = "#235"; + ctx.strokeRect(0,0,canvas.width,canvas.height); + + /* + if(this.render_shadows) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 6; + } + else + ctx.shadowColor = "rgba(0,0,0,0)"; + */ + + //draw connections + if(!this.live_mode) + this.drawConnections(ctx); + + //restore state + ctx.restore(); + } + + this.dirty_bgcanvas = false; + this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas +} + +/* Renders the LGraphNode on the canvas */ +LGraphCanvas.prototype.drawNode = function(node, ctx ) +{ + var glow = false; + + var color = node.color || LiteGraph.NODE_DEFAULT_COLOR; + //if (this.selected) color = "#88F"; + + var render_title = true; + if(node.flags.skip_title_render || node.graph.isLive()) + render_title = false; + if(node.mouseOver) + render_title = true; + + //shadow and glow + if (node.mouseOver) glow = true; + + if(node.selected) + { + /* + ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 1; + */ + } + else if(this.render_shadows) + { + ctx.shadowColor = "#111"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 4; + } + else + ctx.shadowColor = "transparent"; + + //only render if it forces it to do it + if(this.live_mode) + { + if(!node.flags.collapsed) + { + ctx.shadowColor = "transparent"; + if(node.onDrawBackground) + node.onDrawBackground(ctx); + if(node.onDrawForeground) + node.onDrawForeground(ctx); + } + + return; + } + + //draw in collapsed form + /* + if(node.flags.collapsed) + { + if(!node.onDrawCollapsed || node.onDrawCollapsed(ctx) == false) + this.drawNodeCollapsed(node, ctx, color, node.bgcolor); + return; + } + */ + + //clip if required (mask) + var shape = node.shape || "box"; + var size = new Float32Array(node.size); + if(node.flags.collapsed) + size.set([LiteGraph.NODE_COLLAPSED_WIDTH, 0]); + + //Start cliping + if(node.flags.clip_area) + { + ctx.save(); + if(shape == "box") + { + ctx.beginPath(); + ctx.rect(0,0,size[0], size[1]); + } + else if (shape == "round") + { + ctx.roundRect(0,0,size[0], size[1],10); + } + else if (shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + ctx.clip(); + } + + //draw shape + this.drawNodeShape(node, ctx, size, color, node.bgcolor, !render_title, node.selected ); + ctx.shadowColor = "transparent"; + + //connection slots + ctx.textAlign = "left"; + ctx.font = "12px Arial"; + + var render_text = node.graph.config.canvas_scale > 0.6; + + //render inputs and outputs + if(!node.flags.collapsed) + { + //input connection slots + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + + ctx.globalAlpha = 1.0; + if (this.connecting_node != null && this.connecting_output.type != 0 && node.inputs[i].type != 0 && this.connecting_output.type != node.inputs[i].type) + ctx.globalAlpha = 0.4; + + ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; + + var pos = node.getConnectionPos(true,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.beginPath(); + + 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); + + ctx.fill(); + + //render name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text,pos[0] + 10,pos[1] + 5); + } + } + } + + //output connection slots + if(this.connecting_node) + ctx.globalAlpha = 0.4; + + ctx.lineWidth = 1; + + ctx.textAlign = "right"; + ctx.strokeStyle = "black"; + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + + var pos = node.getConnectionPos(false,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA"; + 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); + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + ctx.stroke(); + + //render output name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text, pos[0] - 10,pos[1] + 5); + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1.0; + + if(node.onDrawForeground) + node.onDrawForeground(ctx); + }//!collapsed + + if(node.flags.clip_area) + ctx.restore(); +} + +/* Renders the node shape */ +LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, no_title, selected ) +{ + //bg rect + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + /* gradient test + var grad = ctx.createLinearGradient(0,0,0,node.size[1]); + grad.addColorStop(0, "#AAA"); + grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR); + grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR); + ctx.fillStyle = grad; + */ + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + + //render depending on shape + var shape = node.shape || "box"; + if(shape == "box") + { + if(selected) + { + ctx.strokeStyle = "#CCC"; + ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) ); + ctx.strokeStyle = fgcolor; + } + + ctx.beginPath(); + ctx.rect(0,no_title ? 0.5 : -title_height + 1,size[0]+1, no_title ? size[1] : size[1] + title_height); + } + else if (node.shape == "round") + { + ctx.roundRect(0,no_title ? 0 : -title_height,size[0], no_title ? size[1] : size[1] + title_height, 10); + } + else if (node.shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + + ctx.fill(); + ctx.shadowColor = "transparent"; + + //ctx.stroke(); + + //image + if (node.bgImage && node.bgImage.width) + ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5); + + if(node.bgImageUrl && !node.bgImage) + node.bgImage = node.loadImage(node.bgImageUrl); + + if(node.onDrawBackground) + node.onDrawBackground(ctx); + + //title bg + if(!no_title) + { + ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + + if(shape == "box") + { + ctx.beginPath(); + ctx.fillRect(0,-title_height,size[0]+1,title_height); + ctx.stroke(); + } + else if (shape == "round") + { + ctx.roundRect(0,-title_height,size[0], title_height,10,0); + //ctx.fillRect(0,8,size[0],NODE_TITLE_HEIGHT - 12); + ctx.fill(); + ctx.stroke(); + } + + //box + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + if (shape == "round") + ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); + else + ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6); + ctx.fill(); + + //title text + ctx.font = "bold 12px Arial"; + if(node.name != "" && node.graph.config.canvas_scale > 0.8) + { + ctx.fillStyle = "#222"; + ctx.fillText(node.name,16,13-title_height ); + } + } +} + +/* Renders the node when collapsed */ +LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor) +{ + //draw default collapsed shape + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS; + + //circle shape + var shape = node.shape || "box"; + if(shape == "circle") + { + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius,0,Math.PI * 2); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2); + ctx.fill(); + } + else if(shape == "round") //rounded box + { + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2); + ctx.fill(); + } + else //flat box + { + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius, 2*collapsed_radius); + ctx.rect(0, 0, node.size[0], collapsed_radius * 2 ); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius); + ctx.rect(collapsed_radius*0.5, collapsed_radius*0.5, collapsed_radius, collapsed_radius); + ctx.fill(); + } +} + +LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"]; + +LGraphCanvas.prototype.drawConnections = function(ctx) +{ + //draw connections + ctx.lineWidth = LGraphCanvas.link_width; + + ctx.fillStyle = "#AAA"; + ctx.strokeStyle = "#AAA"; + //for every node + for (var n in this.graph.nodes) + { + 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) + { + var input = node.inputs[i]; + if(!input || !input.link ) continue; + var link = input.link; + + var start_node = this.graph.getNodeById( link[1] ); + if(start_node == null) continue; + var start_node_slot = link[2]; + var start_node_slotpos = null; + + if(start_node_slot == -1) + start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10]; + else + start_node_slotpos = start_node.getConnectionPos(false, start_node_slot); + + var color = LGraphCanvas.link_type_colors[node.inputs[i].type]; + if(color == null) + color = LGraphCanvas.link_colors[node.id % LGraphCanvas.link_colors.length]; + ctx.fillStyle = ctx.strokeStyle = color; + this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i) ); + } + } +} + +LGraphCanvas.prototype.renderLink = function(ctx,a,b) +{ + var curved_lines = true; + + if(!this.highquality_render) + { + ctx.beginPath(); + ctx.moveTo(a[0],a[1]); + ctx.lineTo(b[0],b[1]); + ctx.stroke(); + return; + } + + var dist = distance(a,b); + + ctx.beginPath(); + + if(curved_lines) + { + ctx.moveTo(a[0],a[1]); + ctx.bezierCurveTo(a[0] + dist*0.25, a[1], + b[0] - dist*0.25 , b[1], + b[0] ,b[1] ); + } + else + { + ctx.moveTo(a[0]+10,a[1]); + ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]); + ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]); + ctx.lineTo(b[0]-10,b[1]); + } + ctx.stroke(); + + //render arrow + if(this.graph.config.canvas_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); + var angle = 0; + if(curved_lines) + angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]); + else + angle = b[1] > a[1] ? 0 : Math.PI; + + ctx.save(); + ctx.translate(pos[0],pos[1]); + ctx.rotate(angle); + ctx.beginPath(); + ctx.moveTo(-5,-5); + ctx.lineTo(0,+5); + ctx.lineTo(+5,-5); + ctx.fill(); + ctx.restore(); + } +} + +LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t) +{ + var dist = distance(a,b); + var p0 = a; + var p1 = [ a[0] + dist*0.25, a[1] ]; + var p2 = [ b[0] - dist*0.25, b[1] ]; + var p3 = b; + + var c1 = (1-t)*(1-t)*(1-t); + var c2 = 3*((1-t)*(1-t))*t; + var c3 = 3*(1-t)*(t*t); + var c4 = t*t*t; + + var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0]; + var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1]; + return [x,y]; +} + +LGraphCanvas.prototype.resizeCanvas = function(width,height) +{ + this.canvas.width = width; + if(height) + this.canvas.height = height; + + this.bgcanvas.width = this.canvas.width; + this.bgcanvas.height = this.canvas.height; + this.draw(true,true); +} + +LGraphCanvas.prototype.switchLiveMode = function() +{ + this.live_mode = !this.live_mode; + this.dirty_canvas = true; + this.dirty_bgcanvas = true; +} + +LGraphCanvas.prototype.onNodeSelectionChange = function(node) +{ + return; //disabled + //if(this.node_in_panel) this.showNodePanel(node); +} + +LGraphCanvas.prototype.touchHandler = function(event) +{ + //alert("foo"); + var touches = event.changedTouches, + first = touches[0], + type = ""; + + switch(event.type) + { + case "touchstart": type = "mousedown"; break; + case "touchmove": type="mousemove"; break; + case "touchend": type="mouseup"; break; + default: return; + } + + //initMouseEvent(type, canBubble, cancelable, view, clickCount, + // screenX, screenY, clientX, clientY, ctrlKey, + // altKey, shiftKey, metaKey, button, relatedTarget); + + var simulatedEvent = document.createEvent("MouseEvent"); + simulatedEvent.initMouseEvent(type, true, true, window, 1, + first.screenX, first.screenY, + first.clientX, first.clientY, false, + false, false, false, 0/*left*/, null); + first.target.dispatchEvent(simulatedEvent); + event.preventDefault(); +} + +/* CONTEXT MENU ********************/ + +LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event ) +{ + var values = LiteGraph.getNodeTypesCategories(); + var entries = {}; + for(var i in values) + 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}); + + function inner_clicked(v, e) + { + var category = v.value; + var node_types = LiteGraph.getNodeTypesInCategory(category); + var values = []; + 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}); + return false; + } + + function inner_create(v, e) + { + var node = LiteGraph.createNode( v.value ); + if(node) + { + node.pos = canvas.convertEventToCanvas(first_event); + canvas.graph.add( node ); + } + } + + return false; +} + +LGraphCanvas.onMenuCollapseAll = function() +{ + +} + + +LGraphCanvas.onMenuNodeEdit = function() +{ + +} + +LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu) +{ + if(!node) return; + + var options = node.optional_inputs; + if(node.onGetInputs) + options = node.onGetInputs(); + if(options) + { + var entries = []; + for (var i in options) + { + var option = options[i]; + var label = option[0]; + if(option[2] && option[2].label) + label = option[2].label; + entries.push({content: label, value: option}); + } + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + } + + function inner_clicked(v) + { + if(!node) return; + node.addInput(v.value[0],v.value[1], v.value[2]); + } + + return false; +} + +LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu) +{ + if(!node) return; + + var options = node.optional_outputs; + if(node.onGetOutputs) + options = node.onGetOutputs(); + if(options) + { + var entries = []; + for (var i in options) + { + if(node.findOutputSlot(options[i][0]) != -1) + continue; //skip the ones already on + entries.push({content: options[i][0], value: options[i]}); + } + if(entries.length) + var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu}); + } + + function inner_clicked(v) + { + if(!node) return; + node.addOutput(v.value[0],v.value[1]); + } + + return false; +} + +LGraphCanvas.onMenuNodeCollapse = function(node) +{ + node.flags.collapsed = !node.flags.collapsed; + node.graph.canvas.setDirty(true,true); +} + +LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) +{ + var values = []; + for(var i in LGraphCanvas.node_colors) + { + var color = LGraphCanvas.node_colors[i]; + var value = {value:i, content:""+i+""}; + values.push(value); + } + LiteGraph.createContextualMenu(values, {event: e, callback: inner_clicked, from: prev_menu}); + + function inner_clicked(v) + { + if(!node) return; + var color = LGraphCanvas.node_colors[v.value]; + if(color) + { + node.color = color.color; + node.bgcolor = color.bgcolor; + node.graph.canvas.setDirty(true); + } + } + + return false; +} + +LGraphCanvas.onMenuNodeShapes = function(node,e) +{ + LiteGraph.createContextualMenu(["box","round","circle"], {event: e, callback: inner_clicked}); + + function inner_clicked(v) + { + if(!node) return; + node.shape = v; + node.graph.canvas.setDirty(true); + } + + return false; +} + +LGraphCanvas.onMenuNodeRemove = function(node) +{ + if(node.removable == false) return; + node.graph.remove(node); + node.graph.canvas.setDirty(true,true); +} + +LGraphCanvas.onMenuNodeClone = function(node) +{ + if(node.clonable == false) return; + var newnode = node.clone(); + if(!newnode) return; + newnode.pos = [node.pos[0]+5,node.pos[1]+5]; + node.graph.add(newnode); + node.graph.canvas.setDirty(true,true); +} + +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() +{ + return [ + {content:"Add Node", is_menu: true, callback: LGraphCanvas.onMenuAdd } + //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + ]; +} + +LGraphCanvas.prototype.getNodeMenuOptions = function(node) +{ + var options = [ + {content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeInputs }, + {content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeOutputs }, + null, + {content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse }, + {content:"Colors", is_menu: true, callback: LGraphCanvas.onMenuNodeColors }, + {content:"Shapes", is_menu: true, callback: LGraphCanvas.onMenuNodeShapes }, + null, + {content:"Clone", callback: LGraphCanvas.onMenuNodeClone }, + null, + {content:"Remove", callback: LGraphCanvas.onMenuNodeRemove } + ]; + + if( node.clonable == false ) + options[7].disabled = true; + if( node.removable == false ) + options[9].disabled = true; + + if(node.onGetInputs && node.onGetInputs().length ) + options[0].disabled = false; + if(node.onGetOutputs && node.onGetOutputs().length ) + options[1].disabled = false; + + return options; +} + +LGraphCanvas.prototype.processContextualMenu = function(node,event) +{ + var that = this; + var menu = LiteGraph.createContextualMenu(node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(), {event: event, callback: inner_option_clicked}); + + function inner_option_clicked(v,e) + { + if(!v) return; + + if(v.callback) + return v.callback(node, e, menu, that, event ); + } +} + + + + + + +//API ************************************************* +//function roundRect(ctx, x, y, width, height, radius, radius_low) { +CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) { + if ( radius === undefined ) { + radius = 5; + } + + if(radius_low === undefined) + radius_low = radius; + + this.beginPath(); + this.moveTo(x + radius, y); + this.lineTo(x + width - radius, y); + this.quadraticCurveTo(x + width, y, x + width, y + radius); + + this.lineTo(x + width, y + height - radius_low); + this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height); + this.lineTo(x + radius_low, y + height); + this.quadraticCurveTo(x, y + height, x, y + height - radius_low); + this.lineTo(x, y + radius); + this.quadraticCurveTo(x, y, x + radius, y); +} + +function compareObjects(a,b) +{ + for(var i in a) + if(a[i] != b[i]) + return false; + return true; +} + +function distance(a,b) +{ + return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) ); +} + +function colorToString(c) +{ + return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")"; +} + +function isInsideRectangle(x,y, left, top, width, height) +{ + if (left < x && (left + width) > x && + top < y && (top + height) > y) + return true; + return false; +} + +//[minx,miny,maxx,maxy] +function growBounding(bounding, x,y) +{ + if(x < bounding[0]) + bounding[0] = x; + else if(x > bounding[2]) + bounding[2] = x; + + if(y < bounding[1]) + bounding[1] = y; + else if(y > bounding[3]) + bounding[3] = y; +} + +//point inside boundin box +function isInsideBounding(p,bb) +{ + if (p[0] < bb[0][0] || + p[1] < bb[0][1] || + p[0] > bb[1][0] || + p[1] > bb[1][1]) + return false; + return true; +} + +//boundings overlap, format: [start,end] +function overlapBounding(a,b) +{ + if ( a[0] > b[2] || + a[1] > b[3] || + a[2] < b[0] || + a[3] < b[1]) + return false; + return true; +} + +//Convert a hex value to its decimal value - the inputted hex must be in the +// format of a hex triplet - the kind we use for HTML colours. The function +// will return an array with three values. +function hex2num(hex) { + if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one. + hex = hex.toUpperCase(); + var hex_alphabets = "0123456789ABCDEF"; + var value = new Array(3); + var k = 0; + var int1,int2; + for(var i=0;i<6;i+=2) { + int1 = hex_alphabets.indexOf(hex.charAt(i)); + int2 = hex_alphabets.indexOf(hex.charAt(i+1)); + value[k] = (int1 * 16) + int2; + k++; + } + return(value); +} +//Give a array with three values as the argument and the function will return +// the corresponding hex triplet. +function num2hex(triplet) { + var hex_alphabets = "0123456789ABCDEF"; + var hex = "#"; + var int1,int2; + for(var i=0;i<3;i++) { + int1 = triplet[i] / 16; + int2 = triplet[i] % 16; + + hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); + } + return(hex); +} + +/* LiteGraph GUI elements *************************************/ + +LiteGraph.createContextualMenu = function(values,options) +{ + options = options || {}; + this.options = options; + + if(!options.from) + LiteGraph.closeAllContextualMenus(); + + var root = document.createElement("div"); + root.className = "litecontextualmenu litemenubar-panel"; + this.root = root; + var style = root.style; + + style.minWidth = "100px"; + style.minHeight = "20px"; + + style.position = "fixed"; + style.top = "100px"; + style.left = "100px"; + style.color = "#AAF"; + style.padding = "2px"; + style.borderBottom = "2px solid #AAF"; + style.backgroundColor = "#444"; + + //avoid a context menu in a context menu + root.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; }); + + for(var i in values) + { + var item = values[i]; + var element = document.createElement("div"); + element.className = "litemenu-entry"; + + if(item == null) + { + element.className = "litemenu-entry separator"; + root.appendChild(element); + continue; + } + + if(item.is_menu) + element.className += " submenu"; + + if(item.disabled) + element.className += " disabled"; + + element.style.cursor = "pointer"; + element.dataset["value"] = typeof(item) == "string" ? item : item.value; + element.data = item; + if(typeof(item) == "string") + element.innerHTML = values.constructor == Array ? values[i] : i; + else + element.innerHTML = item.content ? item.content : i; + + element.addEventListener("click", on_click ); + root.appendChild(element); + } + + root.addEventListener("mouseover", function(e) { + this.mouse_inside = true; + }); + + root.addEventListener("mouseout", function(e) { + //console.log("OUT!"); + var aux = e.toElement; + while(aux != this && aux != document) + aux = aux.parentNode; + + if(aux == this) return; + this.mouse_inside = false; + if(!this.block_close) + this.closeMenu(); + }); + + /* MS specific + root.addEventListener("mouseleave", function(e) { + + this.mouse_inside = false; + if(!this.block_close) + this.closeMenu(); + }); + */ + + //insert before checking position + document.body.appendChild(root); + + var root_rect = root.getClientRects()[0]; + + //link menus + if(options.from) + { + options.from.block_close = true; + } + + var left = options.left || 0; + var top = options.top || 0; + if(options.event) + { + left = (options.event.pageX - 10); + top = (options.event.pageY - 10); + if(options.left) + left = options.left; + + var rect = document.body.getClientRects()[0]; + + if(options.from) + { + var parent_rect = options.from.getClientRects()[0]; + left = parent_rect.left + parent_rect.width; + } + + + if(left > (rect.width - root_rect.width - 10)) + left = (rect.width - root_rect.width - 10); + if(top > (rect.height - root_rect.height - 10)) + top = (rect.height - root_rect.height - 10); + } + + root.style.left = left + "px"; + root.style.top = top + "px"; + + function on_click(e) { + var value = this.dataset["value"]; + var close = true; + if(options.callback) + { + var ret = options.callback.call(root, this.data, e ); + if( ret != undefined ) close = ret; + } + + if(close) + LiteGraph.closeAllContextualMenus(); + //root.closeMenu(); + } + + root.closeMenu = function() + { + if(options.from) + { + options.from.block_close = false; + if(!options.from.mouse_inside) + options.from.closeMenu(); + } + if(this.parentNode) + document.body.removeChild(this); + }; + + return root; +} + +LiteGraph.closeAllContextualMenus = function() +{ + var elements = document.querySelectorAll(".litecontextualmenu"); + if(!elements.length) return; + + var result = []; + for(var i = 0; i < elements.length; i++) + result.push(elements[i]); + + for(var i in result) + if(result[i].parentNode) + result[i].parentNode.removeChild( result[i] ); +} + +LiteGraph.extendClass = function(origin, target) +{ + for(var i in origin) //copy class properties + target[i] = origin[i]; + if(origin.prototype) //copy prototype properties + for(var i in origin.prototype) + target.prototype[i] = origin.prototype[i]; +} +//basic nodes + +LiteGraph.registerNodeType("basic/const",{ + title: "Const", + desc: "Constant", + outputs: [["value","number"]], + properties: {value:1.0}, + editable: { property:"value", type:"number" }, + + setValue: function(v) + { + if( typeof(v) == "string") v = parseFloat(v); + this.properties["value"] = v; + this.setDirtyCanvas(true); + }, + + onExecute: function() + { + this.setOutputData(0, parseFloat( this.properties["value"] ) ); + }, + + onDrawBackground: function(ctx) + { + //show the current value + this.outputs[0].label = this.properties["value"].toFixed(3); + }, + + onWidget: function(e,widget) + { + if(widget.name == "value") + this.setValue(widget.value); + } +}); + +LiteGraph.registerNodeType("math/rand",{ + title: "Rand", + desc: "Random number", + outputs: [["value","number"]], + properties: {min:0,max:1}, + size: [60,20], + + onExecute: function() + { + var min = this.properties.min; + var max = this.properties.max; + this._last_v = Math.random() * (max-min) + min; + this.setOutputData(0, this._last_v ); + }, + + onDrawBackground: function(ctx) + { + //show the current value + if(this._last_v) + this.outputs[0].label = this._last_v.toFixed(3); + else + this.outputs[0].label = "?"; + } +}); + +LiteGraph.registerNodeType("math/clamp",{ + title: "Clamp", + desc: "Clamp number between min and max", + inputs: [["in","number"]], + outputs: [["out","number"]], + size: [60,20], + properties: {min:0,max:1}, + + onExecute: function() + { + var v = this.getInputData(0); + if(v == null) return; + v = Math.max(this.properties.min,v); + v = Math.min(this.properties.max,v); + this.setOutputData(0, v ); + } +}); + +LiteGraph.registerNodeType("math/abs",{ + title: "Abs", + desc: "Absolute", + inputs: [["in","number"]], + outputs: [["out","number"]], + size: [60,20], + + onExecute: function() + { + var v = this.getInputData(0); + if(v == null) return; + this.setOutputData(0, Math.abs(v) ); + } +}); + +LiteGraph.registerNodeType("math/floor",{ + title: "Floor", + desc: "Floor number to remove fractional part", + inputs: [["in","number"]], + outputs: [["out","number"]], + size: [60,20], + + onExecute: function() + { + var v = this.getInputData(0); + if(v == null) return; + this.setOutputData(0, v|1 ); + } +}); + + +LiteGraph.registerNodeType("math/frac",{ + title: "Frac", + desc: "Returns fractional part", + inputs: [["in","number"]], + outputs: [["out","number"]], + size: [60,20], + + onExecute: function() + { + var v = this.getInputData(0); + if(v == null) return; + this.setOutputData(0, v%1 ); + } +}); + + +LiteGraph.registerNodeType("basic/watch", { + title: "Watch", + desc: "Show value", + size: [60,20], + inputs: [["value",0,{label:""}]], + outputs: [["value",0,{label:""}]], + properties: {value:""}, + + onExecute: function() + { + this.properties.value = this.getInputData(0); + this.setOutputData(0, this.properties.value); + }, + + onDrawBackground: function(ctx) + { + //show the current value + if(this.inputs[0] && this.properties["value"] != null) + { + if (this.properties["value"].constructor === Number ) + this.inputs[0].label = this.properties["value"].toFixed(3); + else + this.inputs[0].label = this.properties["value"]; + } + } +}); + + +LiteGraph.registerNodeType("math/scale",{ + title: "Scale", + desc: "1 - value", + inputs: [["value","number",{label:""}]], + outputs: [["value","number",{label:""}]], + size:[70,20], + properties: {"factor":1}, + + onExecute: function() + { + var value = this.getInputData(0); + if(value != null) + this.setOutputData(0, value * this.properties.factor ); + } +}); + + +LiteGraph.registerNodeType("math/operation",{ + title: "Operation", + desc: "Easy math operators", + inputs: [["A","number"],["B","number"]], + outputs: [["A+B","number"]], + size: [80,20], + //optional_inputs: [["start","number"]], + + properties: {A:1.0, B:1.0}, + + setValue: function(v) + { + if( typeof(v) == "string") v = parseFloat(v); + this.properties["value"] = v; + this.setDirtyCanvas(true); + }, + + onExecute: function() + { + var A = this.getInputData(0); + var B = this.getInputData(1); + if(A!=null) + this.properties["A"] = A; + else + A = this.properties["A"]; + + if(B!=null) + this.properties["B"] = B; + else + B = this.properties["B"]; + + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + if(!output.links || !output.links.length) + continue; + switch( output.name ) + { + case "A+B": value = A+B; break; + case "A-B": value = A-B; break; + case "A*B": value = A*B; break; + case "A/B": value = A/B; break; + } + this.setOutputData(i, value ); + } + }, + + onGetOutputs: function() + { + return [["A-B","number"],["A*B","number"],["A/B","number"]]; + } +}); + +LiteGraph.registerNodeType("math/compare",{ + title: "Compare", + desc: "compares between two values", + + inputs: [["A","number"],["B","number"]], + outputs: [["A==B","number"],["A!=B","number"]], + properties:{A:0,B:0}, + onExecute: function() + { + var A = this.getInputData(0); + var B = this.getInputData(1); + if(A!=null) + this.properties["A"] = A; + else + A = this.properties["A"]; + + if(B!=null) + this.properties["B"] = B; + else + B = this.properties["B"]; + + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + if(!output.links || !output.links.length) + continue; + switch( output.name ) + { + case "A==B": value = A==B; break; + case "A!=B": value = A!=B; break; + case "A>B": value = A>B; break; + case "A=B": value = A>=B; break; + } + this.setOutputData(i, value ); + } + }, + + onGetOutputs: function() + { + return [["A==B","number"],["A!=B","number"],["A>B","number"],["A=B","number"],["A<=B","number"]]; + } +}); + +if(window.math) //math library for safe math operations without eval +LiteGraph.registerNodeType("math/formula",{ + title: "Formula", + desc: "Compute safe formula", + inputs: [["x","number"],["y","number"]], + outputs: [["","number"]], + properties: {x:1.0, y:1.0, formula:"x+y"}, + + onExecute: function() + { + var x = this.getInputData(0); + var y = this.getInputData(1); + if(x != null) + this.properties["x"] = x; + else + x = this.properties["x"]; + + if(y!=null) + this.properties["y"] = y; + else + y = this.properties["y"]; + + var f = this.properties["formula"]; + var value = math.eval(f,{x:x,y:y,T: this.graph.globaltime }); + this.setOutputData(0, value ); + }, + + onDrawBackground: function() + { + var f = this.properties["formula"]; + this.outputs[0].label = f; + }, + + onGetOutputs: function() + { + return [["A-B","number"],["A*B","number"],["A/B","number"]]; + } +}); + + +LiteGraph.registerNodeType("math/trigonometry",{ + title: "Trigonometry", + desc: "Sin Cos Tan", + bgImageUrl: "nodes/imgs/icon-sin.png", + + inputs: [["v","number"]], + outputs: [["sin","number"]], + properties: {amplitude:1.0}, + size:[100,20], + + onExecute: function() + { + var v = this.getInputData(0); + var amp = this.properties["amplitude"]; + for(var i = 0, l = this.outputs.length; i < l; ++i) + { + var output = this.outputs[i]; + switch( output.name ) + { + case "sin": value = Math.sin(v); break; + case "cos": value = Math.cos(v); break; + case "tan": value = Math.tan(v); break; + case "asin": value = Math.asin(v); break; + case "acos": value = Math.acos(v); break; + case "atan": value = Math.atan(v); break; + } + this.setOutputData(i, amp * value ); + } + }, + + onGetOutputs: function() + { + return [["sin","number"],["cos","number"],["tan","number"],["asin","number"],["acos","number"],["atan","number"]]; + } +}); + +//if glMatrix is installed... +if(window.glMatrix) +{ + LiteGraph.registerNodeType("math3d/vec3-to-xyz",{ + title: "Vec3->XYZ", + desc: "vector 3 to components", + inputs: [["vec3","vec3"]], + outputs: [["x","number"],["y","number"],["z","number"]], + + onExecute: function() + { + var v = this.getInputData(0); + if(v == null) return; + + this.setOutputData( 0, v[0] ); + this.setOutputData( 1, v[1] ); + this.setOutputData( 2, v[2] ); + } + }); + + LiteGraph.registerNodeType("math3d/xyz-to-vec3",{ + title: "XYZ->Vec3", + desc: "components to vector3", + inputs: [["x","number"],["y","number"],["z","number"]], + outputs: [["vec3","vec3"]], + + onExecute: function() + { + var x = this.getInputData(0); + if(x == null) x = 0; + var y = this.getInputData(1); + if(y == null) y = 0; + var z = this.getInputData(2); + if(z == null) z = 0; + + this.setOutputData( 0, vec3.fromValues(x,y,z) ); + } + }); + + LiteGraph.registerNodeType("math3d/rotation",{ + title: "Rotation", + desc: "rotation quaternion", + inputs: [["degrees","number"],["axis","vec3"]], + outputs: [["quat","quat"]], + properties: {angle:90.0, axis:[0,1,0]}, + + onExecute: function() + { + var angle = this.getInputData(0); + if(angle == null) angle = this.properties.angle; + var axis = this.getInputData(1); + if(axis == null) axis = this.properties.axis; + + var R = quat.setAxisAngle(quat.create(), axis, angle * 0.0174532925 ); + this.setOutputData( 0, R ); + } + }); + + LiteGraph.registerNodeType("math3d/rotate_vec3",{ + title: "Rot. Vec3", + desc: "rotate a point", + inputs: [["vec3","vec3"],["quat","quat"]], + outputs: [["result","vec3"]], + properties: {vec:[0,0,1]}, + + onExecute: function() + { + var vec = this.getInputData(0); + if(vec == null) vec = this.properties.vec; + var quat = this.getInputData(1); + if(quat == null) + this.setOutputData(vec); + else + this.setOutputData( 0, vec3.transformQuat( vec3.create(), vec, quat ) ); + } + }); + + + LiteGraph.registerNodeType("math3d/mult-quat",{ + title: "Mult. Quat", + desc: "rotate quaternion", + inputs: [["A","quat"],["B","quat"]], + outputs: [["A*B","quat"]], + + onExecute: function() + { + var A = this.getInputData(0); + if(A == null) return; + var B = this.getInputData(1); + if(B == null) return; + + var R = quat.multiply(quat.create(), A,B); + this.setOutputData( 0, R ); + } + }); + +} //glMatrix + + +/* +LiteGraph.registerNodeType("math/sinusoid",{ + title: "Sin", + desc: "Sinusoidal value generator", + bgImageUrl: "nodes/imgs/icon-sin.png", + + inputs: [["f",'number'],["q",'number'],["a",'number'],["t",'number']], + outputs: [["",'number']], + properties: {amplitude:1.0, freq: 1, phase:0}, + + onExecute: function() + { + var f = this.getInputData(0); + if(f != null) + this.properties["freq"] = f; + + var q = this.getInputData(1); + if(q != null) + this.properties["phase"] = q; + + var a = this.getInputData(2); + if(a != null) + this.properties["amplitude"] = a; + + var t = this.graph.getFixedTime(); + if(this.getInputData(3) != null) + t = this.getInputData(3); + // t = t/(2*Math.PI); t = (t-Math.floor(t))*(2*Math.PI); + + var v = this.properties["amplitude"] * Math.sin((2*Math.PI) * t * this.properties["freq"] + this.properties["phase"]); + this.setOutputData(0, v ); + }, + + onDragBackground: function(ctx) + { + this.boxcolor = colorToString(v > 0 ? [0.5,0.8,1,0.5] : [0,0,0,0.5]); + this.setDirtyCanvas(true); + }, +}); +*/ + +/* +LiteGraph.registerNodeType("basic/number",{ + title: "Number", + desc: "Fixed number output", + outputs: [["","number"]], + color: "#66A", + bgcolor: "#336", + widgets: [{name:"value",text:"Value",type:"input",property:"value"}], + + properties: {value:1.0}, + + setValue: function(v) + { + if( typeof(v) == "string") v = parseFloat(v); + this.properties["value"] = v; + this.setDirtyCanvas(true); + }, + + onExecute: function() + { + this.outputs[0].name = this.properties["value"].toString(); + this.setOutputData(0, this.properties["value"]); + }, + + onWidget: function(e,widget) + { + if(widget.name == "value") + this.setValue(widget.value); + } +}); + + +LiteGraph.registerNodeType("basic/string",{ + title: "String", + desc: "Fixed string output", + outputs: [["","string"]], + color: "#66A", + bgcolor: "#336", + widgets: [{name:"value",text:"Value",type:"input"}], + + properties: {value:"..."}, + + setValue: function(v) + { + this.properties["value"] = v; + this.setDirtyCanvas(true); + }, + + onExecute: function() + { + this.outputs[0].name = this.properties["value"].toString(); + this.setOutputData(0, this.properties["value"]); + }, + + onWidget: function(e,widget) + { + if(widget.name == "value") + this.setValue(widget.value); + } +}); + +LiteGraph.registerNodeType("basic/trigger",{ + title: "Trigger", + desc: "Triggers node action", + inputs: [["!0","number"]], + outputs: [["M","node"]], + + properties: {triggerName:null}, + + onExecute: function() + { + if( this.getInputData(0) ) + { + var m = this.getOutputNode(0); + if(m && m.onTrigger) + m.onTrigger(); + if(m && this.properties.triggerName && typeof(m[this.properties.triggerName]) == "function") + m[this.properties.triggerName].call(m); + } + } +}); + + +LiteGraph.registerNodeType("basic/switch",{ + title: "Switch", + desc: "Switch between two inputs", + inputs: [["i","number"],["A",0],["B",0]], + outputs: [["",0]], + + onExecute: function() + { + var f = this.getInputData(0); + if(f) + { + f = Math.round(f)+1; + if(f < 1) f = 1; + if(f > 2) f = 2; + this.setOutputData(0, this.getInputData(f) ); + } + else + this.setOutputData(0, null); + } +}); + +// System vars ********************************* + +LiteGraph.registerNodeType("session/info",{ + title: "Time", + desc: "Seconds since start", + + outputs: [["secs",'number']], + properties: {scale:1.0}, + onExecute: function() + { + this.setOutputData(0, this.session.getTime() * this.properties.scale); + } +}); + +LiteGraph.registerNodeType("system/fixedtime",{ + title: "F.Time", + desc: "Constant time value", + + outputs: [["secs",'number']], + properties: {scale:1.0}, + onExecute: function() + { + this.setOutputData(0, this.session.getFixedTime() * this.properties.scale); + } +}); + + +LiteGraph.registerNodeType("system/elapsedtime",{ + title: "Elapsed", + desc: "Seconds elapsed since last execution", + + outputs: [["secs",'number']], + properties: {scale:1.0}, + onExecute: function() + { + this.setOutputData(0, this.session.getElapsedTime() * this.properties.scale); + } +}); + +LiteGraph.registerNodeType("system/iterations",{ + title: "Iterations", + desc: "Number of iterations (executions)", + + outputs: [["",'number']], + onExecute: function() + { + this.setOutputData(0, this.session.iterations ); + } +}); + +LiteGraph.registerNodeType("system/trace",{ + desc: "Outputs input to browser's console", + + inputs: [["",0]], + onExecute: function() + { + var data = this.getInputData(0); + if(data) + trace("DATA: "+data); + } +}); + +/* +LiteGraph.registerNodeType("math/not",{ + title: "Not", + desc: "0 -> 1 or 0 -> 1", + inputs: [["A",'number']], + outputs: [["!A",'number']], + size: [60,22], + onExecute: function() + { + var v = this.getInputData(0); + if(v != null) + this.setOutputData(0, v ? 0 : 1); + } +}); + + + +// Nodes for network in and out +LiteGraph.registerNodeType("network/general/network_input",{ + title: "N.Input", + desc: "Network Input", + outputs: [["",0]], + color: "#00ff96", + bgcolor: "#004327", + + setValue: function(v) + { + this.value = v; + }, + + onExecute: function() + { + this.setOutputData(0, this.value); + } +}); + +LiteGraph.registerNodeType("network/general/network_output",{ + title: "N.Output", + desc: "Network output", + inputs: [["",0]], + color: "#a8ff00", + bgcolor: "#293e00", + + properties: {value:null}, + + getValue: function() + { + return this.value; + }, + + onExecute: function() + { + this.value = this.getOutputData(0); + } +}); + +LiteGraph.registerNodeType("network/network_trigger",{ + title: "N.Trigger", + desc: "Network input trigger", + outputs: [["",0]], + color: "#ff9000", + bgcolor: "#522e00", + + onTrigger: function(v) + { + this.triggerOutput(0,v); + }, +}); + +LiteGraph.registerNodeType("network/network_callback",{ + title: "N.Callback", + desc: "Network callback output.", + outputs: [["",0]], + color: "#6A6", + bgcolor: "#363", + + setTrigger: function(func) + { + this.callback = func; + }, + + onTrigger: function(v) + { + if(this.callback) + this.callback(v); + }, +}); + +*/ +//widgets + + LiteGraph.registerNodeType("widget/knob",{ + title: "Knob", + desc: "Circular controller", + size: [64,84], + outputs: [["",'number']], + properties: {min:0,max:1,value:0.5,wcolor:"#7AF",size:50}, + widgets: [{name:"increase",text:"+",type:"minibutton"},{name:"decrease",text:"-",type:"minibutton"}], + + onInit: function() + { + this.value = (this.properties["value"] - this.properties["min"]) / (this.properties["max"] - this.properties["min"]); + + this.imgbg = this.loadImage("imgs/knob_bg.png"); + this.imgfg = this.loadImage("imgs/knob_fg.png"); + }, + + onDrawImageKnob: function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + var d = this.imgbg.width*0.5; + var scale = this.size[0] / this.imgfg.width; + + ctx.save(); + ctx.translate(0,20); + ctx.scale(scale,scale); + ctx.drawImage(this.imgbg,0,0); + //ctx.drawImage(this.imgfg,0,20); + + ctx.translate(d,d); + ctx.rotate(this.value * (Math.PI*2) * 6/8 + Math.PI * 10/8); + //ctx.rotate(this.value * (Math.PI*2)); + ctx.translate(-d,-d); + ctx.drawImage(this.imgfg,0,0); + + ctx.restore(); + + ctx.font = "bold 16px Criticized,Tahoma"; + ctx.fillStyle="rgba(100,100,100,0.8)"; + ctx.textAlign = "center"; + + ctx.fillText(this.name.toUpperCase(), this.size[0] * 0.5, 18 ); + ctx.textAlign = "left"; + }, + + onDrawVectorKnob: function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + //circle around + ctx.lineWidth = 1; + ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; + ctx.fillStyle="#000"; + ctx.beginPath(); + ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.5,0,Math.PI*2,true); + ctx.stroke(); + + if(this.value > 0) + { + ctx.strokeStyle=this.properties["wcolor"]; + ctx.lineWidth = (this.properties.size * 0.2); + ctx.beginPath(); + ctx.arc(this.size[0] * 0.5,this.size[1] * 0.5 + 10,this.properties.size * 0.35,Math.PI * -0.5 + Math.PI*2 * this.value,Math.PI * -0.5,true); + ctx.stroke(); + ctx.lineWidth = 1; + } + + ctx.font = (this.properties.size * 0.2) + "px Arial"; + ctx.fillStyle="#AAA"; + ctx.textAlign = "center"; + + var str = this.properties["value"]; + if(typeof(str) == 'number') + str = str.toFixed(2); + + ctx.fillText(str,this.size[0] * 0.5,this.size[1]*0.65); + ctx.textAlign = "left"; + }, + + onDrawBackground: function(ctx) + { + this.onDrawImageKnob(ctx); + }, + + onExecute: function() + { + this.setOutputData(0, this.properties["value"] ); + + this.boxcolor = colorToString([this.value,this.value,this.value]); + }, + + onMouseDown: function(e) + { + if(!this.imgfg || !this.imgfg.width) return; + + //this.center = [this.imgbg.width * 0.5, this.imgbg.height * 0.5 + 20]; + //this.radius = this.imgbg.width * 0.5; + this.center = [this.size[0] * 0.5, this.size[1] * 0.5 + 20]; + this.radius = this.size[0] * 0.5; + + if(e.canvasY - this.pos[1] < 20 || distance([e.canvasX,e.canvasY],[this.pos[0] + this.center[0],this.pos[1] + this.center[1]]) > this.radius) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + + /* + var tmp = this.localToScreenSpace(0,0); + this.trace(tmp[0] + "," + tmp[1]); */ + return true; + }, + + onMouseMove: function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + var v = this.value; + v -= (m[1] - this.oldmouse[1]) * 0.01; + if(v > 1.0) v = 1.0; + else if(v < 0.0) v = 0.0; + + this.value = v; + this.properties["value"] = this.properties["min"] + (this.properties["max"] - this.properties["min"]) * this.value; + + this.oldmouse = m; + this.setDirtyCanvas(true); + }, + + onMouseUp: function(e) + { + if(this.oldmouse) + { + this.oldmouse = null; + this.captureInput(false); + } + }, + + onMouseLeave: function(e) + { + //this.oldmouse = null; + }, + + onWidget: function(e,widget) + { + if(widget.name=="increase") + this.onPropertyChange("size", this.properties.size + 10); + else if(widget.name=="decrease") + this.onPropertyChange("size", this.properties.size - 10); + }, + + onPropertyChange: function(name,value) + { + if(name=="wcolor") + this.properties[name] = value; + else if(name=="size") + { + value = parseInt(value); + this.properties[name] = value; + this.size = [value+4,value+24]; + this.setDirtyCanvas(true,true); + } + else if(name=="min" || name=="max" || name=="value") + { + this.properties[name] = parseFloat(value); + } + else + return false; + return true; + } + }); + + LiteGraph.registerNodeType("widget/hslider",{ + title: "H.Slider", + desc: "Linear slider controller", + size: [160,26], + outputs: [["",'number']], + properties: {wcolor:"#7AF",min:0,max:1,value:0.5}, + onInit: function() + { + this.value = 0.5; + this.imgfg = this.loadImage("imgs/slider_fg.png"); + }, + + onDrawVectorial: function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + //border + ctx.lineWidth = 1; + ctx.strokeStyle= this.mouseOver ? "#FFF" : "#AAA"; + ctx.fillStyle="#000"; + ctx.beginPath(); + ctx.rect(2,0,this.size[0]-4,20); + ctx.stroke(); + + ctx.fillStyle=this.properties["wcolor"]; + ctx.beginPath(); + ctx.rect(2+(this.size[0]-4-20)*this.value,0, 20,20); + ctx.fill(); + }, + + onDrawImage: function(ctx) + { + if(!this.imgfg || !this.imgfg.width) return; + + //border + ctx.lineWidth = 1; + ctx.fillStyle="#000"; + ctx.fillRect(2,9,this.size[0]-4,2); + + ctx.strokeStyle= "#333"; + ctx.beginPath(); + ctx.moveTo(2,9); + ctx.lineTo(this.size[0]-4,9); + ctx.stroke(); + + ctx.strokeStyle= "#AAA"; + ctx.beginPath(); + ctx.moveTo(2,11); + ctx.lineTo(this.size[0]-4,11); + ctx.stroke(); + + ctx.drawImage(this.imgfg, 2+(this.size[0]-4)*this.value - this.imgfg.width*0.5,-this.imgfg.height*0.5 + 10); + }, + + onDrawBackground: function(ctx) + { + this.onDrawImage(ctx); + }, + + 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]); + }, + + onMouseDown: function(e) + { + if(e.canvasY - this.pos[1] < 0) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + return true; + }, + + onMouseMove: function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + var v = this.value; + var delta = (m[0] - this.oldmouse[0]); + v += delta / this.size[0]; + if(v > 1.0) v = 1.0; + else if(v < 0.0) v = 0.0; + + this.value = v; + + this.oldmouse = m; + this.setDirtyCanvas(true); + }, + + onMouseUp: function(e) + { + this.oldmouse = null; + this.captureInput(false); + }, + + onMouseLeave: function(e) + { + //this.oldmouse = null; + }, + + onPropertyChange: function(name,value) + { + if(name=="wcolor") + this.properties[name] = value; + else + return false; + return true; + } + }); + + LiteGraph.registerNodeType("widget/kpad",{ + title: "KPad", + desc: "bidimensional slider", + size: [200,200], + outputs: [["x",'number'],["y",'number']], + properties:{x:0,y:0,borderColor:"#333",bgcolorTop:"#444",bgcolorBottom:"#000",shadowSize:1, borderRadius:2}, + + createGradient: function(ctx) + { + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + }, + + onDrawBackground: function(ctx) + { + if(!this.lineargradient) + this.createGradient(ctx); + + ctx.lineWidth = 1; + ctx.strokeStyle = this.properties["borderColor"]; + //ctx.fillStyle = "#ebebeb"; + ctx.fillStyle = this.lineargradient; + + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + ctx.roundRect(0,0,this.size[0],this.size[1],this.properties["shadowSize"]); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = "#A00"; + ctx.fillRect(this.size[0] * this.properties["x"] - 5, this.size[1] * this.properties["y"] - 5,10,10); + }, + + onWidget: function(e,widget) + { + if(widget.name == "update") + { + this.lineargradient = null; + this.setDirtyCanvas(true); + } + }, + + onExecute: function() + { + this.setOutputData(0, this.properties["x"] ); + this.setOutputData(1, this.properties["y"] ); + }, + + onMouseDown: function(e) + { + if(e.canvasY - this.pos[1] < 0) + return false; + + this.oldmouse = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + this.captureInput(true); + return true; + }, + + onMouseMove: function(e) + { + if(!this.oldmouse) return; + + var m = [ e.canvasX - this.pos[0], e.canvasY - this.pos[1] ]; + + this.properties.x = m[0] / this.size[0]; + this.properties.y = m[1] / this.size[1]; + + if(this.properties.x > 1.0) this.properties.x = 1.0; + else if(this.properties.x < 0.0) this.properties.x = 0.0; + + if(this.properties.y > 1.0) this.properties.y = 1.0; + else if(this.properties.y < 0.0) this.properties.y = 0.0; + + this.oldmouse = m; + this.setDirtyCanvas(true); + }, + + onMouseUp: function(e) + { + if(this.oldmouse) + { + this.oldmouse = null; + this.captureInput(false); + } + }, + + onMouseLeave: function(e) + { + //this.oldmouse = null; + } + }); + + + LiteGraph.registerNodeType("widget/button", { + title: "Button", + desc: "A send command button", + + widgets: [{name:"test",text:"Test Button",type:"button"}], + size: [100,40], + properties:{text:"clickme",command:"",color:"#7AF",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",fontsize:"16"}, + outputs:[["M","module"]], + + createGradient: function(ctx) + { + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + }, + + drawVectorShape: function(ctx) + { + ctx.fillStyle = this.mouseOver ? this.properties["color"] : "#AAA"; + + if(this.clicking) + ctx.fillStyle = "#FFF"; + + ctx.strokeStyle = "#AAA"; + ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); + ctx.stroke(); + + if(this.mouseOver) + ctx.fill(); + + //ctx.fillRect(5,20,this.size[0] - 10,this.size[1] - 30); + + ctx.fillStyle = this.mouseOver ? "#000" : "#AAA"; + ctx.font = "bold " + this.properties["fontsize"] + "px Criticized,Tahoma"; + ctx.textAlign = "center"; + ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.5*parseInt(this.properties["fontsize"])); + ctx.textAlign = "left"; + }, + + drawBevelShape: function(ctx) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + + if(!this.lineargradient) + this.createGradient(ctx); + + ctx.fillStyle = this.mouseOver ? this.properties["color"] : this.lineargradient; + if(this.clicking) + ctx.fillStyle = "#444"; + + ctx.strokeStyle = "#FFF"; + ctx.roundRect(5,5,this.size[0] - 10,this.size[1] - 10,4); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = this.mouseOver ? "#000" : "#444"; + ctx.font = "bold " + this.properties["fontsize"] + "px Century Gothic"; + ctx.textAlign = "center"; + ctx.fillText(this.properties["text"],this.size[0]*0.5,this.size[1]*0.5 + 0.40*parseInt(this.properties["fontsize"])); + ctx.textAlign = "left"; + }, + + onDrawBackground: function(ctx) + { + this.drawBevelShape(ctx); + }, + + clickButton: function() + { + var module = this.getOutputModule(0); + if(this.properties["command"] && this.properties["command"] != "") + { + if (! module.executeAction(this.properties["command"]) ) + this.trace("Error executing action in other module"); + } + else if(module && module.onTrigger) + { + module.onTrigger(); + } + }, + + onMouseDown: function(e) + { + if(e.canvasY - this.pos[1] < 2) + return false; + this.clickButton(); + this.clicking = true; + return true; + }, + + onMouseUp: function(e) + { + this.clicking = false; + }, + + onExecute: function() + { + }, + + onWidget: function(e,widget) + { + if(widget.name == "test") + { + this.clickButton(); + } + }, + + onPropertyChange: function(name,value) + { + this.properties[name] = value; + return true; + } + }); + + LiteGraph.registerNodeType("widget/progress",{ + title: "Progress", + desc: "Shows data in linear progress", + size: [160,26], + inputs: [["",'number']], + properties: {min:0,max:1,value:0,wcolor:"#AAF"}, + onExecute: function() + { + var v = this.getInputData(0); + if( v != undefined ) + this.properties["value"] = v; + }, + onDrawBackground: function(ctx) + { + //border + ctx.lineWidth = 1; + ctx.fillStyle=this.properties.wcolor; + var v = (this.properties.value - this.properties.min) / (this.properties.max - this.properties.min); + v = Math.min(1,v); + v = Math.max(0,v); + ctx.fillRect(2,2,(this.size[0]-4)*v,this.size[1]-4); + } + }); + + LiteGraph.registerNodeType("widget/text", { + title: "Text", + desc: "Shows the input value", + + widgets: [{name:"resize",text:"Resize box",type:"button"},{name:"led_text",text:"LED",type:"minibutton"},{name:"normal_text",text:"Normal",type:"minibutton"}], + inputs: [["",0]], + properties:{value:"...",font:"Arial", fontsize:18, color:"#AAA", align:"left", glowSize:0, decimals:1}, + + onDrawBackground: function(ctx) + { + //ctx.fillStyle="#000"; + //ctx.fillRect(0,0,100,60); + ctx.fillStyle = this.properties["color"]; + var v = this.properties["value"]; + + if(this.properties["glowSize"]) + { + ctx.shadowColor = this.properties["color"]; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["glowSize"]; + } + else + ctx.shadowColor = "transparent"; + + var fontsize = this.properties["fontsize"]; + + ctx.textAlign = this.properties["align"]; + ctx.font = fontsize.toString() + "px " + this.properties["font"]; + this.str = typeof(v) == 'number' ? v.toFixed(this.properties["decimals"]) : v; + + if( typeof(this.str) == 'string') + { + var lines = this.str.split("\\n"); + for(var i in lines) + ctx.fillText(lines[i],this.properties["align"] == "left" ? 15 : this.size[0] - 15, fontsize * -0.15 + fontsize * (parseInt(i)+1) ); + } + + ctx.shadowColor = "transparent"; + this.last_ctx = ctx; + ctx.textAlign = "left"; + }, + + onExecute: function() + { + var v = this.getInputData(0); + if(v != null) + this.properties["value"] = v; + else + this.properties["value"] = ""; + this.setDirtyCanvas(true); + }, + + resize: function() + { + if(!this.last_ctx) return; + + var lines = this.str.split("\\n"); + this.last_ctx.font = this.properties["fontsize"] + "px " + this.properties["font"]; + var max = 0; + for(var i in lines) + { + var w = this.last_ctx.measureText(lines[i]).width; + if(max < w) max = w; + } + this.size[0] = max + 20; + this.size[1] = 4 + lines.length * this.properties["fontsize"]; + + this.setDirtyCanvas(true); + }, + + onWidget: function(e,widget) + { + if(widget.name == "resize") + this.resize(); + else if (widget.name == "led_text") + { + this.properties["font"] = "Digital"; + this.properties["glowSize"] = 4; + this.setDirtyCanvas(true); + } + else if (widget.name == "normal_text") + { + this.properties["font"] = "Arial"; + this.setDirtyCanvas(true); + } + }, + + onPropertyChange: function(name,value) + { + this.properties[name] = value; + this.str = typeof(value) == 'number' ? value.toFixed(3) : value; + //this.resize(); + return true; + } + }); + + LiteGraph.registerNodeType("widget/panel", { + title: "Panel", + desc: "Non interactive panel", + + widgets: [{name:"update",text:"Update",type:"button"}], + size: [200,100], + properties:{borderColor:"#ffffff",bgcolorTop:"#f0f0f0",bgcolorBottom:"#e0e0e0",shadowSize:2, borderRadius:3}, + + createGradient: function(ctx) + { + if(this.properties["bgcolorTop"] == "" || this.properties["bgcolorBottom"] == "") + { + this.lineargradient = 0; + return; + } + + this.lineargradient = ctx.createLinearGradient(0,0,0,this.size[1]); + this.lineargradient.addColorStop(0,this.properties["bgcolorTop"]); + this.lineargradient.addColorStop(1,this.properties["bgcolorBottom"]); + }, + + onDrawBackground: function(ctx) + { + if(this.lineargradient == null) + this.createGradient(ctx); + + if(!this.lineargradient) + return; + + ctx.lineWidth = 1; + ctx.strokeStyle = this.properties["borderColor"]; + //ctx.fillStyle = "#ebebeb"; + ctx.fillStyle = this.lineargradient; + + if(this.properties["shadowSize"]) + { + ctx.shadowColor = "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = this.properties["shadowSize"]; + } + else + ctx.shadowColor = "transparent"; + + ctx.roundRect(0,0,this.size[0]-1,this.size[1]-1,this.properties["shadowSize"]); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.stroke(); + }, + + onWidget: function(e,widget) + { + if(widget.name == "update") + { + this.lineargradient = null; + this.setDirtyCanvas(true); + } + } + }); + +LiteGraph.registerNodeType("color/palette",{ + title: "Palette", + desc: "Generates a color", + + inputs: [["f","number"]], + outputs: [["Color","color"]], + properties: {colorA:"#444444",colorB:"#44AAFF",colorC:"#44FFAA",colorD:"#FFFFFF"}, + + onExecute: function() + { + var c = []; + + if (this.properties.colorA != null) + c.push( hex2num( this.properties.colorA ) ); + if (this.properties.colorB != null) + c.push( hex2num( this.properties.colorB ) ); + if (this.properties.colorC != null) + c.push( hex2num( this.properties.colorC ) ); + if (this.properties.colorD != null) + c.push( hex2num( this.properties.colorD ) ); + + var f = this.getInputData(0); + if(f == null) f = 0.5; + if (f > 1.0) + f = 1.0; + else if (f < 0.0) + f = 0.0; + + if(c.length == 0) + return; + + var result = [0,0,0]; + if(f == 0) + result = c[0]; + else if(f == 1) + result = c[ c.length - 1]; + else + { + var pos = (c.length - 1)* f; + var c1 = c[ Math.floor(pos) ]; + var c2 = c[ Math.floor(pos)+1 ]; + var t = pos - Math.floor(pos); + result[0] = c1[0] * (1-t) + c2[0] * (t); + result[1] = c1[1] * (1-t) + c2[1] * (t); + result[2] = c1[2] * (1-t) + c2[2] * (t); + } + + /* + c[0] = 1.0 - Math.abs( Math.sin( 0.1 * reModular.getTime() * Math.PI) ); + c[1] = Math.abs( Math.sin( 0.07 * reModular.getTime() * Math.PI) ); + c[2] = Math.abs( Math.sin( 0.01 * reModular.getTime() * Math.PI) ); + */ + + for(var i in result) + result[i] /= 255; + + this.boxcolor = colorToString(result); + this.setOutputData(0, result); + } + }); + +LiteGraph.registerNodeType("graphics/frame", { + title: "Frame", + desc: "Frame viewerew", + + inputs: [["","image"]], + size: [200,200], + widgets: [{name:"resize",text:"Resize box",type:"button"},{name:"view",text:"View Image",type:"button"}], + + onDrawBackground: function(ctx) + { + if(this.frame) + ctx.drawImage(this.frame, 0,0,this.size[0],this.size[1]); + }, + + onExecute: function() + { + this.frame = this.getInputData(0); + this.setDirtyCanvas(true); + }, + + onWidget: function(e,widget) + { + if(widget.name == "resize" && this.frame) + { + var width = this.frame.width; + var height = this.frame.height; + + if(!width && this.frame.videoWidth != null ) + { + width = this.frame.videoWidth; + height = this.frame.videoHeight; + } + + if(width && height) + this.size = [width, height]; + this.setDirtyCanvas(true,true); + } + else if(widget.name == "view") + this.show(); + }, + + show: function() + { + //var str = this.canvas.toDataURL("image/png"); + if(showElement && this.frame) + showElement(this.frame); + } + }); + +LiteGraph.registerNodeType("visualization/graph", { + desc: "Shows a graph of the inputs", + + inputs: [["",0],["",0],["",0],["",0]], + size: [200,200], + properties: {min:-1,max:1,bgColor:"#000"}, + onDrawBackground: function(ctx) + { + /* + ctx.save(); + ctx.beginPath(); + ctx.rect(2,2,this.size[0] - 4, this.size[1]-4); + ctx.clip(); + //*/ + + var colors = ["#FFF","#FAA","#AFA","#AAF"]; + + if(this.properties.bgColor != null && this.properties.bgColor != "") + { + ctx.fillStyle="#000"; + ctx.fillRect(2,2,this.size[0] - 4, this.size[1]-4); + } + + if(this.data) + { + var min = this.properties["min"]; + var max = this.properties["max"]; + + for(var i in this.data) + { + var data = this.data[i]; + if(!data) continue; + + if(this.getInputInfo(i) == null) continue; + + ctx.strokeStyle = colors[i]; + ctx.beginPath(); + + var d = data.length / this.size[0]; + for(var j = 0; j < data.length; j += d) + { + var value = data[ Math.floor(j) ]; + value = (value - min) / (max - min); + if (value > 1.0) value = 1.0; + else if(value < 0) value = 0; + + if(j == 0) + ctx.moveTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); + else + ctx.lineTo( j / d, (this.size[1] - 5) - (this.size[1] - 10) * value); + } + + ctx.stroke(); + } + } + //*/ + + //ctx.restore(); + }, + + onExecute: function() + { + if(!this.data) this.data = []; + + for(var i in this.inputs) + { + var value = this.getInputData(i); + + if(typeof(value) == "number") + { + value = value ? value : 0; + if(!this.data[i]) + this.data[i] = []; + this.data[i].push(value); + + if(this.data[i].length > (this.size[1] - 4)) + this.data[i] = this.data[i].slice(1,this.data[i].length); + } + else + this.data[i] = value; + } + + if(this.data.length) + this.setDirtyCanvas(true); + } + }); + +LiteGraph.registerNodeType("graphics/supergraph", { + title: "Supergraph", + desc: "Shows a nice circular graph", + + inputs: [["x","number"],["y","number"],["c","color"]], + outputs: [["","image"]], + widgets: [{name:"clear_alpha",text:"Clear Alpha",type:"minibutton"},{name:"clear_color",text:"Clear color",type:"minibutton"}], + properties: {size:256,bgcolor:"#000",lineWidth:1}, + bgcolor: "#000", + flags: {allow_fastrender:true}, + onLoad: function() + { + this.createCanvas(); + }, + + createCanvas: function() + { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["size"]; + this.canvas.height = this.properties["size"]; + this.oldpos = null; + this.clearCanvas(true); + }, + + onExecute: function() + { + var x = this.getInputData(0); + var y = this.getInputData(1); + var c = this.getInputData(2); + + if(x == null && y == null) return; + + if(!x) x = 0; + if(!y) y = 0; + x*= 0.95; + y*= 0.95; + + var size = this.properties["size"]; + if(size != this.canvas.width || size != this.canvas.height) + this.createCanvas(); + + if (!this.oldpos) + { + this.oldpos = [ (x * 0.5 + 0.5) * size, (y*0.5 + 0.5) * size]; + return; + } + + var ctx = this.canvas.getContext("2d"); + + if(c == null) + c = "rgba(255,255,255,0.5)"; + else if(typeof(c) == "object") //array + c = colorToString(c); + + //stroke line + ctx.strokeStyle = c; + ctx.beginPath(); + ctx.moveTo( this.oldpos[0], this.oldpos[1] ); + this.oldpos = [ (x * 0.5 + 0.5) * size, (y*0.5 + 0.5) * size]; + ctx.lineTo( this.oldpos[0], this.oldpos[1] ); + ctx.stroke(); + + this.canvas.dirty = true; + this.setOutputData(0,this.canvas); + }, + + clearCanvas: function(alpha) + { + var ctx = this.canvas.getContext("2d"); + if(alpha) + { + ctx.clearRect(0,0,this.canvas.width,this.canvas.height); + this.trace("Clearing alpha"); + } + else + { + ctx.fillStyle = this.properties["bgcolor"]; + ctx.fillRect(0,0,this.canvas.width,this.canvas.height); + } + }, + + onWidget: function(e,widget) + { + if(widget.name == "clear_color") + { + this.clearCanvas(false); + } + else if(widget.name == "clear_alpha") + { + this.clearCanvas(true); + } + }, + + onPropertyChange: function(name,value) + { + if(name == "size") + { + this.properties["size"] = parseInt(value); + this.createCanvas(); + } + else if(name == "bgcolor") + { + this.properties["bgcolor"] = value; + this.createCanvas(); + } + else if(name == "lineWidth") + { + this.properties["lineWidth"] = parseInt(value); + this.canvas.getContext("2d").lineWidth = this.properties["lineWidth"]; + } + else + return false; + + return true; + } + }); + + +LiteGraph.registerNodeType("graphics/imagefade", { + title: "Image fade", + desc: "Fades between images", + + inputs: [["img1","image"],["img2","image"],["fade","number"]], + outputs: [["","image"]], + properties: {fade:0.5,width:512,height:512}, + widgets: [{name:"resizeA",text:"Resize to A",type:"button"},{name:"resizeB",text:"Resize to B",type:"button"}], + + onLoad: function() + { + this.createCanvas(); + var ctx = this.canvas.getContext("2d"); + ctx.fillStyle = "#000"; + ctx.fillRect(0,0,this.properties["width"],this.properties["height"]); + }, + + createCanvas: function() + { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; + }, + + onExecute: function() + { + var ctx = this.canvas.getContext("2d"); + this.canvas.width = this.canvas.width; + + var A = this.getInputData(0); + if (A != null) + { + ctx.drawImage(A,0,0,this.canvas.width, this.canvas.height); + } + + var fade = this.getInputData(2); + if(fade == null) + fade = this.properties["fade"]; + else + this.properties["fade"] = fade; + + ctx.globalAlpha = fade; + var B = this.getInputData(1); + if (B != null) + { + ctx.drawImage(B,0,0,this.canvas.width, this.canvas.height); + } + ctx.globalAlpha = 1.0; + + this.setOutputData(0,this.canvas); + this.setDirtyCanvas(true); + } + }); + +LiteGraph.registerNodeType("graphics/image", { + title: "Image", + desc: "Image loader", + + inputs: [], + outputs: [["frame","image"]], + properties: {"url":""}, + widgets: [{name:"load",text:"Load",type:"button"}], + + onLoad: function() + { + if(this.properties["url"] != "" && this.img == null) + { + this.loadImage(this.properties["url"]); + } + }, + + onStart: function() + { + }, + + onExecute: function() + { + if(!this.img) + this.boxcolor = "#000"; + if(this.img && this.img.width) + this.setOutputData(0,this.img); + else + this.setOutputData(0,null); + if(this.img.dirty) + this.img.dirty = false; + }, + + onPropertyChange: function(name,value) + { + this.properties[name] = value; + if (name == "url" && value != "") + this.loadImage(value); + + return true; + }, + + loadImage: function(url) + { + if(url == "") + { + this.img = null; + return; + } + + this.trace("loading image..."); + this.img = document.createElement("img"); + this.img.src = "miniproxy.php?url=" + url; + this.boxcolor = "#F95"; + var that = this; + this.img.onload = function() + { + that.trace("Image loaded, size: " + that.img.width + "x" + that.img.height ); + this.dirty = true; + that.boxcolor = "#9F9"; + that.setDirtyCanvas(true); + } + }, + + onWidget: function(e,widget) + { + if(widget.name == "load") + { + this.loadImage(this.properties["url"]); + } + } + }); + +LiteGraph.registerNodeType("graphics/cropImage", { + title: "Crop", + desc: "Crop Image", + + inputs: [["","image"]], + outputs: [["","image"]], + properties: {width:256,height:256,x:0,y:0,scale:1.0 }, + size: [50,20], + + onLoad: function() + { + this.createCanvas(); + }, + + createCanvas: function() + { + this.canvas = document.createElement("canvas"); + this.canvas.width = this.properties["width"]; + this.canvas.height = this.properties["height"]; + }, + + onExecute: function() + { + var input = this.getInputData(0); + if(!input) return; + + if(input.width) + { + var ctx = this.canvas.getContext("2d"); + + ctx.drawImage(input, -this.properties["x"],-this.properties["y"], input.width * this.properties["scale"], input.height * this.properties["scale"]); + this.setOutputData(0,this.canvas); + } + else + this.setOutputData(0,null); + }, + + onPropertyChange: function(name,value) + { + this.properties[name] = value; + + if(name == "scale") + { + this.properties[name] = parseFloat(value); + if(this.properties[name] == 0) + { + this.trace("Error in scale"); + this.properties[name] = 1.0; + } + } + else + this.properties[name] = parseInt(value); + + this.createCanvas(); + + return true; + } + }); + + +LiteGraph.registerNodeType("graphics/video", { + title: "Video", + desc: "Video playback", + + inputs: [["t","number"]], + outputs: [["frame","image"],["t","number"],["d","number"]], + properties: {"url":""}, + 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"}], + + onClick: function(e) + { + if(!this.video) return; + + //press play + if( distance( [e.canvasX,e.canvasY], [ this.pos[0] + 55, this.pos[1] + 40] ) < 20 ) + { + this.play(); + return true; + } + }, + + onKeyDown: function(e) + { + if(e.keyCode == 32) + this.playPause(); + }, + + onLoad: function() + { + if(this.properties.url != "") + this.loadVideo(this.properties.url); + }, + + play: function() + { + if(this.video) + { + this.trace("Video playing"); + this.video.play(); + } + }, + + playPause: function() + { + if(this.video) + { + if(this.video.paused) + this.play(); + else + this.pause(); + } + }, + + stop: function() + { + if(this.video) + { + this.trace("Video stopped"); + this.video.pause(); + this.video.currentTime = 0; + } + }, + + pause: function() + { + if(this.video) + { + this.trace("Video paused"); + this.video.pause(); + } + }, + + onExecute: function() + { + if(!this.video) + return; + + var t = this.getInputData(0); + if(t && t >= 0 && t <= 1.0) + { + this.video.currentTime = t * this.video.duration; + this.video.pause(); + } + + this.video.dirty = true; + this.setOutputData(0,this.video); + this.setOutputData(1,this.video.currentTime); + this.setOutputData(2,this.video.duration); + this.setDirtyCanvas(true); + }, + + onStart: function() + { + //this.play(); + }, + + onStop: function() + { + this.pause(); + }, + + loadVideo: function(url) + { + this.video = document.createElement("video"); + if(url) + this.video.src = url; + else + { + this.video.src = "modules/data/video.webm"; + this.properties.url = this.video.src; + } + this.video.type = "type=video/mp4"; + //this.video.loop = true; //not work in FF + this.video.muted = true; + this.video.autoplay = false; + + //if(reModular.status == "running") this.play(); + + var that = this; + this.video.addEventListener("loadedmetadata",function(e) { + //onload + that.trace("Duration: " + that.video.duration + " seconds"); + that.trace("Size: " + that.video.videoWidth + "," + that.video.videoHeight); + that.setDirtyCanvas(true); + this.width = this.videoWidth; + this.height = this.videoHeight; + }); + this.video.addEventListener("progress",function(e) { + //onload + //that.trace("loading..."); + }); + this.video.addEventListener("error",function(e) { + that.trace("Error loading video: " + this.src); + if (this.error) { + switch (this.error.code) { + case this.error.MEDIA_ERR_ABORTED: + that.trace("You stopped the video."); + break; + case this.error.MEDIA_ERR_NETWORK: + that.trace("Network error - please try again later."); + break; + case this.error.MEDIA_ERR_DECODE: + that.trace("Video is broken.."); + break; + case this.error.MEDIA_ERR_SRC_NOT_SUPPORTED: + that.trace("Sorry, your browser can't play this video."); + break; + } + } + }); + + this.video.addEventListener("ended",function(e) { + that.trace("Ended."); + this.play(); + }); + + //$("body").append(this.video); + }, + + onPropertyChange: function(name,value) + { + this.properties[name] = value; + if (name == "url" && value != "") + this.loadVideo(value); + + return true; + }, + onWidget: function(e,widget) + { + if(widget.name == "demo") + { + this.loadVideo(); + } + else if(widget.name == "play") + { + if(this.video) + this.playPause(); + } + if(widget.name == "stop") + { + this.stop(); + } + else if(widget.name == "mute") + { + if(this.video) + this.video.muted = !this.video.muted; + } + + } + }); + diff --git a/demo/code.js b/demo/code.js index 4465196b7..face0b02e 100644 --- a/demo/code.js +++ b/demo/code.js @@ -18,7 +18,7 @@ $(window).load(function() { if(graph.status == LGraph.STATUS_STOPPED) { $(this).html(" Stop"); - graph.run(1); + graph.start(1); } else { diff --git a/doc/api.js b/doc/api.js new file mode 100644 index 000000000..8f212c487 --- /dev/null +++ b/doc/api.js @@ -0,0 +1,11 @@ +YUI.add("yuidoc-meta", function(Y) { + Y.YUIDoc = { meta: { + "classes": [ + "LGraph", + "LGraphNode", + "LiteGraph" + ], + "modules": [], + "allModules": [] +} }; +}); \ No newline at end of file diff --git a/doc/assets/css/external-small.png b/doc/assets/css/external-small.png new file mode 100644 index 000000000..759a1cdcb Binary files /dev/null and b/doc/assets/css/external-small.png differ diff --git a/doc/assets/css/logo.png b/doc/assets/css/logo.png new file mode 100644 index 000000000..609b336c7 Binary files /dev/null and b/doc/assets/css/logo.png differ diff --git a/doc/assets/css/main.css b/doc/assets/css/main.css new file mode 100644 index 000000000..d745d4438 --- /dev/null +++ b/doc/assets/css/main.css @@ -0,0 +1,783 @@ +/* +Font sizes for all selectors other than the body are given in percentages, +with 100% equal to 13px. To calculate a font size percentage, multiply the +desired size in pixels by 7.6923076923. + +Here's a quick lookup table: + +10px - 76.923% +11px - 84.615% +12px - 92.308% +13px - 100% +14px - 107.692% +15px - 115.385% +16px - 123.077% +17px - 130.769% +18px - 138.462% +19px - 146.154% +20px - 153.846% +*/ + +html { + background: #fff; + color: #333; + overflow-y: scroll; +} + +body { + /*font: 13px/1.4 'Lucida Grande', 'Lucida Sans Unicode', 'DejaVu Sans', 'Bitstream Vera Sans', 'Helvetica', 'Arial', sans-serif;*/ + font: 13px/1.4 'Helvetica', 'Arial', sans-serif; + margin: 0; + padding: 0; +} + +/* -- Links ----------------------------------------------------------------- */ +a { + color: #356de4; + text-decoration: none; +} + +.hidden { + display: none; +} + +a:hover { text-decoration: underline; } + +/* "Jump to Table of Contents" link is shown to assistive tools, but hidden from + sight until it's focused. */ +.jump { + position: absolute; + padding: 3px 6px; + left: -99999px; + top: 0; +} + +.jump:focus { left: 40%; } + +/* -- Paragraphs ------------------------------------------------------------ */ +p { margin: 1.3em 0; } +dd p, td p { margin-bottom: 0; } +dd p:first-child, td p:first-child { margin-top: 0; } + +/* -- Headings -------------------------------------------------------------- */ +h1, h2, h3, h4, h5, h6 { + color: #D98527;/*was #f80*/ + font-family: 'Trebuchet MS', sans-serif; + font-weight: bold; + line-height: 1.1; + margin: 1.1em 0 0.5em; +} + +h1 { + font-size: 184.6%; + color: #30418C; + margin: 0.75em 0 0.5em; +} + +h2 { + font-size: 153.846%; + color: #E48A2B; +} + +h3 { font-size: 138.462%; } + +h4 { + border-bottom: 1px solid #DBDFEA; + color: #E48A2B; + font-size: 115.385%; + font-weight: normal; + padding-bottom: 2px; +} + +h5, h6 { font-size: 107.692%; } + +/* -- Code and examples ----------------------------------------------------- */ +code, kbd, pre, samp { + font-family: Menlo, Monaco, 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Courier New', Courier, monospace; + font-size: 92.308%; + line-height: 1.35; +} + +p code, p kbd, p samp { + background: #FCFBFA; + border: 1px solid #EFEEED; + padding: 0 3px; +} + +a code, a kbd, a samp, +pre code, pre kbd, pre samp, +table code, table kbd, table samp, +.intro code, .intro kbd, .intro samp, +.toc code, .toc kbd, .toc samp { + background: none; + border: none; + padding: 0; +} + +pre.code, pre.terminal, pre.cmd { + overflow-x: auto; + *overflow-x: scroll; + padding: 0.3em 0.6em; +} + +pre.code { + background: #FCFBFA; + border: 1px solid #EFEEED; + border-left-width: 5px; +} + +pre.terminal, pre.cmd { + background: #F0EFFC; + border: 1px solid #D0CBFB; + border-left: 5px solid #D0CBFB; +} + +/* Don't reduce the font size of // elements inside
+   blocks. */
+pre code, pre kbd, pre samp { font-size: 100%; }
+
+/* Used to denote text that shouldn't be selectable, such as line numbers or
+   shell prompts. Guess which browser this doesn't work in. */
+.noselect {
+    -moz-user-select: -moz-none;
+    -khtml-user-select: none;
+    -webkit-user-select: none;
+    -o-user-select: none;
+    user-select: none;
+}
+
+/* -- Lists ----------------------------------------------------------------- */
+dd { margin: 0.2em 0 0.7em 1em; }
+dl { margin: 1em 0; }
+dt { font-weight: bold; }
+
+/* -- Tables ---------------------------------------------------------------- */
+caption, th { text-align: left; }
+
+table {
+    border-collapse: collapse;
+    width: 100%;
+}
+
+td, th {
+    border: 1px solid #fff;
+    padding: 5px 12px;
+    vertical-align: top;
+}
+
+td { background: #E6E9F5; }
+td dl { margin: 0; }
+td dl dl { margin: 1em 0; }
+td pre:first-child { margin-top: 0; }
+
+th {
+    background: #D2D7E6;/*#97A0BF*/
+    border-bottom: none;
+    border-top: none;
+    color: #000;/*#FFF1D5*/
+    font-family: 'Trebuchet MS', sans-serif;
+    font-weight: bold;
+    line-height: 1.3;
+    white-space: nowrap;
+}
+
+
+/* -- Layout and Content ---------------------------------------------------- */
+#doc {
+    margin: auto;
+    min-width: 1024px;
+}
+
+.content { padding: 0 20px 0 25px; }
+
+.sidebar {
+    padding: 0 15px 0 10px;
+}
+#bd {
+    padding: 7px 0 130px;
+    position: relative;
+    width: 99%;
+}
+
+/* -- Table of Contents ----------------------------------------------------- */
+
+/* The #toc id refers to the single global table of contents, while the .toc
+   class refers to generic TOC lists that could be used throughout the page. */
+
+.toc code, .toc kbd, .toc samp { font-size: 100%; }
+.toc li { font-weight: bold; }
+.toc li li { font-weight: normal; }
+
+/* -- Intro and Example Boxes ----------------------------------------------- */
+/*
+.intro, .example { margin-bottom: 2em; }
+.example {
+    -moz-border-radius: 4px;
+    -webkit-border-radius: 4px;
+    border-radius: 4px;
+    -moz-box-shadow: 0 0 5px #bfbfbf;
+    -webkit-box-shadow: 0 0 5px #bfbfbf;
+    box-shadow: 0 0 5px #bfbfbf;
+    padding: 1em;
+}
+.intro {
+    background: none repeat scroll 0 0 #F0F1F8; border: 1px solid #D4D8EB; padding: 0 1em;
+}
+*/
+
+/* -- Other Styles ---------------------------------------------------------- */
+
+/* These are probably YUI-specific, and should be moved out of Selleck's default
+   theme. */
+
+.button {
+    border: 1px solid #dadada;
+    -moz-border-radius: 3px;
+    -webkit-border-radius: 3px;
+    border-radius: 3px;
+    color: #444;
+    display: inline-block;
+    font-family: Helvetica, Arial, sans-serif;
+    font-size: 92.308%;
+    font-weight: bold;
+    padding: 4px 13px 3px;
+    -moz-text-shadow: 1px 1px 0 #fff;
+    -webkit-text-shadow: 1px 1px 0 #fff;
+    text-shadow: 1px 1px 0 #fff;
+    white-space: nowrap;
+
+    background: #EFEFEF; /* old browsers */
+    background: -moz-linear-gradient(top, #f5f5f5 0%, #efefef 50%, #e5e5e5 51%, #dfdfdf 100%); /* firefox */
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#f5f5f5), color-stop(50%,#efefef), color-stop(51%,#e5e5e5), color-stop(100%,#dfdfdf)); /* webkit */
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#f5f5f5', endColorstr='#dfdfdf',GradientType=0 ); /* ie */
+}
+
+.button:hover {
+    border-color: #466899;
+    color: #fff;
+    text-decoration: none;
+    -moz-text-shadow: 1px 1px 0 #222;
+    -webkit-text-shadow: 1px 1px 0 #222;
+    text-shadow: 1px 1px 0 #222;
+
+    background: #6396D8; /* old browsers */
+    background: -moz-linear-gradient(top, #6396D8 0%, #5A83BC 50%, #547AB7 51%, #466899 100%); /* firefox */
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#6396D8), color-stop(50%,#5A83BC), color-stop(51%,#547AB7), color-stop(100%,#466899)); /* webkit */
+    filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#6396D8', endColorstr='#466899',GradientType=0 ); /* ie */
+}
+
+.newwindow { text-align: center; }
+
+.header .version em {
+    display: block;
+    text-align: right;
+}
+
+
+#classdocs .item {
+    border-bottom: 1px solid #466899;
+    margin: 1em 0;
+    padding: 1.5em;
+}
+
+#classdocs .item .params p,
+    #classdocs .item .returns p,{
+    display: inline;
+}
+
+#classdocs .item em code, #classdocs .item em.comment {
+    color: green;
+}
+
+#classdocs .item em.comment a {
+    color: green;
+    text-decoration: underline;
+}
+
+#classdocs .foundat {
+    font-size: 11px;
+    font-style: normal;
+}
+
+.attrs .emits {
+    margin-left: 2em;
+    padding: .5em;
+    border-left: 1px dashed #ccc;
+}
+
+abbr {
+    border-bottom: 1px dashed #ccc;
+    font-size: 80%;
+    cursor: help;
+}
+
+.prettyprint li.L0, 
+.prettyprint li.L1, 
+.prettyprint li.L2, 
+.prettyprint li.L3, 
+.prettyprint li.L5, 
+.prettyprint li.L6, 
+.prettyprint li.L7, 
+.prettyprint li.L8 {
+    list-style: decimal;
+}
+
+ul li p {
+    margin-top: 0;
+}
+
+.method .name {
+    font-size: 110%;
+}
+
+.apidocs .methods .extends .method,
+.apidocs .properties .extends .property,
+.apidocs .attrs .extends .attr,
+.apidocs .events .extends .event {
+    font-weight: bold;
+}
+
+.apidocs .methods .extends .inherited,
+.apidocs .properties .extends .inherited,
+.apidocs .attrs .extends .inherited,
+.apidocs .events .extends .inherited {
+    font-weight: normal;
+}
+
+#hd {
+    background: whiteSmoke;
+    background: -moz-linear-gradient(top,#DCDBD9 0,#F6F5F3 100%);
+    background: -webkit-gradient(linear,left top,left bottom,color-stop(0%,#DCDBD9),color-stop(100%,#F6F5F3));
+    filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#dcdbd9',endColorstr='#F6F5F3',GradientType=0);
+    border-bottom: 1px solid #DFDFDF;
+    padding: 0 15px 1px 20px;
+    margin-bottom: 15px;
+}
+
+#hd img {
+    margin-right: 10px;
+    vertical-align: middle;
+}
+
+
+/* -- API Docs CSS ---------------------------------------------------------- */
+
+/*
+This file is organized so that more generic styles are nearer the top, and more
+specific styles are nearer the bottom of the file. This allows us to take full
+advantage of the cascade to avoid redundant style rules. Please respect this
+convention when making changes.
+*/
+
+/* -- Generic TabView styles ------------------------------------------------ */
+
+/*
+These styles apply to all API doc tabviews. To change styles only for a
+specific tabview, see the other sections below.
+*/
+
+.yui3-js-enabled .apidocs .tabview {
+    visibility: hidden; /* Hide until the TabView finishes rendering. */
+    _visibility: visible;
+}
+
+.apidocs .tabview.yui3-tabview-content { visibility: visible; }
+.apidocs .tabview .yui3-tabview-panel { background: #fff; }
+
+/* -- Generic Content Styles ------------------------------------------------ */
+
+/* Headings */
+h2, h3, h4, h5, h6 {
+    border: none;
+    color: #30418C;
+    font-weight: bold;
+    text-decoration: none;
+}
+
+.link-docs {
+    float: right;
+    font-size: 15px;
+    margin: 4px 4px 6px;
+    padding: 6px 30px 5px;
+}
+
+.apidocs { zoom: 1; }
+
+/* Generic box styles. */
+.apidocs .box {
+    border: 1px solid;
+    border-radius: 3px;
+    margin: 1em 0;
+    padding: 0 1em;
+}
+
+/* A flag is a compact, capsule-like indicator of some kind. It's used to
+   indicate private and protected items, item return types, etc. in an
+   attractive and unobtrusive way. */
+.apidocs .flag {
+    background: #bababa;
+    border-radius: 3px;
+    color: #fff;
+    font-size: 11px;
+    margin: 0 0.5em;
+    padding: 2px 4px 1px;
+}
+
+/* Class/module metadata such as "Uses", "Extends", "Defined in", etc. */
+.apidocs .meta {
+    background: #f9f9f9;
+    border-color: #efefef;
+    color: #555;
+    font-size: 11px;
+    padding: 3px 6px;
+}
+
+.apidocs .meta p { margin: 0; }
+
+/* Deprecation warning. */
+.apidocs .box.deprecated,
+.apidocs .flag.deprecated {
+    background: #fdac9f;
+    border: 1px solid #fd7775;
+}
+
+.apidocs .box.deprecated p { margin: 0.5em 0; }
+.apidocs .flag.deprecated { color: #333; }
+
+/* Module/Class intro description. */
+.apidocs .intro {
+    background: #f0f1f8;
+    border-color: #d4d8eb;
+}
+
+/* Loading spinners. */
+#bd.loading .apidocs,
+#api-list.loading .yui3-tabview-panel {
+    background: #fff url(../img/spinner.gif) no-repeat center 70px;
+    min-height: 150px;
+}
+
+#bd.loading .apidocs .content,
+#api-list.loading .yui3-tabview-panel .apis {
+    display: none;
+}
+
+.apidocs .no-visible-items { color: #666; }
+
+/* Generic inline list. */
+.apidocs ul.inline {
+    display: inline;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+}
+
+.apidocs ul.inline li { display: inline; }
+
+/* Comma-separated list. */
+.apidocs ul.commas li:after { content: ','; }
+.apidocs ul.commas li:last-child:after { content: ''; }
+
+/* Keyboard shortcuts. */
+kbd .cmd { font-family: Monaco, Helvetica; }
+
+/* -- Generic Access Level styles ------------------------------------------- */
+.apidocs .item.protected,
+.apidocs .item.private,
+.apidocs .index-item.protected,
+.apidocs .index-item.deprecated,
+.apidocs .index-item.private {
+    display: none;
+}
+
+.show-deprecated .item.deprecated,
+.show-deprecated .index-item.deprecated,
+.show-protected .item.protected,
+.show-protected .index-item.protected,
+.show-private .item.private,
+.show-private .index-item.private {
+    display: block;
+}
+
+.hide-inherited .item.inherited,
+.hide-inherited .index-item.inherited {
+    display: none;
+}
+
+/* -- Generic Item Index styles --------------------------------------------- */
+.apidocs .index { margin: 1.5em 0 3em; }
+
+.apidocs .index h3 {
+    border-bottom: 1px solid #efefef;
+    color: #333;
+    font-size: 13px;
+    margin: 2em 0 0.6em;
+    padding-bottom: 2px;
+}
+
+.apidocs .index .no-visible-items { margin-top: 2em; }
+
+.apidocs .index-list {
+    border-color: #efefef;
+    font-size: 12px;
+    list-style: none;
+    margin: 0;
+    padding: 0;
+    -moz-column-count: 4;
+    -moz-column-gap: 10px;
+    -moz-column-width: 170px;
+    -ms-column-count: 4;
+    -ms-column-gap: 10px;
+    -ms-column-width: 170px;
+    -o-column-count: 4;
+    -o-column-gap: 10px;
+    -o-column-width: 170px;
+    -webkit-column-count: 4;
+    -webkit-column-gap: 10px;
+    -webkit-column-width: 170px;
+    column-count: 4;
+    column-gap: 10px;
+    column-width: 170px;
+}
+
+.apidocs .no-columns .index-list {
+    -moz-column-count: 1;
+    -ms-column-count: 1;
+    -o-column-count: 1;
+    -webkit-column-count: 1;
+    column-count: 1;
+}
+
+.apidocs .index-item { white-space: nowrap; }
+
+.apidocs .index-item .flag {
+    background: none;
+    border: none;
+    color: #afafaf;
+    display: inline;
+    margin: 0 0 0 0.2em;
+    padding: 0;
+}
+
+/* -- Generic API item styles ----------------------------------------------- */
+.apidocs .args {
+    display: inline;
+    margin: 0 0.5em;
+}
+
+.apidocs .flag.chainable { background: #46ca3b; }
+.apidocs .flag.protected { background: #9b86fc; }
+.apidocs .flag.private { background: #fd6b1b; }
+.apidocs .flag.async { background: #356de4; }
+.apidocs .flag.required { background: #e60923; }
+
+.apidocs .item {
+    border-bottom: 1px solid #efefef;
+    margin: 1.5em 0 2em;
+    padding-bottom: 2em;
+}
+
+.apidocs .item h4,
+.apidocs .item h5,
+.apidocs .item h6 {
+    color: #333;
+    font-family: inherit;
+    font-size: 100%;
+}
+
+.apidocs .item .description p,
+.apidocs .item pre.code {
+    margin: 1em 0 0;
+}
+
+.apidocs .item .meta {
+    background: none;
+    border: none;
+    padding: 0;
+}
+
+.apidocs .item .name {
+    display: inline;
+    font-size: 14px;
+}
+
+.apidocs .item .type,
+.apidocs .item .type a,
+.apidocs .returns-inline {
+    color: #555;
+}
+
+.apidocs .item .type,
+.apidocs .returns-inline {
+    font-size: 11px;
+    margin: 0 0 0 0;
+}
+
+.apidocs .item .type a { border-bottom: 1px dotted #afafaf; }
+.apidocs .item .type a:hover { border: none; }
+
+/* -- Item Parameter List --------------------------------------------------- */
+.apidocs .params-list {
+    list-style: square;
+    margin: 1em 0 0 2em;
+    padding: 0;
+}
+
+.apidocs .param { margin-bottom: 1em; }
+
+.apidocs .param .type,
+.apidocs .param .type a {
+    color: #666;
+}
+
+.apidocs .param .type {
+    margin: 0 0 0 0.5em;
+    *margin-left: 0.5em;
+}
+
+.apidocs .param-name { font-weight: bold; }
+
+/* -- Item "Emits" block ---------------------------------------------------- */
+.apidocs .item .emits {
+    background: #f9f9f9;
+    border-color: #eaeaea;
+}
+
+/* -- Item "Returns" block -------------------------------------------------- */
+.apidocs .item .returns .type,
+.apidocs .item .returns .type a {
+    font-size: 100%;
+    margin: 0;
+}
+
+/* -- Class Constructor block ----------------------------------------------- */
+.apidocs .constructor .item {
+    border: none;
+    padding-bottom: 0;
+}
+
+/* -- File Source View ------------------------------------------------------ */
+.apidocs .file pre.code,
+#doc .apidocs .file pre.prettyprint {
+    background: inherit;
+    border: none;
+    overflow: visible;
+    padding: 0;
+}
+
+.apidocs .L0,
+.apidocs .L1,
+.apidocs .L2,
+.apidocs .L3,
+.apidocs .L4,
+.apidocs .L5,
+.apidocs .L6,
+.apidocs .L7,
+.apidocs .L8,
+.apidocs .L9 {
+    background: inherit;
+}
+
+/* -- Submodule List -------------------------------------------------------- */
+.apidocs .module-submodule-description {
+    font-size: 12px;
+    margin: 0.3em 0 1em;
+}
+
+.apidocs .module-submodule-description p:first-child { margin-top: 0; }
+
+/* -- Sidebar TabView ------------------------------------------------------- */
+#api-tabview { margin-top: 0.6em; }
+
+#api-tabview-filter,
+#api-tabview-panel {
+    border: 1px solid #dfdfdf;
+}
+
+#api-tabview-filter {
+    border-bottom: none;
+    border-top: none;
+    padding: 0.6em 10px 0 10px;
+}
+
+#api-tabview-panel { border-top: none; }
+#api-filter { width: 97%; }
+
+/* -- Content TabView ------------------------------------------------------- */
+#classdocs .yui3-tabview-panel { border: none; }
+
+/* -- Source File Contents -------------------------------------------------- */
+.prettyprint li.L0,
+.prettyprint li.L1,
+.prettyprint li.L2,
+.prettyprint li.L3,
+.prettyprint li.L5,
+.prettyprint li.L6,
+.prettyprint li.L7,
+.prettyprint li.L8 {
+    list-style: decimal;
+}
+
+/* -- API options ----------------------------------------------------------- */
+#api-options {
+    font-size: 11px;
+    margin-top: 2.2em;
+    position: absolute;
+    right: 1.5em;
+}
+
+/*#api-options label { margin-right: 0.6em; }*/
+
+/* -- API list -------------------------------------------------------------- */
+#api-list {
+    margin-top: 1.5em;
+    *zoom: 1;
+}
+
+.apis {
+    font-size: 12px;
+    line-height: 1.4;
+    list-style: none;
+    margin: 0;
+    padding: 0.5em 0 0.5em 0.4em;
+}
+
+.apis a {
+    border: 1px solid transparent;
+    display: block;
+    margin: 0 0 0 -4px;
+    padding: 1px 4px 0;
+    text-decoration: none;
+    _border: none;
+    _display: inline;
+}
+
+.apis a:hover,
+.apis a:focus {
+    background: #E8EDFC;
+    background: -moz-linear-gradient(top, #e8edfc 0%, #becef7 100%);
+    background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#E8EDFC), color-stop(100%,#BECEF7));
+    border-color: #AAC0FA;
+    border-radius: 3px;
+    color: #333;
+    outline: none;
+}
+
+.api-list-item a:hover,
+.api-list-item a:focus {
+    font-weight: bold;
+    text-shadow: 1px 1px 1px #fff;
+}
+
+.apis .message { color: #888; }
+.apis .result a { padding: 3px 5px 2px; }
+
+.apis .result .type {
+    right: 4px;
+    top: 7px;
+}
+
+.api-list-item .yui3-highlight {
+    font-weight: bold;
+}
+
diff --git a/doc/assets/favicon.png b/doc/assets/favicon.png
new file mode 100644
index 000000000..5a95ddab6
Binary files /dev/null and b/doc/assets/favicon.png differ
diff --git a/doc/assets/img/spinner.gif b/doc/assets/img/spinner.gif
new file mode 100644
index 000000000..44f96ba68
Binary files /dev/null and b/doc/assets/img/spinner.gif differ
diff --git a/doc/assets/index.html b/doc/assets/index.html
new file mode 100644
index 000000000..487fe15b2
--- /dev/null
+++ b/doc/assets/index.html
@@ -0,0 +1,10 @@
+
+
+    
+        Redirector
+        
+    
+    
+        Click here to redirect
+    
+
diff --git a/doc/assets/js/api-filter.js b/doc/assets/js/api-filter.js
new file mode 100644
index 000000000..37aefbab9
--- /dev/null
+++ b/doc/assets/js/api-filter.js
@@ -0,0 +1,52 @@
+YUI.add('api-filter', function (Y) {
+
+Y.APIFilter = Y.Base.create('apiFilter', Y.Base, [Y.AutoCompleteBase], {
+    // -- Initializer ----------------------------------------------------------
+    initializer: function () {
+        this._bindUIACBase();
+        this._syncUIACBase();
+    },
+    getDisplayName: function(name) {
+
+        Y.each(Y.YUIDoc.meta.allModules, function(i) {
+            if (i.name === name && i.displayName) {
+                name = i.displayName;
+            }
+        });
+
+        return name;
+    }
+
+}, {
+    // -- Attributes -----------------------------------------------------------
+    ATTRS: {
+        resultHighlighter: {
+            value: 'phraseMatch'
+        },
+
+        // May be set to "classes" or "modules".
+        queryType: {
+            value: 'classes'
+        },
+
+        source: {
+            valueFn: function() {
+                var self = this;
+                return function(q) {
+                    var data = Y.YUIDoc.meta[self.get('queryType')],
+                        out = [];
+                    Y.each(data, function(v) {
+                        if (v.toLowerCase().indexOf(q.toLowerCase()) > -1) {
+                            out.push(v);
+                        }
+                    });
+                    return out;
+                };
+            }
+        }
+    }
+});
+
+}, '3.4.0', {requires: [
+    'autocomplete-base', 'autocomplete-highlighters', 'autocomplete-sources'
+]});
diff --git a/doc/assets/js/api-list.js b/doc/assets/js/api-list.js
new file mode 100644
index 000000000..88905b52e
--- /dev/null
+++ b/doc/assets/js/api-list.js
@@ -0,0 +1,251 @@
+YUI.add('api-list', function (Y) {
+
+var Lang   = Y.Lang,
+    YArray = Y.Array,
+
+    APIList = Y.namespace('APIList'),
+
+    classesNode    = Y.one('#api-classes'),
+    inputNode      = Y.one('#api-filter'),
+    modulesNode    = Y.one('#api-modules'),
+    tabviewNode    = Y.one('#api-tabview'),
+
+    tabs = APIList.tabs = {},
+
+    filter = APIList.filter = new Y.APIFilter({
+        inputNode : inputNode,
+        maxResults: 1000,
+
+        on: {
+            results: onFilterResults
+        }
+    }),
+
+    search = APIList.search = new Y.APISearch({
+        inputNode : inputNode,
+        maxResults: 100,
+
+        on: {
+            clear  : onSearchClear,
+            results: onSearchResults
+        }
+    }),
+
+    tabview = APIList.tabview = new Y.TabView({
+        srcNode  : tabviewNode,
+        panelNode: '#api-tabview-panel',
+        render   : true,
+
+        on: {
+            selectionChange: onTabSelectionChange
+        }
+    }),
+
+    focusManager = APIList.focusManager = tabviewNode.plug(Y.Plugin.NodeFocusManager, {
+        circular   : true,
+        descendants: '#api-filter, .yui3-tab-panel-selected .api-list-item a, .yui3-tab-panel-selected .result a',
+        keys       : {next: 'down:40', previous: 'down:38'}
+    }).focusManager,
+
+    LIST_ITEM_TEMPLATE =
+        '
  • ' + + '{displayName}' + + '
  • '; + +// -- Init --------------------------------------------------------------------- + +// Duckpunch FocusManager's key event handling to prevent it from handling key +// events when a modifier is pressed. +Y.before(function (e, activeDescendant) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return new Y.Do.Prevent(); + } +}, focusManager, '_focusPrevious', focusManager); + +Y.before(function (e, activeDescendant) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return new Y.Do.Prevent(); + } +}, focusManager, '_focusNext', focusManager); + +// Create a mapping of tabs in the tabview so we can refer to them easily later. +tabview.each(function (tab, index) { + var name = tab.get('label').toLowerCase(); + + tabs[name] = { + index: index, + name : name, + tab : tab + }; +}); + +// Switch tabs on Ctrl/Cmd-Left/Right arrows. +tabviewNode.on('key', onTabSwitchKey, 'down:37,39'); + +// Focus the filter input when the `/` key is pressed. +Y.one(Y.config.doc).on('key', onSearchKey, 'down:83'); + +// Keep the Focus Manager up to date. +inputNode.on('focus', function () { + focusManager.set('activeDescendant', inputNode); +}); + +// Update all tabview links to resolved URLs. +tabview.get('panelNode').all('a').each(function (link) { + link.setAttribute('href', link.get('href')); +}); + +// -- Private Functions -------------------------------------------------------- +function getFilterResultNode() { + return filter.get('queryType') === 'classes' ? classesNode : modulesNode; +} + +// -- Event Handlers ----------------------------------------------------------- +function onFilterResults(e) { + var frag = Y.one(Y.config.doc.createDocumentFragment()), + resultNode = getFilterResultNode(), + typePlural = filter.get('queryType'), + typeSingular = typePlural === 'classes' ? 'class' : 'module'; + + if (e.results.length) { + YArray.each(e.results, function (result) { + frag.append(Lang.sub(LIST_ITEM_TEMPLATE, { + rootPath : APIList.rootPath, + displayName : filter.getDisplayName(result.highlighted), + name : result.text, + typePlural : typePlural, + typeSingular: typeSingular + })); + }); + } else { + frag.append( + '
  • ' + + 'No ' + typePlural + ' found.' + + '
  • ' + ); + } + + resultNode.empty(true); + resultNode.append(frag); + + focusManager.refresh(); +} + +function onSearchClear(e) { + + focusManager.refresh(); +} + +function onSearchKey(e) { + var target = e.target; + + if (target.test('input,select,textarea') + || target.get('isContentEditable')) { + return; + } + + e.preventDefault(); + + inputNode.focus(); + focusManager.refresh(); +} + +function onSearchResults(e) { + var frag = Y.one(Y.config.doc.createDocumentFragment()); + + if (e.results.length) { + YArray.each(e.results, function (result) { + frag.append(result.display); + }); + } else { + frag.append( + '
  • ' + + 'No results found. Maybe you\'ll have better luck with a ' + + 'different query?' + + '
  • ' + ); + } + + + focusManager.refresh(); +} + +function onTabSelectionChange(e) { + var tab = e.newVal, + name = tab.get('label').toLowerCase(); + + tabs.selected = { + index: tab.get('index'), + name : name, + tab : tab + }; + + switch (name) { + case 'classes': // fallthru + case 'modules': + filter.setAttrs({ + minQueryLength: 0, + queryType : name + }); + + search.set('minQueryLength', -1); + + // Only send a request if this isn't the initially-selected tab. + if (e.prevVal) { + filter.sendRequest(filter.get('value')); + } + break; + + case 'everything': + filter.set('minQueryLength', -1); + search.set('minQueryLength', 1); + + if (search.get('value')) { + search.sendRequest(search.get('value')); + } else { + inputNode.focus(); + } + break; + + default: + // WTF? We shouldn't be here! + filter.set('minQueryLength', -1); + search.set('minQueryLength', -1); + } + + if (focusManager) { + setTimeout(function () { + focusManager.refresh(); + }, 1); + } +} + +function onTabSwitchKey(e) { + var currentTabIndex = tabs.selected.index; + + if (!(e.ctrlKey || e.metaKey)) { + return; + } + + e.preventDefault(); + + switch (e.keyCode) { + case 37: // left arrow + if (currentTabIndex > 0) { + tabview.selectChild(currentTabIndex - 1); + inputNode.focus(); + } + break; + + case 39: // right arrow + if (currentTabIndex < (Y.Object.size(tabs) - 2)) { + tabview.selectChild(currentTabIndex + 1); + inputNode.focus(); + } + break; + } +} + +}, '3.4.0', {requires: [ + 'api-filter', 'api-search', 'event-key', 'node-focusmanager', 'tabview' +]}); diff --git a/doc/assets/js/api-search.js b/doc/assets/js/api-search.js new file mode 100644 index 000000000..175f6a617 --- /dev/null +++ b/doc/assets/js/api-search.js @@ -0,0 +1,98 @@ +YUI.add('api-search', function (Y) { + +var Lang = Y.Lang, + Node = Y.Node, + YArray = Y.Array; + +Y.APISearch = Y.Base.create('apiSearch', Y.Base, [Y.AutoCompleteBase], { + // -- Public Properties ---------------------------------------------------- + RESULT_TEMPLATE: + '
  • ' + + '' + + '

    {name}

    ' + + '{resultType}' + + '
    {description}
    ' + + '{class}' + + '
    ' + + '
  • ', + + // -- Initializer ---------------------------------------------------------- + initializer: function () { + this._bindUIACBase(); + this._syncUIACBase(); + }, + + // -- Protected Methods ---------------------------------------------------- + _apiResultFilter: function (query, results) { + // Filter components out of the results. + return YArray.filter(results, function (result) { + return result.raw.resultType === 'component' ? false : result; + }); + }, + + _apiResultFormatter: function (query, results) { + return YArray.map(results, function (result) { + var raw = Y.merge(result.raw), // create a copy + desc = raw.description || ''; + + // Convert description to text and truncate it if necessary. + desc = Node.create('
    ' + desc + '
    ').get('text'); + + if (desc.length > 65) { + desc = Y.Escape.html(desc.substr(0, 65)) + ' …'; + } else { + desc = Y.Escape.html(desc); + } + + raw['class'] || (raw['class'] = ''); + raw.description = desc; + + // Use the highlighted result name. + raw.name = result.highlighted; + + return Lang.sub(this.RESULT_TEMPLATE, raw); + }, this); + }, + + _apiTextLocator: function (result) { + return result.displayName || result.name; + } +}, { + // -- Attributes ----------------------------------------------------------- + ATTRS: { + resultFormatter: { + valueFn: function () { + return this._apiResultFormatter; + } + }, + + resultFilters: { + valueFn: function () { + return this._apiResultFilter; + } + }, + + resultHighlighter: { + value: 'phraseMatch' + }, + + resultListLocator: { + value: 'data.results' + }, + + resultTextLocator: { + valueFn: function () { + return this._apiTextLocator; + } + }, + + source: { + value: '/api/v1/search?q={query}&count={maxResults}' + } + } +}); + +}, '3.4.0', {requires: [ + 'autocomplete-base', 'autocomplete-highlighters', 'autocomplete-sources', + 'escape' +]}); diff --git a/doc/assets/js/apidocs.js b/doc/assets/js/apidocs.js new file mode 100644 index 000000000..c64bb4632 --- /dev/null +++ b/doc/assets/js/apidocs.js @@ -0,0 +1,370 @@ +YUI().use( + 'yuidoc-meta', + 'api-list', 'history-hash', 'node-screen', 'node-style', 'pjax', +function (Y) { + +var win = Y.config.win, + localStorage = win.localStorage, + + bdNode = Y.one('#bd'), + + pjax, + defaultRoute, + + classTabView, + selectedTab; + +// Kill pjax functionality unless serving over HTTP. +if (!Y.getLocation().protocol.match(/^https?\:/)) { + Y.Router.html5 = false; +} + +// Create the default route with middleware which enables syntax highlighting +// on the loaded content. +defaultRoute = Y.Pjax.defaultRoute.concat(function (req, res, next) { + prettyPrint(); + bdNode.removeClass('loading'); + + next(); +}); + +pjax = new Y.Pjax({ + container : '#docs-main', + contentSelector: '#docs-main > .content', + linkSelector : '#bd a', + titleSelector : '#xhr-title', + + navigateOnHash: true, + root : '/', + routes : [ + // -- / ---------------------------------------------------------------- + { + path : '/(index.html)?', + callbacks: defaultRoute + }, + + // -- /classes/* ------------------------------------------------------- + { + path : '/classes/:class.html*', + callbacks: [defaultRoute, 'handleClasses'] + }, + + // -- /files/* --------------------------------------------------------- + { + path : '/files/*file', + callbacks: [defaultRoute, 'handleFiles'] + }, + + // -- /modules/* ------------------------------------------------------- + { + path : '/modules/:module.html*', + callbacks: defaultRoute + } + ] +}); + +// -- Utility Functions -------------------------------------------------------- + +pjax.checkVisibility = function (tab) { + tab || (tab = selectedTab); + + if (!tab) { return; } + + var panelNode = tab.get('panelNode'), + visibleItems; + + // If no items are visible in the tab panel due to the current visibility + // settings, display a message to that effect. + visibleItems = panelNode.all('.item,.index-item').some(function (itemNode) { + if (itemNode.getComputedStyle('display') !== 'none') { + return true; + } + }); + + panelNode.all('.no-visible-items').remove(); + + if (!visibleItems) { + if (Y.one('#index .index-item')) { + panelNode.append( + '
    ' + + '

    ' + + 'Some items are not shown due to the current visibility ' + + 'settings. Use the checkboxes at the upper right of this ' + + 'page to change the visibility settings.' + + '

    ' + + '
    ' + ); + } else { + panelNode.append( + '
    ' + + '

    ' + + 'This class doesn\'t provide any methods, properties, ' + + 'attributes, or events.' + + '

    ' + + '
    ' + ); + } + } + + // Hide index sections without any visible items. + Y.all('.index-section').each(function (section) { + var items = 0, + visibleItems = 0; + + section.all('.index-item').each(function (itemNode) { + items += 1; + + if (itemNode.getComputedStyle('display') !== 'none') { + visibleItems += 1; + } + }); + + section.toggleClass('hidden', !visibleItems); + section.toggleClass('no-columns', visibleItems < 4); + }); +}; + +pjax.initClassTabView = function () { + if (!Y.all('#classdocs .api-class-tab').size()) { + return; + } + + if (classTabView) { + classTabView.destroy(); + selectedTab = null; + } + + classTabView = new Y.TabView({ + srcNode: '#classdocs', + + on: { + selectionChange: pjax.onTabSelectionChange + } + }); + + pjax.updateTabState(); + classTabView.render(); +}; + +pjax.initLineNumbers = function () { + var hash = win.location.hash.substring(1), + container = pjax.get('container'), + hasLines, node; + + // Add ids for each line number in the file source view. + container.all('.linenums>li').each(function (lineNode, index) { + lineNode.set('id', 'l' + (index + 1)); + lineNode.addClass('file-line'); + hasLines = true; + }); + + // Scroll to the desired line. + if (hasLines && /^l\d+$/.test(hash)) { + if ((node = container.getById(hash))) { + win.scroll(0, node.getY()); + } + } +}; + +pjax.initRoot = function () { + var terminators = /^(?:classes|files|modules)$/, + parts = pjax._getPathRoot().split('/'), + root = [], + i, len, part; + + for (i = 0, len = parts.length; i < len; i += 1) { + part = parts[i]; + + if (part.match(terminators)) { + // Makes sure the path will end with a "/". + root.push(''); + break; + } + + root.push(part); + } + + pjax.set('root', root.join('/')); +}; + +pjax.updateTabState = function (src) { + var hash = win.location.hash.substring(1), + defaultTab, node, tab, tabPanel; + + function scrollToNode() { + if (node.hasClass('protected')) { + Y.one('#api-show-protected').set('checked', true); + pjax.updateVisibility(); + } + + if (node.hasClass('private')) { + Y.one('#api-show-private').set('checked', true); + pjax.updateVisibility(); + } + + setTimeout(function () { + // For some reason, unless we re-get the node instance here, + // getY() always returns 0. + var node = Y.one('#classdocs').getById(hash); + win.scrollTo(0, node.getY() - 70); + }, 1); + } + + if (!classTabView) { + return; + } + + if (src === 'hashchange' && !hash) { + defaultTab = 'index'; + } else { + if (localStorage) { + defaultTab = localStorage.getItem('tab_' + pjax.getPath()) || + 'index'; + } else { + defaultTab = 'index'; + } + } + + if (hash && (node = Y.one('#classdocs').getById(hash))) { + if ((tabPanel = node.ancestor('.api-class-tabpanel', true))) { + if ((tab = Y.one('#classdocs .api-class-tab.' + tabPanel.get('id')))) { + if (classTabView.get('rendered')) { + Y.Widget.getByNode(tab).set('selected', 1); + } else { + tab.addClass('yui3-tab-selected'); + } + } + } + + // Scroll to the desired element if this is a hash URL. + if (node) { + if (classTabView.get('rendered')) { + scrollToNode(); + } else { + classTabView.once('renderedChange', scrollToNode); + } + } + } else { + tab = Y.one('#classdocs .api-class-tab.' + defaultTab); + + // When the `defaultTab` node isn't found, `localStorage` is stale. + if (!tab && defaultTab !== 'index') { + tab = Y.one('#classdocs .api-class-tab.index'); + } + + if (classTabView.get('rendered')) { + Y.Widget.getByNode(tab).set('selected', 1); + } else { + tab.addClass('yui3-tab-selected'); + } + } +}; + +pjax.updateVisibility = function () { + var container = pjax.get('container'); + + container.toggleClass('hide-inherited', + !Y.one('#api-show-inherited').get('checked')); + + container.toggleClass('show-deprecated', + Y.one('#api-show-deprecated').get('checked')); + + container.toggleClass('show-protected', + Y.one('#api-show-protected').get('checked')); + + container.toggleClass('show-private', + Y.one('#api-show-private').get('checked')); + + pjax.checkVisibility(); +}; + +// -- Route Handlers ----------------------------------------------------------- + +pjax.handleClasses = function (req, res, next) { + var status = res.ioResponse.status; + + // Handles success and local filesystem XHRs. + if (!status || (status >= 200 && status < 300)) { + pjax.initClassTabView(); + } + + next(); +}; + +pjax.handleFiles = function (req, res, next) { + var status = res.ioResponse.status; + + // Handles success and local filesystem XHRs. + if (!status || (status >= 200 && status < 300)) { + pjax.initLineNumbers(); + } + + next(); +}; + +// -- Event Handlers ----------------------------------------------------------- + +pjax.onNavigate = function (e) { + var hash = e.hash, + originTarget = e.originEvent && e.originEvent.target, + tab; + + if (hash) { + tab = originTarget && originTarget.ancestor('.yui3-tab', true); + + if (hash === win.location.hash) { + pjax.updateTabState('hashchange'); + } else if (!tab) { + win.location.hash = hash; + } + + e.preventDefault(); + return; + } + + // Only scroll to the top of the page when the URL doesn't have a hash. + this.set('scrollToTop', !e.url.match(/#.+$/)); + + bdNode.addClass('loading'); +}; + +pjax.onOptionClick = function (e) { + pjax.updateVisibility(); +}; + +pjax.onTabSelectionChange = function (e) { + var tab = e.newVal, + tabId = tab.get('contentBox').getAttribute('href').substring(1); + + selectedTab = tab; + + // If switching from a previous tab (i.e., this is not the default tab), + // replace the history entry with a hash URL that will cause this tab to + // be selected if the user navigates away and then returns using the back + // or forward buttons. + if (e.prevVal && localStorage) { + localStorage.setItem('tab_' + pjax.getPath(), tabId); + } + + pjax.checkVisibility(tab); +}; + +// -- Init --------------------------------------------------------------------- + +pjax.on('navigate', pjax.onNavigate); + +pjax.initRoot(); +pjax.upgrade(); +pjax.initClassTabView(); +pjax.initLineNumbers(); +pjax.updateVisibility(); + +Y.APIList.rootPath = pjax.get('root'); + +Y.one('#api-options').delegate('click', pjax.onOptionClick, 'input'); + +Y.on('hashchange', function (e) { + pjax.updateTabState('hashchange'); +}, win); + +}); diff --git a/doc/assets/js/yui-prettify.js b/doc/assets/js/yui-prettify.js new file mode 100644 index 000000000..18de86495 --- /dev/null +++ b/doc/assets/js/yui-prettify.js @@ -0,0 +1,17 @@ +YUI().use('node', function(Y) { + var code = Y.all('.prettyprint.linenums'); + if (code.size()) { + code.each(function(c) { + var lis = c.all('ol li'), + l = 1; + lis.each(function(n) { + n.prepend(''); + l++; + }); + }); + var h = location.hash; + location.hash = ''; + h = h.replace('LINE_', 'LINENUM_'); + location.hash = h; + } +}); diff --git a/doc/assets/vendor/prettify/CHANGES.html b/doc/assets/vendor/prettify/CHANGES.html new file mode 100644 index 000000000..b50b84149 --- /dev/null +++ b/doc/assets/vendor/prettify/CHANGES.html @@ -0,0 +1,130 @@ + + + + Change Log + + + README + +

    Known Issues

    +
      +
    • Perl formatting is really crappy. Partly because the author is lazy and + partly because Perl is + hard to parse. +
    • On some browsers, <code> elements with newlines in the text + which use CSS to specify white-space:pre will have the newlines + improperly stripped if the element is not attached to the document at the time + the stripping is done. Also, on IE 6, all newlines will be stripped from + <code> elements because of the way IE6 produces + innerHTML. Workaround: use <pre> for code with + newlines. +
    + +

    Change Log

    +

    29 March 2007

    +
      +
    • Added tests for PHP support + to address + issue 3. +
    • Fixed + bug: prettyPrintOne was not halting. This was not + reachable through the normal entry point. +
    • Fixed + bug: recursing into a script block or PHP tag that was not properly + closed would not silently drop the content. + (test) +
    • Fixed + bug: was eating tabs + (test) +
    • Fixed entity handling so that the caveat +
      +

      Caveats: please properly escape less-thans. x&lt;y + instead of x<y, and use " instead of + &quot; for string delimiters.

      +
      + is no longer applicable. +
    • Added noisefree's C# + patch +
    • Added a distribution that has comments and + whitespace removed to reduce download size from 45.5kB to 12.8kB. +
    +

    4 Jul 2008

    +
      +
    • Added language specific formatters that are triggered by the presence + of a lang-<language-file-extension>
    • +
    • Fixed bug: python handling of '''string''' +
    • Fixed bug: / in regex [charsets] should not end regex +
    +

    5 Jul 2008

    +
      +
    • Defined language extensions for Lisp and Lua +
    +

    14 Jul 2008

    +
      +
    • Language handlers for F#, OCAML, SQL +
    • Support for nocode spans to allow embedding of line + numbers and code annotations which should not be styled or otherwise + affect the tokenization of prettified code. + See the issue 22 + testcase. +
    +

    6 Jan 2009

    +
      +
    • Language handlers for Visual Basic, Haskell, CSS, and WikiText
    • +
    • Added .mxml extension to the markup style handler for + Flex MXML files. See + issue 37. +
    • Added .m extension to the C style handler so that Objective + C source files properly highlight. See + issue 58. +
    • Changed HTML lexer to use the same embedded source mechanism as the + wiki language handler, and changed to use the registered + CSS handler for STYLE element content. +
    +

    21 May 2009

    +
      +
    • Rewrote to improve performance on large files. + See benchmarks.
    • +
    • Fixed bugs with highlighting of Haskell line comments, Lisp + number literals, Lua strings, C preprocessor directives, + newlines in Wiki code on Windows, and newlines in IE6.
    • +
    +

    14 August 2009

    +
      +
    • Fixed prettifying of <code> blocks with embedded newlines. +
    +

    3 October 2009

    +
      +
    • Fixed prettifying of XML/HTML tags that contain uppercase letters. +
    +

    19 July 2010

    +
      +
    • Added support for line numbers. Bug + 22
    • +
    • Added YAML support. Bug + 123
    • +
    • Added VHDL support courtesy Le Poussin.
    • +
    • IE performance improvements. Bug + 102 courtesy jacobly.
    • +
    • A variety of markup formatting fixes courtesy smain and thezbyg.
    • +
    • Fixed copy and paste in IE[678]. +
    • Changed output to use &#160; instead of + &nbsp; so that the output works when embedded in XML. + Bug + 108.
    • +
    + + diff --git a/doc/assets/vendor/prettify/COPYING b/doc/assets/vendor/prettify/COPYING new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/doc/assets/vendor/prettify/COPYING @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/doc/assets/vendor/prettify/README.html b/doc/assets/vendor/prettify/README.html new file mode 100644 index 000000000..c6fe1a32c --- /dev/null +++ b/doc/assets/vendor/prettify/README.html @@ -0,0 +1,203 @@ + + + + + Javascript code prettifier + + + + + + + + + + Languages : CH +

    Javascript code prettifier

    + +

    Setup

    +
      +
    1. Download a distribution +
    2. Include the script and stylesheets in your document + (you will need to make sure the css and js file are on your server, and + adjust the paths in the script and link tag) +
      +<link href="prettify.css" type="text/css" rel="stylesheet" />
      +<script type="text/javascript" src="prettify.js"></script>
      +
    3. Add onload="prettyPrint()" to your + document's body tag. +
    4. Modify the stylesheet to get the coloring you prefer
    5. +
    + +

    Usage

    +

    Put code snippets in + <pre class="prettyprint">...</pre> + or <code class="prettyprint">...</code> + and it will automatically be pretty printed. + + + + +
    The original + Prettier +
    class Voila {
    +public:
    +  // Voila
    +  static const string VOILA = "Voila";
    +
    +  // will not interfere with embedded tags.
    +}
    + +
    class Voila {
    +public:
    +  // Voila
    +  static const string VOILA = "Voila";
    +
    +  // will not interfere with embedded tags.
    +}
    +
    + +

    FAQ

    +

    Which languages does it work for?

    +

    The comments in prettify.js are authoritative but the lexer + should work on a number of languages including C and friends, + Java, Python, Bash, SQL, HTML, XML, CSS, Javascript, and Makefiles. + It works passably on Ruby, PHP, VB, and Awk and a decent subset of Perl + and Ruby, but, because of commenting conventions, doesn't work on + Smalltalk, or CAML-like languages.

    + +

    LISPy languages are supported via an extension: + lang-lisp.js.

    +

    And similarly for + CSS, + Haskell, + Lua, + OCAML, SML, F#, + Visual Basic, + SQL, + Protocol Buffers, and + WikiText.. + +

    If you'd like to add an extension for your favorite language, please + look at src/lang-lisp.js and file an + issue including your language extension, and a testcase.

    + +

    How do I specify which language my code is in?

    +

    You don't need to specify the language since prettyprint() + will guess. You can specify a language by specifying the language extension + along with the prettyprint class like so:

    +
    <pre class="prettyprint lang-html">
    +  The lang-* class specifies the language file extensions.
    +  File extensions supported by default include
    +    "bsh", "c", "cc", "cpp", "cs", "csh", "cyc", "cv", "htm", "html",
    +    "java", "js", "m", "mxml", "perl", "pl", "pm", "py", "rb", "sh",
    +    "xhtml", "xml", "xsl".
    +</pre>
    + +

    It doesn't work on <obfuscated code sample>?

    +

    Yes. Prettifying obfuscated code is like putting lipstick on a pig + — i.e. outside the scope of this tool.

    + +

    Which browsers does it work with?

    +

    It's been tested with IE 6, Firefox 1.5 & 2, and Safari 2.0.4. + Look at the test page to see if it + works in your browser.

    + +

    What's changed?

    +

    See the change log

    + +

    Why doesn't Prettyprinting of strings work on WordPress?

    +

    Apparently wordpress does "smart quoting" which changes close quotes. + This causes end quotes to not match up with open quotes. +

    This breaks prettifying as well as copying and pasting of code samples. + See + WordPress's help center for info on how to stop smart quoting of code + snippets.

    + +

    How do I put line numbers in my code?

    +

    You can use the linenums class to turn on line + numbering. If your code doesn't start at line number 1, you can + add a colon and a line number to the end of that class as in + linenums:52. + +

    For example +

    <pre class="prettyprint linenums:4"
    +>// This is line 4.
    +foo();
    +bar();
    +baz();
    +boo();
    +far();
    +faz();
    +<pre>
    + produces +
    // This is line 4.
    +foo();
    +bar();
    +baz();
    +boo();
    +far();
    +faz();
    +
    + +

    How do I prevent a portion of markup from being marked as code?

    +

    You can use the nocode class to identify a span of markup + that is not code. +

    <pre class=prettyprint>
    +int x = foo();  /* This is a comment  <span class="nocode">This is not code</span>
    +  Continuation of comment */
    +int y = bar();
    +</pre>
    +produces +
    +int x = foo();  /* This is a comment  This is not code
    +  Continuation of comment */
    +int y = bar();
    +
    + +

    For a more complete example see the issue22 + testcase.

    + +

    I get an error message "a is not a function" or "opt_whenDone is not a function"

    +

    If you are calling prettyPrint via an event handler, wrap it in a function. + Instead of doing +

    + addEventListener('load', prettyPrint, false); +
    + wrap it in a closure like +
    + addEventListener('load', function (event) { prettyPrint() }, false); +
    + so that the browser does not pass an event object to prettyPrint which + will confuse it. + +


    + + + + diff --git a/doc/assets/vendor/prettify/prettify-min.css b/doc/assets/vendor/prettify/prettify-min.css new file mode 100644 index 000000000..d44b3a228 --- /dev/null +++ b/doc/assets/vendor/prettify/prettify-min.css @@ -0,0 +1 @@ +.pln{color:#000}@media screen{.str{color:#080}.kwd{color:#008}.com{color:#800}.typ{color:#606}.lit{color:#066}.pun,.opn,.clo{color:#660}.tag{color:#008}.atn{color:#606}.atv{color:#080}.dec,.var{color:#606}.fun{color:red}}@media print,projection{.str{color:#060}.kwd{color:#006;font-weight:bold}.com{color:#600;font-style:italic}.typ{color:#404;font-weight:bold}.lit{color:#044}.pun,.opn,.clo{color:#440}.tag{color:#006;font-weight:bold}.atn{color:#404}.atv{color:#060}}pre.prettyprint{padding:2px;border:1px solid #888}ol.linenums{margin-top:0;margin-bottom:0}li.L0,li.L1,li.L2,li.L3,li.L5,li.L6,li.L7,li.L8{list-style-type:none}li.L1,li.L3,li.L5,li.L7,li.L9{background:#eee} \ No newline at end of file diff --git a/doc/assets/vendor/prettify/prettify-min.js b/doc/assets/vendor/prettify/prettify-min.js new file mode 100644 index 000000000..4845d05d4 --- /dev/null +++ b/doc/assets/vendor/prettify/prettify-min.js @@ -0,0 +1 @@ +window.PR_SHOULD_USE_CONTINUATION=true;var prettyPrintOne;var prettyPrint;(function(){var O=window;var j=["break,continue,do,else,for,if,return,while"];var v=[j,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"];var q=[v,"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"];var m=[q,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"];var y=[q,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"];var T=[y,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,let,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var,virtual,where"];var s="all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,throw,true,try,unless,until,when,while,yes";var x=[q,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"];var t="caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END";var J=[j,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"];var g=[j,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"];var I=[j,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"];var B=[m,T,x,t+J,g,I];var f=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)\b/;var D="str";var A="kwd";var k="com";var Q="typ";var H="lit";var M="pun";var G="pln";var n="tag";var F="dec";var K="src";var R="atn";var o="atv";var P="nocode";var N="(?:^^\\.?|[+-]|[!=]=?=?|\\#|%=?|&&?=?|\\(|\\*=?|[+\\-]=|->|\\/=?|::?|<>?>?=?|,|;|\\?|@|\\[|~|{|\\^\\^?=?|\\|\\|?=?|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\\s*";function l(ab){var af=0;var U=false;var ae=false;for(var X=0,W=ab.length;X122)){if(!(am<65||ai>90)){ah.push([Math.max(65,ai)|32,Math.min(am,90)|32])}if(!(am<97||ai>122)){ah.push([Math.max(97,ai)&~32,Math.min(am,122)&~32])}}}}ah.sort(function(aw,av){return(aw[0]-av[0])||(av[1]-aw[1])});var ak=[];var aq=[];for(var at=0;atau[0]){if(au[1]+1>au[0]){ao.push("-")}ao.push(V(au[1]))}}ao.push("]");return ao.join("")}function Y(an){var al=an.source.match(new RegExp("(?:\\[(?:[^\\x5C\\x5D]|\\\\[\\s\\S])*\\]|\\\\u[A-Fa-f0-9]{4}|\\\\x[A-Fa-f0-9]{2}|\\\\[0-9]+|\\\\[^ux0-9]|\\(\\?[:!=]|[\\(\\)\\^]|[^\\x5B\\x5C\\(\\)\\^]+)","g"));var aj=al.length;var ap=[];for(var am=0,ao=0;am=2&&ak==="["){al[am]=Z(ai)}else{if(ak!=="\\"){al[am]=ai.replace(/[a-zA-Z]/g,function(aq){var ar=aq.charCodeAt(0);return"["+String.fromCharCode(ar&~32,ar|32)+"]"})}}}}return al.join("")}var ac=[];for(var X=0,W=ab.length;X=0;){U[ae.charAt(ag)]=aa}}var ah=aa[1];var ac=""+ah;if(!ai.hasOwnProperty(ac)){aj.push(ah);ai[ac]=null}}aj.push(/[\0-\uffff]/);X=l(aj)})();var Z=V.length;var Y=function(aj){var ab=aj.sourceCode,aa=aj.basePos;var af=[aa,G];var ah=0;var ap=ab.match(X)||[];var al={};for(var ag=0,at=ap.length;ag=5&&"lang-"===ar.substring(0,5);if(ao&&!(ak&&typeof ak[1]==="string")){ao=false;ar=K}if(!ao){al[ai]=ar}}var ad=ah;ah+=ai.length;if(!ao){af.push(aa+ad,ar)}else{var an=ak[1];var am=ai.indexOf(an);var ae=am+an.length;if(ak[2]){ae=ai.length-ak[2].length;am=ae-an.length}var au=ar.substring(5);C(aa+ad,ai.substring(0,am),Y,af);C(aa+ad+am,an,r(au,an),af);C(aa+ad+ae,ai.substring(ae),Y,af)}}aj.decorations=af};return Y}function i(V){var Y=[],U=[];if(V.tripleQuotedStrings){Y.push([D,/^(?:\'\'\'(?:[^\'\\]|\\[\s\S]|\'{1,2}(?=[^\']))*(?:\'\'\'|$)|\"\"\"(?:[^\"\\]|\\[\s\S]|\"{1,2}(?=[^\"]))*(?:\"\"\"|$)|\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$))/,null,"'\""])}else{if(V.multiLineStrings){Y.push([D,/^(?:\'(?:[^\\\']|\\[\s\S])*(?:\'|$)|\"(?:[^\\\"]|\\[\s\S])*(?:\"|$)|\`(?:[^\\\`]|\\[\s\S])*(?:\`|$))/,null,"'\"`"])}else{Y.push([D,/^(?:\'(?:[^\\\'\r\n]|\\.)*(?:\'|$)|\"(?:[^\\\"\r\n]|\\.)*(?:\"|$))/,null,"\"'"])}}if(V.verbatimStrings){U.push([D,/^@\"(?:[^\"]|\"\")*(?:\"|$)/,null])}var ab=V.hashComments;if(ab){if(V.cStyleComments){if(ab>1){Y.push([k,/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,null,"#"])}else{Y.push([k,/^#(?:(?:define|e(?:l|nd)if|else|error|ifn?def|include|line|pragma|undef|warning)\b|[^\r\n]*)/,null,"#"])}U.push([D,/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h(?:h|pp|\+\+)?|[a-z]\w*)>/,null])}else{Y.push([k,/^#[^\r\n]*/,null,"#"])}}if(V.cStyleComments){U.push([k,/^\/\/[^\r\n]*/,null]);U.push([k,/^\/\*[\s\S]*?(?:\*\/|$)/,null])}if(V.regexLiterals){var aa=("/(?=[^/*])(?:[^/\\x5B\\x5C]|\\x5C[\\s\\S]|\\x5B(?:[^\\x5C\\x5D]|\\x5C[\\s\\S])*(?:\\x5D|$))+/");U.push(["lang-regex",new RegExp("^"+N+"("+aa+")")])}var X=V.types;if(X){U.push([Q,X])}var W=(""+V.keywords).replace(/^ | $/g,"");if(W.length){U.push([A,new RegExp("^(?:"+W.replace(/[\s,]+/g,"|")+")\\b"),null])}Y.push([G,/^\s+/,null," \r\n\t\xA0"]);var Z=/^.[^\s\w\.$@\'\"\`\/\\]*/;U.push([H,/^@[a-z_$][a-z_$@0-9]*/i,null],[Q,/^(?:[@_]?[A-Z]+[a-z][A-Za-z_$@0-9]*|\w+_t\b)/,null],[G,/^[a-z_$][a-z_$@0-9]*/i,null],[H,new RegExp("^(?:0x[a-f0-9]+|(?:\\d(?:_\\d+)*\\d*(?:\\.\\d*)?|\\.\\d\\+)(?:e[+\\-]?\\d+)?)[a-z]*","i"),null,"0123456789"],[G,/^\\[\s\S]?/,null],[M,Z,null]);return h(Y,U)}var L=i({keywords:B,hashComments:true,cStyleComments:true,multiLineStrings:true,regexLiterals:true});function S(W,ah,aa){var V=/(?:^|\s)nocode(?:\s|$)/;var ac=/\r\n?|\n/;var ad=W.ownerDocument;var ag=ad.createElement("li");while(W.firstChild){ag.appendChild(W.firstChild)}var X=[ag];function af(am){switch(am.nodeType){case 1:if(V.test(am.className)){break}if("br"===am.nodeName){ae(am);if(am.parentNode){am.parentNode.removeChild(am)}}else{for(var ao=am.firstChild;ao;ao=ao.nextSibling){af(ao)}}break;case 3:case 4:if(aa){var an=am.nodeValue;var ak=an.match(ac);if(ak){var aj=an.substring(0,ak.index);am.nodeValue=aj;var ai=an.substring(ak.index+ak[0].length);if(ai){var al=am.parentNode;al.insertBefore(ad.createTextNode(ai),am.nextSibling)}ae(am);if(!aj){am.parentNode.removeChild(am)}}}break}}function ae(al){while(!al.nextSibling){al=al.parentNode;if(!al){return}}function aj(am,at){var ar=at?am.cloneNode(false):am;var ap=am.parentNode;if(ap){var aq=aj(ap,1);var ao=am.nextSibling;aq.appendChild(ar);for(var an=ao;an;an=ao){ao=an.nextSibling;aq.appendChild(an)}}return ar}var ai=aj(al.nextSibling,0);for(var ak;(ak=ai.parentNode)&&ak.nodeType===1;){ai=ak}X.push(ai)}for(var Z=0;Z=U){aj+=2}if(Y>=ar){ac+=2}}}finally{if(au){au.style.display=ak}}}var u={};function d(W,X){for(var U=X.length;--U>=0;){var V=X[U];if(!u.hasOwnProperty(V)){u[V]=W}else{if(O.console){console.warn("cannot override language handler %s",V)}}}}function r(V,U){if(!(V&&u.hasOwnProperty(V))){V=/^\s*]*(?:>|$)/],[k,/^<\!--[\s\S]*?(?:-\->|$)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],[M,/^(?:<[%?]|[%?]>)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),["default-markup","htm","html","mxml","xhtml","xml","xsl"]);d(h([[G,/^[\s]+/,null," \t\r\n"],[o,/^(?:\"[^\"]*\"?|\'[^\']*\'?)/,null,"\"'"]],[[n,/^^<\/?[a-z](?:[\w.:-]*\w)?|\/?>$/i],[R,/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^>\'\"\s]*(?:[^>\'\"\s\/]|\/(?=\s)))/],[M,/^[=<>\/]+/],["lang-js",/^on\w+\s*=\s*\"([^\"]+)\"/i],["lang-js",/^on\w+\s*=\s*\'([^\']+)\'/i],["lang-js",/^on\w+\s*=\s*([^\"\'>\s]+)/i],["lang-css",/^style\s*=\s*\"([^\"]+)\"/i],["lang-css",/^style\s*=\s*\'([^\']+)\'/i],["lang-css",/^style\s*=\s*([^\"\'>\s]+)/i]]),["in.tag"]);d(h([],[[o,/^[\s\S]+/]]),["uq.val"]);d(i({keywords:m,hashComments:true,cStyleComments:true,types:f}),["c","cc","cpp","cxx","cyc","m"]);d(i({keywords:"null,true,false"}),["json"]);d(i({keywords:T,hashComments:true,cStyleComments:true,verbatimStrings:true,types:f}),["cs"]);d(i({keywords:y,cStyleComments:true}),["java"]);d(i({keywords:I,hashComments:true,multiLineStrings:true}),["bsh","csh","sh"]);d(i({keywords:J,hashComments:true,multiLineStrings:true,tripleQuotedStrings:true}),["cv","py"]);d(i({keywords:t,hashComments:true,multiLineStrings:true,regexLiterals:true}),["perl","pl","pm"]);d(i({keywords:g,hashComments:true,multiLineStrings:true,regexLiterals:true}),["rb"]);d(i({keywords:x,cStyleComments:true,regexLiterals:true}),["js"]);d(i({keywords:s,hashComments:3,cStyleComments:true,multilineStrings:true,tripleQuotedStrings:true,regexLiterals:true}),["coffee"]);d(h([],[[D,/^[\s\S]+/]]),["regex"]);function e(X){var W=X.langExtension;try{var U=b(X.sourceNode,X.pre);var V=U.sourceCode;X.sourceCode=V;X.spans=U.spans;X.basePos=0;r(W,V)(X);E(X)}catch(Y){if(O.console){console.log(Y&&Y.stack?Y.stack:Y)}}}function z(Y,X,W){var U=document.createElement("pre");U.innerHTML=Y;if(W){S(U,W,true)}var V={langExtension:X,numberLines:W,sourceNode:U,pre:1};e(V);return U.innerHTML}function c(aj){function ab(al){return document.getElementsByTagName(al)}var ah=[ab("pre"),ab("code"),ab("xmp")];var V=[];for(var ae=0;ae]*(?:>|$)/],[PR.PR_COMMENT,/^<\!--[\s\S]*?(?:-\->|$)/],[PR.PR_PUNCTUATION,/^(?:<[%?]|[%?]>)/],["lang-",/^<\?([\s\S]+?)(?:\?>|$)/],["lang-",/^<%([\s\S]+?)(?:%>|$)/],["lang-",/^]*>([\s\S]+?)<\/xmp\b[^>]*>/i],["lang-handlebars",/^]*type\s*=\s*['"]?text\/x-handlebars-template['"]?\b[^>]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-js",/^]*>([\s\S]*?)(<\/script\b[^>]*>)/i],["lang-css",/^]*>([\s\S]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i],[PR.PR_DECLARATION,/^{{[#^>/]?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{&?\s*[\w.][^}]*}}/],[PR.PR_DECLARATION,/^{{{>?\s*[\w.][^}]*}}}/],[PR.PR_COMMENT,/^{{![^}]*}}/]]),["handlebars","hbs"]);PR.registerLangHandler(PR.createSimpleLexer([[PR.PR_PLAIN,/^[ \t\r\n\f]+/,null," \t\r\n\f"]],[[PR.PR_STRING,/^\"(?:[^\n\r\f\\\"]|\\(?:\r\n?|\n|\f)|\\[\s\S])*\"/,null],[PR.PR_STRING,/^\'(?:[^\n\r\f\\\']|\\(?:\r\n?|\n|\f)|\\[\s\S])*\'/,null],["lang-css-str",/^url\(([^\)\"\']*)\)/i],[PR.PR_KEYWORD,/^(?:url|rgb|\!important|@import|@page|@media|@charset|inherit)(?=[^\-\w]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|(?:\\[0-9a-f]+ ?))(?:[_a-z0-9\-]|\\(?:\\[0-9a-f]+ ?))*)\s*:/i],[PR.PR_COMMENT,/^\/\*[^*]*\*+(?:[^\/*][^*]*\*+)*\//],[PR.PR_COMMENT,/^(?:)/],[PR.PR_LITERAL,/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],[PR.PR_LITERAL,/^#(?:[0-9a-f]{3}){1,2}/i],[PR.PR_PLAIN,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i],[PR.PR_PUNCTUATION,/^[^\s\w\'\"]+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_KEYWORD,/^-?(?:[_a-z]|(?:\\[\da-f]+ ?))(?:[_a-z\d\-]|\\(?:\\[\da-f]+ ?))*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[[PR.PR_STRING,/^[^\)\"\']+/]]),["css-str"]); \ No newline at end of file diff --git a/doc/classes/LGraph.html b/doc/classes/LGraph.html new file mode 100644 index 000000000..9f8bfb531 --- /dev/null +++ b/doc/classes/LGraph.html @@ -0,0 +1,2056 @@ + + + + + LGraph + + + + + + + + +
    +
    +
    + +

    + +
    +
    + API Docs for: +
    +
    +
    + +
    + +
    +
    +
    + Show: + + + + + + + +
    + + +
    +
    +
    +

    LGraph Class

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

    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:302 + +

    + + + + + +
    + +
    + +
    + + + + + + +
    + +
    + + +
    + + +
    +
    +

    Item Index

    + + +
    +

    Methods

    + + +
    + + + + + + + +
    + + +
    +

    Methods

    + + +
    +

    add

    + + +
    + (
      + +
    • + + node + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Adds a new node instasnce to this graph

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + node + LGraphNode + + + + +
      +

      the instance of the node

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

    clear

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

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

    + + + + + +
    + +
    +

    Removes all nodes from this graph

    + +
    + + + + + + +
    + + +
    +

    findNodesByName

    + + +
    + (
      + +
    • + + name + +
    • + +
    ) +
    + + + + + Array + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Returns a list of nodes that matches a name

    + +
    + + +
    +

    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:721 + +

    + + + + + +
    + +
    +

    Returns a list of nodes that matches a type

    + +
    + + +
    +

    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:575 + +

    + + + + + +
    + +
    +

    Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct +if the nodes are using graphical actions

    + +
    + + + + +
    +

    Returns:

    + +
    + + + Number: + +

    number of milliseconds it took the last cycle

    + + +
    +
    + + + +
    + + +
    +

    getFixedTime

    + + + () + + + + + Number + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant

    + +
    + + + + +
    +

    Returns:

    + +
    + + + Number: + +

    number of milliseconds the graph has been running

    + + +
    +
    + + + +
    + + +
    +

    getNodeById

    + + +
    + (
      + +
    • + + id + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Returns a node by its id.

    + +
    + + +
    +

    Parameters:

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

    getNodeOnPos

    + + +
    + (
      + +
    • + + x + +
    • + +
    • + + y + +
    • + +
    • + + nodes_list + +
    • + +
    ) +
    + + + + + Array + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Returns the top-most node in this position of the canvas

    + +
    + + +
    +

    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:553 + +

    + + + + + +
    + +
    +

    Returns the amount of time the graph has been running in milliseconds

    + +
    + + + + +
    +

    Returns:

    + +
    + + + Number: + +

    number of milliseconds the graph has been running

    + + +
    +
    + + + +
    + + +
    +

    remove

    + + +
    + (
      + +
    • + + node + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Removes a node from the graph

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + node + LGraphNode + + + + +
      +

      the instance of the node

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

    runStep

    + + +
    + (
      + +
    • + + num + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Run N steps (cycles) of the graph

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + num + Number + + + + +
      +

      number of steps to run, default is 1

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

    sendEventToAllNodes

    + + +
    + (
      + +
    • + + eventname + +
    • + +
    • + + param + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Sends an event to all the nodes, useful to trigger stuff

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + eventname + String + + + + +
      +

      the name of the event

      + +
      + + +
    • + +
    • + + param + Object + + + + +
      +

      an object containing the info

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

    serialize

    + + + () + + + + + String + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Creates a JSON String containing all the info about this graph

    + +
    + + + + +
    +

    Returns:

    + +
    + + + String: + +

    value of the node

    + + +
    +
    + + + +
    + + +
    +

    setInputData

    + + +
    + (
      + +
    • + + name + +
    • + +
    • + + value + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Assigns a value to all the nodes that matches this name. This is used to create global variables of the node that +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:789 + +

    + + + + + +
    + +
    +

    Returns the value of the first node with this name. This is used to access global variables of the graph from the outside

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + name + String + + + + +
      +

      the name of the node

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

    Returns:

    + +
    + + + : + +

    value of the node

    + + +
    +
    + + + +
    + + +
    +

    start

    + + +
    + (
      + +
    • + + interval + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Starts running this graph every interval milliseconds.

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + interval + Number + + + + +
      +

      amount of milliseconds between executions, default is 1

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

    stop

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

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

    + + + + + +
    + +
    +

    Stops the execution loop of the graph

    + +
    + + + + + + +
    + + +
    +

    unserialize

    + + +
    + (
      + +
    • + + str + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Configure a graph from a JSON string

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + str + String + + + + +
      +

      configure a graph from a JSON string

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

    updateExecutionOrder

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

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

    + + + + + +
    + +
    +

    Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than +nodes with only inputs.

    + +
    + + + + + + +
    + + +
    + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + diff --git a/doc/classes/LGraphNode.html b/doc/classes/LGraphNode.html new file mode 100644 index 000000000..ea04b3c2e --- /dev/null +++ b/doc/classes/LGraphNode.html @@ -0,0 +1,163 @@ + + + + + LGraphNode + + + + + + + + +
    +
    +
    + +

    + +
    +
    + API Docs for: +
    +
    +
    + +
    + +
    +
    +
    + Show: + + + + + + + +
    + + +
    +
    +
    +

    LGraphNode Class

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

    Base Class for all the node type classes

    + +
    + + + +
    + + +
    +
    +

    Item Index

    + + + + + + + + +
    + + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + diff --git a/doc/classes/LiteGraph.html b/doc/classes/LiteGraph.html new file mode 100644 index 000000000..f7132dbab --- /dev/null +++ b/doc/classes/LiteGraph.html @@ -0,0 +1,802 @@ + + + + + LiteGraph + + + + + + + + +
    +
    +
    + +

    + +
    +
    + API Docs for: +
    +
    +
    + +
    + +
    +
    +
    + Show: + + + + + + + +
    + + +
    +
    +
    +

    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

    + + +
    + + + + + + + +
    + + +
    +

    Methods

    + + +
    +

    createNode

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

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

    + + + + + +
    + +
    +

    Create a node of a given type with a name. The node is not attached to any graph yet.

    + +
    + + +
    +

    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:143 + +

    + + + + + +
    + +
    +

    Returns a registered node type with a given name

    + +
    + + +
    +

    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:156 + +

    + + + + + +
    + +
    +

    Returns a list of node types matching one category

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + category + String + + + + +
      +

      category name

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

    Returns:

    + +
    + + + Array: + +

    array with all the node classes

    + + +
    +
    + + + +
    + + +
    +

    getNodeTypesCategories

    + + + () + + + + + Array + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Returns a list with all the node type categories

    + +
    + + + + +
    +

    Returns:

    + +
    + + + Array: + +

    array with all the names of the categories

    + + +
    +
    + + + +
    + + +
    +

    registerNodeType

    + + +
    + (
      + +
    • + + type + +
    • + +
    • + + base_class + +
    • + +
    ) +
    + + + + + + + + + + + + + + + + +
    + + + +

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

    + + + + + +
    + +
    +

    Register a node class so it can be listed when the user wants to create a new one

    + +
    + + +
    +

    Parameters:

    + +
      + +
    • + + type + String + + + + +
      +

      name of the node and path

      + +
      + + +
    • + +
    • + + base_class + Class + + + + +
      +

      class containing the structure of a node

      + +
      + + +
    • + +
    +
    + + + + + +
    + + +
    + + + + + + + +
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + diff --git a/doc/classes/index.html b/doc/classes/index.html new file mode 100644 index 000000000..487fe15b2 --- /dev/null +++ b/doc/classes/index.html @@ -0,0 +1,10 @@ + + + + Redirector + + + + Click here to redirect + + diff --git a/doc/data.json b/doc/data.json new file mode 100644 index 000000000..cfce2454f --- /dev/null +++ b/doc/data.json @@ -0,0 +1,450 @@ +{ + "project": {}, + "files": { + "../src/litegraph.js": { + "name": "../src/litegraph.js", + "modules": {}, + "classes": { + "LiteGraph": 1, + "LGraph": 1, + "LGraphNode": 1 + }, + "fors": {}, + "namespaces": {} + } + }, + "modules": {}, + "classes": { + "LiteGraph": { + "name": "LiteGraph", + "shortname": "LiteGraph", + "classitems": [], + "plugins": [], + "extensions": [], + "plugin_for": [], + "extension_for": [], + "file": "../src/litegraph.js", + "line": 6, + "description": "The Global Scope. It contains all the registered node classes.", + "is_constructor": 1 + }, + "LGraph": { + "name": "LGraph", + "shortname": "LGraph", + "classitems": [], + "plugins": [], + "extensions": [], + "plugin_for": [], + "extension_for": [], + "file": "../src/litegraph.js", + "line": 302, + "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 + }, + "LGraphNode": { + "name": "LGraphNode", + "shortname": "LGraphNode", + "classitems": [], + "plugins": [], + "extensions": [], + "plugin_for": [], + "extension_for": [], + "file": "../src/litegraph.js", + "line": 947, + "description": "Base Class for all the node type classes", + "params": [ + { + "name": "name", + "description": "a name for the node", + "type": "String" + } + ] + } + }, + "classitems": [ + { + "file": "../src/litegraph.js", + "line": 33, + "description": "Register a node class so it can be listed when the user wants to create a new one", + "itemtype": "method", + "name": "registerNodeType", + "params": [ + { + "name": "type", + "description": "name of the node and path", + "type": "String" + }, + { + "name": "base_class", + "description": "class containing the structure of a node", + "type": "Class" + } + ], + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 67, + "description": "Create a node of a given type with a name. The node is not attached to any graph yet.", + "itemtype": "method", + "name": "createNode", + "params": [ + { + "name": "type", + "description": "full name of the node class. p.e. \"math/sin\"", + "type": "String" + }, + { + "name": "name", + "description": "a name to distinguish from other nodes", + "type": "String" + }, + { + "name": "options", + "description": "to set options", + "type": "Object" + } + ], + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 143, + "description": "Returns a registered node type with a given name", + "itemtype": "method", + "name": "getNodeType", + "params": [ + { + "name": "type", + "description": "full name of the node class. p.e. \"math/sin\"", + "type": "String" + } + ], + "return": { + "description": "the node class", + "type": "Class" + }, + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 156, + "description": "Returns a list of node types matching one category", + "itemtype": "method", + "name": "getNodeType", + "params": [ + { + "name": "category", + "description": "category name", + "type": "String" + } + ], + "return": { + "description": "array with all the node classes", + "type": "Array" + }, + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 178, + "description": "Returns a list with all the node type categories", + "itemtype": "method", + "name": "getNodeTypesCategories", + "return": { + "description": "array with all the names of the categories", + "type": "Array" + }, + "class": "LiteGraph" + }, + { + "file": "../src/litegraph.js", + "line": 321, + "description": "Removes all nodes from this graph", + "itemtype": "method", + "name": "clear", + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 364, + "description": "Starts running this graph every interval milliseconds.", + "itemtype": "method", + "name": "start", + "params": [ + { + "name": "interval", + "description": "amount of milliseconds between executions, default is 1", + "type": "Number" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 391, + "description": "Stops the execution loop of the graph", + "itemtype": "method", + "name": "stop", + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 413, + "description": "Run N steps (cycles) of the graph", + "itemtype": "method", + "name": "runStep", + "params": [ + { + "name": "num", + "description": "number of steps to run, default is 1", + "type": "Number" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 457, + "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", + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 553, + "description": "Returns the amount of time the graph has been running in milliseconds", + "itemtype": "method", + "name": "getTime", + "return": { + "description": "number of milliseconds the graph has been running", + "type": "Number" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 564, + "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", + "return": { + "description": "number of milliseconds the graph has been running", + "type": "Number" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 575, + "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", + "return": { + "description": "number of milliseconds it took the last cycle", + "type": "Number" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 587, + "description": "Sends an event to all the nodes, useful to trigger stuff", + "itemtype": "method", + "name": "sendEventToAllNodes", + "params": [ + { + "name": "eventname", + "description": "the name of the event", + "type": "String" + }, + { + "name": "param", + "description": "an object containing the info", + "type": "Object" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 602, + "description": "Adds a new node instasnce to this graph", + "itemtype": "method", + "name": "add", + "params": [ + { + "name": "node", + "description": "the instance of the node", + "type": "LGraphNode" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 647, + "description": "Removes a node from the graph", + "itemtype": "method", + "name": "remove", + "params": [ + { + "name": "node", + "description": "the instance of the node", + "type": "LGraphNode" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 708, + "description": "Returns a node by its id.", + "itemtype": "method", + "name": "getNodeById", + "params": [ + { + "name": "id", + "description": "", + "type": "String" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 721, + "description": "Returns a list of nodes that matches a type", + "itemtype": "method", + "name": "findNodesByType", + "params": [ + { + "name": "type", + "description": "the name of the node type", + "type": "String" + } + ], + "return": { + "description": "a list with all the nodes of this type", + "type": "Array" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 737, + "description": "Returns a list of nodes that matches a name", + "itemtype": "method", + "name": "findNodesByName", + "params": [ + { + "name": "name", + "description": "the name of the node to search", + "type": "String" + } + ], + "return": { + "description": "a list with all the nodes with this name", + "type": "Array" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 753, + "description": "Returns the top-most node in this position of the canvas", + "itemtype": "method", + "name": "getNodeOnPos", + "params": [ + { + "name": "x", + "description": "the x coordinate in canvas space", + "type": "Number" + }, + { + "name": "y", + "description": "the y coordinate in canvas space", + "type": "Number" + }, + { + "name": "nodes_list", + "description": "a list with all the nodes to search from, by default is all the nodes in the graph", + "type": "Array" + } + ], + "return": { + "description": "a list with all the nodes that intersect this coordinate", + "type": "Array" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 774, + "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", + "params": [ + { + "name": "name", + "description": "the name of the node", + "type": "String" + }, + { + "name": "value", + "description": "value to assign to this node", + "type": "*" + } + ], + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 789, + "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", + "params": [ + { + "name": "name", + "description": "the name of the node", + "type": "String" + } + ], + "return": { + "description": "value of the node", + "type": "*" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 843, + "description": "Creates a JSON String containing all the info about this graph", + "itemtype": "method", + "name": "serialize", + "return": { + "description": "value of the node", + "type": "String" + }, + "class": "LGraph" + }, + { + "file": "../src/litegraph.js", + "line": 869, + "description": "Configure a graph from a JSON string", + "itemtype": "method", + "name": "unserialize", + "params": [ + { + "name": "str", + "description": "configure a graph from a JSON string", + "type": "String" + } + ], + "class": "LGraph" + } + ], + "warnings": [] +} \ No newline at end of file diff --git a/doc/files/.._src_litegraph.js.html b/doc/files/.._src_litegraph.js.html new file mode 100644 index 000000000..49442c502 --- /dev/null +++ b/doc/files/.._src_litegraph.js.html @@ -0,0 +1,3894 @@ + + + + + ../src/litegraph.js + + + + + + + + +
    +
    +
    + +

    + +
    +
    + API Docs for: +
    +
    +
    + +
    + +
    +
    +
    + Show: + + + + + + + +
    + + +
    +
    +
    +

    File: ../src/litegraph.js

    + +
    +
    +
    +// *************************************************************
    +//   LiteGraph CLASS                                     *******
    +// *************************************************************
    +
    +/**
    +* The Global Scope. It contains all the registered node classes.
    +*
    +* @class LiteGraph
    +* @constructor
    +*/
    +
    +var LiteGraph = {
    +
    +	NODE_TITLE_HEIGHT: 16,
    +	NODE_SLOT_HEIGHT: 15,
    +	NODE_WIDTH: 140,
    +	NODE_MIN_WIDTH: 50,
    +	NODE_COLLAPSED_RADIUS: 10,
    +	CANVAS_GRID_SIZE: 10,
    +	NODE_DEFAULT_COLOR: "#888",
    +	NODE_DEFAULT_BGCOLOR: "#333",
    +	NODE_DEFAULT_BOXCOLOR: "#AEF",
    +	NODE_DEFAULT_SHAPE: "box",
    +	MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops
    +	DEFAULT_POSITION: [100,100],
    +	node_images_path: "",
    +
    +	debug: false,
    +	registered_node_types: {},
    +	graphs: [],
    +
    +	/**
    +	* Register a node class so it can be listed when the user wants to create a new one
    +	* @method registerNodeType
    +	* @param {String} type name of the node and path
    +	* @param {Class} base_class class containing the structure of a node
    +	*/
    +
    +	registerNodeType: function(type, base_class)
    +	{
    +		var title = type;
    +		if(base_class.prototype && base_class.prototype.title)
    +			title = base_class.prototype.title;
    +		else if(base_class.title)
    +			title = base_class.title;
    +
    +		base_class.type = type;
    +		if(LiteGraph.debug)
    +			console.log("Node registered: " + type);
    +
    +		var categories = type.split("/");
    +
    +		var pos = type.lastIndexOf("/");
    +		base_class.category = type.substr(0,pos);
    +		//info.name = name.substr(pos+1,name.length - pos);
    +
    +		//inheritance
    +		if(base_class.prototype) //is a class
    +			for(var i in LGraphNode.prototype)
    +				if(!base_class.prototype[i])
    +					base_class.prototype[i] = LGraphNode.prototype[i];
    +
    +		this.registered_node_types[type] = base_class;
    +	},
    +
    +	/**
    +	* Create a node of a given type with a name. The node is not attached to any graph yet.
    +	* @method createNode
    +	* @param {String} type full name of the node class. p.e. "math/sin"
    +	* @param {String} name a name to distinguish from other nodes
    +	* @param {Object} options to set options
    +	*/
    +
    +	createNode: function(type,name, options)
    +	{
    +		var base_class = this.registered_node_types[type];
    +		if (!base_class)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("GraphNode type \"" + type + "\" not registered.");
    +			return null;
    +		}
    +
    +		var prototype = base_class.prototype || base_class;
    +
    +		name = name || prototype.title || base_class.title || type;
    +
    +		var node = null;
    +		if (base_class.prototype) //is a class
    +		{
    +			node = new base_class(name);
    +		}
    +		else
    +		{
    +			node = new LGraphNode(name);
    +			node.inputs = [];
    +			node.outputs = [];
    +
    +			//add inputs and outputs
    +			for (var i in prototype)
    +			{
    +				if(i == "inputs")
    +				{
    +					for(var j in prototype[i])
    +						node.addInput( prototype[i][j][0],prototype[i][j][1], prototype[i][j][2] );
    +				}
    +				else if(i == "outputs")
    +				{
    +					for(var j in prototype[i])
    +						node.addOutput( prototype[i][j][0],prototype[i][j][1], prototype[i][j][2] );
    +				}
    +				else
    +				{
    +					if( prototype[i].concat ) //array
    +						node[i] = prototype[i].concat();
    +					else if (typeof(prototype[i]) == 'object')
    +						node[i] = jQuery.extend({}, prototype[i]);
    +					else
    +						node[i] = prototype[i];
    +				}
    +			}
    +			//set size
    +			if(base_class.size) node.size = base_class.size.concat(); //save size
    +		}
    +
    +		node.type = type;
    +		if(!node.name) node.name = name;
    +		if(!node.flags) node.flags = {};
    +		if(!node.size) node.size = node.computeSize();
    +		if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
    +
    +		//extra options
    +		if(options)
    +		{
    +			for(var i in options)
    +				node[i] = options[i];								
    +		}
    +
    +		return node;
    +	},
    +
    +	/**
    +	* Returns a registered node type with a given name
    +	* @method getNodeType
    +	* @param {String} type full name of the node class. p.e. "math/sin"
    +	* @return {Class} the node class
    +	*/
    +
    +	getNodeType: function(type)
    +	{
    +		return this.registered_node_types[type];
    +	},
    +
    +
    +	/**
    +	* Returns a list of node types matching one category
    +	* @method getNodeType
    +	* @param {String} category category name
    +	* @return {Array} array with all the node classes
    +	*/
    +
    +	getNodeTypesInCategory: function(category)
    +	{
    +		var r = [];
    +		for(var i in this.registered_node_types)
    +			if(category == "")
    +			{
    +				if (this.registered_node_types[i].category == null)
    +					r.push(this.registered_node_types[i]);
    +			}
    +			else if (this.registered_node_types[i].category == category)
    +				r.push(this.registered_node_types[i]);
    +
    +		return r;
    +	},
    +
    +	/**
    +	* Returns a list with all the node type categories
    +	* @method getNodeTypesCategories
    +	* @return {Array} array with all the names of the categories 
    +	*/
    +
    +	getNodeTypesCategories: function()
    +	{
    +		var categories = {"":1};
    +		for(var i in this.registered_node_types)
    +			if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list)
    +				categories[ this.registered_node_types[i].category ] = 1;
    +		var result = [];
    +		for(var i in categories)
    +			result.push(i);
    +		return result;
    +	},
    +
    +	//debug purposes: reloads all the js scripts that matches a wilcard
    +	reloadNodes: function (folder_wildcard)
    +	{
    +		var tmp = document.getElementsByTagName("script");
    +		//weird, this array changes by its own, so we use a copy
    +		var script_files = [];
    +		for(var i in tmp)
    +			script_files.push(tmp[i]);
    +
    +
    +		var docHeadObj = document.getElementsByTagName("head")[0];
    +		folder_wildcard = document.location.href + folder_wildcard;
    +
    +		for(var i in script_files)
    +		{
    +			var src = script_files[i].src;
    +			if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard)
    +				continue;
    +
    +			try
    +			{
    +				if(LiteGraph.debug)
    +					console.log("Reloading: " + src);
    +				var dynamicScript = document.createElement("script");
    +				dynamicScript.type = "text/javascript";
    +				dynamicScript.src = src;
    +				docHeadObj.appendChild(dynamicScript);
    +				docHeadObj.removeChild(script_files[i]);
    +			}
    +			catch (err)
    +			{
    +				if(LiteGraph.throw_errors)
    +					throw err;
    +				if(LiteGraph.debug)
    +					console.log("Error while reloading " + src);
    +			}
    +		}
    +
    +		for (var i in LiteGraph.graphs)
    +		{
    +			for (var j in LiteGraph.graphs[i].nodes)
    +			{
    +				var m = LiteGraph.graphs[i].nodes[j];
    +				var t = LiteGraph.getNodeType(n.type);
    +				if(!t) continue;
    +
    +				for (var k in t)
    +					if( typeof(t[k]) == "function" )
    +						m[k] = t[k];
    +			}
    +		}
    +
    +		if(LiteGraph.debug)
    +			console.log("Nodes reloaded");
    +	}
    +
    +	/*
    +	benchmark: function(mode)
    +	{
    +		mode = mode || "all";
    +
    +		trace("Benchmarking " + mode + "...");
    +		trace("  Num. nodes: " + this.nodes.length );
    +		var links = 0;
    +		for(var i in this.nodes)
    +			for(var j in this.nodes[i].outputs)
    +				if(this.nodes[i].outputs[j].node_id != null)
    +					links++;
    +		trace("  Num. links: " + links );
    +		
    +		var numTimes = 200;
    +		if(mode == "core")
    +			numTimes = 30000;
    +
    +		var start = new Date().getTime();
    +
    +		for(var i = 0; i < numTimes; i++)
    +		{
    +			if(mode == "render")
    +				this.draw(false);
    +			else if(mode == "core")
    +				this.sendEventToAllNodes("onExecute");
    +			else
    +			{
    +				this.sendEventToAllNodes("onExecute");
    +				this.draw(false);
    +			}
    +		}
    +
    +		var elapsed = (new Date().getTime()) - start;
    +		trace("  Time take for  " + numTimes + " iterations: " + (elapsed*0.001).toFixed(3) + " seconds.");
    +		var seconds_per_iteration = (elapsed*0.001)/numTimes;
    +		trace("  Time per iteration:  " + seconds_per_iteration.toFixed( seconds_per_iteration < 0.001 ? 6 : 3) + " seconds");
    +		trace("  Avg FPS: " + (1000/(elapsed/numTimes)).toFixed(3));
    +	}
    +	*/
    +};
    +
    +
    +
    +
    +
    +//*********************************************************************************
    +// LGraph CLASS                                  
    +//*********************************************************************************
    +
    +/**
    +* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
    +*
    +* @class LGraph
    +* @constructor
    +*/
    +
    +function LGraph()
    +{
    +	if (LiteGraph.debug)
    +		console.log("Graph created");
    +	this.canvas = null;
    +	LiteGraph.graphs.push(this);
    +	this.clear();
    +}
    +
    +LGraph.STATUS_STOPPED = 1;
    +LGraph.STATUS_RUNNING = 2;
    +
    +/**
    +* Removes all nodes from this graph
    +* @method clear
    +*/
    +
    +LGraph.prototype.clear = function()
    +{
    +	this.stop();
    +	this.status = LGraph.STATUS_STOPPED;
    +	this.last_node_id = 0;
    +
    +	//nodes
    +	this.nodes = [];
    +	this.nodes_by_id = {};
    +
    +	//links
    +	this.last_link_id = 0;
    +	this.links = {};
    +
    +	//iterations
    +	this.iteration = 0;
    +
    +	this.config = {
    +		canvas_offset: [0,0],
    +		canvas_scale: 1.0
    +	};
    +
    +	//timing
    +	this.globaltime = 0;
    +	this.runningtime = 0;
    +	this.fixedtime =  0;
    +	this.fixedtime_lapse = 0.01;
    +	this.elapsed_time = 0.01;
    +	this.starttime = 0;
    +
    +	this.graph = {};
    +	this.debug = true;
    +
    +	this.change();
    +	if(this.canvas)
    +		this.canvas.clear();
    +}
    +
    +/**
    +* Starts running this graph every interval milliseconds.
    +* @method start
    +* @param {number} interval amount of milliseconds between executions, default is 1
    +*/
    +
    +LGraph.prototype.start = function(interval)
    +{
    +	if(this.status == LGraph.STATUS_RUNNING) return;
    +	this.status = LGraph.STATUS_RUNNING;
    +
    +	if(this.onPlayEvent)
    +		this.onPlayEvent();
    +
    +	this.sendEventToAllNodes("onStart");
    +
    +	//launch
    +	this.starttime = new Date().getTime();
    +	interval = interval || 1;
    +	var that = this;	
    +
    +	this.execution_timer_id = setInterval( function() { 
    +		//execute
    +		that.runStep(1); 
    +	},interval);
    +}
    +
    +/**
    +* Stops the execution loop of the graph
    +* @method stop
    +*/
    +
    +LGraph.prototype.stop = function()
    +{
    +	if(this.status == LGraph.STATUS_STOPPED)
    +		return;
    +
    +	this.status = LGraph.STATUS_STOPPED;
    +
    +	if(this.onStopEvent)
    +		this.onStopEvent();
    +
    +	if(this.execution_timer_id != null)
    +		clearInterval(this.execution_timer_id);
    +	this.execution_timer_id = null;
    +
    +	this.sendEventToAllNodes("onStop");
    +}
    +
    +/**
    +* Run N steps (cycles) of the graph
    +* @method runStep
    +* @param {number} num number of steps to run, default is 1
    +*/
    +
    +LGraph.prototype.runStep = function(num)
    +{
    +	num = num || 1;
    +
    +	var start = new Date().getTime();
    +	this.globaltime = 0.001 * (start - this.starttime);
    +
    +	try
    +	{
    +		for(var i = 0; i < num; i++)
    +		{
    +			this.sendEventToAllNodes("onExecute");
    +			this.fixedtime += this.fixedtime_lapse;
    +			if( this.onExecuteStep )
    +				this.onExecuteStep();
    +		}
    +
    +		if( this.onAfterExecute )
    +			this.onAfterExecute();
    +		this.errors_in_execution = false;
    +	}
    +	catch (err)
    +	{
    +		this.errors_in_execution = true;
    +		if(LiteGraph.throw_errors)
    +			throw err;
    +		if(LiteGraph.debug)
    +			console.log("Error during execution: " + err);
    +		this.stop();
    +	}
    +
    +	var elapsed = (new Date().getTime()) - start;
    +	if (elapsed == 0) elapsed = 1;
    +	this.elapsed_time = 0.001 * elapsed;
    +	this.globaltime += 0.001 * elapsed;
    +	this.iteration += 1;
    +}
    +
    +/**
    +* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
    +* nodes with only inputs.
    +* @method updateExecutionOrder
    +*/
    +	
    +LGraph.prototype.updateExecutionOrder = function()
    +{
    +	this.nodes_in_order = this.computeExecutionOrder();
    +}
    +
    +//This is more internal, it computes the order and returns it
    +LGraph.prototype.computeExecutionOrder = function()
    +{
    +	var L = [];
    +	var S = [];
    +	var M = {};
    +	var visited_links = {}; //to avoid repeating links
    +	var remaining_links = {}; //to a
    +	
    +	//search for the nodes without inputs (starting nodes)
    +	for (var i in this.nodes)
    +	{
    +		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++)
    +				if(n.inputs[j] && n.inputs[j].link != null)
    +					num += 1;
    +
    +		if(num == 0) //is a starting node
    +			S.push(n);
    +		else //num of input links 
    +			remaining_links[n.id] = num;
    +	}
    +
    +	while(true)
    +	{
    +		if(S.length == 0)
    +			break;
    +			
    +		//get an starting node
    +		var n = S.shift();
    +		L.push(n); //add to ordered list
    +		delete M[n.id]; //remove from the pending nodes
    +		
    +		//for every output
    +		if(n.outputs)
    +			for(var i = 0; i < n.outputs.length; i++)
    +			{
    +				var output = n.outputs[i];
    +				//not connected
    +				if(output == null || output.links == null || output.links.length == 0)
    +					continue;
    +
    +				//for every connection
    +				for(var j = 0; j < output.links.length; j++)
    +				{
    +					var link = output.links[j];
    +
    +					//already visited link (ignore it)
    +					if(visited_links[ link[0] ])
    +						continue;
    +
    +					var target_node = this.getNodeById( link[3] );
    +					if(target_node == null)
    +					{
    +						visited_links[ link[0] ] = true;
    +						continue;
    +					}
    +
    +					visited_links[link[0]] = true; //mark as visited
    +					remaining_links[target_node.id] -= 1; //reduce the number of links remaining
    +					if (remaining_links[target_node.id] == 0)
    +						S.push(target_node); //if no more links, then add to Starters array
    +				}
    +			}
    +	}
    +	
    +	//the remaining ones (loops)
    +	for(var i in M)
    +		L.push(M[i]);
    +		
    +	if(L.length != this.nodes.length && LiteGraph.debug)
    +		console.log("something went wrong, nodes missing");
    +
    +	//save order number in the node
    +	for(var i in L)
    +		L[i].order = i;
    +	
    +	return L;
    +}
    +
    +
    +/**
    +* Returns the amount of time the graph has been running in milliseconds
    +* @method getTime
    +* @return {number} number of milliseconds the graph has been running
    +*/
    +
    +LGraph.prototype.getTime = function()
    +{
    +	return this.globaltime;
    +}
    +
    +/**
    +* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant
    +* @method getFixedTime
    +* @return {number} number of milliseconds the graph has been running
    +*/
    +
    +LGraph.prototype.getFixedTime = function()
    +{
    +	return this.fixedtime;
    +}
    +
    +/**
    +* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct
    +* if the nodes are using graphical actions
    +* @method getElapsedTime
    +* @return {number} number of milliseconds it took the last cycle
    +*/
    +
    +LGraph.prototype.getElapsedTime = function()
    +{
    +	return this.elapsed_time;
    +}
    +
    +/**
    +* Sends an event to all the nodes, useful to trigger stuff
    +* @method sendEventToAllNodes
    +* @param {String} eventname the name of the event
    +* @param {Object} param an object containing the info
    +*/
    +
    +LGraph.prototype.sendEventToAllNodes = function(eventname, param)
    +{
    +	var M = this.nodes_in_order ? this.nodes_in_order : this.nodes;
    +	for(var j in M)
    +		if(M[j][eventname])
    +			M[j][eventname](param);
    +}
    +
    +/**
    +* Adds a new node instasnce to this graph
    +* @method add
    +* @param {LGraphNode} node the instance of the node
    +*/
    +
    +LGraph.prototype.add = function(node)
    +{
    +	if(!node || (node.id != -1 && this.nodes_by_id[node.id] != null))
    +		return; //already added
    +
    +	if(this.nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES)
    +		throw("LiteGraph: max number of nodes in a graph reached");
    +
    +	//give him an id
    +	if(node.id == null || node.id == -1)
    +		node.id = this.last_node_id++;
    +
    +	node.graph = this;
    +
    +	this.nodes.push(node);
    +	this.nodes_by_id[node.id] = node;
    +
    +	/*
    +	// rendering stuf... 
    +	if(node.bgImageUrl)
    +		node.bgImage = node.loadImage(node.bgImageUrl);
    +	*/
    +
    +	if(node.onInit)
    +		node.onInit();
    +
    +	if(this.config.align_to_grid)
    +		node.alignToGrid();
    +		
    +	this.updateExecutionOrder();	
    +
    +	if(this.canvas)
    +		this.canvas.dirty_canvas = true;
    +
    +	this.change();
    +
    +	return node; //to chain actions
    +}
    +
    +/**
    +* Removes a node from the graph
    +* @method remove
    +* @param {LGraphNode} node the instance of the node
    +*/
    +
    +LGraph.prototype.remove = function(node)
    +{
    +	if(this.nodes_by_id[node.id] == null)
    +		return; //not found
    +
    +	if(node.ignore_remove) 
    +		return; //cannot be removed
    +
    +	//disconnect inputs
    +	if(node.inputs)
    +		for(var i = 0; i < node.inputs.length; i++)
    +		{
    +			var slot = node.inputs[i];
    +			if(slot.link != null)
    +				node.disconnectInput(i);
    +		}
    +
    +	//disconnect outputs
    +	if(node.outputs)
    +		for(var i = 0; i < node.outputs.length; i++)
    +		{
    +			var slot = node.outputs[i];
    +			if(slot.links != null && slot.links.length)
    +				node.disconnectOutput(i);
    +		}
    +
    +	node.id = -1;
    +
    +	//callback
    +	if(node.onDelete)
    +		node.onDelete();
    +
    +	//remove from environment
    +	if(this.canvas)
    +	{
    +		if(this.canvas.selected_nodes[node.id])
    +			delete this.canvas.selected_nodes[node.id];
    +		if(this.canvas.node_dragged == node)
    +			this.canvas.node_dragged = null;
    +	}
    +
    +	//remove from containers
    +	var pos = this.nodes.indexOf(node);
    +	if(pos != -1)
    +		this.nodes.splice(pos,1);
    +	delete this.nodes_by_id[node.id];
    +
    +	if(this.canvas)
    +		this.canvas.setDirty(true,true);
    +
    +	this.change();
    +
    +	this.updateExecutionOrder();
    +}
    +
    +/**
    +* Returns a node by its id.
    +* @method getNodeById
    +* @param {String} id
    +*/
    +
    +LGraph.prototype.getNodeById = function(id)
    +{
    +	if(id==null) return null;
    +	return this.nodes_by_id[id];
    +}
    +
    +
    +/**
    +* Returns a list of nodes that matches a type
    +* @method findNodesByType
    +* @param {String} type the name of the node type
    +* @return {Array} a list with all the nodes of this type
    +*/
    +
    +LGraph.prototype.findNodesByType = function(type)
    +{
    +	var r = [];
    +	for(var i in this.nodes)
    +		if(this.nodes[i].type == type)
    +			r.push(this.nodes[i]);
    +	return r;
    +}
    +
    +/**
    +* Returns a list of nodes that matches a name
    +* @method findNodesByName
    +* @param {String} name the name of the node to search
    +* @return {Array} a list with all the nodes with this name
    +*/
    +
    +LGraph.prototype.findNodesByName = function(name)
    +{
    +	var result = [];
    +	for (var i in this.nodes)
    +		if(this.nodes[i].name == name)
    +			result.push(this.nodes[i]);
    +	return result;
    +}
    +
    +/**
    +* Returns the top-most node in this position of the canvas
    +* @method getNodeOnPos
    +* @param {number} x the x coordinate in canvas space
    +* @param {number} y the y coordinate in canvas space
    +* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
    +* @return {Array} a list with all the nodes that intersect this coordinate
    +*/
    +
    +LGraph.prototype.getNodeOnPos = function(x,y, nodes_list)
    +{
    +	nodes_list = nodes_list || this.nodes;
    +	for (var i = nodes_list.length - 1; i >= 0; i--)
    +	{
    +		var n = nodes_list[i];
    +		if(n.isPointInsideNode(x,y))
    +			return n;
    +	}
    +	return null;
    +}
    +
    +/**
    +* Assigns a value to all the nodes that matches this name. This is used to create global variables of the node that
    +* can be easily accesed from the outside of the graph
    +* @method setInputData
    +* @param {String} name the name of the node
    +* @param {*} value value to assign to this node
    +*/
    +
    +LGraph.prototype.setInputData = function(name,value)
    +{
    +	var m = this.findNodesByName(name);
    +	for(var i in m)
    +		m[i].setValue(value);
    +}
    +
    +/**
    +* Returns the value of the first node with this name. This is used to access global variables of the graph from the outside
    +* @method setInputData
    +* @param {String} name the name of the node
    +* @return {*} value of the node
    +*/
    +
    +LGraph.prototype.getOutputData = function(name)
    +{
    +	var n = this.findNodesByName(name);
    +	if(n.length)
    +		return m[0].getValue();
    +	return null;
    +}
    +
    +//This feature is not finished yet, is to create graphs where nodes are not executed unless a trigger message is received
    +
    +LGraph.prototype.triggerInput = function(name,value)
    +{
    +	var m = this.findNodesByName(name);
    +	for(var i in m)
    +		m[i].onTrigger(value);
    +}
    +
    +LGraph.prototype.setCallback = function(name,func)
    +{
    +	var m = this.findNodesByName(name);
    +	for(var i in m)
    +		m[i].setTrigger(func);
    +}
    +
    +//**********
    +
    +
    +LGraph.prototype.onConnectionChange = function()
    +{
    +	this.updateExecutionOrder();
    +}
    +
    +LGraph.prototype.isLive = function()
    +{
    +	if(!this.canvas) return false;
    +	return this.canvas.live_mode;
    +}
    +
    +LGraph.prototype.change = function()
    +{
    +	if(LiteGraph.debug)
    +		console.log("Graph changed");
    +	if(this.on_change)
    +		this.on_change(this);
    +}
    +
    +//save and recover app state ***************************************
    +/**
    +* Creates a JSON String containing all the info about this graph
    +* @method serialize
    +* @return {String} value of the node
    +*/
    +LGraph.prototype.serialize = function()
    +{
    +	var nodes_info = [];
    +	for (var i in this.nodes)
    +		nodes_info.push( this.nodes[i].objectivize() );
    +
    +	var data = {
    +		graph: this.graph,
    +
    +		iteration: this.iteration,
    +		frame: this.frame,
    +		last_node_id: this.last_node_id,
    +		last_link_id: this.last_link_id,
    +
    +		config: this.config,
    +		nodes: nodes_info
    +	};
    +
    +	return JSON.stringify(data);
    +}
    +
    +/**
    +* Configure a graph from a JSON string 
    +* @method unserialize
    +* @param {String} str configure a graph from a JSON string
    +*/
    +LGraph.prototype.unserialize = function(str, keep_old)
    +{
    +	if(!keep_old)
    +		this.clear();
    +
    +	var data = JSON.parse(str);
    +	var nodes = data.nodes;
    +
    +	//copy all stored fields
    +	for (var i in data)
    +		this[i] = data[i];
    +
    +	var error = false;
    +
    +	//create nodes
    +	this.nodes = [];
    +	for (var i in nodes)
    +	{
    +		var n_info = nodes[i]; //stored info
    +		var n = LiteGraph.createNode( n_info.type, n_info.name );
    +		if(!n)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("Node not found: " + n_info.type);
    +			error = true;
    +			continue;
    +		}
    +
    +		n.copyFromObject(n_info);
    +		this.add(n);
    +	}
    +
    +	//TODO: dispatch redraw
    +	if(this.canvas)
    +		this.canvas.draw(true,true);
    +
    +	return error;
    +}
    +
    +LGraph.prototype.onNodeTrace = function(node, msg, color)
    +{
    +	if(this.canvas)
    +		this.canvas.onNodeTrace(node,msg,color);
    +}
    +
    +// *************************************************************
    +//   Node CLASS                                          *******
    +// *************************************************************
    +
    +/* flags:
    +		+ skip_title_render
    +		+ clip_area
    +		+ unsafe_execution: not allowed for safe execution
    +
    +	supported callbacks: 
    +		+ onInit: when added to graph
    +		+ onStart:	when starts playing
    +		+ onStop:	when stops playing
    +		+ onDrawForeground
    +		+ onDrawBackground
    +		+ onMouseMove
    +		+ onMouseOver
    +		+ onExecute: execute the node
    +		+ onPropertyChange: 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
    +		+ onClick
    +		+ onDblClick
    +		+ onSerialize
    +		+ onSelected
    +		+ onDeselected
    +*/
    +
    +/**
    +* Base Class for all the node type classes
    +* @class LGraphNode
    +* @param {String} name a name for the node
    +*/
    +
    +function LGraphNode(name)
    +{
    +	this.name = name || "Unnamed";
    +	this.size = [LiteGraph.NODE_WIDTH,60];
    +	this.graph = null;
    +
    +	this.pos = [10,10];
    +	this.id = -1; //not know till not added
    +	this.type = null;
    +
    +	//inputs available: array of inputs
    +	this.inputs = [];
    +	this.outputs = [];
    +	this.connections = [];
    +
    +	//local data
    +	this.data = null; //persistent local data
    +	this.flags = {
    +		//skip_title_render: true,
    +		//unsafe_execution: false,
    +	};
    +}
    +
    +//serialization *************************
    +
    +LGraphNode.prototype.objectivize = function()
    +{
    +	var o = {
    +		id: this.id,
    +		name: this.name,
    +		type: this.type,
    +		pos: this.pos,
    +		size: this.size,
    +		data: this.data,
    +		properties: jQuery.extend({}, this.properties),
    +		flags: jQuery.extend({}, this.flags),
    +		inputs: this.inputs,
    +		outputs: this.outputs
    +	};
    +
    +	if(!o.type)
    +		o.type = this.constructor.type;
    +
    +	if(this.color)
    +		o.color = this.color;
    +	if(this.bgcolor)
    +		o.bgcolor = this.bgcolor;
    +	if(this.boxcolor)
    +		o.boxcolor = this.boxcolor;
    +	if(this.shape)
    +		o.shape = this.shape;
    +
    +	return o;
    +}
    +
    +//reduced version of objectivize: NOT FINISHED
    +LGraphNode.prototype.reducedObjectivize = function()
    +{
    +	var o = this.objectivize();
    +	
    +	var type = LiteGraph.getNodeType(o.type);
    +
    +	if(type.name == o.name)
    +		delete o["name"];
    +
    +	if(type.size && compareObjects(o.size,type.size))
    +		delete o["size"];
    +
    +	if(type.properties && compareObjects(o.properties, type.properties))
    +		delete o["properties"];
    +
    +	return o;
    +}
    +
    +
    +LGraphNode.prototype.serialize = function()
    +{
    +	if(this.onSerialize)
    +		this.onSerialize();
    +	return JSON.stringify( this.reducedObjectivize() );
    +}
    +//LGraphNode.prototype.unserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
    +
    +
    +// Execution *************************
    +
    +LGraphNode.prototype.setOutputData = function(slot,data)
    +{
    +	if(!this.outputs) return;
    +	if(slot > -1 && slot < this.outputs.length && this.outputs[slot] && this.outputs[slot].links != null)
    +	{
    +		for(var i = 0; i < this.outputs[slot].links.length; i++)
    +			this.graph.links[ this.outputs[slot].links[i][0] ] = data;
    +	}
    +}
    +
    +LGraphNode.prototype.getInputData = function(slot)
    +{
    +	if(!this.inputs) return null;
    +	if(slot < this.inputs.length && this.inputs[slot].link != null)
    +		return this.graph.links[ this.inputs[slot].link[0] ];
    +	return null;
    +}
    +
    +LGraphNode.prototype.isInputConnected = function(slot)
    +{
    +	if(!this.inputs) return null;
    +	return (slot < this.inputs.length && this.inputs[slot].link != null);
    +}
    +
    +LGraphNode.prototype.getInputInfo = function(slot)
    +{
    +	if(!this.inputs) return null;
    +	if(slot < this.inputs.length)
    +		return this.inputs[slot];
    +	return null;
    +}
    +
    +
    +LGraphNode.prototype.getOutputInfo = function(slot)
    +{
    +	if(!this.outputs) return null;
    +	if(slot < this.outputs.length)
    +		return this.outputs[slot];
    +	return null;
    +}
    +
    +LGraphNode.prototype.isOutputConnected = function(slot)
    +{
    +	if(!this.outputs) return null;
    +	return (slot < this.outputs.length && this.outputs[slot].links && this.outputs[slot].links.length);
    +}
    +
    +LGraphNode.prototype.getOutputNodes = function(slot)
    +{
    +	if(!this.outputs || this.outputs.length == 0) return null;
    +	if(slot < this.outputs.length)
    +	{
    +		var output = this.outputs[slot];
    +		var r = [];
    +		for(var i = 0; i < output.length; i++)
    +			r.push( this.graph.getNodeById( output.links[i][3] ));
    +		return r;
    +	}
    +	return null;
    +}
    +
    +LGraphNode.prototype.triggerOutput = function(slot,param)
    +{
    +	var n = this.getOutputNode(slot);
    +	if(n && n.onTrigger)
    +		n.onTrigger(param);
    +}
    +
    +//connections
    +
    +LGraphNode.prototype.addOutput = function(name,type,extra_info)
    +{
    +	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 = [];
    +	this.outputs.push(o);
    +	this.size = this.computeSize();
    +}
    +
    +LGraphNode.prototype.removeOutput = function(slot)
    +{
    +	this.disconnectOutput(slot);
    +	this.outputs.splice(slot,1);
    +	this.size = this.computeSize();
    +}
    +
    +LGraphNode.prototype.addInput = function(name,type,extra_info)
    +{
    +	var o = {name:name,type:type,link:null};
    +	if(extra_info)
    +		for(var i in extra_info)
    +			o[i] = extra_info[i];
    +
    +	if(!this.inputs) this.inputs = [];
    +	this.inputs.push(o);
    +	this.size = this.computeSize();
    +}
    +
    +LGraphNode.prototype.removeInput = function(slot)
    +{
    +	this.disconnectInput(slot);
    +	this.inputs.splice(slot,1);
    +	this.size = this.computeSize();
    +}
    +
    +//trigger connection
    +LGraphNode.prototype.addConnection = function(name,type,pos,direction)
    +{
    +	this.connections.push( {name:name,type:type,pos:pos,direction:direction,links:null});
    +}
    +
    +
    +LGraphNode.prototype.computeSize = function(minHeight)
    +{
    +	var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
    +	var size = [0,0];
    +	size[1] = rows * 14 + 6;
    +	if(!this.inputs || this.inputs.length == 0 || !this.outputs || this.outputs.length == 0)
    +		size[0] = LiteGraph.NODE_WIDTH * 0.5;
    +	else
    +		size[0] = LiteGraph.NODE_WIDTH;
    +	return size;
    +}
    +
    +//returns the bounding of the object, used for rendering purposes
    +LGraphNode.prototype.getBounding = function()
    +{
    +	return new Float32Array([this.pos[0] - 4, this.pos[1] - LGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]);
    +}
    +
    +//checks if a point is inside the shape of a node
    +LGraphNode.prototype.isPointInsideNode = function(x,y)
    +{
    +	var margin_top = this.graph.isLive() ? 0 : 20;
    +	if(this.flags.collapsed)
    +	{
    +		if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
    +			return true;
    +	}
    +	else if (this.pos[0] - 4 < x && (this.pos[0] + this.size[0] + 4) > x
    +		&& (this.pos[1] - margin_top) < y && (this.pos[1] + this.size[1]) > y)
    +		return true;
    +	return false;
    +}
    +
    +LGraphNode.prototype.findInputSlot = function(name)
    +{
    +	if(!this.inputs) return -1;
    +	for(var i = 0, l = this.inputs.length; i < l; ++i)
    +		if(name == this.inputs[i].name)
    +			return i;
    +	return -1;
    +}
    +
    +LGraphNode.prototype.findOutputSlot = function(name)
    +{
    +	if(!this.outputs) return -1;
    +	for(var i = 0, l = this.outputs.length; i < l; ++i)
    +		if(name == this.outputs[i].name)
    +			return i;
    +	return -1;
    +}
    +
    +//connect this node output to the input of another node
    +LGraphNode.prototype.connect = function(slot, node, target_slot)
    +{
    +	target_slot = target_slot || 0;
    +
    +	//seek for the output slot
    +	if( slot.constructor === String )
    +	{
    +		slot = this.findOutputSlot(slot);
    +		if(slot == -1)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("Connect: Error, no slot of name " + slot);
    +			return false;
    +		}
    +	}
    +	else if(!this.outputs || slot >= this.outputs.length) 
    +	{
    +		if(LiteGraph.debug)
    +			console.log("Connect: Error, slot number not found");
    +		return false;
    +	}
    +
    +	//avoid loopback
    +	if(node == this) return false; 
    +	//if( node.constructor != LGraphNode ) throw ("LGraphNode.connect: node is not of type LGraphNode");
    +
    +	if(target_slot.constructor === String)
    +	{
    +		target_slot = node.findInputSlot(target_slot);
    +		if(target_slot == -1)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("Connect: Error, no slot of name " + target_slot);
    +			return false;
    +		}
    +	}
    +	else if(!node.inputs || target_slot >= node.inputs.length) 
    +	{
    +		if(LiteGraph.debug)
    +			console.log("Connect: Error, slot number not found");
    +		return false;
    +	}
    +
    +	//if there is something already plugged there, disconnect
    +	if(target_slot != -1 && node.inputs[target_slot].link != null)
    +		node.disconnectInput(target_slot);
    +		
    +	//special case: -1 means node-connection, used for triggers
    +	var output = this.outputs[slot];
    +	if(target_slot == -1)
    +	{
    +		if( output.links == null )
    +			output.links = [];
    +		output.links.push({id:node.id, slot: -1});
    +	}
    +	else if(output.type == 0 ||  //generic output
    +			node.inputs[target_slot].type == 0 || //generic input
    +			output.type == node.inputs[target_slot].type) //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 ];
    +
    +		//connect
    +		if( output.links == null )	output.links = [];
    +		output.links.push(link);
    +		node.inputs[target_slot].link = link;
    +
    +		this.setDirtyCanvas(false,true);
    +		this.graph.onConnectionChange();
    +	}
    +	return true;
    +}
    +
    +LGraphNode.prototype.disconnectOutput = function(slot, target_node)
    +{
    +	if( slot.constructor === String )
    +	{
    +		slot = this.findOutputSlot(slot);
    +		if(slot == -1)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("Connect: Error, no slot of name " + slot);
    +			return false;
    +		}
    +	}
    +	else if(!this.outputs || slot >= this.outputs.length) 
    +	{
    +		if(LiteGraph.debug)
    +			console.log("Connect: Error, slot number not found");
    +		return false;
    +	}
    +
    +	//get output slot
    +	var output = this.outputs[slot];
    +	if(!output.links || output.links.length == 0)
    +		return false;
    +
    +	if(target_node)
    +	{
    +		for(var i = 0, l = output.links.length; i < l; i++)
    +		{
    +			var link = output.links[i];
    +			//is the link we are searching for...
    +			if( link[3] == target_node.id )
    +			{
    +				output.links.splice(i,1); //remove here
    +				target_node.inputs[ link[4] ].link = null; //remove there
    +				delete this.graph.links[link[0]];
    +				break;
    +			}
    +		}
    +	}
    +	else
    +	{
    +		for(var i = 0, l = output.links.length; i < l; i++)
    +		{
    +			var link = output.links[i];
    +			var target_node = this.graph.getNodeById( link[3] );
    +			if(target_node)
    +				target_node.inputs[ link[4] ].link = null; //remove other side link
    +		}
    +		output.links = null;
    +	}
    +
    +	this.setDirtyCanvas(false,true);
    +	this.graph.onConnectionChange();
    +	return true;
    +}
    +
    +LGraphNode.prototype.disconnectInput = function(slot)
    +{
    +	//seek for the output slot
    +	if( slot.constructor === String )
    +	{
    +		slot = this.findInputSlot(slot);
    +		if(slot == -1)
    +		{
    +			if(LiteGraph.debug)
    +				console.log("Connect: Error, no slot of name " + slot);
    +			return false;
    +		}
    +	}
    +	else if(!this.inputs || slot >= this.inputs.length) 
    +	{
    +		if(LiteGraph.debug)
    +			console.log("Connect: Error, slot number not found");
    +		return false;
    +	}
    +
    +	var input = this.inputs[slot];
    +	if(!input) return false;
    +	var link = this.inputs[slot].link;
    +	this.inputs[slot].link = null;
    +
    +	//remove other side
    +	var node = this.graph.getNodeById( link[1] );
    +	if(!node) return false;
    +
    +	var output = node.outputs[ link[2] ];
    +	if(!output || !output.links || output.links.length == 0) 
    +		return false;
    +
    +	for(var i = 0, l = output.links.length; i < l; i++)
    +	{
    +		var link = output.links[i];
    +		if( link[3] == this.id )
    +		{
    +			output.links.splice(i,1);
    +			break;
    +		}
    +	}
    +
    +	this.setDirtyCanvas(false,true);
    +	this.graph.onConnectionChange();
    +	return true;
    +}
    +
    +//returns the center of a connection point in canvas coords
    +LGraphNode.prototype.getConnectionPos = function(is_input,slot_number)
    +{
    +	if(this.flags.collapsed)
    +		return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5];
    +
    +	if(is_input && slot_number == -1)
    +	{
    +		return [this.pos[0] + 10, this.pos[1] + 10];
    +	}
    +
    +	if(is_input && this.inputs.length > slot_number && this.inputs[slot_number].pos)
    +		return [this.pos[0] + this.inputs[slot_number].pos[0],this.pos[1] + this.inputs[slot_number].pos[1]];
    +	else if(!is_input && this.outputs.length > slot_number && this.outputs[slot_number].pos)
    +		return [this.pos[0] + this.outputs[slot_number].pos[0],this.pos[1] + this.outputs[slot_number].pos[1]];
    +
    +	if(!is_input) //output
    +		return [this.pos[0] + this.size[0] + 1, this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT];
    +	return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT];
    +}
    +
    +/* Renders the LGraphNode on the canvas */
    +LGraphNode.prototype.draw = function(ctx, canvasrender)
    +{
    +	var glow = false;
    +
    +	var color = this.color || LiteGraph.NODE_DEFAULT_COLOR;
    +	//if (this.selected) color = "#88F";
    +
    +	var render_title = true;
    +	if(this.flags.skip_title_render || this.graph.isLive())
    +		render_title = false;
    +	if(this.mouseOver)
    +		render_title = true;
    +
    +	//shadow and glow
    +	if (this.mouseOver) glow = true;
    +	
    +	if(this.selected)
    +	{
    +		/*
    +		ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000";
    +		ctx.shadowOffsetX = 0;
    +		ctx.shadowOffsetY = 0;
    +		ctx.shadowBlur = 1;
    +		*/
    +	}
    +	else if(canvasrender.render_shadows)
    +	{
    +		ctx.shadowColor = "#111";
    +		ctx.shadowOffsetX = 2;
    +		ctx.shadowOffsetY = 2;
    +		ctx.shadowBlur = 4;
    +	}
    +	else
    +		ctx.shadowColor = "transparent";
    +
    +	//only render if it forces it to do it
    +	if(canvasrender.live_mode)
    +	{
    +		if(!this.flags.collapsed)
    +		{
    +			ctx.shadowColor = "transparent";
    +			if(this.onDrawBackground)
    +				this.onDrawBackground(ctx);
    +			if(this.onDrawForeground)
    +				this.onDrawForeground(ctx);
    +		}
    +
    +		return;
    +	}
    +
    +	//draw in collapsed form
    +	if(this.flags.collapsed)
    +	{
    +		if(!this.onDrawCollapsed || this.onDrawCollapsed(ctx) == false)
    +			this.drawNodeCollapsed(ctx,color,this.bgcolor);
    +		return;
    +	}
    +
    +	//clip if required (mask)
    +	if(this.flags.clip_area)
    +	{
    +		ctx.save();
    +		if(this.shape == null || this.shape == "box")
    +		{
    +			ctx.beginPath();
    +			ctx.rect(0,0,this.size[0], this.size[1]);
    +		}
    +		else if (this.shape == "round")
    +		{
    +			ctx.roundRect(0,0,this.size[0], this.size[1],10);
    +		}
    +		else if (this.shape == "circle")
    +		{
    +			ctx.beginPath();
    +			ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2);
    +		}
    +		ctx.clip();
    +	}
    +
    +	//draw shape
    +	this.drawNodeShape(ctx,color, this.bgcolor, !render_title, this.selected );
    +	ctx.shadowColor = "transparent";
    +
    +	//connection slots
    +	ctx.textAlign = "left";
    +	ctx.font = "12px Arial";
    +
    +	var render_text = this.graph.config.canvas_scale > 0.6;
    +
    +	//input connection slots
    +	if(this.inputs)
    +		for(var i = 0; i < this.inputs.length; i++)
    +		{
    +			var slot = this.inputs[i];
    +
    +			ctx.globalAlpha = 1.0;
    +			if (canvasrender.connecting_node != null && canvasrender.connecting_output.type != 0 && this.inputs[i].type != 0 && canvasrender.connecting_output.type != this.inputs[i].type)
    +				ctx.globalAlpha = 0.4;
    +
    +			ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA";
    +
    +			var pos = this.getConnectionPos(true,i);
    +			pos[0] -= this.pos[0];
    +			pos[1] -= this.pos[1];
    +
    +			ctx.beginPath();
    +
    +			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);
    +
    +			ctx.fill();
    +
    +			//render name
    +			if(render_text)
    +			{
    +				var text = slot.label != null ? slot.label : slot.name;
    +				if(text)
    +				{
    +					ctx.fillStyle = color; 
    +					ctx.fillText(text,pos[0] + 10,pos[1] + 5);
    +				}
    +			}
    +		}
    +
    +	//output connection slots
    +	if(canvasrender.connecting_node)
    +		ctx.globalAlpha = 0.4;
    +
    +	ctx.lineWidth = 1;
    +
    +	ctx.textAlign = "right";
    +	ctx.strokeStyle = "black";
    +	if(this.outputs)
    +		for(var i = 0; i < this.outputs.length; i++)
    +		{
    +			var slot = this.outputs[i];
    +
    +			var pos = this.getConnectionPos(false,i);
    +			pos[0] -= this.pos[0];
    +			pos[1] -= this.pos[1];
    +
    +			ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA";
    +			ctx.beginPath();
    +			//ctx.rect( this.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);
    +
    +			//trigger
    +			//if(slot.node_id != null && slot.slot == -1)
    +			//	ctx.fillStyle = "#F85";
    +
    +			//if(slot.links != null && slot.links.length)
    +			ctx.fill();
    +			ctx.stroke();
    +
    +			//render output name
    +			if(render_text)
    +			{
    +				var text = slot.label != null ? slot.label : slot.name;
    +				if(text)
    +				{
    +					ctx.fillStyle = color;
    +					ctx.fillText(text, pos[0] - 10,pos[1] + 5);
    +				}
    +			}
    +		}
    +
    +	ctx.textAlign = "left";
    +	ctx.globalAlpha = 1.0;
    +
    +	if(this.onDrawForeground)
    +		this.onDrawForeground(ctx);
    +
    +	if(this.flags.clip_area)
    +		ctx.restore();
    +}
    +
    +/* Renders the node shape */
    +LGraphNode.prototype.drawNodeShape = function(ctx, fgcolor, bgcolor, no_title, selected )
    +{
    +	//bg rect
    +	ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
    +	ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
    +
    +	/* gradient test
    +	var grad = ctx.createLinearGradient(0,0,0,this.size[1]);
    +	grad.addColorStop(0, "#AAA");
    +	grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR);
    +	grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR);
    +	ctx.fillStyle = grad;
    +	*/
    +
    +	var title_height = LiteGraph.NODE_TITLE_HEIGHT;
    +
    +	//render depending on shape
    +	if(this.shape == null || this.shape == "box")
    +	{
    +		if(selected)
    +		{
    +			ctx.strokeStyle = "#CCC";
    +			ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5,this.size[0]+2, no_title ? (this.size[1]+2) : (this.size[1] + title_height+2) );
    +			ctx.strokeStyle = fgcolor;
    +		}
    +
    +		ctx.beginPath();
    +		ctx.rect(0.5,no_title ? 0.5 : -title_height + 0.5,this.size[0], no_title ? this.size[1] : this.size[1] + title_height);
    +	}
    +	else if (this.shape == "round")
    +	{
    +		ctx.roundRect(0,no_title ? 0 : -title_height,this.size[0], no_title ? this.size[1] : this.size[1] + title_height, 10);
    +	}
    +	else if (this.shape == "circle")
    +	{
    +		ctx.beginPath();
    +		ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2);
    +	}
    +
    +	ctx.fill();
    +	ctx.shadowColor = "transparent";
    +	ctx.stroke();
    +
    +	//image
    +	if (this.bgImage && this.bgImage.width)
    +		ctx.drawImage( this.bgImage, (this.size[0] - this.bgImage.width) * 0.5 , (this.size[1] - this.bgImage.height) * 0.5);
    +
    +	if(this.bgImageUrl && !this.bgImage)
    +		this.bgImage = this.loadImage(this.bgImageUrl);
    +
    +	if(this.onDrawBackground)
    +		this.onDrawBackground(ctx);
    +
    +	//title bg
    +	if(!no_title)
    +	{
    +		ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
    +
    +		if(this.shape == null || this.shape == "box")
    +		{
    +			ctx.fillRect(0,-title_height,this.size[0],title_height);
    +			ctx.stroke();
    +		}
    +		else if (this.shape == "round")
    +		{
    +			ctx.roundRect(0,-title_height,this.size[0], title_height,10,0);
    +			//ctx.fillRect(0,8,this.size[0],NODE_TITLE_HEIGHT - 12);
    +			ctx.fill();
    +			ctx.stroke();
    +		}
    +
    +		//box
    +		ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
    +		ctx.beginPath();
    +		if (this.shape == "round")
    +			ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2);
    +		else
    +			ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6);
    +		ctx.fill();
    +
    +		//title text
    +		ctx.font = "bold 12px Arial";
    +		if(this.name != "" && this.graph.config.canvas_scale > 0.8)
    +		{
    +			ctx.fillStyle = "#222";
    +			ctx.fillText(this.name,16,13-title_height );
    +		}
    +	}
    +}
    +
    +/* Renders the node when collapsed */
    +LGraphNode.prototype.drawNodeCollapsed = function(ctx, fgcolor, bgcolor)
    +{
    +	//draw default collapsed shape
    +	ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR;
    +	ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR;
    +
    +	var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS;
    +
    +	//circle shape
    +	if(this.shape == "circle")
    +	{
    +		ctx.beginPath();
    +		ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius,0,Math.PI * 2);
    +		ctx.fill();
    +		ctx.shadowColor = "rgba(0,0,0,0)";
    +		ctx.stroke();
    +
    +		ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
    +		ctx.beginPath();
    +		ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2);
    +		ctx.fill();
    +	}
    +	else if(this.shape == "round") //rounded box
    +	{
    +		ctx.beginPath();
    +		ctx.roundRect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5);
    +		ctx.fill();
    +		ctx.shadowColor = "rgba(0,0,0,0)";
    +		ctx.stroke();
    +
    +		ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
    +		ctx.beginPath();
    +		ctx.roundRect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2);
    +		ctx.fill();
    +	}
    +	else //flat box
    +	{
    +		ctx.beginPath();
    +		ctx.rect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius);
    +		ctx.fill();
    +		ctx.shadowColor = "rgba(0,0,0,0)";
    +		ctx.stroke();
    +
    +		ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR;
    +		ctx.beginPath();
    +		ctx.rect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius);
    +		ctx.fill();
    +	}
    +}
    +
    +/* Force align to grid */
    +LGraphNode.prototype.alignToGrid = function()
    +{
    +	this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
    +	this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
    +}
    +
    +/* Copy all the info from one object to this node (used for serialization) */
    +LGraphNode.prototype.copyFromObject = function(info, ignore_connections)
    +{
    +	var outputs = null;
    +	var inputs = null;
    +	var properties = null;
    +	var local_data = null;
    +
    +	for (var j in info)
    +	{
    +		if(ignore_connections && (j == "outputs" || j == "inputs"))
    +			continue;
    +
    +		if(j == "console") continue;
    +
    +		if(info[j] == null)
    +			continue;
    +		else if( info[j].concat ) //array
    +			this[j] = info[j].concat();
    +		else if (typeof(info[j]) == 'object') //object
    +			this[j] = jQuery.extend({}, info[j]);
    +		else //value
    +			this[j] = info[j];
    +	}
    +
    +	//redo the connections
    +	/*
    +	if(outputs)
    +		this.outputs = outputs.concat();
    +	if(inputs)
    +		this.inputs = inputs.concat();
    +
    +	if(local_data)
    +		this.data = local_data;
    +	if(properties)
    +	{
    +		//copy only the ones defined
    +		for (var j in properties)
    +			if (this.properties[j] != null)
    +				this.properties[j] = properties[j];
    +	}
    +	*/
    +}
    +
    +/* Creates a clone of this node */
    +LGraphNode.prototype.clone = function()
    +{
    +	var node = LiteGraph.createNode(this.type);
    +
    +	node.size = this.size.concat();
    +	if(this.inputs)
    +		for(var i = 0, l = this.inputs.length; i < l; ++i)
    +		{
    +			if(node.findInputSlot( this.inputs[i].name ) == -1)
    +				node.addInput( this.inputs[i].name, this.inputs[i].type );
    +		}
    +
    +	if(this.outputs)
    +		for(var i = 0, l = this.outputs.length; i < l; ++i)
    +		{
    +			if(node.findOutputSlot( this.outputs[i].name ) == -1)
    +				node.addOutput( this.outputs[i].name, this.outputs[i].type );
    +		}
    +
    +
    +	return node;
    +}
    +
    +/* Console output */
    +LGraphNode.prototype.trace = function(msg)
    +{
    +	if(!this.console)
    +		this.console = [];
    +	this.console.push(msg);
    +	if(this.console.length > LGraphNode.MAX_CONSOLE)
    +		this.console.shift();
    +
    +	this.graph.onNodeTrace(this,msg);
    +}
    +
    +/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
    +LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background)
    +{
    +	if(!this.graph || !this.graph.canvas)
    +		return;
    +
    +	if(dirty_foreground)
    +		this.graph.canvas.dirty_canvas = true;
    +	if(dirty_background)
    +		this.graph.canvas.dirty_bgcanvas = true;
    +}
    +
    +LGraphNode.prototype.loadImage = function(url)
    +{
    +	var img = new Image();
    +	img.src = LiteGraph.node_images_path + url;	
    +	img.ready = false;
    +
    +	var that = this;
    +	img.onload = function() { 
    +		this.ready = true;
    +		that.setDirtyCanvas(true);
    +	}
    +	return img;
    +}
    +
    +//safe LGraphNode action execution (not sure if safe)
    +LGraphNode.prototype.executeAction = function(action)
    +{
    +	if(action == "") return false;
    +
    +	if( action.indexOf(";") != -1 || action.indexOf("}") != -1)
    +	{
    +		this.trace("Error: Action contains unsafe characters");
    +		return false;
    +	}
    +
    +	var tokens = action.split("(");
    +	var func_name = tokens[0];
    +	if( typeof(this[func_name]) != "function")
    +	{
    +		this.trace("Error: Action not found on node: " + func_name);
    +		return false;
    +	}
    +
    +	var code = action;
    +
    +	try
    +	{
    +		var _foo = eval;
    +		eval = null;
    +		(new Function("with(this) { " + code + "}")).call(this);
    +		eval = _foo;
    +	}
    +	catch (err)
    +	{
    +		this.trace("Error executing action {" + action + "} :" + err);
    +		return false;
    +	}
    +
    +	return true;
    +}
    +
    +/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
    +LGraphNode.prototype.captureInput = function(v)
    +{
    +	if(!this.graph || !this.graph.canvas)
    +		return;
    +
    +	//releasing somebody elses capture?!
    +	if(!v && this.graph.canvas.node_capturing_input != this)
    +		return;
    +
    +	//change
    +	this.graph.canvas.node_capturing_input = v ? this : null;
    +	if(this.graph.debug)
    +		console.log(this.name + ": Capturing input " + (v?"ON":"OFF"));
    +}
    +
    +/* Collapse the node */
    +LGraphNode.prototype.collapse = function()
    +{
    +	if(!this.flags.collapsed)
    +		this.flags.collapsed = true;
    +	else
    +		this.flags.collapsed = false;
    +	this.setDirtyCanvas(true,true);
    +}
    +
    +/* Forces the node to do not move or realign on Z */
    +LGraphNode.prototype.pin = function()
    +{
    +	if(!this.flags.pinned)
    +		this.flags.pinned = true;
    +	else
    +		this.flags.pinned = false;
    +}
    +
    +LGraphNode.prototype.localToScreen = function(x,y)
    +{
    +	return [(x + this.pos[0]) * this.graph.config.canvas_scale + this.graph.config.canvas_offset[0],
    +		(y + this.pos[1]) * this.graph.config.canvas_scale + this.graph.config.canvas_offset[1]];
    +}
    +
    +
    +
    +//*********************************************************************************
    +// LGraphCanvas: LGraph renderer CLASS                                  
    +//*********************************************************************************
    +
    +function LGraphCanvas(canvas, graph)
    +{
    +	if(graph === undefined)
    +		throw ("No graph assigned");
    +
    +	if( typeof(window) != "undefined" )
    +	{
    +		window.requestAnimFrame = (function(){
    +		  return  window.requestAnimationFrame       ||
    +				  window.webkitRequestAnimationFrame ||
    +				  window.mozRequestAnimationFrame    ||
    +				  function( callback ){
    +					window.setTimeout(callback, 1000 / 60);
    +				  };
    +		})();
    +	}
    +
    +	//link canvas and graph
    +	this.graph = graph;
    +	if(graph)
    +		graph.canvas = this;
    +
    +	this.setCanvas(canvas);
    +	this.clear();
    +
    +	this.startRendering();
    +}
    +
    +LGraphCanvas.link_type_colors = {'number':"#AAC",'node':"#DCA"};
    +LGraphCanvas.link_width = 2;
    +
    +LGraphCanvas.prototype.clear = function()
    +{
    +	this.frame = 0;
    +	this.last_draw_time = 0;
    +	this.render_time = 0;
    +	this.fps = 0;
    +
    +	this.selected_nodes = {};
    +	this.node_dragged = null;
    +	this.node_over = null;
    +	this.node_capturing_input = null;
    +	this.connecting_node = null;
    +
    +	this.highquality_render = true;
    +	this.pause_rendering = false;
    +	this.render_shadows = true;
    +	this.dirty_canvas = true;
    +	this.dirty_bgcanvas = true;
    +	this.dirty_area = null;
    +
    +	this.render_only_selected = true;
    +	this.live_mode = false;
    +	this.show_info = true;
    +	this.allow_dragcanvas = true;
    +	this.allow_dragnodes = true;
    +
    +	this.node_in_panel = null;
    +
    +	this.last_mouse = [0,0];
    +	this.last_mouseclick = 0;
    +
    +	if(this.onClear) this.onClear();
    +	//this.UIinit();
    +}
    +
    +LGraphCanvas.prototype.setGraph = function(graph)
    +{
    +	if(this.graph == graph) return;
    +
    +	this.clear();
    +	if(this.graph)
    +		this.graph.canvas = null; //remove old graph link to the canvas
    +	this.graph = graph;
    +	if(this.graph)
    +		this.graph.canvas = this;
    +	this.setDirty(true,true);
    +}
    +
    +LGraphCanvas.prototype.resize = function(width, height)
    +{
    +	if(this.canvas.width == width && this.canvas.height == height)
    +		return;
    +
    +	this.canvas.width = width;
    +	this.canvas.height = height;
    +	this.bgcanvas.width = this.canvas.width;
    +	this.bgcanvas.height = this.canvas.height;
    +	this.setDirty(true,true);
    +}
    +
    +
    +LGraphCanvas.prototype.setCanvas = function(canvas)
    +{
    +	var that = this;
    +
    +	//Canvas association
    +	if(typeof(canvas) == "string")
    +		canvas = document.getElementById(canvas);
    +
    +	if(canvas == null)
    +		throw("Error creating LiteGraph canvas: Canvas not found");
    +	if(canvas == this.canvas) return;
    +
    +	this.canvas = canvas;
    +	//this.canvas.tabindex = "1000";
    +	this.canvas.className += " lgraphcanvas";
    +	this.canvas.data = this;
    +
    +	//bg canvas: used for non changing stuff
    +	this.bgcanvas = null;
    +	if(!this.bgcanvas)
    +	{
    +		this.bgcanvas = document.createElement("canvas");
    +		this.bgcanvas.width = this.canvas.width;
    +		this.bgcanvas.height = this.canvas.height;
    +	}
    +
    +	if(this.canvas.getContext == null)
    +	{
    +		throw("This browser doesnt support Canvas");
    +	}
    +
    +	this.ctx = this.canvas.getContext("2d");
    +	this.bgctx = this.bgcanvas.getContext("2d");
    +
    +	//input:  (move and up could be unbinded)
    +	this._mousemove_callback = this.processMouseMove.bind(this);
    +	this._mouseup_callback = this.processMouseUp.bind(this);
    +
    +	this.canvas.addEventListener("mousedown", this.processMouseDown.bind(this) ); //down do not need to store the binded
    +	this.canvas.addEventListener("mousemove", this._mousemove_callback);
    +
    +	this.canvas.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
    +	
    +
    +	this.canvas.addEventListener("mousewheel", this.processMouseWheel.bind(this), false);
    +	this.canvas.addEventListener("DOMMouseScroll", this.processMouseWheel.bind(this), false);
    +
    +	//touch events
    +	//if( 'touchstart' in document.documentElement )
    +	{
    +		//alert("doo");
    +		this.canvas.addEventListener("touchstart", this.touchHandler, true);
    +		this.canvas.addEventListener("touchmove", this.touchHandler, true);
    +		this.canvas.addEventListener("touchend", this.touchHandler, true);
    +		this.canvas.addEventListener("touchcancel", this.touchHandler, true);    
    +	}
    +
    +	//this.canvas.onselectstart = function () { return false; };
    +	this.canvas.addEventListener("keydown", function(e) { 
    +		that.processKeyDown(e); 
    +	});
    +
    +	this.canvas.addEventListener("keyup", function(e) { 
    +		that.processKeyUp(e); 
    +	});
    +}
    +
    +/*
    +LGraphCanvas.prototype.UIinit = function()
    +{
    +	var that = this;
    +	$("#node-console input").change(function(e)
    +	{
    +		if(e.target.value == "")
    +			return;
    +
    +		var node = that.node_in_panel;
    +		if(!node)
    +			return;
    +			
    +		node.trace("] " + e.target.value, "#333");
    +		if(node.onConsoleCommand)
    +		{
    +			if(!node.onConsoleCommand(e.target.value))
    +				node.trace("command not found", "#A33");
    +		}
    +		else if (e.target.value == "info")
    +		{
    +			node.trace("Special methods:");
    +			for(var i in node)
    +			{
    +				if(typeof(node[i]) == "function" && LGraphNode.prototype[i] == null && i.substr(0,2) != "on" && i[0] != "_")
    +					node.trace(" + " + i);
    +			}
    +		}
    +		else
    +		{
    +			try
    +			{
    +				eval("var _foo = function() { return ("+e.target.value+"); }");
    +				var result = _foo.call(node);
    +				if(result)
    +					node.trace(result.toString());
    +				delete window._foo;
    +			}
    +			catch(err)
    +			{
    +				node.trace("error: " + err, "#A33");
    +			}
    +		}
    +		
    +		this.value = "";
    +	});
    +}
    +*/
    +
    +LGraphCanvas.prototype.setDirty = function(fgcanvas,bgcanvas)
    +{
    +	if(fgcanvas)
    +		this.dirty_canvas = true;
    +	if(bgcanvas)
    +		this.dirty_bgcanvas = true;
    +}
    +
    +LGraphCanvas.prototype.startRendering = function()
    +{
    +	if(this.is_rendering) return; //already rendering
    +
    +	this.is_rendering = true;
    +	renderFrame.call(this);
    +
    +	function renderFrame()
    +	{
    +		if(!this.pause_rendering)
    +			this.draw();
    +
    +		if(this.is_rendering)
    +			window.requestAnimFrame( renderFrame.bind(this) );
    +	}
    +
    +
    +	/*
    +	this.rendering_timer_id = setInterval( function() { 
    +		//trace("Frame: " + new Date().getTime() );
    +		that.draw(); 
    +	}, 1000/50);
    +	*/
    +}
    +
    +LGraphCanvas.prototype.stopRendering = function()
    +{
    +	this.is_rendering = false;
    +	/*
    +	if(this.rendering_timer_id)
    +	{
    +		clearInterval(this.rendering_timer_id);
    +		this.rendering_timer_id = null;
    +	}
    +	*/
    +}
    +
    +/* LiteGraphCanvas input */
    +
    +LGraphCanvas.prototype.processMouseDown = function(e)
    +{
    +	if(!this.graph) return;
    +
    +	this.adjustMouseEvent(e);
    +	
    +	this.canvas.removeEventListener("mousemove", this._mousemove_callback );
    +	document.addEventListener("mousemove", this._mousemove_callback );
    +	document.addEventListener("mouseup", this._mouseup_callback );
    +
    +	var n = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes);
    +	var skip_dragging = false;
    +
    +	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]);
    +		}
    +		var clicking_canvas_bg = false;
    +
    +		//when clicked on top of a node
    +		//and it is not interactive
    +		if(n) 
    +		{
    +			if(!this.live_mode && !n.flags.pinned)
    +				this.bringToFront(n); //if it wasnt selected?
    +			var skip_action = false;
    +
    +			//not dragging mouse to connect two slots
    +			if(!this.connecting_node && !n.flags.collapsed && !this.live_mode)
    +			{
    +				//search for outputs
    +				if(n.outputs)
    +					for(var i = 0, l = n.outputs.length; i < l; ++i)
    +					{
    +						var output = n.outputs[i];
    +						var link_pos = n.getConnectionPos(false,i);
    +						if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
    +						{
    +							this.connecting_node = n;
    +							this.connecting_output = output;
    +							this.connecting_pos = n.getConnectionPos(false,i);
    +							this.connecting_slot = i;
    +
    +							skip_action = true;
    +							break;
    +						}
    +					}
    +
    +				//search for inputs
    +				if(n.inputs)
    +					for(var i = 0, l = n.inputs.length; i < l; ++i)
    +					{
    +						var input = n.inputs[i];
    +						var link_pos = n.getConnectionPos(true,i);
    +						if( isInsideRectangle(e.canvasX, e.canvasY, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
    +						{
    +							if(input.link)
    +							{
    +								n.disconnectInput(i);
    +								this.dirty_bgcanvas = true;
    +								skip_action = true;
    +							}
    +						}
    +					}
    +
    +				//Search for corner
    +				if( !skip_action && isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 ))
    +				{
    +					this.resizing_node = n;
    +					this.canvas.style.cursor = "se-resize";
    +					skip_action = true;
    +				}
    +			}
    +
    +			//it wasnt clicked on the links boxes
    +			if(!skip_action) 
    +			{
    +				var block_drag_node = false;
    +
    +				//double clicking
    +				var now = new Date().getTime();
    +				if ((now - this.last_mouseclick) < 300 && this.selected_nodes[n.id])
    +				{
    +					//double click node
    +					if( n.onDblClick)
    +						n.onDblClick(e);
    +					this.processNodeDblClicked(n);
    +					block_drag_node = true;
    +				}
    +
    +				//if do not capture mouse
    +
    +				if( n.onMouseDown && n.onMouseDown(e) )
    +					block_drag_node = true;
    +				else if(this.live_mode)
    +				{
    +					clicking_canvas_bg = true;
    +					block_drag_node = true;
    +				}
    +				
    +				if(!block_drag_node)
    +				{
    +					if(this.allow_dragnodes)
    +						this.node_dragged = n;
    +
    +					if(!this.selected_nodes[n.id])
    +						this.processNodeSelected(n,e);
    +				}
    +
    +				this.dirty_canvas = true;
    +			}
    +		}
    +		else
    +			clicking_canvas_bg = true;
    +
    +		if(clicking_canvas_bg && this.allow_dragcanvas)
    +		{
    +			this.dragging_canvas = true;
    +		}
    +	}
    +	else if (e.which == 2) //middle button
    +	{
    +
    +	}
    +	else if (e.which == 3) //right button
    +	{
    +		this.processContextualMenu(n,e);
    +	}
    +
    +	//TODO
    +	//if(this.node_selected != prev_selected)
    +	//	this.onNodeSelectionChange(this.node_selected);
    +
    +	this.last_mouse[0] = e.localX;
    +	this.last_mouse[1] = e.localY;
    +	this.last_mouseclick = new Date().getTime();
    +	this.canvas_mouse = [e.canvasX, e.canvasY];
    +
    +	/*
    +	if( (this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) 
    +		this.draw();
    +	*/
    +
    +	this.graph.change();
    +
    +	//this is to ensure to defocus(blur) if a text input element is on focus
    +	if(!document.activeElement || (document.activeElement.nodeName.toLowerCase() != "input" && document.activeElement.nodeName.toLowerCase() != "textarea"))
    +		e.preventDefault();
    +	e.stopPropagation();
    +	return false;
    +}
    +
    +LGraphCanvas.prototype.processMouseMove = function(e)
    +{
    +	if(!this.graph) return;
    +
    +	this.adjustMouseEvent(e);
    +	var mouse = [e.localX, e.localY];
    +	var delta = [mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1]];
    +	this.last_mouse = mouse;
    +	this.canvas_mouse = [e.canvasX, e.canvasY];
    +
    +	if(this.dragging_canvas)
    +	{
    +		this.graph.config.canvas_offset[0] += delta[0] / this.graph.config.canvas_scale;
    +		this.graph.config.canvas_offset[1] += delta[1] / this.graph.config.canvas_scale;
    +		this.dirty_canvas = true;
    +		this.dirty_bgcanvas = true;
    +	}
    +	else
    +	{
    +		if(this.connecting_node)
    +			this.dirty_canvas = true;
    +
    +		//get node over
    +		var n = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes);
    +
    +		//remove mouseover flag
    +		for(var i in this.graph.nodes)
    +		{
    +			if(this.graph.nodes[i].mouseOver && n != this.graph.nodes[i])
    +			{
    +				//mouse leave
    +				this.graph.nodes[i].mouseOver = false;
    +				if(this.node_over && this.node_over.onMouseLeave)
    +					this.node_over.onMouseLeave(e);
    +				this.node_over = null;
    +				this.dirty_canvas = true;
    +			}
    +		}
    +
    +		//mouse over a node
    +		if(n)
    +		{
    +			//this.canvas.style.cursor = "move";
    +			if(!n.mouseOver)
    +			{
    +				//mouse enter
    +				n.mouseOver = true;
    +				this.node_over = n;
    +				this.dirty_canvas = true;
    +
    +				if(n.onMouseEnter) n.onMouseEnter(e);
    +			}
    +
    +			if(n.onMouseMove) n.onMouseMove(e);
    +
    +			//ontop 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(slot_type == this.connecting_output.type || slot_type == "*" || this.connecting_output.type == "*")
    +						this._highlight_input = pos;
    +				}
    +				else
    +					this._highlight_input = null;
    +			}
    +
    +			//Search for corner
    +			if( isInsideRectangle(e.canvasX, e.canvasY, n.pos[0] + n.size[0] - 5, n.pos[1] + n.size[1] - 5 ,5,5 ))
    +				this.canvas.style.cursor = "se-resize";
    +			else
    +				this.canvas.style.cursor = null;
    +		}
    +		else
    +			this.canvas.style.cursor = null;
    +
    +		if(this.node_capturing_input && this.node_capturing_input != n && this.node_capturing_input.onMouseMove)
    +		{
    +			this.node_capturing_input.onMouseMove(e);
    +		}
    +
    +
    +		if(this.node_dragged && !this.live_mode)
    +		{
    +			/*
    +			this.node_dragged.pos[0] += delta[0] / this.graph.config.canvas_scale;
    +			this.node_dragged.pos[1] += delta[1] / this.graph.config.canvas_scale;
    +			this.node_dragged.pos[0] = Math.round(this.node_dragged.pos[0]);
    +			this.node_dragged.pos[1] = Math.round(this.node_dragged.pos[1]);
    +			*/
    +			
    +			for(var i in this.selected_nodes)
    +			{
    +				var n = this.selected_nodes[i];
    +				
    +				n.pos[0] += delta[0] / this.graph.config.canvas_scale;
    +				n.pos[1] += delta[1] / this.graph.config.canvas_scale;
    +				n.pos[0] = Math.round(n.pos[0]);
    +				n.pos[1] = Math.round(n.pos[1]);
    +			}
    +			
    +			this.dirty_canvas = true;
    +			this.dirty_bgcanvas = true;
    +		}
    +
    +		if(this.resizing_node && !this.live_mode)
    +		{
    +			this.resizing_node.size[0] += delta[0] / this.graph.config.canvas_scale;
    +			this.resizing_node.size[1] += delta[1] / this.graph.config.canvas_scale;
    +			var max_slots = Math.max( this.resizing_node.inputs ? this.resizing_node.inputs.length : 0, this.resizing_node.outputs ? this.resizing_node.outputs.length : 0);
    +			if(this.resizing_node.size[1] < max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4)
    +				this.resizing_node.size[1] = max_slots * LiteGraph.NODE_SLOT_HEIGHT + 4;
    +			if(this.resizing_node.size[0] < LiteGraph.NODE_MIN_WIDTH)
    +				this.resizing_node.size[0] = LiteGraph.NODE_MIN_WIDTH;
    +
    +			this.canvas.style.cursor = "se-resize";
    +			this.dirty_canvas = true;
    +			this.dirty_bgcanvas = true;
    +		}
    +	}
    +
    +	/*
    +	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null) 
    +		this.draw();
    +	*/
    +
    +	e.preventDefault();
    +	e.stopPropagation();
    +	return false;
    +	//this is not really optimal
    +	//this.graph.change();
    +}
    +
    +LGraphCanvas.prototype.processMouseUp = function(e)
    +{
    +	if(!this.graph) return;
    +
    +	document.removeEventListener("mousemove", this._mousemove_callback, true );
    +	this.canvas.addEventListener("mousemove", this._mousemove_callback, true);
    +	document.removeEventListener("mouseup", this._mouseup_callback, true );
    +
    +	this.adjustMouseEvent(e);
    +
    +	if (e.which == 1) //left button
    +	{
    +		//dragging a connection
    +		if(this.connecting_node)
    +		{
    +			this.dirty_canvas = true;
    +			this.dirty_bgcanvas = true;
    +
    +			var node = this.graph.getNodeOnPos(e.canvasX, e.canvasY, this.visible_nodes);
    +
    +			//node below mouse
    +			if(node)
    +			{
    +			
    +				if(this.connecting_output.type == 'node')
    +				{
    +					this.connecting_node.connect(this.connecting_slot, node, -1);
    +				}
    +				else
    +				{
    +					//slot below mouse? connect
    +					var slot = this.isOverNodeInput(node, e.canvasX, e.canvasY);
    +					if(slot != -1)
    +					{
    +						this.connecting_node.connect(this.connecting_slot, node, slot);
    +					}
    +				}
    +			}
    +
    +			this.connecting_output = null;
    +			this.connecting_pos = null;
    +			this.connecting_node = null;
    +			this.connecting_slot = -1;
    +
    +		}//not dragging connection
    +		else if(this.resizing_node)
    +		{
    +			this.dirty_canvas = true;
    +			this.dirty_bgcanvas = true;
    +			this.resizing_node = null;
    +		}
    +		else if(this.node_dragged) //node being dragged?
    +		{
    +			this.dirty_canvas = true;
    +			this.dirty_bgcanvas = true;
    +
    +			if(this.graph.config.align_to_grid)
    +				this.node_dragged.alignToGrid();
    +			this.node_dragged = null;
    +		}
    +		else //no node being dragged
    +		{
    +			this.dirty_canvas = true;
    +			this.dragging_canvas = false;
    +
    +			if( this.node_over && this.node_over.onMouseUp )
    +				this.node_over.onMouseUp(e);
    +			if( this.node_capturing_input && this.node_capturing_input.onMouseUp )
    +				this.node_capturing_input.onMouseUp(e);
    +		}
    +	}
    +	else if (e.which == 2) //middle button
    +	{
    +		//trace("middle");
    +		this.dirty_canvas = true;
    +		this.dragging_canvas = false;
    +	}
    +	else if (e.which == 3) //right button
    +	{
    +		//trace("right");
    +		this.dirty_canvas = true;
    +		this.dragging_canvas = false;
    +	}
    +
    +	/*
    +	if((this.dirty_canvas || this.dirty_bgcanvas) && this.rendering_timer_id == null)
    +		this.draw();
    +	*/
    +
    +	this.graph.change();
    +
    +	e.stopPropagation();
    +	e.preventDefault();
    +	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)
    +		{
    +			var input = node.inputs[i];
    +			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] };
    +				return i;
    +			}
    +		}
    +	return -1;
    +}
    +
    +LGraphCanvas.prototype.processKeyDown = function(e) 
    +{
    +	if(!this.graph) return;
    +	var block_default = false;
    +
    +	//select all Control A
    +	if(e.keyCode == 65 && e.ctrlKey)
    +	{
    +		this.selectAllNodes();
    +		block_default = true;
    +	}
    +
    +	//delete or backspace
    +	if(e.keyCode == 46 || e.keyCode == 8)
    +	{
    +		this.deleteSelectedNodes();
    +	}
    +
    +	//collapse
    +	//...
    +
    +	//TODO
    +	if(this.selected_nodes) 
    +		for (var i in this.selected_nodes)
    +			if(this.selected_nodes[i].onKeyDown)
    +				this.selected_nodes[i].onKeyDown(e);
    +
    +	this.graph.change();
    +
    +	if(block_default)
    +	{
    +		e.preventDefault();
    +		return false;
    +	}
    +}
    +
    +LGraphCanvas.prototype.processKeyUp = function(e) 
    +{
    +	if(!this.graph) return;
    +	//TODO
    +	if(this.selected_nodes)
    +		for (var i in this.selected_nodes)
    +			if(this.selected_nodes[i].onKeyUp)
    +				this.selected_nodes[i].onKeyUp(e);
    +
    +	this.graph.change();
    +}
    +
    +LGraphCanvas.prototype.processMouseWheel = function(e) 
    +{
    +	if(!this.graph) return;
    +	if(!this.allow_dragcanvas) return;
    +
    +	var delta = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
    +
    +	this.adjustMouseEvent(e);
    +
    +	var zoom = this.graph.config.canvas_scale;
    +
    +	if (delta > 0)
    +		zoom *= 1.1;
    +	else if (delta < 0)
    +		zoom *= 1/(1.1);
    +
    +	this.setZoom( zoom, [ e.localX, e.localY ] );
    +
    +	/*
    +	if(this.rendering_timer_id == null)
    +		this.draw();
    +	*/
    +
    +	this.graph.change();
    +
    +	e.preventDefault();
    +	return false; // prevent default
    +}
    +
    +LGraphCanvas.prototype.processNodeSelected = function(n,e)
    +{
    +	n.selected = true;
    +	if (n.onSelected)
    +		n.onSelected();
    +		
    +	if(e && e.shiftKey) //add to selection
    +		this.selected_nodes[n.id] = n;
    +	else
    +	{
    +		this.selected_nodes = {};
    +		this.selected_nodes[ n.id ] = n;
    +	}
    +		
    +	this.dirty_canvas = true;
    +
    +	if(this.onNodeSelected)
    +		this.onNodeSelected(n);
    +
    +	//if(this.node_in_panel) this.showNodePanel(n);
    +}
    +
    +LGraphCanvas.prototype.processNodeDeselected = function(n)
    +{
    +	n.selected = false;
    +	if(n.onDeselected)
    +		n.onDeselected();
    +		
    +	delete this.selected_nodes[n.id];
    +
    +	if(this.onNodeDeselected)
    +		this.onNodeDeselected();
    +
    +	this.dirty_canvas = true;
    +
    +	//this.showNodePanel(null);
    +}
    +
    +LGraphCanvas.prototype.processNodeDblClicked = function(n)
    +{
    +	if(this.onShowNodePanel)
    +		this.onShowNodePanel(n);
    +
    +	if(this.onNodeDblClicked)
    +		this.onNodeDblClicked(n);
    +
    +	this.setDirty(true);
    +}
    +
    +LGraphCanvas.prototype.selectNode = function(node)
    +{
    +	this.deselectAllNodes();
    +
    +	if(!node)
    +		return;
    +
    +	if(!node.selected && node.onSelected)
    +		node.onSelected();
    +	node.selected = true;
    +	this.selected_nodes[ node.id ] = node;
    +	this.setDirty(true);
    +}
    +
    +LGraphCanvas.prototype.selectAllNodes = function()
    +{
    +	for(var i in this.graph.nodes)
    +	{
    +		var n = this.graph.nodes[i];
    +		if(!n.selected && n.onSelected)
    +			n.onSelected();
    +		n.selected = true;
    +		this.selected_nodes[this.graph.nodes[i].id] = n;
    +	}
    +
    +	this.setDirty(true);
    +}
    +
    +LGraphCanvas.prototype.deselectAllNodes = function()
    +{
    +	for(var i in this.selected_nodes)
    +	{
    +		var n = this.selected_nodes;
    +		if(n.onDeselected)
    +			n.onDeselected();
    +		n.selected = false;
    +	}
    +	this.selected_nodes = {};
    +	this.setDirty(true);
    +}
    +
    +LGraphCanvas.prototype.deleteSelectedNodes = function()
    +{
    +	for(var i in this.selected_nodes)
    +	{
    +		var m = this.selected_nodes[i];
    +		//if(m == this.node_in_panel) this.showNodePanel(null);
    +		this.graph.remove(m);
    +	}
    +	this.selected_nodes = {};
    +	this.setDirty(true);
    +}
    +
    +LGraphCanvas.prototype.centerOnNode = function(node)
    +{
    +	this.graph.config.canvas_offset[0] = -node.pos[0] - node.size[0] * 0.5 + (this.canvas.width * 0.5 / this.graph.config.canvas_scale);
    +	this.graph.config.canvas_offset[1] = -node.pos[1] - node.size[1] * 0.5 + (this.canvas.height * 0.5 / this.graph.config.canvas_scale);
    +	this.setDirty(true,true);
    +}
    +
    +LGraphCanvas.prototype.adjustMouseEvent = function(e)
    +{
    +	var b = this.canvas.getBoundingClientRect();
    +	e.localX = e.pageX - b.left;
    +	e.localY = e.pageY - b.top;
    +
    +	e.canvasX = e.localX / this.graph.config.canvas_scale - this.graph.config.canvas_offset[0];
    +	e.canvasY = e.localY / this.graph.config.canvas_scale - this.graph.config.canvas_offset[1];
    +}
    +
    +LGraphCanvas.prototype.setZoom = function(value, zooming_center)
    +{
    +	if(!zooming_center)
    +		zooming_center = [this.canvas.width * 0.5,this.canvas.height * 0.5];
    +
    +	var center = this.convertOffsetToCanvas( zooming_center );
    +
    +	this.graph.config.canvas_scale = value;
    +
    +	if(this.graph.config.canvas_scale > 4)
    +		this.graph.config.canvas_scale = 4;
    +	else if(this.graph.config.canvas_scale < 0.1)
    +		this.graph.config.canvas_scale = 0.1;
    +	
    +	var new_center = this.convertOffsetToCanvas( zooming_center );
    +	var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
    +
    +	this.graph.config.canvas_offset[0] += delta_offset[0];
    +	this.graph.config.canvas_offset[1] += delta_offset[1];
    +
    +	this.dirty_canvas = true;
    +	this.dirty_bgcanvas = true;
    +}
    +
    +LGraphCanvas.prototype.convertOffsetToCanvas = function(pos)
    +{
    +	return [pos[0] / this.graph.config.canvas_scale - this.graph.config.canvas_offset[0], pos[1] / this.graph.config.canvas_scale - this.graph.config.canvas_offset[1]];
    +}
    +
    +LGraphCanvas.prototype.convertCanvasToOffset = function(pos)
    +{
    +	return [(pos[0] + this.graph.config.canvas_offset[0]) * this.graph.config.canvas_scale, 
    +		(pos[1] + this.graph.config.canvas_offset[1]) * this.graph.config.canvas_scale ];
    +}
    +
    +LGraphCanvas.prototype.convertEventToCanvas = function(e)
    +{
    +	var rect = this.canvas.getClientRects()[0];
    +	return this.convertOffsetToCanvas([e.pageX - rect.left,e.pageY - rect.top]);
    +}
    +
    +LGraphCanvas.prototype.bringToFront = function(n)
    +{
    +	var i = this.graph.nodes.indexOf(n);
    +	if(i == -1) return;
    +	
    +	this.graph.nodes.splice(i,1);
    +	this.graph.nodes.push(n);
    +}
    +
    +LGraphCanvas.prototype.sendToBack = function(n)
    +{
    +	var i = this.graph.nodes.indexOf(n);
    +	if(i == -1) return;
    +	
    +	this.graph.nodes.splice(i,1);
    +	this.graph.nodes.unshift(n);
    +}
    +	
    +/* Interaction */
    +
    +
    +
    +/* LGraphCanvas render */
    +
    +LGraphCanvas.prototype.computeVisibleNodes = function()
    +{
    +	var visible_nodes = [];
    +	for (var i in this.graph.nodes)
    +	{
    +		var n = this.graph.nodes[i];
    +
    +		//skip rendering nodes in live mode
    +		if(this.live_mode && !n.onDrawBackground && !n.onDrawForeground)
    +			continue;
    +
    +		if(!overlapBounding(this.visible_area, n.getBounding() ))
    +			continue; //out of the visible area
    +
    +		visible_nodes.push(n);
    +	}
    +	return visible_nodes;
    +}
    +
    +LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas)
    +{
    +	//fps counting
    +	var now = new Date().getTime();
    +	this.render_time = (now - this.last_draw_time)*0.001;
    +	this.last_draw_time = now;
    +
    +	if(this.graph)
    +	{
    +		var start = [-this.graph.config.canvas_offset[0], -this.graph.config.canvas_offset[1] ];
    +		var end = [start[0] + this.canvas.width / this.graph.config.canvas_scale, start[1] + this.canvas.height / this.graph.config.canvas_scale];
    +		this.visible_area = new Float32Array([start[0],start[1],end[0],end[1]]);
    +	}
    +
    +	if(this.dirty_bgcanvas || force_bgcanvas)
    +		this.drawBgcanvas();
    +
    +	if(this.dirty_canvas || force_canvas)
    +		this.drawFrontCanvas();
    +
    +	this.fps = this.render_time ? (1.0 / this.render_time) : 0;
    +	this.frame += 1;
    +}
    +
    +LGraphCanvas.prototype.drawFrontCanvas = function()
    +{
    +	var ctx = this.ctx;
    +	var canvas = this.canvas;
    +
    +	//reset in case of error
    +	ctx.restore();
    +	ctx.setTransform(1, 0, 0, 1, 0, 0);
    +
    +	//clip dirty area if there is one, otherwise work in full canvas
    +	if(this.dirty_area)
    +	{
    +		ctx.save();
    +		ctx.beginPath();
    +		ctx.rect(this.dirty_area[0],this.dirty_area[1],this.dirty_area[2],this.dirty_area[3]);
    +		ctx.clip();
    +	}
    +
    +	//clear
    +	//canvas.width = canvas.width;
    +	ctx.clearRect(0,0,canvas.width, canvas.height);
    +
    +	//draw bg canvas
    +	ctx.drawImage(this.bgcanvas,0,0);
    +
    +	//info widget
    +	if(this.show_info)
    +	{
    +		ctx.font = "10px Arial";
    +		ctx.fillStyle = "#888";
    +		if(this.graph)
    +		{
    +			ctx.fillText( "T: " + this.graph.globaltime.toFixed(2)+"s",5,13*1 );
    +			ctx.fillText( "I: " + this.graph.iteration,5,13*2 );
    +			ctx.fillText( "F: " + this.frame,5,13*3 );
    +			ctx.fillText( "FPS:" + this.fps.toFixed(2),5,13*4 );
    +		}
    +		else
    +			ctx.fillText( "No graph selected",5,13*1 );
    +	}
    +
    +	if(this.graph)
    +	{
    +		//apply transformations
    +		ctx.save();
    +		ctx.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale);
    +		ctx.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]);
    +
    +		//draw nodes
    +		var drawn_nodes = 0;
    +		var visible_nodes = this.computeVisibleNodes();
    +		this.visible_nodes = visible_nodes;
    +
    +		for (var i in visible_nodes)
    +		{
    +			var n = visible_nodes[i];
    +
    +			//transform coords system
    +			ctx.save();
    +			ctx.translate( n.pos[0], n.pos[1] );
    +
    +			//Draw
    +			n.draw(ctx,this);
    +			drawn_nodes += 1;
    +
    +			//Restore
    +			ctx.restore();
    +		}
    +		
    +		//connections ontop?
    +		if(this.graph.config.links_ontop)
    +			if(!this.live_mode)
    +				this.drawConnections(ctx);
    +
    +		//current connection
    +		if(this.connecting_pos != null)
    +		{
    +			ctx.lineWidth = LGraphCanvas.link_width;
    +			ctx.fillStyle = this.connecting_output.type == 'node' ? "#F85" : "#AFA";
    +			ctx.strokeStyle = ctx.fillStyle;
    +			this.renderLink(ctx, this.connecting_pos, [this.canvas_mouse[0],this.canvas_mouse[1]] );
    +
    +			ctx.beginPath();
    +			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);
    +			else
    +				ctx.rect( this.connecting_pos[0], this.connecting_pos[1],12,6);
    +			*/
    +			ctx.fill();
    +
    +			ctx.fillStyle = "#ffcc00";
    +			if(this._highlight_input)
    +			{
    +				ctx.beginPath();
    +				ctx.arc( this._highlight_input[0], this._highlight_input[1],6,0,Math.PI*2);
    +				ctx.fill();
    +			}
    +		}
    +		ctx.restore();
    +	}
    +
    +	if(this.dirty_area)
    +	{
    +		ctx.restore();
    +		//this.dirty_area = null;
    +	}
    +
    +	this.dirty_canvas = false;
    +}
    +
    +LGraphCanvas.prototype.drawBgcanvas = function()
    +{
    +	var canvas = this.bgcanvas;
    +	var ctx = this.bgctx;
    +
    +
    +	//clear
    +	canvas.width = canvas.width;
    +
    +	//reset in case of error
    +	ctx.restore();
    +	ctx.setTransform(1, 0, 0, 1, 0, 0);
    +
    +	if(this.graph)
    +	{
    +		//apply transformations
    +		ctx.save();
    +		ctx.scale(this.graph.config.canvas_scale,this.graph.config.canvas_scale);
    +		ctx.translate(this.graph.config.canvas_offset[0],this.graph.config.canvas_offset[1]);
    +
    +		//render BG
    +		if(this.background_image && this.graph.config.canvas_scale > 0.5)
    +		{
    +			ctx.globalAlpha = 1.0 - 0.5 / this.graph.config.canvas_scale;
    +			ctx.webkitImageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false
    +			if(!this._bg_img || this._bg_img.name != this.background_image)
    +			{
    +				this._bg_img = new Image();
    +				this._bg_img.name = this.background_image; 
    +				this._bg_img.src = this.background_image;
    +				var that = this;
    +				this._bg_img.onload = function() { 
    +					that.draw(true,true);
    +				}
    +			}
    +
    +			var pattern = null;
    +			if(this._bg_img != this._pattern_img && this._bg_img.width > 0)
    +			{
    +				pattern = ctx.createPattern( this._bg_img, 'repeat' );
    +				this._pattern_img = this._bg_img;
    +				this._pattern = pattern;
    +			}
    +			else
    +				pattern = this._pattern;
    +			if(pattern)
    +			{
    +				ctx.fillStyle = pattern;
    +				ctx.fillRect(this.visible_area[0],this.visible_area[1],this.visible_area[2]-this.visible_area[0],this.visible_area[3]-this.visible_area[1]);
    +				ctx.fillStyle = "transparent";
    +			}
    +
    +			ctx.globalAlpha = 1.0;
    +		}
    +
    +		//DEBUG: show clipping area
    +		//ctx.fillStyle = "red";
    +		//ctx.fillRect( this.visible_area[0] + 10, this.visible_area[1] + 10, this.visible_area[2] - this.visible_area[0] - 20, this.visible_area[3] - this.visible_area[1] - 20);
    +
    +		//bg
    +		ctx.strokeStyle = "#235";
    +		ctx.strokeRect(0,0,canvas.width,canvas.height);
    +
    +		/*
    +		if(this.render_shadows)
    +		{
    +			ctx.shadowColor = "#000";
    +			ctx.shadowOffsetX = 0;
    +			ctx.shadowOffsetY = 0;
    +			ctx.shadowBlur = 6;
    +		}
    +		else
    +			ctx.shadowColor = "rgba(0,0,0,0)";
    +		*/
    +
    +		//draw connections
    +		if(!this.live_mode)
    +			this.drawConnections(ctx);
    +
    +		//restore state
    +		ctx.restore();
    +	}
    +
    +	this.dirty_bgcanvas = false;
    +	this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas 
    +}
    +
    +LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"];
    +
    +LGraphCanvas.prototype.drawConnections = function(ctx)
    +{
    +	//draw connections
    +	ctx.lineWidth = LGraphCanvas.link_width;
    +
    +	ctx.fillStyle = "#AAA";
    +	ctx.strokeStyle = "#AAA";
    +	//for every node
    +	for (var n in this.graph.nodes)
    +	{
    +		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)
    +			{
    +				var input = node.inputs[i];
    +				if(!input || !input.link ) continue;
    +				var link = input.link;
    +
    +				var start_node = this.graph.getNodeById( link[1] );
    +				if(start_node == null) continue;
    +				var start_node_slot = link[2];
    +				var start_node_slotpos = null;
    +
    +				if(start_node_slot == -1)
    +					start_node_slotpos = [start_node.pos[0] + 10, start_node.pos[1] + 10];
    +				else
    +					start_node_slotpos = start_node.getConnectionPos(false, start_node_slot);
    +
    +				var color = LGraphCanvas.link_type_colors[node.inputs[i].type];
    +				if(color == null)
    +					color = LGraphCanvas.link_colors[node.id % LGraphCanvas.link_colors.length];
    +				ctx.fillStyle = ctx.strokeStyle = color;
    +				this.renderLink(ctx, start_node_slotpos, node.getConnectionPos(true,i) );
    +			}
    +	}
    +}
    +
    +LGraphCanvas.prototype.renderLink = function(ctx,a,b)
    +{
    +	var curved_lines = true;
    +	
    +	if(!this.highquality_render)
    +	{
    +		ctx.beginPath();
    +		ctx.moveTo(a[0],a[1]);
    +		ctx.lineTo(b[0],b[1]);
    +		ctx.stroke();
    +		return;
    +	}
    +
    +	var dist = distance(a,b);
    +
    +	ctx.beginPath();
    +	
    +	if(curved_lines)
    +	{
    +		ctx.moveTo(a[0],a[1]);
    +		ctx.bezierCurveTo(a[0] + dist*0.25, a[1],
    +							b[0] - dist*0.25 , b[1],
    +							b[0] ,b[1] );
    +	}
    +	else
    +	{
    +		ctx.moveTo(a[0]+10,a[1]);
    +		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,a[1]);
    +		ctx.lineTo(((a[0]+10) + (b[0]-10))*0.5,b[1]);
    +		ctx.lineTo(b[0]-10,b[1]);
    +	}
    +	ctx.stroke();
    +
    +	//render arrow
    +	if(this.graph.config.canvas_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);
    +		var angle = 0;
    +		if(curved_lines)
    +			angle = -Math.atan2( pos2[0] - pos[0], pos2[1] - pos[1]);
    +		else
    +			angle = b[1] > a[1] ? 0 : Math.PI;
    +
    +		ctx.save();
    +		ctx.translate(pos[0],pos[1]);
    +		ctx.rotate(angle);
    +		ctx.beginPath();
    +		ctx.moveTo(-5,-5);
    +		ctx.lineTo(0,+5);
    +		ctx.lineTo(+5,-5);
    +		ctx.fill();
    +		ctx.restore();
    +	}
    +}
    +
    +LGraphCanvas.prototype.computeConnectionPoint = function(a,b,t)
    +{
    +	var dist = distance(a,b);
    +	var p0 = a;
    +	var p1 = [ a[0] + dist*0.25, a[1] ];
    +	var p2 = [ b[0] - dist*0.25, b[1] ];
    +	var p3 = b;
    +
    +	var c1 = (1-t)*(1-t)*(1-t);
    +	var c2 = 3*((1-t)*(1-t))*t;
    +	var c3 = 3*(1-t)*(t*t);
    +	var c4 = t*t*t;
    +
    +	var x = c1*p0[0] + c2*p1[0] + c3*p2[0] + c4*p3[0];
    +	var y = c1*p0[1] + c2*p1[1] + c3*p2[1] + c4*p3[1];
    +	return [x,y];
    +}
    +
    +LGraphCanvas.prototype.resizeCanvas = function(width,height)
    +{
    +	this.canvas.width = width;
    +	if(height)
    +		this.canvas.height = height;
    +
    +	this.bgcanvas.width = this.canvas.width;
    +	this.bgcanvas.height = this.canvas.height;
    +	this.draw(true,true);
    +}
    +
    +LGraphCanvas.prototype.switchLiveMode = function()
    +{
    +	this.live_mode = !this.live_mode;
    +	this.dirty_canvas = true;
    +	this.dirty_bgcanvas = true;
    +}
    +
    +LGraphCanvas.prototype.onNodeSelectionChange = function(node)
    +{
    +	return; //disabled
    +	//if(this.node_in_panel) this.showNodePanel(node);
    +}
    +
    +LGraphCanvas.prototype.touchHandler = function(event)
    +{
    +	//alert("foo");
    +    var touches = event.changedTouches,
    +        first = touches[0],
    +        type = "";
    +
    +         switch(event.type)
    +    {
    +        case "touchstart": type = "mousedown"; break;
    +        case "touchmove":  type="mousemove"; break;        
    +        case "touchend":   type="mouseup"; break;
    +        default: return;
    +    }
    +
    +             //initMouseEvent(type, canBubble, cancelable, view, clickCount,
    +    //           screenX, screenY, clientX, clientY, ctrlKey,
    +    //           altKey, shiftKey, metaKey, button, relatedTarget);
    +    
    +    var simulatedEvent = document.createEvent("MouseEvent");
    +    simulatedEvent.initMouseEvent(type, true, true, window, 1,
    +                              first.screenX, first.screenY,
    +                              first.clientX, first.clientY, false,
    +                              false, false, false, 0/*left*/, null);
    +	first.target.dispatchEvent(simulatedEvent);
    +    event.preventDefault();
    +}
    +
    +/* CONTEXT MENU ********************/
    +
    +LGraphCanvas.onMenuAdd = function(node, e, prev_menu, canvas, first_event )
    +{
    +	var values = LiteGraph.getNodeTypesCategories();
    +	var entries = {};
    +	for(var i in values)
    +		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});
    +
    +	function inner_clicked(v, e)
    +	{
    +		var category = v.value;
    +		var node_types = LiteGraph.getNodeTypesInCategory(category);
    +		var values = [];
    +		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});
    +		return false;
    +	}
    +
    +	function inner_create(v, e)
    +	{
    +		var node = LiteGraph.createNode( v.value );
    +		if(node)
    +		{
    +			node.pos = canvas.convertEventToCanvas(first_event);
    +			canvas.graph.add( node );
    +		}
    +	}
    +
    +	return false;
    +}
    +
    +LGraphCanvas.onMenuCollapseAll = function()
    +{
    +
    +}
    +
    +
    +LGraphCanvas.onMenuNodeEdit = function()
    +{
    +
    +}
    +
    +LGraphCanvas.onMenuNodeInputs = function(node, e, prev_menu)
    +{
    +	if(!node) return;
    +
    +	var options = node.optional_inputs;
    +	if(node.onGetInputs)
    +		options = node.onGetInputs();
    +	if(options)
    +	{
    +		var entries = [];
    +		for (var i in options)
    +		{
    +			var option = options[i];
    +			var label = option[0];
    +			if(option[2] && option[2].label)
    +				label = option[2].label;
    +			entries.push({content: label, value: option});
    +		}
    +		var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});
    +	}
    +
    +	function inner_clicked(v)
    +	{
    +		if(!node) return;
    +		node.addInput(v.value[0],v.value[1], v.value[2]);
    +	}
    +
    +	return false;
    +}
    +
    +LGraphCanvas.onMenuNodeOutputs = function(node, e, prev_menu)
    +{
    +	if(!node) return;
    +
    +	var options = node.optional_outputs;
    +	if(node.onGetOutputs)
    +		options = node.onGetOutputs();
    +	if(options)
    +	{
    +		var entries = [];
    +		for (var i in options)
    +		{
    +			if(node.findOutputSlot(options[i][0]) != -1)
    +				continue; //skip the ones already on
    +			entries.push({content: options[i][0], value: options[i]});
    +		}
    +		if(entries.length)
    +			var menu = LiteGraph.createContextualMenu(entries, {event: e, callback: inner_clicked, from: prev_menu});
    +	}
    +
    +	function inner_clicked(v)
    +	{
    +		if(!node) return;
    +		node.addOutput(v.value[0],v.value[1]);
    +	}
    +
    +	return false;
    +}
    +
    +LGraphCanvas.onMenuNodeCollapse = function(node)
    +{
    +	node.flags.collapsed = !node.flags.collapsed;
    +	node.graph.canvas.setDirty(true,true);
    +}
    +
    +LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu)
    +{
    +	var values = [];
    +	for(var i in LGraphCanvas.node_colors)
    +	{
    +		var color = LGraphCanvas.node_colors[i];
    +		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});
    +
    +	function inner_clicked(v)
    +	{
    +		if(!node) return;
    +		var color = LGraphCanvas.node_colors[v.value];
    +		if(color)
    +		{
    +			node.color = color.color;
    +			node.bgcolor = color.bgcolor;
    +			node.graph.canvas.setDirty(true);
    +		}
    +	}
    +
    +	return false;
    +}
    +
    +LGraphCanvas.onMenuNodeShapes = function(node,e)
    +{
    +	LiteGraph.createContextualMenu(["box","round","circle"], {event: e, callback: inner_clicked});
    +
    +	function inner_clicked(v)
    +	{
    +		if(!node) return;
    +		node.shape = v;
    +		node.graph.canvas.setDirty(true);
    +	}
    +
    +	return false;
    +}
    +
    +LGraphCanvas.onMenuNodeRemove = function(node)
    +{
    +	if(node.removable == false) return;
    +	node.graph.remove(node);
    +	node.graph.canvas.setDirty(true,true);
    +}
    +
    +LGraphCanvas.onMenuNodeClone = function(node)
    +{
    +	if(node.clonable == false) return;
    +	var newnode = node.clone();
    +	if(!newnode) return;
    +	newnode.pos = [node.pos[0]+5,node.pos[1]+5];
    +	node.graph.add(newnode);
    +	node.graph.canvas.setDirty(true,true);
    +}
    +
    +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()
    +{
    +	return [
    +		{content:"Add Node", is_menu: true, callback: LGraphCanvas.onMenuAdd }
    +		//{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll }
    +	];
    +}
    +
    +LGraphCanvas.prototype.getNodeMenuOptions = function(node)
    +{
    +	var options = [
    +		{content:"Inputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeInputs },
    +		{content:"Outputs", is_menu: true, disabled:true, callback: LGraphCanvas.onMenuNodeOutputs },
    +		null,
    +		{content:"Collapse", callback: LGraphCanvas.onMenuNodeCollapse },
    +		{content:"Colors", is_menu: true, callback: LGraphCanvas.onMenuNodeColors },
    +		{content:"Shapes", is_menu: true, callback: LGraphCanvas.onMenuNodeShapes },
    +		null,
    +		{content:"Clone", callback: LGraphCanvas.onMenuNodeClone },
    +		null,
    +		{content:"Remove", callback: LGraphCanvas.onMenuNodeRemove }
    +	];
    +
    +	if( node.clonable == false )
    +		options[7].disabled = true;
    +	if( node.removable == false )
    +		options[9].disabled = true;
    +
    +	if(node.onGetInputs && node.onGetInputs().length )
    +		options[0].disabled = false;
    +	if(node.onGetOutputs && node.onGetOutputs().length )
    +		options[1].disabled = false;
    +
    +	return options;
    +}
    +
    +LGraphCanvas.prototype.processContextualMenu = function(node,event)
    +{
    +	var that = this;
    +	var menu = LiteGraph.createContextualMenu(node ? this.getNodeMenuOptions(node) : this.getCanvasMenuOptions(), {event: event, callback: inner_option_clicked});
    +
    +	function inner_option_clicked(v,e)
    +	{
    +		if(!v) return;
    +
    +		if(v.callback)
    +			return v.callback(node, e, menu, that, event );
    +	}
    +}
    +
    +
    +
    +
    +
    +
    +//API *************************************************
    +//function roundRect(ctx, x, y, width, height, radius, radius_low) {
    +CanvasRenderingContext2D.prototype.roundRect = function (x, y, width, height, radius, radius_low) {
    +  if ( radius === undefined ) {
    +    radius = 5;
    +  }
    +
    +  if(radius_low === undefined)
    +	 radius_low  = radius;
    +
    +  this.beginPath();
    +  this.moveTo(x + radius, y);
    +  this.lineTo(x + width - radius, y);
    +  this.quadraticCurveTo(x + width, y, x + width, y + radius);
    +
    +  this.lineTo(x + width, y + height - radius_low);
    +  this.quadraticCurveTo(x + width, y + height, x + width - radius_low, y + height);
    +  this.lineTo(x + radius_low, y + height);
    +  this.quadraticCurveTo(x, y + height, x, y + height - radius_low);
    +  this.lineTo(x, y + radius);
    +  this.quadraticCurveTo(x, y, x + radius, y);
    +}
    +
    +function compareObjects(a,b)
    +{
    +	for(var i in a)
    +		if(a[i] != b[i])
    +			return false;
    +	return true;
    +}
    +
    +function distance(a,b)
    +{
    +	return Math.sqrt( (b[0] - a[0]) * (b[0] - a[0]) + (b[1] - a[1]) * (b[1] - a[1]) );
    +}
    +
    +function colorToString(c)
    +{
    +	return "rgba(" + Math.round(c[0] * 255).toFixed() + "," + Math.round(c[1] * 255).toFixed() + "," + Math.round(c[2] * 255).toFixed() + "," + (c.length == 4 ? c[3].toFixed(2) : "1.0") + ")";
    +}
    +
    +function isInsideRectangle(x,y, left, top, width, height)
    +{
    +	if (left < x && (left + width) > x &&
    +		top < y && (top + height) > y)
    +		return true;	
    +	return false;
    +}
    +
    +//[minx,miny,maxx,maxy]
    +function growBounding(bounding, x,y)
    +{
    +	if(x < bounding[0])
    +		bounding[0] = x;
    +	else if(x > bounding[2])
    +		bounding[2] = x;
    +
    +	if(y < bounding[1])
    +		bounding[1] = y;
    +	else if(y > bounding[3])
    +		bounding[3] = y;
    +}
    +
    +//point inside boundin box
    +function isInsideBounding(p,bb)
    +{
    +	if (p[0] < bb[0][0] || 
    +		p[1] < bb[0][1] || 
    +		p[0] > bb[1][0] || 
    +		p[1] > bb[1][1])
    +		return false;
    +	return true;
    +}
    +
    +//boundings overlap, format: [start,end]
    +function overlapBounding(a,b)
    +{
    +	if ( a[0] > b[2] ||
    +		a[1] > b[3] ||
    +		a[2] < b[0] ||
    +		a[3] < b[1])
    +		return false;
    +	return true;
    +}
    +
    +//Convert a hex value to its decimal value - the inputted hex must be in the
    +//	format of a hex triplet - the kind we use for HTML colours. The function
    +//	will return an array with three values.
    +function hex2num(hex) {
    +	if(hex.charAt(0) == "#") hex = hex.slice(1); //Remove the '#' char - if there is one.
    +	hex = hex.toUpperCase();
    +	var hex_alphabets = "0123456789ABCDEF";
    +	var value = new Array(3);
    +	var k = 0;
    +	var int1,int2;
    +	for(var i=0;i<6;i+=2) {
    +		int1 = hex_alphabets.indexOf(hex.charAt(i));
    +		int2 = hex_alphabets.indexOf(hex.charAt(i+1)); 
    +		value[k] = (int1 * 16) + int2;
    +		k++;
    +	}
    +	return(value);
    +}
    +//Give a array with three values as the argument and the function will return
    +//	the corresponding hex triplet.
    +function num2hex(triplet) {
    +	var hex_alphabets = "0123456789ABCDEF";
    +	var hex = "#";
    +	var int1,int2;
    +	for(var i=0;i<3;i++) {
    +		int1 = triplet[i] / 16;
    +		int2 = triplet[i] % 16;
    +
    +		hex += hex_alphabets.charAt(int1) + hex_alphabets.charAt(int2); 
    +	}
    +	return(hex);
    +}
    +
    +/* LiteGraph GUI elements *************************************/
    +
    +LiteGraph.createContextualMenu = function(values,options)
    +{
    +	options = options || {};
    +	this.options = options;
    +
    +	if(!options.from)
    +		LiteGraph.closeAllContextualMenus();
    +
    +	var root = document.createElement("div");
    +	root.className = "litecontextualmenu litemenubar-panel";
    +	this.root = root;
    +	var style = root.style;
    +
    +	style.minWidth = "100px";
    +	style.minHeight = "20px";
    +
    +	style.position = "fixed";
    +	style.top = "100px";
    +	style.left = "100px";
    +	style.color = "#AAF";
    +	style.padding = "2px";
    +	style.borderBottom = "2px solid #AAF";
    +	style.backgroundColor = "#444";
    +
    +	//avoid a context menu in a context menu
    +	root.addEventListener("contextmenu", function(e) { e.preventDefault(); return false; });
    +
    +	for(var i in values)
    +	{
    +		var item = values[i];
    +		var element = document.createElement("div");
    +		element.className = "litemenu-entry";
    +
    +		if(item == null)
    +		{
    +			element.className = "litemenu-entry separator";
    +			root.appendChild(element);
    +			continue;
    +		}
    +
    +		if(item.is_menu)
    +			element.className += " submenu";
    +
    +		if(item.disabled)
    +			element.className += " disabled";
    +
    +		element.style.cursor = "pointer";
    +		element.dataset["value"] = typeof(item) == "string" ? item : item.value;
    +		element.data = item;
    +		if(typeof(item) == "string")
    +			element.innerHTML = values.constructor == Array ? values[i] : i;
    +		else
    +			element.innerHTML = item.content ? item.content : i;
    +
    +		element.addEventListener("click", on_click );
    +		root.appendChild(element);
    +	}
    +
    +	root.addEventListener("mouseover", function(e) {
    +		this.mouse_inside = true;
    +	});
    +
    +	root.addEventListener("mouseout", function(e) {
    +		//console.log("OUT!");
    +		var aux = e.toElement;
    +		while(aux != this && aux != document)
    +			aux = aux.parentNode;
    +
    +		if(aux == this) return;
    +		this.mouse_inside = false;
    +		if(!this.block_close)
    +			this.closeMenu();
    +	});
    +
    +	/* MS specific
    +	root.addEventListener("mouseleave", function(e) {
    +		
    +		this.mouse_inside = false;
    +		if(!this.block_close)
    +			this.closeMenu();
    +	});
    +	*/
    +
    +	//insert before checking position
    +	document.body.appendChild(root);
    +
    +	var root_rect = root.getClientRects()[0];
    +
    +	//link menus
    +	if(options.from)
    +	{
    +		options.from.block_close = true;
    +	}
    +
    +	var left = options.left || 0;
    +	var top = options.top || 0;
    +	if(options.event)
    +	{
    +		left = (options.event.pageX - 10);
    +		top = (options.event.pageY - 10);
    +		if(options.left)
    +			left = options.left;
    +
    +		var rect = document.body.getClientRects()[0];
    +
    +		if(options.from)
    +		{
    +			var parent_rect = options.from.getClientRects()[0];
    +			left = parent_rect.left + parent_rect.width;
    +		}
    +
    +		
    +		if(left > (rect.width - root_rect.width - 10))
    +			left = (rect.width - root_rect.width - 10);
    +		if(top > (rect.height - root_rect.height - 10))
    +			top = (rect.height - root_rect.height - 10);
    +	}
    +
    +	root.style.left = left + "px";
    +	root.style.top = top  + "px";
    +
    +	function on_click(e) {
    +		var value = this.dataset["value"];
    +		var close = true;
    +		if(options.callback)
    +		{
    +			var ret = options.callback.call(root, this.data, e );
    +			if( ret != undefined ) close = ret;
    +		}
    +
    +		if(close)
    +			LiteGraph.closeAllContextualMenus();
    +			//root.closeMenu();
    +	}
    +
    +	root.closeMenu = function()
    +	{
    +		if(options.from)
    +		{
    +			options.from.block_close = false;
    +			if(!options.from.mouse_inside)
    +				options.from.closeMenu();
    +		}
    +		if(this.parentNode)
    +			document.body.removeChild(this);
    +	};
    +
    +	return root;
    +}
    +
    +LiteGraph.closeAllContextualMenus = function()
    +{
    +	var elements = document.querySelectorAll(".litecontextualmenu");
    +	if(!elements.length) return;
    +
    +	var result = [];
    +	for(var i = 0; i < elements.length; i++)
    +		result.push(elements[i]);
    +
    +	for(var i in result)
    +		if(result[i].parentNode)
    +			result[i].parentNode.removeChild( result[i] );
    +}
    +
    +LiteGraph.extendClass = function(origin, target)
    +{
    +	for(var i in origin) //copy class properties
    +		target[i] = origin[i];
    +	if(origin.prototype) //copy prototype properties
    +		for(var i in origin.prototype)
    +			target.prototype[i] = origin.prototype[i];
    +}
    +    
    +
    + +
    +
    +
    +
    +
    +
    + + + + + + + + + + diff --git a/doc/files/index.html b/doc/files/index.html new file mode 100644 index 000000000..487fe15b2 --- /dev/null +++ b/doc/files/index.html @@ -0,0 +1,10 @@ + + + + Redirector + + + + Click here to redirect + + diff --git a/doc/index.html b/doc/index.html new file mode 100644 index 000000000..b60c37e4b --- /dev/null +++ b/doc/index.html @@ -0,0 +1,126 @@ + + + + + + + + + + + + + +
    +
    +
    + +

    + +
    +
    + API Docs for: +
    +
    +
    + +
    + +
    +
    +
    + Show: + + + + + + + +
    + + +
    +
    +
    +
    +
    +

    + Browse to a module or class using the sidebar to view its API documentation. +

    + +

    Keyboard Shortcuts

    + +
      +
    • Press s to focus the API search box.

    • + +
    • Use Up and Down to select classes, modules, and search results.

    • + +
    • With the API search box or sidebar focused, use -Left or -Right to switch sidebar tabs.

    • + +
    • With the API search box or sidebar focused, use Ctrl+Left and Ctrl+Right to switch sidebar tabs.

    • +
    +
    +
    + + + +
    +
    +
    +
    +
    +
    + + + + + + + + + + diff --git a/doc/modules/index.html b/doc/modules/index.html new file mode 100644 index 000000000..487fe15b2 --- /dev/null +++ b/doc/modules/index.html @@ -0,0 +1,10 @@ + + + + Redirector + + + + Click here to redirect + + diff --git a/src/litegraph.js b/src/litegraph.js index c6ed3420a..7418cd59b 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -17,9 +17,10 @@ var LiteGraph = { NODE_WIDTH: 140, NODE_MIN_WIDTH: 50, NODE_COLLAPSED_RADIUS: 10, + NODE_COLLAPSED_WIDTH: 80, CANVAS_GRID_SIZE: 10, - NODE_DEFAULT_COLOR: "#888", - NODE_DEFAULT_BGCOLOR: "#333", + NODE_DEFAULT_COLOR: "#999", + NODE_DEFAULT_BGCOLOR: "#444", NODE_DEFAULT_BOXCOLOR: "#AEF", NODE_DEFAULT_SHAPE: "box", MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops @@ -1166,7 +1167,7 @@ LGraphNode.prototype.computeSize = function(minHeight) //returns the bounding of the object, used for rendering purposes LGraphNode.prototype.getBounding = function() { - return new Float32Array([this.pos[0] - 4, this.pos[1] - LGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]); + return new Float32Array([this.pos[0] - 4, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, this.pos[0] + this.size[0] + 4, this.pos[1] + this.size[1] + LGraph.NODE_TITLE_HEIGHT]); } //checks if a point is inside the shape of a node @@ -1175,7 +1176,8 @@ LGraphNode.prototype.isPointInsideNode = function(x,y) var margin_top = this.graph.isLive() ? 0 : 20; if(this.flags.collapsed) { - if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS) + if( isInsideRectangle(x,y, this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT, LiteGraph.NODE_COLLAPSED_WIDTH, LiteGraph.NODE_TITLE_HEIGHT) ) return true; } else if (this.pos[0] - 4 < x && (this.pos[0] + this.size[0] + 4) > x @@ -1384,7 +1386,13 @@ LGraphNode.prototype.disconnectInput = function(slot) LGraphNode.prototype.getConnectionPos = function(is_input,slot_number) { if(this.flags.collapsed) - return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5]; + { + if(is_input) + return [this.pos[0], this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + else + return [this.pos[0] + LiteGraph.NODE_COLLAPSED_WIDTH, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5]; + //return [this.pos[0] + this.size[0] * 0.5, this.pos[1] + this.size[1] * 0.5]; + } if(is_input && slot_number == -1) { @@ -1401,331 +1409,6 @@ LGraphNode.prototype.getConnectionPos = function(is_input,slot_number) return [this.pos[0] , this.pos[1] + 10 + slot_number * LiteGraph.NODE_SLOT_HEIGHT]; } -/* Renders the LGraphNode on the canvas */ -LGraphNode.prototype.draw = function(ctx, canvasrender) -{ - var glow = false; - - var color = this.color || LiteGraph.NODE_DEFAULT_COLOR; - //if (this.selected) color = "#88F"; - - var render_title = true; - if(this.flags.skip_title_render || this.graph.isLive()) - render_title = false; - if(this.mouseOver) - render_title = true; - - //shadow and glow - if (this.mouseOver) glow = true; - - if(this.selected) - { - /* - ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; - ctx.shadowOffsetX = 0; - ctx.shadowOffsetY = 0; - ctx.shadowBlur = 1; - */ - } - else if(canvasrender.render_shadows) - { - ctx.shadowColor = "#111"; - ctx.shadowOffsetX = 2; - ctx.shadowOffsetY = 2; - ctx.shadowBlur = 4; - } - else - ctx.shadowColor = "transparent"; - - //only render if it forces it to do it - if(canvasrender.live_mode) - { - if(!this.flags.collapsed) - { - ctx.shadowColor = "transparent"; - if(this.onDrawBackground) - this.onDrawBackground(ctx); - if(this.onDrawForeground) - this.onDrawForeground(ctx); - } - - return; - } - - //draw in collapsed form - if(this.flags.collapsed) - { - if(!this.onDrawCollapsed || this.onDrawCollapsed(ctx) == false) - this.drawNodeCollapsed(ctx,color,this.bgcolor); - return; - } - - //clip if required (mask) - if(this.flags.clip_area) - { - ctx.save(); - if(this.shape == null || this.shape == "box") - { - ctx.beginPath(); - ctx.rect(0,0,this.size[0], this.size[1]); - } - else if (this.shape == "round") - { - ctx.roundRect(0,0,this.size[0], this.size[1],10); - } - else if (this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2); - } - ctx.clip(); - } - - //draw shape - this.drawNodeShape(ctx,color, this.bgcolor, !render_title, this.selected ); - ctx.shadowColor = "transparent"; - - //connection slots - ctx.textAlign = "left"; - ctx.font = "12px Arial"; - - var render_text = this.graph.config.canvas_scale > 0.6; - - //input connection slots - if(this.inputs) - for(var i = 0; i < this.inputs.length; i++) - { - var slot = this.inputs[i]; - - ctx.globalAlpha = 1.0; - if (canvasrender.connecting_node != null && canvasrender.connecting_output.type != 0 && this.inputs[i].type != 0 && canvasrender.connecting_output.type != this.inputs[i].type) - ctx.globalAlpha = 0.4; - - ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; - - var pos = this.getConnectionPos(true,i); - pos[0] -= this.pos[0]; - pos[1] -= this.pos[1]; - - ctx.beginPath(); - - 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); - - ctx.fill(); - - //render name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = color; - ctx.fillText(text,pos[0] + 10,pos[1] + 5); - } - } - } - - //output connection slots - if(canvasrender.connecting_node) - ctx.globalAlpha = 0.4; - - ctx.lineWidth = 1; - - ctx.textAlign = "right"; - ctx.strokeStyle = "black"; - if(this.outputs) - for(var i = 0; i < this.outputs.length; i++) - { - var slot = this.outputs[i]; - - var pos = this.getConnectionPos(false,i); - pos[0] -= this.pos[0]; - pos[1] -= this.pos[1]; - - ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA"; - ctx.beginPath(); - //ctx.rect( this.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); - - //trigger - //if(slot.node_id != null && slot.slot == -1) - // ctx.fillStyle = "#F85"; - - //if(slot.links != null && slot.links.length) - ctx.fill(); - ctx.stroke(); - - //render output name - if(render_text) - { - var text = slot.label != null ? slot.label : slot.name; - if(text) - { - ctx.fillStyle = color; - ctx.fillText(text, pos[0] - 10,pos[1] + 5); - } - } - } - - ctx.textAlign = "left"; - ctx.globalAlpha = 1.0; - - if(this.onDrawForeground) - this.onDrawForeground(ctx); - - if(this.flags.clip_area) - ctx.restore(); -} - -/* Renders the node shape */ -LGraphNode.prototype.drawNodeShape = function(ctx, fgcolor, bgcolor, no_title, selected ) -{ - //bg rect - ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - /* gradient test - var grad = ctx.createLinearGradient(0,0,0,this.size[1]); - grad.addColorStop(0, "#AAA"); - grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR); - grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR); - ctx.fillStyle = grad; - */ - - var title_height = LiteGraph.NODE_TITLE_HEIGHT; - - //render depending on shape - if(this.shape == null || this.shape == "box") - { - if(selected) - { - ctx.strokeStyle = "#CCC"; - ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5,this.size[0]+2, no_title ? (this.size[1]+2) : (this.size[1] + title_height+2) ); - ctx.strokeStyle = fgcolor; - } - - ctx.beginPath(); - ctx.rect(0.5,no_title ? 0.5 : -title_height + 0.5,this.size[0], no_title ? this.size[1] : this.size[1] + title_height); - } - else if (this.shape == "round") - { - ctx.roundRect(0,no_title ? 0 : -title_height,this.size[0], no_title ? this.size[1] : this.size[1] + title_height, 10); - } - else if (this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5,this.size[0] * 0.5, 0, Math.PI*2); - } - - ctx.fill(); - ctx.shadowColor = "transparent"; - ctx.stroke(); - - //image - if (this.bgImage && this.bgImage.width) - ctx.drawImage( this.bgImage, (this.size[0] - this.bgImage.width) * 0.5 , (this.size[1] - this.bgImage.height) * 0.5); - - if(this.bgImageUrl && !this.bgImage) - this.bgImage = this.loadImage(this.bgImageUrl); - - if(this.onDrawBackground) - this.onDrawBackground(ctx); - - //title bg - if(!no_title) - { - ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - - if(this.shape == null || this.shape == "box") - { - ctx.fillRect(0,-title_height,this.size[0],title_height); - ctx.stroke(); - } - else if (this.shape == "round") - { - ctx.roundRect(0,-title_height,this.size[0], title_height,10,0); - //ctx.fillRect(0,8,this.size[0],NODE_TITLE_HEIGHT - 12); - ctx.fill(); - ctx.stroke(); - } - - //box - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - if (this.shape == "round") - ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); - else - ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6); - ctx.fill(); - - //title text - ctx.font = "bold 12px Arial"; - if(this.name != "" && this.graph.config.canvas_scale > 0.8) - { - ctx.fillStyle = "#222"; - ctx.fillText(this.name,16,13-title_height ); - } - } -} - -/* Renders the node when collapsed */ -LGraphNode.prototype.drawNodeCollapsed = function(ctx, fgcolor, bgcolor) -{ - //draw default collapsed shape - ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; - ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; - - var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS; - - //circle shape - if(this.shape == "circle") - { - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius,0,Math.PI * 2); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.arc(this.size[0] * 0.5, this.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2); - ctx.fill(); - } - else if(this.shape == "round") //rounded box - { - ctx.beginPath(); - ctx.roundRect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.roundRect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2); - ctx.fill(); - } - else //flat box - { - ctx.beginPath(); - ctx.rect(this.size[0] * 0.5 - collapsed_radius, this.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius); - ctx.fill(); - ctx.shadowColor = "rgba(0,0,0,0)"; - ctx.stroke(); - - ctx.fillStyle = this.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; - ctx.beginPath(); - ctx.rect(this.size[0] * 0.5 - collapsed_radius*0.5, this.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius); - ctx.fill(); - } -} - /* Force align to grid */ LGraphNode.prototype.alignToGrid = function() { @@ -2946,14 +2629,14 @@ LGraphCanvas.prototype.drawFrontCanvas = function() for (var i in visible_nodes) { - var n = visible_nodes[i]; + var node = visible_nodes[i]; //transform coords system ctx.save(); - ctx.translate( n.pos[0], n.pos[1] ); + ctx.translate( node.pos[0], node.pos[1] ); //Draw - n.draw(ctx,this); + this.drawNode(node, ctx ); drawn_nodes += 1; //Restore @@ -3090,6 +2773,349 @@ LGraphCanvas.prototype.drawBgcanvas = function() this.dirty_canvas = true; //to force to repaint the front canvas with the bgcanvas } +/* Renders the LGraphNode on the canvas */ +LGraphCanvas.prototype.drawNode = function(node, ctx ) +{ + var glow = false; + + var color = node.color || LiteGraph.NODE_DEFAULT_COLOR; + //if (this.selected) color = "#88F"; + + var render_title = true; + if(node.flags.skip_title_render || node.graph.isLive()) + render_title = false; + if(node.mouseOver) + render_title = true; + + //shadow and glow + if (node.mouseOver) glow = true; + + if(node.selected) + { + /* + ctx.shadowColor = "#EEEEFF";//glow ? "#AAF" : "#000"; + ctx.shadowOffsetX = 0; + ctx.shadowOffsetY = 0; + ctx.shadowBlur = 1; + */ + } + else if(this.render_shadows) + { + ctx.shadowColor = "#111"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 4; + } + else + ctx.shadowColor = "transparent"; + + //only render if it forces it to do it + if(this.live_mode) + { + if(!node.flags.collapsed) + { + ctx.shadowColor = "transparent"; + if(node.onDrawBackground) + node.onDrawBackground(ctx); + if(node.onDrawForeground) + node.onDrawForeground(ctx); + } + + return; + } + + //draw in collapsed form + /* + if(node.flags.collapsed) + { + if(!node.onDrawCollapsed || node.onDrawCollapsed(ctx) == false) + this.drawNodeCollapsed(node, ctx, color, node.bgcolor); + return; + } + */ + + //clip if required (mask) + var shape = node.shape || "box"; + var size = new Float32Array(node.size); + if(node.flags.collapsed) + size.set([LiteGraph.NODE_COLLAPSED_WIDTH, 0]); + + //Start cliping + if(node.flags.clip_area) + { + ctx.save(); + if(shape == "box") + { + ctx.beginPath(); + ctx.rect(0,0,size[0], size[1]); + } + else if (shape == "round") + { + ctx.roundRect(0,0,size[0], size[1],10); + } + else if (shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + ctx.clip(); + } + + //draw shape + this.drawNodeShape(node, ctx, size, color, node.bgcolor, !render_title, node.selected ); + ctx.shadowColor = "transparent"; + + //connection slots + ctx.textAlign = "left"; + ctx.font = "12px Arial"; + + var render_text = node.graph.config.canvas_scale > 0.6; + + //render inputs and outputs + if(!node.flags.collapsed) + { + //input connection slots + if(node.inputs) + for(var i = 0; i < node.inputs.length; i++) + { + var slot = node.inputs[i]; + + ctx.globalAlpha = 1.0; + if (this.connecting_node != null && this.connecting_output.type != 0 && node.inputs[i].type != 0 && this.connecting_output.type != node.inputs[i].type) + ctx.globalAlpha = 0.4; + + ctx.fillStyle = slot.link != null ? "#7F7" : "#AAA"; + + var pos = node.getConnectionPos(true,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.beginPath(); + + 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); + + ctx.fill(); + + //render name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text,pos[0] + 10,pos[1] + 5); + } + } + } + + //output connection slots + if(this.connecting_node) + ctx.globalAlpha = 0.4; + + ctx.lineWidth = 1; + + ctx.textAlign = "right"; + ctx.strokeStyle = "black"; + if(node.outputs) + for(var i = 0; i < node.outputs.length; i++) + { + var slot = node.outputs[i]; + + var pos = node.getConnectionPos(false,i); + pos[0] -= node.pos[0]; + pos[1] -= node.pos[1]; + + ctx.fillStyle = slot.links && slot.links.length ? "#7F7" : "#AAA"; + 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); + + //trigger + //if(slot.node_id != null && slot.slot == -1) + // ctx.fillStyle = "#F85"; + + //if(slot.links != null && slot.links.length) + ctx.fill(); + ctx.stroke(); + + //render output name + if(render_text) + { + var text = slot.label != null ? slot.label : slot.name; + if(text) + { + ctx.fillStyle = color; + ctx.fillText(text, pos[0] - 10,pos[1] + 5); + } + } + } + + ctx.textAlign = "left"; + ctx.globalAlpha = 1.0; + + if(node.onDrawForeground) + node.onDrawForeground(ctx); + }//!collapsed + + if(node.flags.clip_area) + ctx.restore(); +} + +/* Renders the node shape */ +LGraphCanvas.prototype.drawNodeShape = function(node, ctx, size, fgcolor, bgcolor, no_title, selected ) +{ + //bg rect + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + /* gradient test + var grad = ctx.createLinearGradient(0,0,0,node.size[1]); + grad.addColorStop(0, "#AAA"); + grad.addColorStop(0.5, fgcolor || LiteGraph.NODE_DEFAULT_COLOR); + grad.addColorStop(1, bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR); + ctx.fillStyle = grad; + */ + + var title_height = LiteGraph.NODE_TITLE_HEIGHT; + + //render depending on shape + var shape = node.shape || "box"; + if(shape == "box") + { + if(selected) + { + ctx.strokeStyle = "#CCC"; + ctx.strokeRect(-0.5,no_title ? -0.5 : -title_height + -0.5, size[0]+2, no_title ? (size[1]+2) : (size[1] + title_height+2) ); + ctx.strokeStyle = fgcolor; + } + + ctx.beginPath(); + ctx.rect(0,no_title ? 0.5 : -title_height + 1,size[0]+1, no_title ? size[1] : size[1] + title_height); + } + else if (node.shape == "round") + { + ctx.roundRect(0,no_title ? 0 : -title_height,size[0], no_title ? size[1] : size[1] + title_height, 10); + } + else if (node.shape == "circle") + { + ctx.beginPath(); + ctx.arc(size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, 0, Math.PI*2); + } + + ctx.fill(); + ctx.shadowColor = "transparent"; + + //ctx.stroke(); + + //image + if (node.bgImage && node.bgImage.width) + ctx.drawImage( node.bgImage, (size[0] - node.bgImage.width) * 0.5 , (size[1] - node.bgImage.height) * 0.5); + + if(node.bgImageUrl && !node.bgImage) + node.bgImage = node.loadImage(node.bgImageUrl); + + if(node.onDrawBackground) + node.onDrawBackground(ctx); + + //title bg + if(!no_title) + { + ctx.fillStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + + if(shape == "box") + { + ctx.beginPath(); + ctx.fillRect(0,-title_height,size[0]+1,title_height); + ctx.stroke(); + } + else if (shape == "round") + { + ctx.roundRect(0,-title_height,size[0], title_height,10,0); + //ctx.fillRect(0,8,size[0],NODE_TITLE_HEIGHT - 12); + ctx.fill(); + ctx.stroke(); + } + + //box + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + if (shape == "round") + ctx.arc(title_height *0.5, title_height * -0.5, (title_height - 6) *0.5,0,Math.PI*2); + else + ctx.rect(3,-title_height + 3,title_height - 6,title_height - 6); + ctx.fill(); + + //title text + ctx.font = "bold 12px Arial"; + if(node.name != "" && node.graph.config.canvas_scale > 0.8) + { + ctx.fillStyle = "#222"; + ctx.fillText(node.name,16,13-title_height ); + } + } +} + +/* Renders the node when collapsed */ +LGraphCanvas.prototype.drawNodeCollapsed = function(node, ctx, fgcolor, bgcolor) +{ + //draw default collapsed shape + ctx.strokeStyle = fgcolor || LiteGraph.NODE_DEFAULT_COLOR; + ctx.fillStyle = bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; + + var collapsed_radius = LiteGraph.NODE_COLLAPSED_RADIUS; + + //circle shape + var shape = node.shape || "box"; + if(shape == "circle") + { + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius,0,Math.PI * 2); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.arc(node.size[0] * 0.5, node.size[1] * 0.5, collapsed_radius * 0.5,0,Math.PI * 2); + ctx.fill(); + } + else if(shape == "round") //rounded box + { + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius,2*collapsed_radius,5); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + ctx.roundRect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius,2); + ctx.fill(); + } + else //flat box + { + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius, node.size[1] * 0.5 - collapsed_radius, 2*collapsed_radius, 2*collapsed_radius); + ctx.rect(0, 0, node.size[0], collapsed_radius * 2 ); + ctx.fill(); + ctx.shadowColor = "rgba(0,0,0,0)"; + ctx.stroke(); + + ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.beginPath(); + //ctx.rect(node.size[0] * 0.5 - collapsed_radius*0.5, node.size[1] * 0.5 - collapsed_radius*0.5, collapsed_radius,collapsed_radius); + ctx.rect(collapsed_radius*0.5, collapsed_radius*0.5, collapsed_radius, collapsed_radius); + ctx.fill(); + } +} + LGraphCanvas.link_colors = ["#AAC","#ACA","#CAA"]; LGraphCanvas.prototype.drawConnections = function(ctx) @@ -3398,7 +3424,7 @@ LGraphCanvas.onMenuNodeColors = function(node, e, prev_menu) LGraphCanvas.onMenuNodeShapes = function(node,e) { - LiteGraph.createContextualMenu(["box","round","circle"], {event: e, callback: inner_clicked}); + LiteGraph.createContextualMenu(["box","round"], {event: e, callback: inner_clicked}); function inner_clicked(v) {