diff --git a/build/litegraph.js b/build/litegraph.js
index 4c125bf70..30b576438 100644
--- a/build/litegraph.js
+++ b/build/litegraph.js
@@ -374,7 +374,7 @@
var r = [];
for (var i in this.registered_node_types) {
var type = this.registered_node_types[i];
- if (filter && type.filter && type.filter != filter) {
+ if (type.filter != filter) {
continue;
}
@@ -393,6 +393,7 @@
/**
* Returns a list with all the node type categories
* @method getNodeTypesCategories
+ * @param {String} filter only nodes with ctor.filter equal can be shown
* @return {Array} array with all the names of the categories
*/
getNodeTypesCategories: function( filter ) {
@@ -401,7 +402,7 @@
var type = this.registered_node_types[i];
if ( type.category && !type.skip_list )
{
- if(filter && type.filter != filter)
+ if(type.filter != filter)
continue;
categories[type.category] = 1;
}
@@ -619,6 +620,10 @@
/**
* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
+ * supported callbacks:
+ + onNodeAdded: when a new node is added to the graph
+ + onNodeRemoved: when a node inside this graph is removed
+ + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)
*
* @class LGraph
* @constructor
@@ -677,8 +682,8 @@
//nodes
this._nodes = [];
this._nodes_by_id = {};
- this._nodes_in_order = []; //nodes that are executable sorted in execution order
- this._nodes_executable = null; //nodes that contain onExecute
+ this._nodes_in_order = []; //nodes sorted in execution order
+ this._nodes_executable = null; //nodes that contain onExecute sorted in execution order
//other scene stuff
this._groups = [];
@@ -692,6 +697,7 @@
//custom data
this.config = {};
this.vars = {};
+ this.extra = {}; //to store custom data
//timing
this.globaltime = 0;
@@ -729,6 +735,7 @@
}
graphcanvas.graph = this;
+
if (!this.list_of_graphcanvas) {
this.list_of_graphcanvas = [];
}
@@ -1955,9 +1962,13 @@
links: links,
groups: groups_info,
config: this.config,
+ extra: this.extra,
version: LiteGraph.VERSION
};
+ if(this.onSerialize)
+ this.onSerialize(data);
+
return data;
};
@@ -2050,6 +2061,12 @@
}
this.updateExecutionOrder();
+
+ this.extra = data.extra || {};
+
+ if(this.onConfigure)
+ this.onConfigure(data);
+
this._version++;
this.setDirtyCanvas(true, true);
return error;
@@ -2677,6 +2694,23 @@
return null;
};
+ /**
+ * Returns the link info in the connection of an input slot
+ * @method getInputLink
+ * @param {number} slot
+ * @return {LLink} object or null
+ */
+ LGraphNode.prototype.getInputLink = function(slot) {
+ if (!this.inputs) {
+ return null;
+ }
+ if (slot < this.inputs.length) {
+ var slot_info = this.inputs[slot];
+ return this.graph.links[ slot_info.link ];
+ }
+ return null;
+ };
+
/**
* returns the node connected in the input slot
* @method getInputNode
@@ -3137,7 +3171,7 @@
*/
LGraphNode.prototype.removeInput = function(slot) {
this.disconnectInput(slot);
- this.inputs.splice(slot, 1);
+ var slot_info = this.inputs.splice(slot, 1);
for (var i = slot; i < this.inputs.length; ++i) {
if (!this.inputs[i]) {
continue;
@@ -3150,7 +3184,7 @@
}
this.setSize( this.computeSize() );
if (this.onInputRemoved) {
- this.onInputRemoved(slot);
+ this.onInputRemoved(slot, slot_info[0] );
}
this.setDirtyCanvas(true, true);
};
@@ -4041,12 +4075,14 @@
if (!this.console) {
this.console = [];
}
+
this.console.push(msg);
if (this.console.length > LGraphNode.MAX_CONSOLE) {
this.console.shift();
}
- this.graph.onNodeTrace(this, msg);
+ if(this.graph.onNodeTrace)
+ this.graph.onNodeTrace(this, msg);
};
/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
@@ -4556,6 +4592,7 @@ LGraphNode.prototype.executeAction = function(action)
this.filter = null; //allows to filter to only accept some type of nodes in a graph
+ this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything
this.always_render_background = false;
this.render_shadows = true;
this.render_canvas_border = true;
@@ -4570,7 +4607,9 @@ LGraphNode.prototype.executeAction = function(action)
this.links_render_mode = LiteGraph.SPLINE_LINK;
- this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD
//to personalize the search box
this.onSearchBox = null;
@@ -4645,6 +4684,8 @@ LGraphNode.prototype.executeAction = function(action)
this.connecting_node = null;
this.highlighted_links = {};
+ this.dragging_canvas = false;
+
this.dirty_canvas = true;
this.dirty_bgcanvas = true;
this.dirty_area = null;
@@ -4690,6 +4731,19 @@ LGraphNode.prototype.executeAction = function(action)
this.setDirty(true, true);
};
+ /**
+ * returns the top level graph (in case there are subgraphs open on the canvas)
+ *
+ * @method getTopGraph
+ * @return {LGraph} graph
+ */
+ LGraphCanvas.prototype.getTopGraph = function()
+ {
+ if(this._graph_stack.length)
+ return this._graph_stack[0];
+ return this.graph;
+ }
+
/**
* opens a graph contained inside a node in the current graph
*
@@ -5044,8 +5098,19 @@ LGraphNode.prototype.executeAction = function(action)
/* LiteGraphCanvas input */
+ //used to block future mouse events (because of im gui)
+ LGraphCanvas.prototype.blockClick = function()
+ {
+ this.block_click = true;
+ this.last_mouseclick = 0;
+ }
+
LGraphCanvas.prototype.processMouseDown = function(e) {
- if (!this.graph) {
+
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
+ if (!this.graph) {
return;
}
@@ -5079,9 +5144,12 @@ LGraphNode.prototype.executeAction = function(action)
var skip_action = false;
var now = LiteGraph.getTime();
var is_double_click = now - this.last_mouseclick < 300;
+ this.mouse[0] = e.localX;
+ this.mouse[1] = e.localY;
+ this.graph_mouse[0] = e.canvasX;
+ this.graph_mouse[1] = e.canvasY;
+ this.last_click_position = [this.mouse[0],this.mouse[1]];
- this.canvas_mouse[0] = e.canvasX;
- this.canvas_mouse[1] = e.canvasY;
this.canvas.focus();
LiteGraph.closeAllContextMenus(ref_window);
@@ -5253,7 +5321,7 @@ LGraphNode.prototype.executeAction = function(action)
var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];
//widgets
- var widget = this.processNodeWidgets( node, this.canvas_mouse, e );
+ var widget = this.processNodeWidgets( node, this.graph_mouse, e );
if (widget) {
block_drag_node = true;
this.node_widget = [node, widget];
@@ -5400,6 +5468,9 @@ LGraphNode.prototype.executeAction = function(action)
this.resize();
}
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
if (!this.graph) {
return;
}
@@ -5407,30 +5478,42 @@ LGraphNode.prototype.executeAction = function(action)
LGraphCanvas.active_canvas = this;
this.adjustMouseEvent(e);
var mouse = [e.localX, e.localY];
+ this.mouse[0] = mouse[0];
+ this.mouse[1] = mouse[1];
var delta = [
mouse[0] - this.last_mouse[0],
mouse[1] - this.last_mouse[1]
];
this.last_mouse = mouse;
- this.canvas_mouse[0] = e.canvasX;
- this.canvas_mouse[1] = e.canvasY;
+ this.graph_mouse[0] = e.canvasX;
+ this.graph_mouse[1] = e.canvasY;
+
+ if(this.block_click)
+ {
+ e.preventDefault();
+ return false;
+ }
+
e.dragging = this.last_mouse_dragging;
if (this.node_widget) {
this.processNodeWidgets(
this.node_widget[0],
- this.canvas_mouse,
+ this.graph_mouse,
e,
this.node_widget[1]
);
this.dirty_canvas = true;
}
- if (this.dragging_rectangle) {
+ if (this.dragging_rectangle)
+ {
this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];
this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];
this.dirty_canvas = true;
- } else if (this.selected_group && !this.read_only) {
+ }
+ else if (this.selected_group && !this.read_only)
+ {
//moving/resizing a group
if (this.selected_group_resizing) {
this.selected_group.size = [
@@ -5457,18 +5540,11 @@ LGraphNode.prototype.executeAction = function(action)
}
//get node over
- var node = this.graph.getNodeOnPos(
- e.canvasX,
- e.canvasY,
- this.visible_nodes
- );
+ var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);
//remove mouseover flag
for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {
- if (
- this.graph._nodes[i].mouseOver &&
- node != this.graph._nodes[i]
- ) {
+ if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {
//mouse leave
this.graph._nodes[i].mouseOver = false;
if (this.node_over && this.node_over.onMouseLeave) {
@@ -5499,11 +5575,7 @@ LGraphNode.prototype.executeAction = function(action)
//in case the node wants to do something
if (node.onMouseMove) {
- node.onMouseMove(
- e,
- [e.canvasX - node.pos[0], e.canvasY - node.pos[1]],
- this
- );
+ node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );
}
//if dragging a link
@@ -5515,20 +5587,10 @@ LGraphNode.prototype.executeAction = function(action)
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
- var slot = this.isOverNodeInput(
- node,
- e.canvasX,
- e.canvasY,
- pos
- );
+ var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );
if (slot != -1 && node.inputs[slot]) {
var slot_type = node.inputs[slot].type;
- if (
- LiteGraph.isValidConnection(
- this.connecting_output.type,
- slot_type
- )
- ) {
+ if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {
this._highlight_input = pos;
}
} else {
@@ -5554,7 +5616,7 @@ LGraphNode.prototype.executeAction = function(action)
this.canvas.style.cursor = "crosshair";
}
}
- } else { //outside
+ } else { //not over a node
//search for link connector
var over_link = null;
@@ -5582,13 +5644,16 @@ LGraphNode.prototype.executeAction = function(action)
if (this.canvas) {
this.canvas.style.cursor = "";
}
- }
+ } //end
+ //send event to node if capturing input (used with widgets that allow drag outside of the area of the node)
if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {
this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);
}
+ //node being dragged
if (this.node_dragged && !this.live_mode) {
+ //console.log("draggin!",this.selected_nodes);
for (var i in this.selected_nodes) {
var n = this.selected_nodes[i];
n.pos[0] += delta[0] / this.ds.scale;
@@ -5622,9 +5687,12 @@ LGraphNode.prototype.executeAction = function(action)
* @method processMouseUp
**/
LGraphCanvas.prototype.processMouseUp = function(e) {
- if (!this.graph) {
+
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
+ if (!this.graph)
return;
- }
var window = this.getCanvasWindow();
var document = window.document;
@@ -5639,12 +5707,19 @@ LGraphNode.prototype.executeAction = function(action)
var now = LiteGraph.getTime();
e.click_time = now - this.last_mouseclick;
this.last_mouse_dragging = false;
+ this.last_click_position = null;
+
+ if(this.block_click)
+ {
+ console.log("foo");
+ this.block_click = false; //used to avoid sending twice a click in a immediate button
+ }
if (e.which == 1) {
if( this.node_widget )
{
- this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e );
+ this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );
}
//left button
@@ -6263,10 +6338,8 @@ LGraphNode.prototype.executeAction = function(action)
* selects several nodes (or adds them to the current selection)
* @method selectNodes
**/
- LGraphCanvas.prototype.selectNodes = function(
- nodes,
- add_to_current_selection
- ) {
+ LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )
+ {
if (!add_to_current_selection) {
this.deselectAllNodes();
}
@@ -6562,7 +6635,7 @@ LGraphNode.prototype.executeAction = function(action)
* @method draw
**/
LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {
- if (!this.canvas) {
+ if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {
return;
}
@@ -6643,7 +6716,7 @@ LGraphNode.prototype.executeAction = function(action)
if (this.bgcanvas == this.canvas) {
this.drawBackCanvas();
} else {
- ctx.drawImage(this.bgcanvas, 0, 0);
+ ctx.drawImage( this.bgcanvas, 0, 0 );
}
//rendering
@@ -6712,7 +6785,7 @@ LGraphNode.prototype.executeAction = function(action)
this.renderLink(
ctx,
this.connecting_pos,
- [this.canvas_mouse[0], this.canvas_mouse[1]],
+ [this.graph_mouse[0], this.graph_mouse[1]],
null,
false,
null,
@@ -6786,6 +6859,12 @@ LGraphNode.prototype.executeAction = function(action)
ctx.restore();
}
+ //draws panel in the corner
+ if (this._graph_stack && this._graph_stack.length) {
+ this.drawSubgraphPanel( ctx );
+ }
+
+
if (this.onDrawOverlay) {
this.onDrawOverlay(ctx);
}
@@ -6801,13 +6880,151 @@ LGraphNode.prototype.executeAction = function(action)
}
};
+ /**
+ * draws the panel in the corner that shows subgraph properties
+ * @method drawSubgraphPanel
+ **/
+ LGraphCanvas.prototype.drawSubgraphPanel = function(ctx) {
+ var subgraph = this.graph;
+ var subnode = subgraph._subgraph_node;
+ if(!subnode)
+ {
+ console.warn("subgraph without subnode");
+ return;
+ }
+
+ var num = subnode.inputs ? subnode.inputs.length : 0;
+ var w = 300;
+ var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);
+
+ ctx.fillStyle = "#111";
+ ctx.globalAlpha = 0.8;
+ ctx.beginPath();
+ ctx.roundRect(10,10,w, (num + 1) * h + 50,8 );
+ ctx.fill();
+ ctx.globalAlpha = 1;
+
+ ctx.fillStyle = "#888";
+ ctx.font = "14px Arial";
+ ctx.textAlign = "left";
+ ctx.fillText( "Graph Inputs", 20, 34 );
+ var pos = this.mouse;
+
+ if( this.drawButton( w - 20, 20,20,20, "X", "#151515" ) )
+ {
+ this.closeSubgraph();
+ return;
+ }
+
+ var y = 50;
+ ctx.font = "20px Arial";
+ if(subnode.inputs)
+ for(var i = 0; i < subnode.inputs.length; ++i)
+ {
+ var input = subnode.inputs[i];
+ if(input.not_subgraph_input)
+ continue;
+
+ //input button clicked
+ if( this.drawButton( 20,y+2,w - 20, h - 2 ) )
+ {
+ var type = subnode.constructor.input_node_type || "graph/input";
+ var newnode = LiteGraph.createNode( type );
+ if(newnode)
+ {
+ subgraph.add( newnode );
+ this.block_click = false;
+ this.last_click_position = null;
+ this.selectNodes([newnode]);
+ this.node_dragged = newnode;
+ this.dragging_canvas = false;
+ newnode.setProperty("name",input.name);
+ newnode.setProperty("type",input.type);
+ this.node_dragged.pos[0] = this.graph_mouse[0] - 5;
+ this.node_dragged.pos[1] = this.graph_mouse[1] - 5;
+ }
+ else
+ console.error("graph input node not found:",type);
+ }
+
+ ctx.fillStyle = "#9C9";
+ ctx.beginPath();
+ ctx.arc(w - 16,y + h * 0.5,5,0,2*Math.PI);
+ ctx.fill();
+
+ ctx.fillStyle = "#AAA";
+ ctx.fillText( input.name, 50, y + h*0.75 );
+ var tw = ctx.measureText( input.name );
+ ctx.fillStyle = "#777";
+ ctx.fillText( input.type, 50 + tw.width + 10, y + h*0.75 );
+
+ y += h;
+ }
+
+ //add + button
+ if( this.drawButton( 20,y+2,w - 20, h - 2, "+", "#151515", "#222" ) )
+ {
+ this.showSubgraphPropertiesDialog( subnode );
+ }
+ }
+
+ //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm
+ LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )
+ {
+ var ctx = this.ctx;
+ bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;
+ hovercolor = hovercolor || "#555";
+ textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;
+
+ var pos = this.mouse;
+ var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ pos = this.last_click_position;
+ var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+
+ ctx.fillStyle = hover ? hovercolor : bgcolor;
+ if(clicked)
+ ctx.fillStyle = "#AAA";
+ ctx.beginPath();
+ ctx.roundRect(x,y,w,h,4 );
+ ctx.fill();
+
+ if(text != null)
+ {
+ if(text.constructor == String)
+ {
+ ctx.fillStyle = textcolor;
+ ctx.textAlign = "center";
+ ctx.font = ((h * 0.65)|0) + "px Arial";
+ ctx.fillText( text, x + w * 0.5,y + h * 0.75 );
+ ctx.textAlign = "left";
+ }
+ }
+
+ var was_clicked = clicked && !this.block_click;
+ if(clicked)
+ this.blockClick();
+ return was_clicked;
+ }
+
+ LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )
+ {
+ var pos = this.mouse;
+ var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ pos = this.last_click_position;
+ var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ var was_clicked = clicked && !this.block_click;
+ if(clicked && hold_click)
+ this.blockClick();
+ return was_clicked;
+ }
+
/**
* draws some useful stats in the corner of the canvas
* @method renderInfo
**/
LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {
- x = x || 0;
- y = y || 0;
+ x = x || 10;
+ y = y || this.canvas.height - 80;
ctx.save();
ctx.translate(x, y);
@@ -7520,7 +7737,7 @@ LGraphNode.prototype.executeAction = function(action)
ctx.shadowColor = "transparent";
if (node.onDrawBackground) {
- node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse );
+ node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );
}
//title bg (remember, it is rendered ABOVE the node)
@@ -7670,9 +7887,10 @@ LGraphNode.prototype.executeAction = function(action)
//subgraph box
if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {
- ctx.fillStyle = "#555";
var w = LiteGraph.NODE_TITLE_HEIGHT;
var x = node.size[0] - w;
+ var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );
+ ctx.fillStyle = over ? "#888" : "#555";
if( shape == LiteGraph.BOX_SHAPE || low_quality)
ctx.fillRect(x+2, -w+2, w-4, w-4);
else
@@ -8558,6 +8776,7 @@ LGraphNode.prototype.executeAction = function(action)
}
} else if (delta) { //clicked in arrow, used for combos
var index = -1;
+ this.last_mouseclick = 0; //avoids dobl click event
if(values.constructor === Object)
index = values_list.indexOf( String( w.value ) ) + delta;
else
@@ -8836,8 +9055,11 @@ LGraphNode.prototype.executeAction = function(action)
LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) {
var canvas = LGraphCanvas.active_canvas;
var ref_window = canvas.getCanvasWindow();
+ var graph = canvas.graph;
+ if(!graph)
+ return;
- var values = LiteGraph.getNodeTypesCategories( canvas.filter );
+ var values = LiteGraph.getNodeTypesCategories( canvas.filter || graph.filter );
var entries = [];
for (var i in values) {
if (values[i]) {
@@ -8850,7 +9072,7 @@ LGraphNode.prototype.executeAction = function(action)
function inner_clicked(v, option, e) {
var category = v.value;
- var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter );
+ var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter || graph.filter );
var values = [];
for (var i in node_types) {
if (!node_types[i].skip_list) {
@@ -10085,14 +10307,16 @@ LGraphNode.prototype.executeAction = function(action)
for(var i = 0; i < node.inputs.length; ++i)
{
var input = node.inputs[i];
- var html = "NameType";
+ if(input.not_subgraph_input)
+ continue;
+ var html = " ";
var elem = panel.addHTML(html,"subgraph_property");
elem.dataset["name"] = input.name;
elem.dataset["slot"] = i;
- elem.querySelector(".name").value = input.name;
- elem.querySelector(".type").value = input.type;
+ elem.querySelector(".name").innerText = input.name;
+ elem.querySelector(".type").innerText = input.type;
elem.querySelector("button").addEventListener("click",function(e){
- node.removeInput( this.parentNode.dataset["slot"] );
+ node.removeInput( Number( this.parentNode.dataset["slot"] ) );
inner_refresh();
});
}
@@ -11583,6 +11807,43 @@ if (typeof exports != "undefined") {
}
};
+ Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)
+ {
+ if(this.flags.collapsed)
+ return;
+
+ var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
+
+ //button
+ var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);
+ ctx.fillStyle = over ? "#555" : "#222";
+ ctx.beginPath();
+ ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);
+ ctx.fill();
+
+ //button
+ ctx.textAlign = "center";
+ ctx.font = "24px Arial";
+ ctx.fillStyle = over ? "#DDD" : "#999";
+ ctx.fillText( "+", this.size[0] * 0.5, y + 24 );
+ }
+
+ Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)
+ {
+ var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
+ if(localpos[1] > y)
+ {
+ graphcanvas.showSubgraphPropertiesDialog(this);
+ }
+ }
+
+ Subgraph.prototype.computeSize = function()
+ {
+ var num_inputs = this.inputs ? this.inputs.length : 0;
+ var num_outputs = this.outputs ? this.outputs.length : 0;
+ return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];
+ }
+
//**** INPUTS ***********************************
Subgraph.prototype.onSubgraphTrigger = function(event, param) {
var slot = this.findOutputSlot(event);
@@ -18469,6 +18730,20 @@ void main() {\n\
LGraphTextureToViewport.desc = "Texture to viewport";
LGraphTextureToViewport._prev_viewport = new Float32Array(4);
+
+ LGraphTextureToViewport.prototype.onDrawBackground = function( ctx )
+ {
+ if ( this.flags.collapsed || this.size[1] <= 40 )
+ return;
+
+ var tex = this.getInputData(0);
+ if (!tex) {
+ return;
+ }
+
+ ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40);
+ }
+
LGraphTextureToViewport.prototype.onExecute = function() {
var tex = this.getInputData(0);
if (!tex) {
diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css
index d286949b6..4b1239602 100755
--- a/css/litegraph-editor.css
+++ b/css/litegraph-editor.css
@@ -181,7 +181,7 @@
display: inline-block;
width: 90px;
height: 15px;
- background-image: url("../demo/imgs/load-progress-empty.png");
+ background-image: url("../editor/imgs/load-progress-empty.png");
}
.litegraph-editor .cpuload .fgload,
@@ -190,7 +190,7 @@
width: 4px;
height: 15px;
max-width: 90px;
- background-image: url("../demo/imgs/load-progress-full.png");
+ background-image: url("../editor/imgs/load-progress-full.png");
}
.litegraph-editor textarea.code, .litegraph-editor div.code {
diff --git a/css/litegraph.css b/css/litegraph.css
index 6f03567a4..80a203ad4 100755
--- a/css/litegraph.css
+++ b/css/litegraph.css
@@ -261,12 +261,11 @@
.litegraph .dialog .dialog-content {
height: calc(100% - 90px);
- width: calc(100% - 10px);
+ width: 100%;
min-height: 100px;
- /*background-color: black;*/
- padding: 4px;
display: inline-block;
color: #AAA;
+ /*background-color: black;*/
}
.litegraph .dialog .dialog-content h3 {
@@ -308,7 +307,7 @@
.litegraph .dialog .property {
margin-bottom: 2px;
- padding: 0;
+ padding: 4px;
}
.litegraph .dialog .property_name {
@@ -362,7 +361,7 @@
}
.litegraph .subgraph_property {
- padding-bottom: 4px;
+ padding: 4px;
}
.litegraph .subgraph_property:hover {
diff --git a/demo/demodata/audio.wav b/editor/demodata/audio.wav
similarity index 100%
rename from demo/demodata/audio.wav
rename to editor/demodata/audio.wav
diff --git a/demo/demodata/impulse.wav b/editor/demodata/impulse.wav
similarity index 100%
rename from demo/demodata/impulse.wav
rename to editor/demodata/impulse.wav
diff --git a/demo/demodata/video.webm b/editor/demodata/video.webm
similarity index 100%
rename from demo/demodata/video.webm
rename to editor/demodata/video.webm
diff --git a/demo/examples/audio.json b/editor/examples/audio.json
similarity index 100%
rename from demo/examples/audio.json
rename to editor/examples/audio.json
diff --git a/demo/examples/audio_delay.json b/editor/examples/audio_delay.json
similarity index 100%
rename from demo/examples/audio_delay.json
rename to editor/examples/audio_delay.json
diff --git a/demo/examples/audio_reverb.json b/editor/examples/audio_reverb.json
similarity index 100%
rename from demo/examples/audio_reverb.json
rename to editor/examples/audio_reverb.json
diff --git a/demo/examples/benchmark.json b/editor/examples/benchmark.json
similarity index 100%
rename from demo/examples/benchmark.json
rename to editor/examples/benchmark.json
diff --git a/demo/examples/features.json b/editor/examples/features.json
similarity index 100%
rename from demo/examples/features.json
rename to editor/examples/features.json
diff --git a/demo/examples/midi_generation.json b/editor/examples/midi_generation.json
similarity index 100%
rename from demo/examples/midi_generation.json
rename to editor/examples/midi_generation.json
diff --git a/demo/examples/subgraph.json b/editor/examples/subgraph.json
similarity index 100%
rename from demo/examples/subgraph.json
rename to editor/examples/subgraph.json
diff --git a/demo/imgs/grid.png b/editor/imgs/grid.png
similarity index 100%
rename from demo/imgs/grid.png
rename to editor/imgs/grid.png
diff --git a/demo/imgs/icon-edit.png b/editor/imgs/icon-edit.png
similarity index 100%
rename from demo/imgs/icon-edit.png
rename to editor/imgs/icon-edit.png
diff --git a/demo/imgs/icon-gear.png b/editor/imgs/icon-gear.png
similarity index 100%
rename from demo/imgs/icon-gear.png
rename to editor/imgs/icon-gear.png
diff --git a/demo/imgs/icon-load.png b/editor/imgs/icon-load.png
similarity index 100%
rename from demo/imgs/icon-load.png
rename to editor/imgs/icon-load.png
diff --git a/demo/imgs/icon-maximize.png b/editor/imgs/icon-maximize.png
similarity index 100%
rename from demo/imgs/icon-maximize.png
rename to editor/imgs/icon-maximize.png
diff --git a/demo/imgs/icon-play.png b/editor/imgs/icon-play.png
similarity index 100%
rename from demo/imgs/icon-play.png
rename to editor/imgs/icon-play.png
diff --git a/demo/imgs/icon-playstep.png b/editor/imgs/icon-playstep.png
similarity index 100%
rename from demo/imgs/icon-playstep.png
rename to editor/imgs/icon-playstep.png
diff --git a/demo/imgs/icon-record.png b/editor/imgs/icon-record.png
similarity index 100%
rename from demo/imgs/icon-record.png
rename to editor/imgs/icon-record.png
diff --git a/demo/imgs/icon-save.png b/editor/imgs/icon-save.png
similarity index 100%
rename from demo/imgs/icon-save.png
rename to editor/imgs/icon-save.png
diff --git a/demo/imgs/icon-stop.png b/editor/imgs/icon-stop.png
similarity index 100%
rename from demo/imgs/icon-stop.png
rename to editor/imgs/icon-stop.png
diff --git a/demo/imgs/load-progress-empty.png b/editor/imgs/load-progress-empty.png
similarity index 100%
rename from demo/imgs/load-progress-empty.png
rename to editor/imgs/load-progress-empty.png
diff --git a/demo/imgs/load-progress-full.png b/editor/imgs/load-progress-full.png
similarity index 100%
rename from demo/imgs/load-progress-full.png
rename to editor/imgs/load-progress-full.png
diff --git a/demo/imgs/load-progress-grey.png b/editor/imgs/load-progress-grey.png
similarity index 100%
rename from demo/imgs/load-progress-grey.png
rename to editor/imgs/load-progress-grey.png
diff --git a/demo/imgs/play-icons-light-alpha.png b/editor/imgs/play-icons-light-alpha.png
similarity index 100%
rename from demo/imgs/play-icons-light-alpha.png
rename to editor/imgs/play-icons-light-alpha.png
diff --git a/demo/imgs/play-icons-light.png b/editor/imgs/play-icons-light.png
similarity index 100%
rename from demo/imgs/play-icons-light.png
rename to editor/imgs/play-icons-light.png
diff --git a/demo/imgs/play-icons.png b/editor/imgs/play-icons.png
similarity index 100%
rename from demo/imgs/play-icons.png
rename to editor/imgs/play-icons.png
diff --git a/demo/index.html b/editor/index.html
similarity index 100%
rename from demo/index.html
rename to editor/index.html
diff --git a/demo/js/code.js b/editor/js/code.js
similarity index 99%
rename from demo/js/code.js
rename to editor/js/code.js
index 4f6ecb712..4ff061cca 100644
--- a/demo/js/code.js
+++ b/editor/js/code.js
@@ -162,7 +162,5 @@ function enableWebGL()
gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT );
gl.viewport(0,0,gl.canvas.width, gl.canvas.height );
}
-
-
}
}
diff --git a/demo/js/demos.js b/editor/js/demos.js
similarity index 100%
rename from demo/js/demos.js
rename to editor/js/demos.js
diff --git a/demo/js/libs/audiosynth.js b/editor/js/libs/audiosynth.js
similarity index 100%
rename from demo/js/libs/audiosynth.js
rename to editor/js/libs/audiosynth.js
diff --git a/demo/js/libs/gl-matrix-min.js b/editor/js/libs/gl-matrix-min.js
similarity index 100%
rename from demo/js/libs/gl-matrix-min.js
rename to editor/js/libs/gl-matrix-min.js
diff --git a/demo/js/libs/litegl.js b/editor/js/libs/litegl.js
similarity index 100%
rename from demo/js/libs/litegl.js
rename to editor/js/libs/litegl.js
diff --git a/demo/js/libs/midi-parser.js b/editor/js/libs/midi-parser.js
similarity index 100%
rename from demo/js/libs/midi-parser.js
rename to editor/js/libs/midi-parser.js
diff --git a/demo/style.css b/editor/style.css
similarity index 100%
rename from demo/style.css
rename to editor/style.css
diff --git a/src/litegraph.js b/src/litegraph.js
index 6ca05a999..eaea5ab5f 100755
--- a/src/litegraph.js
+++ b/src/litegraph.js
@@ -372,7 +372,7 @@
var r = [];
for (var i in this.registered_node_types) {
var type = this.registered_node_types[i];
- if (filter && type.filter && type.filter != filter) {
+ if (type.filter != filter) {
continue;
}
@@ -391,6 +391,7 @@
/**
* Returns a list with all the node type categories
* @method getNodeTypesCategories
+ * @param {String} filter only nodes with ctor.filter equal can be shown
* @return {Array} array with all the names of the categories
*/
getNodeTypesCategories: function( filter ) {
@@ -399,7 +400,7 @@
var type = this.registered_node_types[i];
if ( type.category && !type.skip_list )
{
- if(filter && type.filter != filter)
+ if(type.filter != filter)
continue;
categories[type.category] = 1;
}
@@ -617,6 +618,10 @@
/**
* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
+ * supported callbacks:
+ + onNodeAdded: when a new node is added to the graph
+ + onNodeRemoved: when a node inside this graph is removed
+ + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected)
*
* @class LGraph
* @constructor
@@ -675,8 +680,8 @@
//nodes
this._nodes = [];
this._nodes_by_id = {};
- this._nodes_in_order = []; //nodes that are executable sorted in execution order
- this._nodes_executable = null; //nodes that contain onExecute
+ this._nodes_in_order = []; //nodes sorted in execution order
+ this._nodes_executable = null; //nodes that contain onExecute sorted in execution order
//other scene stuff
this._groups = [];
@@ -690,6 +695,7 @@
//custom data
this.config = {};
this.vars = {};
+ this.extra = {}; //to store custom data
//timing
this.globaltime = 0;
@@ -727,6 +733,7 @@
}
graphcanvas.graph = this;
+
if (!this.list_of_graphcanvas) {
this.list_of_graphcanvas = [];
}
@@ -1953,9 +1960,13 @@
links: links,
groups: groups_info,
config: this.config,
+ extra: this.extra,
version: LiteGraph.VERSION
};
+ if(this.onSerialize)
+ this.onSerialize(data);
+
return data;
};
@@ -2048,6 +2059,12 @@
}
this.updateExecutionOrder();
+
+ this.extra = data.extra || {};
+
+ if(this.onConfigure)
+ this.onConfigure(data);
+
this._version++;
this.setDirtyCanvas(true, true);
return error;
@@ -4056,12 +4073,14 @@
if (!this.console) {
this.console = [];
}
+
this.console.push(msg);
if (this.console.length > LGraphNode.MAX_CONSOLE) {
this.console.shift();
}
- this.graph.onNodeTrace(this, msg);
+ if(this.graph.onNodeTrace)
+ this.graph.onNodeTrace(this, msg);
};
/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
@@ -4571,6 +4590,7 @@ LGraphNode.prototype.executeAction = function(action)
this.filter = null; //allows to filter to only accept some type of nodes in a graph
+ this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything
this.always_render_background = false;
this.render_shadows = true;
this.render_canvas_border = true;
@@ -4585,7 +4605,9 @@ LGraphNode.prototype.executeAction = function(action)
this.links_render_mode = LiteGraph.SPLINE_LINK;
- this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle
+ this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD
//to personalize the search box
this.onSearchBox = null;
@@ -4660,6 +4682,8 @@ LGraphNode.prototype.executeAction = function(action)
this.connecting_node = null;
this.highlighted_links = {};
+ this.dragging_canvas = false;
+
this.dirty_canvas = true;
this.dirty_bgcanvas = true;
this.dirty_area = null;
@@ -4705,6 +4729,19 @@ LGraphNode.prototype.executeAction = function(action)
this.setDirty(true, true);
};
+ /**
+ * returns the top level graph (in case there are subgraphs open on the canvas)
+ *
+ * @method getTopGraph
+ * @return {LGraph} graph
+ */
+ LGraphCanvas.prototype.getTopGraph = function()
+ {
+ if(this._graph_stack.length)
+ return this._graph_stack[0];
+ return this.graph;
+ }
+
/**
* opens a graph contained inside a node in the current graph
*
@@ -5059,8 +5096,19 @@ LGraphNode.prototype.executeAction = function(action)
/* LiteGraphCanvas input */
+ //used to block future mouse events (because of im gui)
+ LGraphCanvas.prototype.blockClick = function()
+ {
+ this.block_click = true;
+ this.last_mouseclick = 0;
+ }
+
LGraphCanvas.prototype.processMouseDown = function(e) {
- if (!this.graph) {
+
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
+ if (!this.graph) {
return;
}
@@ -5094,9 +5142,12 @@ LGraphNode.prototype.executeAction = function(action)
var skip_action = false;
var now = LiteGraph.getTime();
var is_double_click = now - this.last_mouseclick < 300;
+ this.mouse[0] = e.localX;
+ this.mouse[1] = e.localY;
+ this.graph_mouse[0] = e.canvasX;
+ this.graph_mouse[1] = e.canvasY;
+ this.last_click_position = [this.mouse[0],this.mouse[1]];
- this.canvas_mouse[0] = e.canvasX;
- this.canvas_mouse[1] = e.canvasY;
this.canvas.focus();
LiteGraph.closeAllContextMenus(ref_window);
@@ -5268,7 +5319,7 @@ LGraphNode.prototype.executeAction = function(action)
var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]];
//widgets
- var widget = this.processNodeWidgets( node, this.canvas_mouse, e );
+ var widget = this.processNodeWidgets( node, this.graph_mouse, e );
if (widget) {
block_drag_node = true;
this.node_widget = [node, widget];
@@ -5415,6 +5466,9 @@ LGraphNode.prototype.executeAction = function(action)
this.resize();
}
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
if (!this.graph) {
return;
}
@@ -5422,30 +5476,42 @@ LGraphNode.prototype.executeAction = function(action)
LGraphCanvas.active_canvas = this;
this.adjustMouseEvent(e);
var mouse = [e.localX, e.localY];
+ this.mouse[0] = mouse[0];
+ this.mouse[1] = mouse[1];
var delta = [
mouse[0] - this.last_mouse[0],
mouse[1] - this.last_mouse[1]
];
this.last_mouse = mouse;
- this.canvas_mouse[0] = e.canvasX;
- this.canvas_mouse[1] = e.canvasY;
+ this.graph_mouse[0] = e.canvasX;
+ this.graph_mouse[1] = e.canvasY;
+
+ if(this.block_click)
+ {
+ e.preventDefault();
+ return false;
+ }
+
e.dragging = this.last_mouse_dragging;
if (this.node_widget) {
this.processNodeWidgets(
this.node_widget[0],
- this.canvas_mouse,
+ this.graph_mouse,
e,
this.node_widget[1]
);
this.dirty_canvas = true;
}
- if (this.dragging_rectangle) {
+ if (this.dragging_rectangle)
+ {
this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0];
this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1];
this.dirty_canvas = true;
- } else if (this.selected_group && !this.read_only) {
+ }
+ else if (this.selected_group && !this.read_only)
+ {
//moving/resizing a group
if (this.selected_group_resizing) {
this.selected_group.size = [
@@ -5472,18 +5538,11 @@ LGraphNode.prototype.executeAction = function(action)
}
//get node over
- var node = this.graph.getNodeOnPos(
- e.canvasX,
- e.canvasY,
- this.visible_nodes
- );
+ var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes);
//remove mouseover flag
for (var i = 0, l = this.graph._nodes.length; i < l; ++i) {
- if (
- this.graph._nodes[i].mouseOver &&
- node != this.graph._nodes[i]
- ) {
+ if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) {
//mouse leave
this.graph._nodes[i].mouseOver = false;
if (this.node_over && this.node_over.onMouseLeave) {
@@ -5514,11 +5573,7 @@ LGraphNode.prototype.executeAction = function(action)
//in case the node wants to do something
if (node.onMouseMove) {
- node.onMouseMove(
- e,
- [e.canvasX - node.pos[0], e.canvasY - node.pos[1]],
- this
- );
+ node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this );
}
//if dragging a link
@@ -5530,20 +5585,10 @@ LGraphNode.prototype.executeAction = function(action)
//mouse on top of the corner box, don't know what to do
} else {
//check if I have a slot below de mouse
- var slot = this.isOverNodeInput(
- node,
- e.canvasX,
- e.canvasY,
- pos
- );
+ var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos );
if (slot != -1 && node.inputs[slot]) {
var slot_type = node.inputs[slot].type;
- if (
- LiteGraph.isValidConnection(
- this.connecting_output.type,
- slot_type
- )
- ) {
+ if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) {
this._highlight_input = pos;
}
} else {
@@ -5569,7 +5614,7 @@ LGraphNode.prototype.executeAction = function(action)
this.canvas.style.cursor = "crosshair";
}
}
- } else { //outside
+ } else { //not over a node
//search for link connector
var over_link = null;
@@ -5597,13 +5642,16 @@ LGraphNode.prototype.executeAction = function(action)
if (this.canvas) {
this.canvas.style.cursor = "";
}
- }
+ } //end
+ //send event to node if capturing input (used with widgets that allow drag outside of the area of the node)
if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) {
this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this);
}
+ //node being dragged
if (this.node_dragged && !this.live_mode) {
+ //console.log("draggin!",this.selected_nodes);
for (var i in this.selected_nodes) {
var n = this.selected_nodes[i];
n.pos[0] += delta[0] / this.ds.scale;
@@ -5637,9 +5685,12 @@ LGraphNode.prototype.executeAction = function(action)
* @method processMouseUp
**/
LGraphCanvas.prototype.processMouseUp = function(e) {
- if (!this.graph) {
+
+ if( this.set_canvas_dirty_on_mouse_event )
+ this.dirty_canvas = true;
+
+ if (!this.graph)
return;
- }
var window = this.getCanvasWindow();
var document = window.document;
@@ -5654,12 +5705,19 @@ LGraphNode.prototype.executeAction = function(action)
var now = LiteGraph.getTime();
e.click_time = now - this.last_mouseclick;
this.last_mouse_dragging = false;
+ this.last_click_position = null;
+
+ if(this.block_click)
+ {
+ console.log("foo");
+ this.block_click = false; //used to avoid sending twice a click in a immediate button
+ }
if (e.which == 1) {
if( this.node_widget )
{
- this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e );
+ this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e );
}
//left button
@@ -6278,10 +6336,8 @@ LGraphNode.prototype.executeAction = function(action)
* selects several nodes (or adds them to the current selection)
* @method selectNodes
**/
- LGraphCanvas.prototype.selectNodes = function(
- nodes,
- add_to_current_selection
- ) {
+ LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection )
+ {
if (!add_to_current_selection) {
this.deselectAllNodes();
}
@@ -6577,7 +6633,7 @@ LGraphNode.prototype.executeAction = function(action)
* @method draw
**/
LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) {
- if (!this.canvas) {
+ if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) {
return;
}
@@ -6658,7 +6714,7 @@ LGraphNode.prototype.executeAction = function(action)
if (this.bgcanvas == this.canvas) {
this.drawBackCanvas();
} else {
- ctx.drawImage(this.bgcanvas, 0, 0);
+ ctx.drawImage( this.bgcanvas, 0, 0 );
}
//rendering
@@ -6727,7 +6783,7 @@ LGraphNode.prototype.executeAction = function(action)
this.renderLink(
ctx,
this.connecting_pos,
- [this.canvas_mouse[0], this.canvas_mouse[1]],
+ [this.graph_mouse[0], this.graph_mouse[1]],
null,
false,
null,
@@ -6801,6 +6857,12 @@ LGraphNode.prototype.executeAction = function(action)
ctx.restore();
}
+ //draws panel in the corner
+ if (this._graph_stack && this._graph_stack.length) {
+ this.drawSubgraphPanel( ctx );
+ }
+
+
if (this.onDrawOverlay) {
this.onDrawOverlay(ctx);
}
@@ -6816,13 +6878,151 @@ LGraphNode.prototype.executeAction = function(action)
}
};
+ /**
+ * draws the panel in the corner that shows subgraph properties
+ * @method drawSubgraphPanel
+ **/
+ LGraphCanvas.prototype.drawSubgraphPanel = function(ctx) {
+ var subgraph = this.graph;
+ var subnode = subgraph._subgraph_node;
+ if(!subnode)
+ {
+ console.warn("subgraph without subnode");
+ return;
+ }
+
+ var num = subnode.inputs ? subnode.inputs.length : 0;
+ var w = 300;
+ var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6);
+
+ ctx.fillStyle = "#111";
+ ctx.globalAlpha = 0.8;
+ ctx.beginPath();
+ ctx.roundRect(10,10,w, (num + 1) * h + 50,8 );
+ ctx.fill();
+ ctx.globalAlpha = 1;
+
+ ctx.fillStyle = "#888";
+ ctx.font = "14px Arial";
+ ctx.textAlign = "left";
+ ctx.fillText( "Graph Inputs", 20, 34 );
+ var pos = this.mouse;
+
+ if( this.drawButton( w - 20, 20,20,20, "X", "#151515" ) )
+ {
+ this.closeSubgraph();
+ return;
+ }
+
+ var y = 50;
+ ctx.font = "20px Arial";
+ if(subnode.inputs)
+ for(var i = 0; i < subnode.inputs.length; ++i)
+ {
+ var input = subnode.inputs[i];
+ if(input.not_subgraph_input)
+ continue;
+
+ //input button clicked
+ if( this.drawButton( 20,y+2,w - 20, h - 2 ) )
+ {
+ var type = subnode.constructor.input_node_type || "graph/input";
+ var newnode = LiteGraph.createNode( type );
+ if(newnode)
+ {
+ subgraph.add( newnode );
+ this.block_click = false;
+ this.last_click_position = null;
+ this.selectNodes([newnode]);
+ this.node_dragged = newnode;
+ this.dragging_canvas = false;
+ newnode.setProperty("name",input.name);
+ newnode.setProperty("type",input.type);
+ this.node_dragged.pos[0] = this.graph_mouse[0] - 5;
+ this.node_dragged.pos[1] = this.graph_mouse[1] - 5;
+ }
+ else
+ console.error("graph input node not found:",type);
+ }
+
+ ctx.fillStyle = "#9C9";
+ ctx.beginPath();
+ ctx.arc(w - 16,y + h * 0.5,5,0,2*Math.PI);
+ ctx.fill();
+
+ ctx.fillStyle = "#AAA";
+ ctx.fillText( input.name, 50, y + h*0.75 );
+ var tw = ctx.measureText( input.name );
+ ctx.fillStyle = "#777";
+ ctx.fillText( input.type, 50 + tw.width + 10, y + h*0.75 );
+
+ y += h;
+ }
+
+ //add + button
+ if( this.drawButton( 20,y+2,w - 20, h - 2, "+", "#151515", "#222" ) )
+ {
+ this.showSubgraphPropertiesDialog( subnode );
+ }
+ }
+
+ //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm
+ LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor )
+ {
+ var ctx = this.ctx;
+ bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR;
+ hovercolor = hovercolor || "#555";
+ textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR;
+
+ var pos = this.mouse;
+ var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ pos = this.last_click_position;
+ var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+
+ ctx.fillStyle = hover ? hovercolor : bgcolor;
+ if(clicked)
+ ctx.fillStyle = "#AAA";
+ ctx.beginPath();
+ ctx.roundRect(x,y,w,h,4 );
+ ctx.fill();
+
+ if(text != null)
+ {
+ if(text.constructor == String)
+ {
+ ctx.fillStyle = textcolor;
+ ctx.textAlign = "center";
+ ctx.font = ((h * 0.65)|0) + "px Arial";
+ ctx.fillText( text, x + w * 0.5,y + h * 0.75 );
+ ctx.textAlign = "left";
+ }
+ }
+
+ var was_clicked = clicked && !this.block_click;
+ if(clicked)
+ this.blockClick();
+ return was_clicked;
+ }
+
+ LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click )
+ {
+ var pos = this.mouse;
+ var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ pos = this.last_click_position;
+ var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h );
+ var was_clicked = clicked && !this.block_click;
+ if(clicked && hold_click)
+ this.blockClick();
+ return was_clicked;
+ }
+
/**
* draws some useful stats in the corner of the canvas
* @method renderInfo
**/
LGraphCanvas.prototype.renderInfo = function(ctx, x, y) {
- x = x || 0;
- y = y || 0;
+ x = x || 10;
+ y = y || this.canvas.height - 80;
ctx.save();
ctx.translate(x, y);
@@ -7535,7 +7735,7 @@ LGraphNode.prototype.executeAction = function(action)
ctx.shadowColor = "transparent";
if (node.onDrawBackground) {
- node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse );
+ node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse );
}
//title bg (remember, it is rendered ABOVE the node)
@@ -7685,9 +7885,10 @@ LGraphNode.prototype.executeAction = function(action)
//subgraph box
if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) {
- ctx.fillStyle = "#555";
var w = LiteGraph.NODE_TITLE_HEIGHT;
var x = node.size[0] - w;
+ var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 );
+ ctx.fillStyle = over ? "#888" : "#555";
if( shape == LiteGraph.BOX_SHAPE || low_quality)
ctx.fillRect(x+2, -w+2, w-4, w-4);
else
@@ -8573,6 +8774,7 @@ LGraphNode.prototype.executeAction = function(action)
}
} else if (delta) { //clicked in arrow, used for combos
var index = -1;
+ this.last_mouseclick = 0; //avoids dobl click event
if(values.constructor === Object)
index = values_list.indexOf( String( w.value ) ) + delta;
else
@@ -8851,8 +9053,11 @@ LGraphNode.prototype.executeAction = function(action)
LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) {
var canvas = LGraphCanvas.active_canvas;
var ref_window = canvas.getCanvasWindow();
+ var graph = canvas.graph;
+ if(!graph)
+ return;
- var values = LiteGraph.getNodeTypesCategories( canvas.filter );
+ var values = LiteGraph.getNodeTypesCategories( canvas.filter || graph.filter );
var entries = [];
for (var i in values) {
if (values[i]) {
@@ -8865,7 +9070,7 @@ LGraphNode.prototype.executeAction = function(action)
function inner_clicked(v, option, e) {
var category = v.value;
- var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter );
+ var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter || graph.filter );
var values = [];
for (var i in node_types) {
if (!node_types[i].skip_list) {
@@ -10100,6 +10305,8 @@ LGraphNode.prototype.executeAction = function(action)
for(var i = 0; i < node.inputs.length; ++i)
{
var input = node.inputs[i];
+ if(input.not_subgraph_input)
+ continue;
var html = " ";
var elem = panel.addHTML(html,"subgraph_property");
elem.dataset["name"] = input.name;
diff --git a/src/nodes/base.js b/src/nodes/base.js
index e0f4050cd..9f82b1636 100755
--- a/src/nodes/base.js
+++ b/src/nodes/base.js
@@ -131,6 +131,43 @@
}
};
+ Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos)
+ {
+ if(this.flags.collapsed)
+ return;
+
+ var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
+
+ //button
+ var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT);
+ ctx.fillStyle = over ? "#555" : "#222";
+ ctx.beginPath();
+ ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8);
+ ctx.fill();
+
+ //button
+ ctx.textAlign = "center";
+ ctx.font = "24px Arial";
+ ctx.fillStyle = over ? "#DDD" : "#999";
+ ctx.fillText( "+", this.size[0] * 0.5, y + 24 );
+ }
+
+ Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas)
+ {
+ var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
+ if(localpos[1] > y)
+ {
+ graphcanvas.showSubgraphPropertiesDialog(this);
+ }
+ }
+
+ Subgraph.prototype.computeSize = function()
+ {
+ var num_inputs = this.inputs ? this.inputs.length : 0;
+ var num_outputs = this.outputs ? this.outputs.length : 0;
+ return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ];
+ }
+
//**** INPUTS ***********************************
Subgraph.prototype.onSubgraphTrigger = function(event, param) {
var slot = this.findOutputSlot(event);
diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js
index 3c24ddf69..0ea30c449 100755
--- a/src/nodes/gltextures.js
+++ b/src/nodes/gltextures.js
@@ -1229,6 +1229,20 @@ void main() {\n\
LGraphTextureToViewport.desc = "Texture to viewport";
LGraphTextureToViewport._prev_viewport = new Float32Array(4);
+
+ LGraphTextureToViewport.prototype.onDrawBackground = function( ctx )
+ {
+ if ( this.flags.collapsed || this.size[1] <= 40 )
+ return;
+
+ var tex = this.getInputData(0);
+ if (!tex) {
+ return;
+ }
+
+ ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40);
+ }
+
LGraphTextureToViewport.prototype.onExecute = function() {
var tex = this.getInputData(0);
if (!tex) {
diff --git a/src/nodes/shaders.js b/src/nodes/shaders.js
index f5b22969c..bf03cfdac 100644
--- a/src/nodes/shaders.js
+++ b/src/nodes/shaders.js
@@ -1,18 +1,112 @@
(function(global) {
- var LiteGraph = global.LiteGraph;
- var LGraphCanvas = global.LGraphCanvas;
if (typeof GL == "undefined")
return;
+ var LiteGraph = global.LiteGraph;
+ var LGraphCanvas = global.LGraphCanvas;
- //common actions to all shader node classes
- function setShaderNode( node_ctor )
+ var SHADERNODES_COLOR = "#345";
+
+ var LGShaders = LiteGraph.Shaders = {};
+
+ var GLSL_types = LGShaders.GLSL_types = ["float","vec2","vec3","vec4","mat3","mat4","sampler2D","samplerCube"];
+ var GLSL_types_const = LGShaders.GLSL_types_const = ["float","vec2","vec3","vec4"];
+
+ var GLSL_functions_desc = {
+ "radians": "T radians(T degrees)",
+ "degrees": "T degrees(T radians)",
+ "sin": "T sin(T angle)",
+ "cos": "T cos(T angle)",
+ "tan": "T tan(T angle)",
+ "asin": "T asin(T x)",
+ "acos": "T acos(T x)",
+ "atan": "T atan(T x)",
+ "atan2": "T atan(T x,T y)",
+ "pow": "T pow(T x,T y)",
+ "exp": "T exp(T x)",
+ "log": "T log(T x)",
+ "exp2": "T exp2(T x)",
+ "log2": "T log2(T x)",
+ "sqrt": "T sqrt(T x)",
+ "inversesqrt": "T inversesqrt(T x)",
+ "abs": "T abs(T x)",
+ "sign": "T sign(T x)",
+ "floor": "T floor(T x)",
+ "ceil": "T ceil(T x)",
+ "fract": "T fract(T x)",
+ "mod": "T mod(T x,T y)", //"T mod(T x,float y)"
+ "min": "T min(T x,T y)",
+ "max": "T max(T x,T y)",
+ "clamp": "T clamp(T x,T minVal,T maxVal)",
+ "mix": "T mix(T x,T y,T a)", //"T mix(T x,T y,float a)"
+ "step": "T step(T edge, T x)", //"T step(float edge, T x)"
+ "smoothstep": "T smoothstep(T edge, T x)", //"T smoothstep(float edge, T x)"
+ "length":"float length(T x)",
+ "distance":"float distance(T p0, T p1)",
+ "normalize":"T normalize(T x)",
+ "dot": "float dot(T x,T y)",
+ "cross": "vec3 cross(vec3 x,vec3 y)"
+ };
+
+ //parse them
+ var GLSL_functions = {};
+ var GLSL_functions_name = [];
+ parseGLSLDescriptions();
+
+ function parseGLSLDescriptions()
{
- node_ctor.color = "#345";
+ GLSL_functions_name.length = 0;
+
+ for(var i in GLSL_functions_desc)
+ {
+ var op = GLSL_functions_desc[i];
+ var index = op.indexOf(" ");
+ var return_type = op.substr(0,index);
+ var index2 = op.indexOf("(",index);
+ var func_name = op.substr(index,index2-index).trim();
+ var params = op.substr(index2 + 1, op.length - index2 - 2).split(",");
+ for(var j in params)
+ {
+ var p = params[j].split(" ");
+ params[j] = { type: p[0], name: p[1] };
+ }
+ GLSL_functions[i] = { return_type: return_type, func: func_name, params: params };
+ GLSL_functions_name.push( func_name );
+ //console.log( GLSL_functions[i] );
+ }
}
- function getShaderInputLinkName( node, slot )
+ //common actions to all shader node classes
+ function registerShaderNode( type, node_ctor )
+ {
+ //static attributes
+ node_ctor.color = SHADERNODES_COLOR;
+ node_ctor.filter = "shader";
+
+ //common methods
+ node_ctor.prototype.clearDestination = function(){ this.shader_destination = {}; }
+ node_ctor.prototype.propagateDestination = function propagateDestination( dest_name )
+ {
+ this.shader_destination[ dest_name ] = true;
+ if(this.inputs)
+ for(var i = 0; i < this.inputs.length; ++i)
+ {
+ var origin_node = this.getInputNode(i);
+ if(origin_node)
+ origin_node.propagateDestination( dest_name );
+ }
+ }
+
+ LiteGraph.registerNodeType( type, node_ctor );
+ }
+
+ function getShaderNodeVarName( node, name )
+ {
+ return "VAR_" + (name || "TEMP") + "_" + node.id;
+ }
+
+ function getInputLinkID( node, slot )
{
if(!node.inputs)
return null;
@@ -28,12 +122,60 @@
return "link_" + origin_node.id + "_" + link.origin_slot;
}
- function getShaderOutputLinkName( node, slot )
+ function getOutputLinkID( node, slot )
{
+ if (!node.isOutputConnected(0))
+ return null;
return "link_" + node.id + "_" + slot;
}
- //used to host a shader body *******************
+ LGShaders.registerShaderNode = registerShaderNode;
+ LGShaders.getInputLinkID = getInputLinkID;
+ LGShaders.getOutputLinkID = getOutputLinkID;
+ LGShaders.getShaderNodeVarName = getShaderNodeVarName;
+ LGShaders.parseGLSLDescriptions = parseGLSLDescriptions;
+
+ var valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type )
+ {
+ var n = 5; //num decimals
+ if(!type)
+ {
+ if(v.constructor === Number)
+ type = "float";
+ else if(v.length)
+ {
+ switch(v.length)
+ {
+ case 2: type = "vec2"; break;
+ case 3: type = "vec3"; break;
+ case 4: type = "vec4"; break;
+ case 9: type = "mat3"; break;
+ case 16: type = "mat4"; break;
+ default:
+ throw("unknown type for glsl value size");
+ }
+ }
+ else
+ throw("unknown type for glsl value: " + v.constructor);
+ }
+ switch(type)
+ {
+ case 'float': return v.toFixed(n); break;
+ case 'vec2': return "vec2(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + ")"; break;
+ case 'color3':
+ case 'vec3': return "vec3(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + ")"; break;
+ case 'color4':
+ case 'vec4': return "vec4(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + "," + v[3].toFixed(n) + ")"; break;
+ case 'mat3': return "mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)"; break; //not fully supported yet
+ case 'mat4': return "mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)"; break;//not fully supported yet
+ default:
+ throw("unknown glsl type in valueToGLSL:", type);
+ }
+
+ return "";
+ }
+
+ //used to host a shader body **************************************
function LGShaderContext()
{
this.vs_template = "";
@@ -42,8 +184,6 @@
this._codeparts = {};
}
- LGShaderContext.valid_types = ["float","vec2","vec3","vec4","sampler2D","mat3","mat4","int","boolean"];
-
LGShaderContext.prototype.clear = function()
{
this._uniforms = {};
@@ -55,16 +195,20 @@
this._uniforms[ name ] = type;
}
- LGShaderContext.prototype.addCode = function( hook, code )
+ LGShaderContext.prototype.addCode = function( hook, code, destinations )
{
- if(!this._codeparts[ hook ])
- this._codeparts[ hook ] = code + "\n";
- else
- this._codeparts[ hook ] += code + "\n";
+ destinations = destinations || {"":""};
+ for(var i in destinations)
+ {
+ var h = i ? i + "_" + hook : hook;
+ if(!this._codeparts[ h ])
+ this._codeparts[ h ] = code + "\n";
+ else
+ this._codeparts[ h ] += code + "\n";
+ }
}
- //generates the shader code from the template and the
- LGShaderContext.prototype.computeShader = function( shader )
+ LGShaderContext.prototype.computeShaderCode = function()
{
var uniforms = "";
for(var i in this._uniforms)
@@ -75,23 +219,77 @@
var vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, parts );
var fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, parts );
+ return {
+ vs_code: vs_code,
+ fs_code: fs_code
+ };
+ }
+
+ //generates the shader code from the template and the
+ LGShaderContext.prototype.computeShader = function( shader )
+ {
+ var finalcode = this.computeShaderCode();
+ console.log( finalcode.vs_code, finalcode.fs_code );
try
{
if(shader)
- shader.updateShader( vs_code, fs_code );
+ shader.updateShader( finalcode.vs_code, finalcode.fs_code );
else
- shader = new GL.Shader( vs_code, fs_code );
+ shader = new GL.Shader( finalcode.vs_code, finalcode.fs_code );
+ this._shader_error = false;
return shader;
}
catch (err)
{
+ if(!this._shader_error)
+ {
+ console.error(err);
+ if(err.indexOf("Fragment shader") != -1)
+ console.log( finalcode.fs_code );
+ else
+ console.log( finalcode.vs_code );
+ }
+ this._shader_error = true;
return null;
}
return null;//never here
}
+ //represents a fragment of code exported by a node
+ /*
+ function LGShaderCodeBlock( node, code, uniforms )
+ {
+ this.node = node || null;
+ this.uniforms = uniforms || null;
+ this.parts = {};
+ if(code)
+ {
+ if(code.constructor === String)
+ this.parts.code = code;
+ else
+ this.parts = code;
+ }
+ }
+
+ LGShaderCodeBlock.prototype.addUniform = function( name, type )
+ {
+ if(!this.uniforms)
+ this.uniforms = {};
+ this.uniforms[ name ] = type;
+ }
+
+ LGShaderCodeBlock.prototype.addCode = function( hook, code )
+ {
+ if(!this.parts[ hook ])
+ this.parts[ hook ] = code + "\n";
+ else
+ this.parts[ hook ] += code + "\n";
+ }
+ */
+
+
// LGraphShaderGraph *****************************
// applies a shader graph to texture, it can be uses as an example
@@ -102,9 +300,10 @@
this.subgraph = new LiteGraph.LGraph();
this.subgraph._subgraph_node = this;
this.subgraph._is_subgraph = true;
+ this.subgraph.filter = "shader";
- var subnode = LiteGraph.createNode("shader/fragcolor");
- this.subgraph.pos = [300,100];
+ var subnode = LiteGraph.createNode("output/fragcolor");
+ subnode.pos = [300,100];
this.subgraph.add( subnode );
this.size = [180,60];
@@ -118,16 +317,17 @@
}
LGraphShaderGraph.template = "\n\
- precision highp float;\n\
- varying vec2 v_coord;\n\
- {{varying}}\n\
- {{uniforms}}\n\
- void main() {\n\
- vec2 uv = v_coord;\n\
- vec4 color = vec4(0.0);\n\
- {{fs_code}}\n\
- gl_FragColor = color;\n\
- }\n\
+precision highp float;\n\
+varying vec2 v_coord;\n\
+{{varying}}\n\
+{{uniforms}}\n\
+{{fs_functions}}\n\
+void main() {\n\n\
+vec2 uv = v_coord;\n\
+vec4 color = vec4(0.0);\n\
+{{fs_code}}\n\
+gl_FragColor = color;\n\
+}\n\
";
LGraphShaderGraph.widgets_info = {
@@ -136,6 +336,8 @@
LGraphShaderGraph.title = "ShaderGraph";
LGraphShaderGraph.desc = "Builds a shader using a graph";
+ LGraphShaderGraph.input_node_type = "input/uniform";
+ LGraphShaderGraph.title_color = SHADERNODES_COLOR;
LGraphShaderGraph.prototype.onSerialize = function(o)
{
@@ -207,17 +409,19 @@
this.setOutputData(0, texture );
};
+ //add input node inside subgraph
LGraphShaderGraph.prototype.onInputAdded = function( slot_info )
{
- var subnode = LiteGraph.createNode("shader/uniform");
- subnode.properties.name = slot_info.name;
- subnode.properties.type = slot_info.type;
+ var subnode = LiteGraph.createNode("input/uniform");
+ subnode.setProperty("name",slot_info.name);
+ subnode.setProperty("type",slot_info.type);
this.subgraph.add( subnode );
}
+ //remove all
LGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info )
{
- var nodes = this.subgraph.findNodesByType("shader/uniform");
+ var nodes = this.subgraph.findNodesByType("input/uniform");
for(var i = 0; i < nodes.length; ++i)
{
var node = nodes[i];
@@ -242,8 +446,25 @@
//prepare context
this._context.clear();
+ //grab output nodes
+ var vertexout = this.subgraph.findNodesByType("output/vertex");
+ vertexout = vertexout && vertexout.length ? vertexout[0] : null;
+ var fragmentout = this.subgraph.findNodesByType("output/fragcolor");
+ fragmentout = fragmentout && fragmentout.length ? fragmentout[0] : null;
+
+ if(!fragmentout) //??
+ return null;
+
+ this.subgraph.sendEventToAllNodes( "clearDestination" );
+
+ //propagate back destinations
+ if(vertexout)
+ vertexout.propagateDestination("vs");
+ if(fragmentout)
+ fragmentout.propagateDestination("fs");
+
//gets code from graph
- this.subgraph.sendEventToAllNodes("onGetCode", this._context);
+ this.subgraph.sendEventToAllNodes("onGetCode", this._context );
//compile shader
var shader = this._context.computeShader();
@@ -265,6 +486,13 @@
if(this.flags.collapsed)
return;
+ //allows to preview the node if the canvas is a webgl canvas
+ var tex = this.getOutputData(0);
+ var inputs_y = this.inputs ? this.inputs.length * LiteGraph.NODE_SLOT_HEIGHT : 0;
+ if (tex && ctx == tex.gl && this.size[1] > inputs_y + LiteGraph.NODE_TITLE_HEIGHT ) {
+ ctx.drawImage( tex, 10,y, this.size[0] - 20, this.size[1] - inputs_y - LiteGraph.NODE_TITLE_HEIGHT );
+ }
+
var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5;
//button
@@ -290,6 +518,17 @@
}
}
+ LGraphShaderGraph.prototype.getExtraMenuOptions = function(graphcanvas)
+ {
+ var that = this;
+ var options = [{ content: "Print Code", callback: function(){
+ var code = that._context.computeShaderCode();
+ console.log( code.vs_code, code.fs_code );
+ }}];
+
+ return options;
+ }
+
LiteGraph.registerNodeType( "texture/shaderGraph", LGraphShaderGraph );
//Shader Nodes ***************************
@@ -305,7 +544,14 @@
LGraphShaderUniform.prototype.getTitle = function()
{
- return "uniform " + this.properties.name;
+ if( this.properties.name && this.flags.collapsed)
+ return this.properties.type + " " + this.properties.name;
+ return "Uniform";
+ }
+
+ LGraphShaderUniform.prototype.onPropertyChanged = function(name,value)
+ {
+ this.outputs[0].name = this.properties.type + " " + this.properties.name;
}
LGraphShaderUniform.prototype.onGetCode = function( context )
@@ -317,9 +563,11 @@
type = "float";
else if(type == "texture")
type = "sampler2D";
- if ( LGShaderContext.valid_types.indexOf(type) == -1 )
+ if ( LGShaders.GLSL_types.indexOf(type) == -1 )
return;
+
context.addUniform( "u_" + this.properties.name, type );
+ this.setOutputData( 0, type );
}
LGraphShaderUniform.prototype.getOutputVarName = function(slot)
@@ -327,10 +575,7 @@
return "u_" + this.properties.name;
}
- setShaderNode( LGraphShaderUniform );
-
- LiteGraph.registerNodeType( "shader/uniform", LGraphShaderUniform );
-
+ registerShaderNode( "input/uniform", LGraphShaderUniform );
function LGraphShaderAttribute() {
@@ -349,12 +594,17 @@
LGraphShaderAttribute.prototype.onGetCode = function( context )
{
var type = this.properties.type;
- if( !type || LGShaderContext.valid_types.indexOf(type) == -1 )
+ if( !type || LGShaders.GLSL_types.indexOf(type) == -1 )
return;
if(type == "number")
type = "float";
if( this.properties.name != "coord")
- context.addCode( "varying", " varying " + type +" v_" + this.properties.name );
+ {
+ context.addCode( "varying", " varying " + type +" v_" + this.properties.name + ";" );
+ //if( !context.varyings[ this.properties.name ] )
+ //context.addCode( "vs_code", "v_" + this.properties.name + " = " + input_name + ";" );
+ }
+ this.setOutputData( 0, type );
}
LGraphShaderAttribute.prototype.getOutputVarName = function(slot)
@@ -362,15 +612,13 @@
return "v_" + this.properties.name;
}
- setShaderNode( LGraphShaderAttribute );
-
- LiteGraph.registerNodeType( "shader/attribute", LGraphShaderAttribute );
-
+ registerShaderNode( "input/attribute", LGraphShaderAttribute );
function LGraphShaderSampler2D() {
this.addInput("tex", "sampler2D");
this.addInput("uv", "vec2");
this.addOutput("rgba", "vec4");
+ this.addOutput("rgb", "vec3");
}
LGraphShaderSampler2D.title = "Sampler2D";
@@ -378,22 +626,301 @@
LGraphShaderSampler2D.prototype.onGetCode = function( context )
{
- var texname = getShaderInputLinkName( this, 0 );
- var code;
+ var texname = getInputLinkID( this, 0 );
+ var varname = getShaderNodeVarName(this);
+ var code = "vec4 " + varname + " = vec4(0.0);\n";
if(texname)
{
- var uvname = getShaderInputLinkName( this, 1 ) || "v_coord";
- code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = texture2D("+texname+","+uvname+");";
+ var uvname = getInputLinkID( this, 1 ) || "v_coord";
+ code += varname + " = texture2D("+texname+","+uvname+");\n";
}
- else
- code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = vec4(0.0);";
- context.addCode( "fs_code", code );
+
+ var link0 = getOutputLinkID( this, 0 );
+ if(link0)
+ code += "vec4 " + getOutputLinkID( this, 0 ) + " = "+varname+";\n";
+
+ var link1 = getOutputLinkID( this, 1 );
+ if(link1)
+ code += "vec3 " + getOutputLinkID( this, 1 ) + " = "+varname+".xyz;\n";
+
+ context.addCode( "code", code, this.shader_destination );
+ this.setOutputData( 0, "vec4" );
+ this.setOutputData( 1, "vec3" );
}
- setShaderNode( LGraphShaderSampler2D );
+ registerShaderNode( "texture/sampler2D", LGraphShaderSampler2D );
- LiteGraph.registerNodeType( "shader/sampler2D", LGraphShaderSampler2D );
+ //*********************************
+ function LGraphShaderConstant()
+ {
+ this.addOutput("","float");
+
+ this.properties = {
+ type: "float",
+ value: 0
+ };
+
+ this.addWidget("combo","type","float",null, { values: GLSL_types_const, property: "type" } );
+ this.updateWidgets();
+ }
+
+ LGraphShaderConstant.title = "const";
+
+ LGraphShaderConstant.prototype.getTitle = function()
+ {
+ if(this.flags.collapsed)
+ return valueToGLSL( this.properties.value, this.properties.type );
+ return "Const";
+ }
+
+ LGraphShaderConstant.prototype.onPropertyChanged = function(name,value)
+ {
+ var that = this;
+ if(name == "type")
+ {
+ if(this.outputs[0].type != value)
+ {
+ this.disconnectOutput(0);
+ this.outputs[0].type = value;
+ this.widgets.length = 1; //remove extra widgets
+ this.updateWidgets();
+ }
+ }
+ }
+
+ LGraphShaderConstant.prototype.updateWidgets = function( old_value )
+ {
+ var that = this;
+ var old_value = this.properties.value;
+ var options = { step: 0.01 };
+ switch(this.properties.type)
+ {
+ case 'float':
+ this.properties.value = 0;
+ this.addWidget("number","v",0,{ step:0.01, property: "value" });
+ break;
+ case 'vec2':
+ this.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0];
+ this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; });
+ this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; });
+ break;
+ case 'vec3':
+ this.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0];
+ this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; });
+ this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; });
+ this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; });
+ break;
+ case 'vec4':
+ this.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0];
+ this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; });
+ this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; });
+ this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; });
+ this.addWidget("number","w",0,options, function(v){ that.properties.value[3] = v; });
+ break;
+ default:
+ console.error("unknown type for constant");
+ }
+ }
+
+ LGraphShaderConstant.prototype.onGetCode = function( context )
+ {
+ var value = valueToGLSL( this.properties.value, this.properties.type );
+ var link_name = getOutputLinkID(this,0);
+ if(!link_name) //not connected
+ return;
+
+ var code = " " + this.properties.type + " " + link_name + " = " + value + ";";
+ context.addCode( "code", code, this.shader_destination );
+
+ this.setOutputData( 0, this.properties.type );
+ }
+
+ registerShaderNode( "const/const", LGraphShaderConstant );
+
+ function LGraphShaderVec2()
+ {
+ this.addInput("xy","vec2");
+ this.addInput("x","float");
+ this.addInput("y","float");
+ this.addOutput("xy","vec2");
+ this.addOutput("x","float");
+ this.addOutput("y","float");
+
+ this.properties = { x: 0, y: 0 };
+ }
+
+ LGraphShaderVec2.title = "vec2";
+ LGraphShaderVec2.varmodes = ["xy","x","y"];
+
+ LGraphShaderVec2.prototype.onPropertyChanged = function()
+ {
+ this.graph._version++;
+ }
+
+ LGraphShaderVec2.prototype.onGetCode = function( context )
+ {
+ var props = this.properties;
+
+ var varname = getShaderNodeVarName(this);
+ var code = " vec2 " + varname + " = " + valueToGLSL([props.x,props.y]) + ";\n";
+
+ for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec2.varmodes[i];
+ var inlink = getInputLinkID(this,i);
+ if(!inlink)
+ continue;
+ code += " " + varname + "."+varmode+" = " + inlink + ";\n";
+ }
+
+ for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec2.varmodes[i];
+ var outlink = getOutputLinkID(this,i);
+ if(!outlink)
+ continue;
+ var type = GLSL_types_const[varmode.length - 1];
+ code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
+ this.setOutputData( i, type );
+ }
+
+ context.addCode( "code", code, this.shader_destination );
+ }
+
+ registerShaderNode( "const/vec2", LGraphShaderVec2 );
+
+ function LGraphShaderVec3()
+ {
+ this.addInput("xyz","vec3");
+ this.addInput("x","float");
+ this.addInput("y","float");
+ this.addInput("z","float");
+ this.addInput("xy","vec2");
+ this.addInput("xz","vec2");
+ this.addInput("yz","vec2");
+ this.addOutput("xyz","vec3");
+ this.addOutput("x","float");
+ this.addOutput("y","float");
+ this.addOutput("z","float");
+ this.addOutput("xy","vec2");
+ this.addOutput("xz","vec2");
+ this.addOutput("yz","vec2");
+
+ this.properties = { x:0, y: 0, z: 0 };
+ }
+
+ LGraphShaderVec3.title = "vec3";
+ LGraphShaderVec3.varmodes = ["xyz","x","y","z","xy","xz","yz"];
+
+ LGraphShaderVec3.prototype.onPropertyChanged = function()
+ {
+ this.graph._version++;
+ }
+
+
+ LGraphShaderVec3.prototype.onPropertyChanged = function()
+ {
+ this.graph._version++;
+ }
+
+ LGraphShaderVec3.prototype.onGetCode = function( context )
+ {
+ var props = this.properties;
+
+ var varname = getShaderNodeVarName(this);
+ var code = "vec3 " + varname + " = " + valueToGLSL([props.x,props.y,props.z]) + ";\n";
+
+ for(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec3.varmodes[i];
+ var inlink = getInputLinkID(this,i);
+ if(!inlink)
+ continue;
+ code += " " + varname + "."+varmode+" = " + inlink + ";\n";
+ }
+
+ for(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec3.varmodes[i];
+ var outlink = getOutputLinkID(this,i);
+ if(!outlink)
+ continue;
+ var type = GLSL_types_const[varmode.length - 1];
+ code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
+ this.setOutputData( i, type );
+ }
+
+ context.addCode( "code", code, this.shader_destination );
+ }
+
+ registerShaderNode( "const/vec3", LGraphShaderVec3 );
+
+
+ function LGraphShaderVec4()
+ {
+ this.addInput("xyzw","vec4");
+ this.addInput("xyz","vec3");
+ this.addInput("x","float");
+ this.addInput("y","float");
+ this.addInput("z","float");
+ this.addInput("w","float");
+ this.addInput("xy","vec2");
+ this.addInput("yz","vec2");
+ this.addInput("zw","vec2");
+ this.addOutput("xyzw","vec4");
+ this.addOutput("xyz","vec3");
+ this.addOutput("x","float");
+ this.addOutput("y","float");
+ this.addOutput("z","float");
+ this.addOutput("xy","vec2");
+ this.addOutput("yz","vec2");
+ this.addOutput("zw","vec2");
+
+ this.properties = { x:0, y: 0, z: 0, w: 0 };
+ }
+
+ LGraphShaderVec4.title = "vec4";
+ LGraphShaderVec4.varmodes = ["xyzw","xyz","x","y","z","w","xy","yz","zw"];
+
+ LGraphShaderVec4.prototype.onPropertyChanged = function()
+ {
+ this.graph._version++;
+ }
+
+ LGraphShaderVec4.prototype.onGetCode = function( context )
+ {
+ var props = this.properties;
+
+ var varname = getShaderNodeVarName(this);
+ var code = "vec4 " + varname + " = " + valueToGLSL([props.x,props.y,props.z,props.w]) + ";\n";
+
+ for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec4.varmodes[i];
+ var inlink = getInputLinkID(this,i);
+ if(!inlink)
+ continue;
+ code += " " + varname + "."+varmode+" = " + inlink + ";\n";
+ }
+
+ for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i)
+ {
+ var varmode = LGraphShaderVec4.varmodes[i];
+ var outlink = getOutputLinkID(this,i);
+ if(!outlink)
+ continue;
+ var type = GLSL_types_const[varmode.length - 1];
+ code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n";
+ this.setOutputData( i, type );
+ }
+
+ context.addCode( "code", code, this.shader_destination );
+
+ }
+
+ registerShaderNode( "const/vec4", LGraphShaderVec4 );
+
//*********************************
function LGraphShaderFragColor() {
@@ -406,7 +933,7 @@
LGraphShaderFragColor.prototype.onGetCode = function( context )
{
- var link_name = getShaderInputLinkName( this, 0 );
+ var link_name = getInputLinkID( this, 0 );
if(!link_name)
return;
@@ -419,11 +946,271 @@
else if(type == "vec3")
code = "vec4(" + code + ",1.0);";
- context.addCode("fs_code", "color = " + code + ";\n");
+ context.addCode("fs_code", "color = " + code + ";");
}
- setShaderNode( LGraphShaderFragColor );
+ registerShaderNode( "output/fragcolor", LGraphShaderFragColor );
- LiteGraph.registerNodeType( "shader/fragcolor", LGraphShaderFragColor );
-})(this);
\ No newline at end of file
+
+ // *************************************************
+
+ function LGraphShaderMath()
+ {
+ this.addInput("A","float,vec2,vec3,vec4");
+ this.addInput("B","float,vec2,vec3,vec4");
+ this.addOutput("out","");
+ this.properties = {
+ func: "floor"
+ };
+ this._current = "floor";
+ this.addWidget("combo","func",this.properties.func,{ property: "func", values: GLSL_functions_name });
+ }
+
+ LGraphShaderMath.title = "Math";
+
+ LGraphShaderMath.prototype.onPropertyChanged = function(name,value)
+ {
+ this.graph._version++;
+
+ if(name == "func")
+ {
+ var func_desc = GLSL_functions[ value ];
+ if(!func_desc)
+ return;
+
+ //remove extra inputs
+ for(var i = func_desc.params.length; i < this.inputs.length; ++i)
+ this.removeInput(i);
+
+ //add and update inputs
+ for(var i = 0; i < func_desc.params.length; ++i)
+ if( this.inputs[i] )
+ this.inputs[i].name = func_desc.params[i].name;
+ else
+ this.addInput( func_desc.params[i].name, "float,vec2,vec3,vec4" );
+ }
+ }
+
+ LGraphShaderMath.prototype.getTitle = function()
+ {
+ if(this.flags.collapsed)
+ return this.properties.func;
+ else
+ return "Func";
+ }
+
+ LGraphShaderMath.prototype.onGetCode = function( context )
+ {
+ if(!this.isOutputConnected(0))
+ return;
+
+ var inlinks = [];
+ for(var i = 0; i < 3; ++i)
+ inlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || "float" } );
+
+ var outlink = getOutputLinkID(this,0);
+ if(!outlink) //not connected
+ return;
+
+ var func_desc = GLSL_functions[ this.properties.func ];
+ if(!func_desc)
+ return;
+
+ //func_desc
+ var base_type = inlinks[0].type;
+ var return_type = func_desc.return_type;
+ if( return_type == "T" )
+ return_type = base_type;
+
+ var params = [];
+ for(var i = 0; i < func_desc.params.length; ++i)
+ {
+ var p = func_desc.params[i];
+ //if( p.type == "T" && inlinks[i].type != base_type )
+ params.push( inlinks[i].name );
+ }
+
+ context.addCode("code", return_type + " " + outlink + " = "+func_desc.func+"("+params.join(",")+");", this.shader_destination );
+
+ this.setOutputData( 0, return_type );
+ }
+
+ registerShaderNode( "math/func", LGraphShaderMath );
+
+
+
+ function LGraphShaderSnippet()
+ {
+ this.addInput("A","float,vec2,vec3,vec4");
+ this.addInput("B","float,vec2,vec3,vec4");
+ this.addOutput("C","vec4");
+ this.properties = {
+ code:"C = A+B",
+ type: "vec4"
+ }
+ this.addWidget("text","code",this.properties.code,{ property: "code" });
+ this.addWidget("combo","type",this.properties.type,{ values:["float","vec2","vec3","vec4"], property: "type" });
+ }
+
+ LGraphShaderSnippet.title = "Snippet";
+
+ LGraphShaderSnippet.prototype.onPropertyChanged = function(name,value)
+ {
+ this.graph._version++;
+
+ if(name == "type"&& this.outputs[0].type != value)
+ {
+ this.disconnectOutput(0);
+ this.outputs[0].type = value;
+ }
+ }
+
+ LGraphShaderSnippet.prototype.getTitle = function()
+ {
+ if(this.flags.collapsed)
+ return this.properties.code;
+ else
+ return "Snippet";
+ }
+
+ LGraphShaderSnippet.prototype.onGetCode = function( context )
+ {
+ if(!this.isOutputConnected(0))
+ return;
+
+ var inlinkA = getInputLinkID(this,0);
+ if(!inlinkA)
+ inlinkA = "1.0";
+ var inlinkB = getInputLinkID(this,1);
+ if(!inlinkB)
+ inlinkB = "1.0";
+ var outlink = getOutputLinkID(this,0);
+ if(!outlink) //not connected
+ return;
+
+ var inA_type = this.getInputData(0) || "float";
+ var inB_type = this.getInputData(1) || "float";
+ var return_type = this.properties.type;
+
+ //cannot resolve input
+ if(inA_type == "T" || inB_type == "T")
+ {
+ return null;
+ }
+
+ var funcname = "funcSnippet" + this.id;
+
+ var func_code = "\n" + return_type + " " + funcname + "( " + inA_type + " A, " + inB_type + " B) {\n";
+ func_code += " " + return_type + " C = " + return_type + "(0.0);\n";
+ func_code += " " + this.properties.code + ";\n";
+ func_code += " return C;\n}\n";
+
+ context.addCode("functions", func_code, this.shader_destination );
+ context.addCode("code", return_type + " " + outlink + " = "+funcname+"("+inlinkA+","+inlinkB+");", this.shader_destination );
+
+ this.setOutputData( 0, return_type );
+ }
+
+ registerShaderNode( "utils/snippet", LGraphShaderSnippet );
+
+
+ //************************************
+ function LGraphShaderRemap()
+ {
+ this.addInput("","T,float,vec2,vec3,vec4");
+ this.addOutput("","T");
+ this.properties = {
+ min_value: 0,
+ max_value: 1,
+ min_value2: 0,
+ max_value2: 1
+ };
+ this.addWidget("number","min",0,{ step: 0.1, property: "min_value" });
+ this.addWidget("number","max",1,{ step: 0.1, property: "max_value" });
+ this.addWidget("number","min2",0,{ step: 0.1, property: "min_value2"});
+ this.addWidget("number","max2",1,{ step: 0.1, property: "max_value2"});
+ }
+
+ LGraphShaderRemap.title = "Remap";
+
+ LGraphShaderRemap.prototype.onPropertyChanged = function()
+ {
+ this.graph._version++;
+ }
+
+ LGraphShaderRemap.prototype.onConnectionsChange = function()
+ {
+ var return_type = this.getInputDataType(0);
+ this.outputs[0].type = return_type || "T";
+ }
+
+ LGraphShaderRemap.prototype.onGetCode = function( context )
+ {
+ if(!this.isOutputConnected(0))
+ return;
+
+ var inlink = getInputLinkID(this,0);
+ var outlink = getOutputLinkID(this,0);
+ if(!inlink && !outlink) //not connected
+ return;
+
+ var return_type = this.getInputDataType(0);
+ this.outputs[0].type = return_type;
+ if(return_type == "T")
+ {
+ console.warn("node type is T and cannot be resolved");
+ return;
+ }
+
+ if(!inlink)
+ {
+ context.addCode("code"," " + return_type + " " + outlink + " = " + return_type + "(0.0);\n");
+ return;
+ }
+
+ var minv = valueToGLSL( this.properties.min_value );
+ var maxv = valueToGLSL( this.properties.max_value );
+ var minv2 = valueToGLSL( this.properties.min_value2 );
+ var maxv2 = valueToGLSL( this.properties.max_value2 );
+
+ context.addCode("code", return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";", this.shader_destination );
+ this.setOutputData( 0, return_type );
+ }
+
+ registerShaderNode( "math/remap", LGraphShaderRemap );
+
+})(this);
+
+
+/*
+// https://blog.undefinist.com/writing-a-shader-graph/
+
+\sin
+f,Out
+float->float
+{1} = sin({0});
+
+\mul
+A,B,Out
+T,T->T
+T,float->T
+{2} = {0} * {1};
+
+\clamp
+f,min,max,Out
+float,float=0,float=1->float
+vec2,vec2=vec2(0.0),vec2=vec2(1.0)->vec2
+vec3,vec3=vec3(0.0),vec3=vec3(1.0)->vec3
+vec4,vec4=vec4(0.0),vec4=vec4(1.0)->vec4
+{3}=clamp({0},{1},{2});
+
+\mix
+A,B,f,Out
+float,float,float->float
+vec2,vec2,float->vec2
+vec3,vec3,float->vec3
+vec4,vec4,float->vec4
+{3} = mix({0},{1},{2});
+
+*/
\ No newline at end of file
diff --git a/utils/deploy_files.txt b/utils/deploy_files.txt
index 03665ddf7..ce998b646 100755
--- a/utils/deploy_files.txt
+++ b/utils/deploy_files.txt
@@ -9,6 +9,7 @@
../src/nodes/logic.js
../src/nodes/graphics.js
../src/nodes/gltextures.js
+../src/nodes/shaders.js
../src/nodes/geometry.js
../src/nodes/glfx.js
../src/nodes/midi.js