From 2f9c67295f476ad2a0e5554058ae1c82263783d0 Mon Sep 17 00:00:00 2001 From: tamat Date: Thu, 2 Jul 2020 00:00:53 +0200 Subject: [PATCH] shader graph support --- build/litegraph.js | 531 ++++++++++++++++++++++++++++++++++++--- css/litegraph-editor.css | 153 +---------- css/litegraph.css | 229 +++++++++++++++++ demo/js/code.js | 5 +- demo/style.css | 2 +- src/litegraph-editor.js | 255 +------------------ src/litegraph.js | 453 ++++++++++++++++++++++++++++++--- src/nodes/base.js | 4 + src/nodes/gltextures.js | 95 ++++++- src/nodes/shaders.js | 429 +++++++++++++++++++++++++++++++ 10 files changed, 1684 insertions(+), 472 deletions(-) create mode 100644 src/nodes/shaders.js diff --git a/build/litegraph.js b/build/litegraph.js index 048fedcef..4c125bf70 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -1382,6 +1382,9 @@ this.onNodeRemoved(node); } + //close panels + this.sendActionToCanvas("checkPanels"); + this.setDirtyCanvas(true, true); this.change(); @@ -3090,9 +3093,11 @@ } this.inputs.push(o); this.setSize( this.computeSize() ); + if (this.onInputAdded) { this.onInputAdded(o); } + this.setDirtyCanvas(true, true); return o; }; @@ -4710,6 +4715,7 @@ LGraphNode.prototype.executeAction = function(action) } graph.attachCanvas(this); + this.checkPanels(); this.setDirty(true, true); }; @@ -4841,6 +4847,7 @@ LGraphNode.prototype.executeAction = function(action) } var canvas = this.canvas; + var ref_window = this.getCanvasWindow(); var document = ref_window.document; //hack used when moving canvas between windows @@ -5243,6 +5250,7 @@ LGraphNode.prototype.executeAction = function(action) //it wasn't clicked on the links boxes if (!skip_action) { var block_drag_node = false; + var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); @@ -5255,18 +5263,31 @@ LGraphNode.prototype.executeAction = function(action) if (is_double_click && this.selected_nodes[node.id]) { //double click node if (node.onDblClick) { - node.onDblClick( e, [ e.canvasX - node.pos[0], e.canvasY - node.pos[1] ], this ); + node.onDblClick( e, pos, this ); } this.processNodeDblClicked(node); block_drag_node = true; } //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ) ) { - block_drag_node = true; - } else if (this.live_mode) { - clicking_canvas_bg = true; + if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { block_drag_node = true; + } else { + //open subgraph button + if(node.subgraph && !node.skip_subgraph_button) + { + if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { + var that = this; + setTimeout(function() { + that.openSubgraph(node.subgraph); + }, 10); + } + } + + if (this.live_mode) { + clicking_canvas_bg = true; + block_drag_node = true; + } } if (!block_drag_node) { @@ -5460,6 +5481,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse over a node if (node) { + + if(node.redraw_on_mouse) + this.dirty_canvas = true; + //this.canvas.style.cursor = "move"; if (!node.mouseOver) { //mouse enter @@ -6200,6 +6225,10 @@ LGraphNode.prototype.executeAction = function(action) if (this.onShowNodePanel) { this.onShowNodePanel(n); } + else + { + this.showShowNodePanel(n); + } if (this.onNodeDblClicked) { this.onNodeDblClicked(n); @@ -6350,6 +6379,10 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.deleteSelectedNodes = function() { for (var i in this.selected_nodes) { var node = this.selected_nodes[i]; + + if(node.block_delete) + continue; + //autoconnect when possible (very basic, only takes into account first input-output) if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) { @@ -6972,14 +7005,8 @@ LGraphNode.prototype.executeAction = function(action) var glow = false; this.current_node = node; - var color = - node.color || - node.constructor.color || - LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = - node.bgcolor || - node.constructor.bgcolor || - LiteGraph.NODE_DEFAULT_BGCOLOR; + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; //shadow and glow if (node.mouseOver) { @@ -7493,20 +7520,14 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas); + node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse ); } //title bg (remember, it is rendered ABOVE the node) if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { //title bar if (node.onDrawTitleBar) { - node.onDrawTitleBar( - ctx, - title_height, - size, - this.ds.scale, - fgcolor - ); + node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); } else if ( title_mode != LiteGraph.TRANSPARENT_TITLE && (node.constructor.title_color || this.render_title_colored) @@ -7647,6 +7668,28 @@ 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; + if( shape == LiteGraph.BOX_SHAPE || low_quality) + ctx.fillRect(x+2, -w+2, w-4, w-4); + else + { + ctx.beginPath(); + ctx.roundRect(x+2, -w+2, w-4, w-4,4); + ctx.fill(); + } + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + } + + //custom title render if (node.onDrawTitle) { node.onDrawTitle(ctx); } @@ -8885,6 +8928,7 @@ LGraphNode.prototype.executeAction = function(action) } if (!entries.length) { + console.log("no input entries"); return; } @@ -9754,6 +9798,341 @@ LGraphNode.prototype.executeAction = function(action) return dialog; }; + LGraphCanvas.prototype.createPanel = function(title, options) { + options = options || {}; + + var ref_window = options.window || window; + var root = document.createElement("div"); + root.className = "litegraph dialog"; + root.innerHTML = "
"; + root.header = root.querySelector(".dialog-header"); + + if(options.width) + root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); + if(options.height) + root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); + if(options.closable) + { + var close = document.createElement("span"); + close.innerHTML = "✕"; + close.classList.add("close"); + close.addEventListener("click",function(){ + root.close(); + }); + root.header.appendChild(close); + } + root.title_element = root.querySelector(".dialog-title"); + root.title_element.innerText = title; + root.content = root.querySelector(".dialog-content"); + root.footer = root.querySelector(".dialog-footer"); + + root.close = function() + { + this.parentNode.removeChild(this); + } + + root.clear = function() + { + this.content.innerHTML = ""; + } + + root.addHTML = function(code, classname, on_footer) + { + var elem = document.createElement("div"); + if(classname) + elem.className = classname; + elem.innerHTML = code; + if(on_footer) + root.footer.appendChild(elem); + else + root.content.appendChild(elem); + return elem; + } + + root.addButton = function( name, callback, options ) + { + var elem = document.createElement("button"); + elem.innerText = name; + elem.options = options; + elem.classList.add("btn"); + elem.addEventListener("click",callback); + root.footer.appendChild(elem); + return elem; + } + + root.addSeparator = function() + { + var elem = document.createElement("div"); + elem.className = "separator"; + root.content.appendChild(elem); + } + + root.addWidget = function( type, name, value, options, callback ) + { + options = options || {}; + var str_value = String(value); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = name; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = name; + elem.dataset["type"] = options.type || type; + elem.options = options; + elem.value = value; + + //if( type == "code" ) + // elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); + if (type == "boolean") + { + elem.classList.add("boolean"); + if(value) + elem.classList.add("bool-on"); + elem.addEventListener("click", function(){ + //var v = node.properties[this.dataset["property"]]; + //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + var propname = this.dataset["property"]; + this.value = !this.value; + this.classList.toggle("bool-on"); + this.querySelector(".property_value").innerText = this.value ? "true" : "false"; + innerChange(propname, this.value ); + }); + } + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("keydown", function(e){ + if(e.code == "Enter") + { + e.preventDefault(); + this.blur(); + } + }); + value_element.addEventListener("blur", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + var proptype = this.parentNode.dataset["type"]; + if( proptype == "number") + v = Number(v); + innerChange(propname, v); + }); + } + else if (type == "enum") + value_element.addEventListener("click", function(event){ + var values = options.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + //node.setProperty(propname,v); + //graphcanvas.dirty_canvas = true; + elem_that.innerText = v; + innerChange(propname,v); + return false; + } + }); + + root.content.appendChild(elem); + + function innerChange(name, value) + { + console.log("change",name,value); + //that.dirty_canvas = true; + if(options.callback) + options.callback(name,value); + if(callback) + callback(name,value); + } + + return elem; + } + + return root; + }; + + LGraphCanvas.prototype.showShowNodePanel = function( node ) + { + window.SELECTED_NODE = node; + var panel = document.querySelector("#node-panel"); + if(panel) + panel.close(); + var ref_window = this.getCanvasWindow(); + panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); + panel.id = "node-panel"; + panel.node = node; + panel.classList.add("settings"); + var that = this; + var graphcanvas = this; + + function inner_refresh() + { + panel.content.innerHTML = ""; //clear + panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); + + panel.addHTML("

Properties

"); + + for(var i in node.properties) + { + var value = node.properties[i]; + var info = node.getPropertyInfo(i); + var type = info.type || "string"; + + //in case the user wants control over the side panel widget + if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) ) + continue; + + panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ + node.setProperty(name,value); + graphcanvas.dirty_canvas = true; + }); + } + + panel.addSeparator(); + + if(node.onShowCustomPanelInfo) + node.onShowCustomPanelInfo(panel); + + /* + panel.addHTML("

Connections

"); + var connection_containers = panel.addHTML("
","connections"); + var inputs = connection_containers.querySelector(".inputs"); + var outputs = connection_containers.querySelector(".outputs"); + */ + + panel.addButton("Delete",function(){ + if(node.block_delete) + return; + node.graph.remove(node); + panel.close(); + }).classList.add("delete"); + } + + function inner_showCodePad( node, propname ) + { + panel.style.top = "calc( 50% - 250px)"; + panel.style.left = "calc( 50% - 400px)"; + panel.style.width = "800px"; + panel.style.height = "500px"; + + if(window.CodeFlask) //disabled for now + { + panel.content.innerHTML = "
"; + var flask = new CodeFlask( "div.code", { language: 'js' }); + flask.updateCode(node.properties[propname]); + flask.onUpdate( function(code) { + node.setProperty(propname, code); + }); + } + else + { + panel.content.innerHTML = ""; + var textarea = panel.content.querySelector("textarea"); + textarea.value = node.properties[propname]; + textarea.addEventListener("keydown", function(e){ + //console.log(e); + if(e.code == "Enter" && e.ctrlKey ) + { + console.log("Assigned"); + node.setProperty(propname, textarea.value); + } + }); + textarea.style.height = "calc(100% - 40px)"; + } + var assign = that.createButton( "Assign", null, function(){ + node.setProperty(propname, textarea.value); + }); + panel.content.appendChild(assign); + var button = that.createButton( "Close", null, function(){ + panel.style.height = ""; + inner_refresh(); + }); + button.style.float = "right"; + panel.content.appendChild(button); + } + + inner_refresh(); + + this.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) + { + console.log("showing subgraph properties dialog"); + + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if(old_panel) + old_panel.close(); + + var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() + { + panel.clear(); + + //show currents + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + var html = "NameType"; + 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("button").addEventListener("click",function(e){ + node.removeInput( this.parentNode.dataset["slot"] ); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html,"subgraph_property extra", true); + elem.querySelector("button").addEventListener("click", function(e){ + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if(!name || node.findInputSlot(name) != -1) + return; + node.addInput(name,type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + }); + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + + LGraphCanvas.prototype.checkPanels = function() + { + if(!this.canvas) + return; + var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); + for(var i = 0; i < panels.length; ++i) + { + var panel = panels[i]; + if( !panel.node ) + continue; + if( !panel.node.graph || panel.graph != this.graph ) + panel.close(); + } + } + LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { node.collapse(); }; @@ -10058,12 +10437,11 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeToSubgraph }); - if (node.removable !== false) { - options.push(null, { - content: "Remove", - callback: LGraphCanvas.onMenuNodeRemove - }); - } + options.push(null, { + content: "Remove", + disabled: !(node.removable !== false && !node.block_delete ), + callback: LGraphCanvas.onMenuNodeRemove + }); if (node.graph && node.graph.onGetNodeMenuOptions) { node.graph.onGetNodeMenuOptions(options, node); @@ -11126,6 +11504,7 @@ if (typeof exports != "undefined") { return [["enabled", "boolean"]]; }; + /* Subgraph.prototype.onDrawTitle = function(ctx) { if (this.flags.collapsed) { return; @@ -11142,6 +11521,7 @@ if (typeof exports != "undefined") { ctx.lineTo(x + w * 0.5, -w * 0.3); ctx.fill(); }; + */ Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { var that = this; @@ -11150,6 +11530,7 @@ if (typeof exports != "undefined") { }, 10); }; + /* Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { if ( !this.flags.collapsed && @@ -11162,6 +11543,7 @@ if (typeof exports != "undefined") { }, 10); } }; + */ Subgraph.prototype.onAction = function(action, param) { this.subgraph.onAction(action, param); @@ -17518,7 +17900,7 @@ if (typeof exports != "undefined") { u_texture: 0, u_textureB: 1, value: value, - texSize: [width, height], + texSize: [width, height,1/width,1/height], time: time }) .draw(mesh); @@ -17533,7 +17915,7 @@ if (typeof exports != "undefined") { uniform sampler2D u_texture;\n\ uniform sampler2D u_textureB;\n\ varying vec2 v_coord;\n\ - uniform vec2 texSize;\n\ + uniform vec4 texSize;\n\ uniform float time;\n\ uniform float value;\n\ \n\ @@ -17570,6 +17952,20 @@ if (typeof exports != "undefined") { LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); + LGraphTextureOperation.registerPreset("normalmap","\n\ + float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\ + float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\ + float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\ + float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\ + float z4 = color.x;\n\ + float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\ + float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\ + float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\ + float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\ + vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\ + normal.xy *= value;\n\ + result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\ + "); LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); //webglstudio stuff... @@ -17602,7 +17998,7 @@ if (typeof exports != "undefined") { }; this.properties.code = LGraphTextureShader.pixel_shader; - this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec2.create(), time: 0 }; + this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 }; } LGraphTextureShader.title = "Shader"; @@ -17768,6 +18164,8 @@ if (typeof exports != "undefined") { } uniforms.texSize[0] = w; uniforms.texSize[1] = h; + uniforms.texSize[2] = 1/w; + uniforms.texSize[3] = 1/h; uniforms.time = this.graph.getTime(); uniforms.u_value = this.properties.u_value; uniforms.u_color.set( this.properties.u_color ); @@ -17788,7 +18186,7 @@ if (typeof exports != "undefined") { \n\ varying vec2 v_coord;\n\ uniform float time; //time in seconds\n\ -uniform vec2 texSize; //tex resolution\n\ +uniform vec4 texSize; //tex resolution\n\ uniform float u_value;\n\ uniform vec4 u_color;\n\n\ void main() {\n\ @@ -18438,6 +18836,69 @@ void main() {\n\ LGraphTextureDownsample ); + + + function LGraphTextureResize() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + size: [512,512], + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureResize.title = "Resize"; + LGraphTextureResize.desc = "Resize Texture"; + LGraphTextureResize.widgets_info = { + iterations: { type: "number", step: 1, precision: 0, min: 0 }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureResize.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex && !this._temp_texture) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + //we do not allow any texture different than texture 2D + if (!tex || tex.texture_type !== GL.TEXTURE_2D) { + return; + } + + var width = this.properties.size[0] | 0; + var height = this.properties.size[1] | 0; + if(width == 0) + width = tex.width; + if(height == 0) + height = tex.height; + var type = tex.type; + if (this.properties.precision === LGraphTexture.LOW) { + type = gl.UNSIGNED_BYTE; + } else if (this.properties.precision === LGraphTexture.HIGH) { + type = gl.HIGH_PRECISION_FORMAT; + } + + if( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type ) + this._texture = new GL.Texture( width, height, { type: type } ); + + tex.copyTo( this._texture ); + + if (this.properties.generate_mipmaps) { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0, this._texture); + }; + + LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize ); + // Texture Average ***************************************** function LGraphTextureAverage() { this.addInput("Texture", "Texture"); @@ -19105,7 +19566,7 @@ void main() {\n\ this.addInput("Texture", "Texture"); this.addInput("Atlas", "Texture"); this.addOutput("", "Texture"); - this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, texture: null }; + this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null }; if (!LGraphTextureEncode._shader) { LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader ); @@ -19181,6 +19642,12 @@ void main() {\n\ tex.toViewport(LGraphTextureEncode._shader, uniforms); }); + if (this.properties.generate_mipmaps) { + this._tex.bind(0); + gl.generateMipmap(this._tex.texture_type); + this._tex.unbind(0); + } + this.setOutputData(0, this._tex); }; diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css index acf575e1f..d286949b6 100755 --- a/css/litegraph-editor.css +++ b/css/litegraph-editor.css @@ -93,7 +93,7 @@ /* BUTTONS **********************/ -.litegraph-editor button { +.litegraph-editor .btn { /*font-family: "Metro Light";*/ color: #ccc; font-size: 20px; @@ -193,157 +193,6 @@ background-image: url("../demo/imgs/load-progress-full.png"); } -.litegraph-editor .dialog { - position: absolute; - top: 50%; - left: 50%; - margin-top: -150px; - margin-left: -200px; - - background-color: #222222; - - min-width: 400px; - min-height: 200px; - box-shadow: 0 0 4px #111; - border-radius: 6px; -} - -.litegraph-editor .dialog.settings { - left: 10px; - top: 10px; - height: calc( 100% - 20px ); - margin: auto; -} - -.litegraph-editor .dialog .close { - float: right; - margin: 4px; - margin-right: 10px; - cursor: pointer; - font-size: 1.4em; -} - -.litegraph-editor .dialog .close:hover { - color: white; -} - -.litegraph-editor .dialog .dialog-header { - color: #AAA; - border-bottom: 1px solid #161616; -} - -.litegraph-editor .dialog .dialog-header { height: 40px; } -.litegraph-editor .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;} - -.litegraph-editor .dialog .dialog-header .dialog-title { - font: 20px "Arial"; - margin: 4px; - padding: 4px 10px; - display: inline-block; -} - -.litegraph-editor .dialog .dialog-content { - height: calc(100% - 90px); - width: calc(100% - 10px); - /*background-color: black;*/ - padding: 4px; - display: inline-block; - color: #AAA; -} - -.litegraph-editor .dialog .dialog-content h3 { - margin: 10px; -} - -.litegraph-editor .dialog .dialog-content .connections { - flex-direction: row; -} - -.litegraph-editor .dialog .dialog-content .connections .connections_side { - width: calc(50% - 5px); - min-height: 100px; - background-color: black; - display: flex; -} - -.litegraph-editor .dialog .node_type { - font-size: 1.2em; - display: block; - margin: 10px; -} - -.litegraph-editor .dialog .node_desc { - opacity: 0.5; - display: block; - margin: 10px; -} - -.litegraph-editor .dialog .separator { - display: block; - width: calc( 100% - 4px ); - height: 1px; - border-top: 1px solid #000; - border-bottom: 1px solid #333; - margin: 10px 2px; - padding: 0; -} - -.litegraph-editor .dialog .property { - margin-bottom: 2px; - padding: 0; -} - -.litegraph-editor .dialog .property_name { - color: #737373; - display: inline-block; - text-align: left; - vertical-align: top; - width: 120px; - padding-left: 4px; - overflow: hidden; -} - -.litegraph-editor .dialog .property_value { - display: inline-block; - text-align: right; - color: #AAA; - background-color: #1A1A1A; - width: calc( 100% - 122px ); - max-height: 300px; - padding: 4px; - padding-right: 12px; - overflow: hidden; - cursor: pointer; - border-radius: 3px; -} - -.litegraph-editor .dialog .property_value:hover { - color: white; -} - -.litegraph-editor .dialog .property.boolean .property_value { - padding-right: 30px; -} - -.litegraph-editor .dialog button { - border-radius: 4px; - padding: 4px 20px; - margin-left: 0px; - background-color: #060606; - color: #8e8e8e; -} - -.litegraph-editor .dialog button:hover { - background-color: #111; - color: #FFF; -} - -.litegraph-editor .dialog button.delete:hover { - background-color: #F33; - color: black; -} - - .litegraph-editor textarea.code, .litegraph-editor div.code { height: 100%; width: 100%; diff --git a/css/litegraph.css b/css/litegraph.css index 7b34e3e89..6f03567a4 100755 --- a/css/litegraph.css +++ b/css/litegraph.css @@ -5,6 +5,7 @@ user-select: none; -moz-user-select: none; -webkit-user-select: none; + outline: none; } .litegraph.litecontextmenu { @@ -207,6 +208,234 @@ color: black; } +/* DIALOGs ******/ + +.litegraph .dialog { + position: absolute; + top: 50%; + left: 50%; + margin-top: -150px; + margin-left: -200px; + + background-color: #2A2A2A; + + min-width: 400px; + min-height: 200px; + box-shadow: 0 0 4px #111; + border-radius: 6px; +} + +.litegraph .dialog.settings { + left: 10px; + top: 10px; + height: calc( 100% - 20px ); + margin: auto; +} + +.litegraph .dialog .close { + float: right; + margin: 4px; + margin-right: 10px; + cursor: pointer; + font-size: 1.4em; +} + +.litegraph .dialog .close:hover { + color: white; +} + +.litegraph .dialog .dialog-header { + color: #AAA; + border-bottom: 1px solid #161616; +} + +.litegraph .dialog .dialog-header { height: 40px; } +.litegraph .dialog .dialog-footer { height: 50px; padding: 10px; border-top: 1px solid #1a1a1a;} + +.litegraph .dialog .dialog-header .dialog-title { + font: 20px "Arial"; + margin: 4px; + padding: 4px 10px; + display: inline-block; +} + +.litegraph .dialog .dialog-content { + height: calc(100% - 90px); + width: calc(100% - 10px); + min-height: 100px; + /*background-color: black;*/ + padding: 4px; + display: inline-block; + color: #AAA; +} + +.litegraph .dialog .dialog-content h3 { + margin: 10px; +} + +.litegraph .dialog .dialog-content .connections { + flex-direction: row; +} + +.litegraph .dialog .dialog-content .connections .connections_side { + width: calc(50% - 5px); + min-height: 100px; + background-color: black; + display: flex; +} + +.litegraph .dialog .node_type { + font-size: 1.2em; + display: block; + margin: 10px; +} + +.litegraph .dialog .node_desc { + opacity: 0.5; + display: block; + margin: 10px; +} + +.litegraph .dialog .separator { + display: block; + width: calc( 100% - 4px ); + height: 1px; + border-top: 1px solid #000; + border-bottom: 1px solid #333; + margin: 10px 2px; + padding: 0; +} + +.litegraph .dialog .property { + margin-bottom: 2px; + padding: 0; +} + +.litegraph .dialog .property_name { + color: #737373; + display: inline-block; + text-align: left; + vertical-align: top; + width: 120px; + padding-left: 4px; + overflow: hidden; +} + +.litegraph .dialog .property_value { + display: inline-block; + text-align: right; + color: #AAA; + background-color: #1A1A1A; + width: calc( 100% - 122px ); + max-height: 300px; + padding: 4px; + padding-right: 12px; + overflow: hidden; + cursor: pointer; + border-radius: 3px; +} + +.litegraph .dialog .property_value:hover { + color: white; +} + +.litegraph .dialog .property.boolean .property_value { + padding-right: 30px; +} + +.litegraph .dialog .btn { + border-radius: 4px; + padding: 4px 20px; + margin-left: 0px; + background-color: #060606; + color: #8e8e8e; +} + +.litegraph .dialog .btn:hover { + background-color: #111; + color: #FFF; +} + +.litegraph .dialog .btn.delete:hover { + background-color: #F33; + color: black; +} + +.litegraph .subgraph_property { + padding-bottom: 4px; +} + +.litegraph .subgraph_property:hover { + background-color: #333; +} + +.litegraph .subgraph_property.extra { + margin-top: 8px; +} + +.litegraph .subgraph_property span.name { + font-size: 1.3em; + padding-left: 4px; +} + +.litegraph .subgraph_property span.type { + opacity: 0.5; + margin-right: 20px; + padding-left: 4px; +} + +.litegraph .subgraph_property span.label { + display: inline-block; + width: 60px; + padding: 0px 10px; +} + +.litegraph .subgraph_property input { + width: 140px; + color: #999; + background-color: #1A1A1A; + border-radius: 4px; + border: 0; + margin-right: 10px; + padding: 4px; + padding-left: 10px; +} + +.litegraph .subgraph_property button { + background-color: #1c1c1c; + color: #aaa; + border: 0; + border-radius: 2px; + padding: 4px 10px; + cursor: pointer; +} + +.litegraph .subgraph_property.extra { + color: #ccc; +} + +.litegraph .subgraph_property.extra input { + background-color: #111; +} + +.litegraph .bullet_icon { + margin-left: 10px; + border-radius: 10px; + width: 12px; + height: 12px; + background-color: #666; + display: inline-block; + margin-top: 2px; + margin-right: 4px; + transition: background-color 0.1s ease 0s; + -moz-transition: background-color 0.1s ease 0s; +} + +.litegraph .bullet_icon:hover { + background-color: #698; + cursor: pointer; +} + /* OLD */ .graphcontextmenu { diff --git a/demo/js/code.js b/demo/js/code.js index 7038567a6..4f6ecb712 100644 --- a/demo/js/code.js +++ b/demo/js/code.js @@ -1,7 +1,7 @@ var webgl_canvas = null; LiteGraph.node_images_path = "../nodes_data/"; -var editor = new LiteGraph.Editor("main",{miniwindow:true}); +var editor = new LiteGraph.Editor("main",{miniwindow:false}); window.graphcanvas = editor.graphcanvas; window.graph = editor.graph; window.addEventListener("resize", function() { editor.graphcanvas.resize(); } ); @@ -17,7 +17,7 @@ LiteGraph.allow_scripts = true; //create scene selector var elem = document.createElement("span"); elem.className = "selector"; -elem.innerHTML = "Demo | "; +elem.innerHTML = "Demo | "; editor.tools.appendChild(elem); var select = elem.querySelector("select"); select.addEventListener("change", function(e){ @@ -102,6 +102,7 @@ function enableWebGL() "js/libs/litegl.js", "../src/nodes/gltextures.js", "../src/nodes/glfx.js", + "../src/nodes/shaders.js", "../src/nodes/geometry.js" ]; diff --git a/demo/style.css b/demo/style.css index ecf79c0e7..02f642f79 100755 --- a/demo/style.css +++ b/demo/style.css @@ -123,7 +123,7 @@ label { color: #AAF; } -input,textarea { +.header input { color: #EEE; background-color: #555; font-size: 1.2em; diff --git a/src/litegraph-editor.js b/src/litegraph-editor.js index 25a40f7bf..e8ddb80b5 100755 --- a/src/litegraph-editor.js +++ b/src/litegraph-editor.js @@ -9,7 +9,7 @@ function Editor(container_id, options) { var root = document.createElement("div"); this.root = root; - root.className = "litegraph-editor"; + root.className = "litegraph litegraph-editor"; root.innerHTML = html; this.tools = root.querySelector(".tools"); @@ -27,7 +27,6 @@ function Editor(container_id, options) { }; graphcanvas.onDropItem = this.onDropItem.bind(this); - graphcanvas.onShowNodePanel = this.onShowNodePanel.bind(this); //add stuff //this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" ); @@ -115,157 +114,12 @@ Editor.prototype.addToolsButton = function( id, name, icon_url, callback, contai this.root.querySelector(container).appendChild(button); }; -Editor.prototype.createPanel = function(title, options) { - options = options || {}; - - var ref_window = options.window || window; - var root = document.createElement("div"); - root.className = "dialog"; - root.innerHTML = "
"; - root.header = root.querySelector(".dialog-header"); - if(options.closable) - { - var close = document.createElement("span"); - close.innerHTML = "✕"; - close.classList.add("close"); - close.addEventListener("click",function(){ - root.close(); - }); - root.header.appendChild(close); - } - root.title_element = root.querySelector(".dialog-title"); - root.title_element.innerText = title; - root.content = root.querySelector(".dialog-content"); - root.footer = root.querySelector(".dialog-footer"); - root.close = function() - { - this.parentNode.removeChild(this); - } - - root.addHTML = function(code, classname) - { - var elem = document.createElement("div"); - if(classname) - elem.className = classname; - elem.innerHTML = code; - root.content.appendChild(elem); - return elem; - } - - root.addButton = function( name, callback, options ) - { - var elem = document.createElement("button"); - elem.innerText = name; - elem.options = options; - elem.addEventListener("click",callback); - root.footer.appendChild(elem); - return elem; - } - - root.addSeparator = function() - { - var elem = document.createElement("div"); - elem.className = "separator"; - root.content.appendChild(elem); - } - - root.addWidget = function( type, name, value, options, callback ) - { - options = options || {}; - var str_value = String(value); - if(type == "number") - str_value = value.toFixed(3); - - var elem = document.createElement("div"); - elem.className = "property"; - elem.innerHTML = ""; - elem.querySelector(".property_name").innerText = name; - var value_element = elem.querySelector(".property_value"); - value_element.innerText = str_value; - elem.dataset["property"] = name; - elem.dataset["type"] = options.type || type; - elem.options = options; - elem.value = value; - - //if( type == "code" ) - // elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); - if (type == "boolean") - { - elem.classList.add("boolean"); - if(value) - elem.classList.add("bool-on"); - elem.addEventListener("click", function(){ - //var v = node.properties[this.dataset["property"]]; - //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; - var propname = this.dataset["property"]; - this.value = !this.value; - this.classList.toggle("bool-on"); - this.querySelector(".property_value").innerText = this.value ? "true" : "false"; - innerChange(propname, this.value ); - }); - } - else if (type == "string" || type == "number") - { - value_element.setAttribute("contenteditable",true); - value_element.addEventListener("keydown", function(e){ - if(e.code == "Enter") - { - e.preventDefault(); - this.blur(); - } - }); - value_element.addEventListener("blur", function(){ - var v = this.innerText; - var propname = this.parentNode.dataset["property"]; - var proptype = this.parentNode.dataset["type"]; - if( proptype == "number") - v = Number(v); - innerChange(propname, v); - }); - } - else if (type == "enum") - value_element.addEventListener("click", function(event){ - var values = options.values || []; - var propname = this.parentNode.dataset["property"]; - var elem_that = this; - var menu = new LiteGraph.ContextMenu(values,{ - event: event, - className: "dark", - callback: inner_clicked - }, - ref_window); - function inner_clicked(v, option, event) { - //node.setProperty(propname,v); - //graphcanvas.dirty_canvas = true; - elem_that.innerText = v; - innerChange(propname,v); - return false; - } - }); - - root.content.appendChild(elem); - - function innerChange(name, value) - { - console.log("change",name,value); - //that.dirty_canvas = true; - if(options.callback) - options.callback(name,value); - if(callback) - callback(name,value); - } - - return elem; - } - - return root; -}; - Editor.prototype.createButton = function(name, icon_url, callback) { var button = document.createElement("button"); if (icon_url) { button.innerHTML = " "; } + button.classList.add("btn"); button.innerHTML += name; if(callback) button.addEventListener("click", callback ); @@ -273,7 +127,7 @@ Editor.prototype.createButton = function(name, icon_url, callback) { }; Editor.prototype.onLoadButton = function() { - var panel = this.createPanel("Load session",{closable:true}); + var panel = this.graphcanvas.createPanel("Load session",{closable:true}); //TO DO this.root.appendChild(panel); @@ -332,107 +186,6 @@ Editor.prototype.onDropItem = function(e) } } -//shows the left side panel with the node info -Editor.prototype.onShowNodePanel = function(node) -{ - window.SELECTED_NODE = node; - var panel = document.querySelector("#node-panel"); - if(panel) - panel.close(); - var ref_window = this.graphcanvas.getCanvasWindow(); - panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); - panel.id = "node-panel"; - panel.classList.add("settings"); - var that = this; - var graphcanvas = this.graphcanvas; - - function inner_refresh() - { - panel.content.innerHTML = ""; //clear - panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); - - panel.addHTML("

Properties

"); - - for(var i in node.properties) - { - var value = node.properties[i]; - var info = node.getPropertyInfo(i); - var type = info.type || "string"; - - //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) ) - continue; - - panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ - node.setProperty(name,value); - graphcanvas.dirty_canvas = true; - }); - } - - panel.addSeparator(); - - /* - panel.addHTML("

Connections

"); - var connection_containers = panel.addHTML("
","connections"); - var inputs = connection_containers.querySelector(".inputs"); - var outputs = connection_containers.querySelector(".outputs"); - */ - - - panel.addButton("Delete",function(){ - node.graph.remove(node); - panel.close(); - }).classList.add("delete"); - } - - function inner_showCodePad( node, propname ) - { - panel.style.top = "calc( 50% - 250px)"; - panel.style.left = "calc( 50% - 400px)"; - panel.style.width = "800px"; - panel.style.height = "500px"; - - if(window.CodeFlask) //disabled for now - { - panel.content.innerHTML = "
"; - var flask = new CodeFlask( "div.code", { language: 'js' }); - flask.updateCode(node.properties[propname]); - flask.onUpdate( function(code) { - node.setProperty(propname, code); - }); - } - else - { - panel.content.innerHTML = ""; - var textarea = panel.content.querySelector("textarea"); - textarea.value = node.properties[propname]; - textarea.addEventListener("keydown", function(e){ - //console.log(e); - if(e.code == "Enter" && e.ctrlKey ) - { - console.log("Assigned"); - node.setProperty(propname, textarea.value); - } - }); - textarea.style.height = "calc(100% - 40px)"; - } - var assign = that.createButton( "Assign", null, function(){ - node.setProperty(propname, textarea.value); - }); - panel.content.appendChild(assign); - var button = that.createButton( "Close", null, function(){ - panel.style.height = ""; - inner_refresh(); - }); - button.style.float = "right"; - panel.content.appendChild(button); - } - - inner_refresh(); - - this.content.appendChild( panel ); -} - Editor.prototype.goFullscreen = function() { if (this.root.requestFullscreen) { this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT); @@ -466,7 +219,7 @@ Editor.prototype.addMiniWindow = function(w, h) { var canvas = miniwindow.querySelector("canvas"); var that = this; - var graphcanvas = new LGraphCanvas(canvas, this.graph); + var graphcanvas = new LGraphCanvas( canvas, this.graph ); graphcanvas.show_info = false; graphcanvas.background_image = "imgs/grid.png"; graphcanvas.scale = 0.25; diff --git a/src/litegraph.js b/src/litegraph.js index b2fcda9fb..6ca05a999 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -1380,6 +1380,9 @@ this.onNodeRemoved(node); } + //close panels + this.sendActionToCanvas("checkPanels"); + this.setDirtyCanvas(true, true); this.change(); @@ -2672,6 +2675,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 @@ -3088,9 +3108,11 @@ } this.inputs.push(o); this.setSize( this.computeSize() ); + if (this.onInputAdded) { this.onInputAdded(o); } + this.setDirtyCanvas(true, true); return o; }; @@ -3130,7 +3152,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; @@ -3143,7 +3165,7 @@ } this.setSize( this.computeSize() ); if (this.onInputRemoved) { - this.onInputRemoved(slot); + this.onInputRemoved(slot, slot_info[0] ); } this.setDirtyCanvas(true, true); }; @@ -4708,6 +4730,7 @@ LGraphNode.prototype.executeAction = function(action) } graph.attachCanvas(this); + this.checkPanels(); this.setDirty(true, true); }; @@ -4839,6 +4862,7 @@ LGraphNode.prototype.executeAction = function(action) } var canvas = this.canvas; + var ref_window = this.getCanvasWindow(); var document = ref_window.document; //hack used when moving canvas between windows @@ -5241,6 +5265,7 @@ LGraphNode.prototype.executeAction = function(action) //it wasn't clicked on the links boxes if (!skip_action) { var block_drag_node = false; + var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); @@ -5253,18 +5278,31 @@ LGraphNode.prototype.executeAction = function(action) if (is_double_click && this.selected_nodes[node.id]) { //double click node if (node.onDblClick) { - node.onDblClick( e, [ e.canvasX - node.pos[0], e.canvasY - node.pos[1] ], this ); + node.onDblClick( e, pos, this ); } this.processNodeDblClicked(node); block_drag_node = true; } //if do not capture mouse - if ( node.onMouseDown && node.onMouseDown( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ) ) { - block_drag_node = true; - } else if (this.live_mode) { - clicking_canvas_bg = true; + if ( node.onMouseDown && node.onMouseDown( e, pos, this ) ) { block_drag_node = true; + } else { + //open subgraph button + if(node.subgraph && !node.skip_subgraph_button) + { + if ( !node.flags.collapsed && pos[0] > node.size[0] - LiteGraph.NODE_TITLE_HEIGHT && pos[1] < 0 ) { + var that = this; + setTimeout(function() { + that.openSubgraph(node.subgraph); + }, 10); + } + } + + if (this.live_mode) { + clicking_canvas_bg = true; + block_drag_node = true; + } } if (!block_drag_node) { @@ -5458,6 +5496,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse over a node if (node) { + + if(node.redraw_on_mouse) + this.dirty_canvas = true; + //this.canvas.style.cursor = "move"; if (!node.mouseOver) { //mouse enter @@ -6198,6 +6240,10 @@ LGraphNode.prototype.executeAction = function(action) if (this.onShowNodePanel) { this.onShowNodePanel(n); } + else + { + this.showShowNodePanel(n); + } if (this.onNodeDblClicked) { this.onNodeDblClicked(n); @@ -6348,6 +6394,10 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.deleteSelectedNodes = function() { for (var i in this.selected_nodes) { var node = this.selected_nodes[i]; + + if(node.block_delete) + continue; + //autoconnect when possible (very basic, only takes into account first input-output) if(node.inputs && node.inputs.length && node.outputs && node.outputs.length && LiteGraph.isValidConnection( node.inputs[0].type, node.outputs[0].type ) && node.inputs[0].link && node.outputs[0].links && node.outputs[0].links.length ) { @@ -6970,14 +7020,8 @@ LGraphNode.prototype.executeAction = function(action) var glow = false; this.current_node = node; - var color = - node.color || - node.constructor.color || - LiteGraph.NODE_DEFAULT_COLOR; - var bgcolor = - node.bgcolor || - node.constructor.bgcolor || - LiteGraph.NODE_DEFAULT_BGCOLOR; + var color = node.color || node.constructor.color || LiteGraph.NODE_DEFAULT_COLOR; + var bgcolor = node.bgcolor || node.constructor.bgcolor || LiteGraph.NODE_DEFAULT_BGCOLOR; //shadow and glow if (node.mouseOver) { @@ -7491,20 +7535,14 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas); + node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse ); } //title bg (remember, it is rendered ABOVE the node) if (render_title || title_mode == LiteGraph.TRANSPARENT_TITLE) { //title bar if (node.onDrawTitleBar) { - node.onDrawTitleBar( - ctx, - title_height, - size, - this.ds.scale, - fgcolor - ); + node.onDrawTitleBar( ctx, title_height, size, this.ds.scale, fgcolor ); } else if ( title_mode != LiteGraph.TRANSPARENT_TITLE && (node.constructor.title_color || this.render_title_colored) @@ -7645,6 +7683,28 @@ 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; + if( shape == LiteGraph.BOX_SHAPE || low_quality) + ctx.fillRect(x+2, -w+2, w-4, w-4); + else + { + ctx.beginPath(); + ctx.roundRect(x+2, -w+2, w-4, w-4,4); + ctx.fill(); + } + ctx.fillStyle = "#333"; + ctx.beginPath(); + ctx.moveTo(x + w * 0.2, -w * 0.6); + ctx.lineTo(x + w * 0.8, -w * 0.6); + ctx.lineTo(x + w * 0.5, -w * 0.3); + ctx.fill(); + } + + //custom title render if (node.onDrawTitle) { node.onDrawTitle(ctx); } @@ -8883,6 +8943,7 @@ LGraphNode.prototype.executeAction = function(action) } if (!entries.length) { + console.log("no input entries"); return; } @@ -9752,6 +9813,341 @@ LGraphNode.prototype.executeAction = function(action) return dialog; }; + LGraphCanvas.prototype.createPanel = function(title, options) { + options = options || {}; + + var ref_window = options.window || window; + var root = document.createElement("div"); + root.className = "litegraph dialog"; + root.innerHTML = "
"; + root.header = root.querySelector(".dialog-header"); + + if(options.width) + root.style.width = options.width + (options.width.constructor === Number ? "px" : ""); + if(options.height) + root.style.height = options.height + (options.height.constructor === Number ? "px" : ""); + if(options.closable) + { + var close = document.createElement("span"); + close.innerHTML = "✕"; + close.classList.add("close"); + close.addEventListener("click",function(){ + root.close(); + }); + root.header.appendChild(close); + } + root.title_element = root.querySelector(".dialog-title"); + root.title_element.innerText = title; + root.content = root.querySelector(".dialog-content"); + root.footer = root.querySelector(".dialog-footer"); + + root.close = function() + { + this.parentNode.removeChild(this); + } + + root.clear = function() + { + this.content.innerHTML = ""; + } + + root.addHTML = function(code, classname, on_footer) + { + var elem = document.createElement("div"); + if(classname) + elem.className = classname; + elem.innerHTML = code; + if(on_footer) + root.footer.appendChild(elem); + else + root.content.appendChild(elem); + return elem; + } + + root.addButton = function( name, callback, options ) + { + var elem = document.createElement("button"); + elem.innerText = name; + elem.options = options; + elem.classList.add("btn"); + elem.addEventListener("click",callback); + root.footer.appendChild(elem); + return elem; + } + + root.addSeparator = function() + { + var elem = document.createElement("div"); + elem.className = "separator"; + root.content.appendChild(elem); + } + + root.addWidget = function( type, name, value, options, callback ) + { + options = options || {}; + var str_value = String(value); + if(type == "number") + str_value = value.toFixed(3); + + var elem = document.createElement("div"); + elem.className = "property"; + elem.innerHTML = ""; + elem.querySelector(".property_name").innerText = name; + var value_element = elem.querySelector(".property_value"); + value_element.innerText = str_value; + elem.dataset["property"] = name; + elem.dataset["type"] = options.type || type; + elem.options = options; + elem.value = value; + + //if( type == "code" ) + // elem.addEventListener("click", function(){ inner_showCodePad( node, this.dataset["property"] ); }); + if (type == "boolean") + { + elem.classList.add("boolean"); + if(value) + elem.classList.add("bool-on"); + elem.addEventListener("click", function(){ + //var v = node.properties[this.dataset["property"]]; + //node.setProperty(this.dataset["property"],!v); this.innerText = v ? "true" : "false"; + var propname = this.dataset["property"]; + this.value = !this.value; + this.classList.toggle("bool-on"); + this.querySelector(".property_value").innerText = this.value ? "true" : "false"; + innerChange(propname, this.value ); + }); + } + else if (type == "string" || type == "number") + { + value_element.setAttribute("contenteditable",true); + value_element.addEventListener("keydown", function(e){ + if(e.code == "Enter") + { + e.preventDefault(); + this.blur(); + } + }); + value_element.addEventListener("blur", function(){ + var v = this.innerText; + var propname = this.parentNode.dataset["property"]; + var proptype = this.parentNode.dataset["type"]; + if( proptype == "number") + v = Number(v); + innerChange(propname, v); + }); + } + else if (type == "enum") + value_element.addEventListener("click", function(event){ + var values = options.values || []; + var propname = this.parentNode.dataset["property"]; + var elem_that = this; + var menu = new LiteGraph.ContextMenu(values,{ + event: event, + className: "dark", + callback: inner_clicked + }, + ref_window); + function inner_clicked(v, option, event) { + //node.setProperty(propname,v); + //graphcanvas.dirty_canvas = true; + elem_that.innerText = v; + innerChange(propname,v); + return false; + } + }); + + root.content.appendChild(elem); + + function innerChange(name, value) + { + console.log("change",name,value); + //that.dirty_canvas = true; + if(options.callback) + options.callback(name,value); + if(callback) + callback(name,value); + } + + return elem; + } + + return root; + }; + + LGraphCanvas.prototype.showShowNodePanel = function( node ) + { + window.SELECTED_NODE = node; + var panel = document.querySelector("#node-panel"); + if(panel) + panel.close(); + var ref_window = this.getCanvasWindow(); + panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); + panel.id = "node-panel"; + panel.node = node; + panel.classList.add("settings"); + var that = this; + var graphcanvas = this; + + function inner_refresh() + { + panel.content.innerHTML = ""; //clear + panel.addHTML(""+node.type+""+(node.constructor.desc || "")+""); + + panel.addHTML("

Properties

"); + + for(var i in node.properties) + { + var value = node.properties[i]; + var info = node.getPropertyInfo(i); + var type = info.type || "string"; + + //in case the user wants control over the side panel widget + if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) ) + continue; + + panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ + node.setProperty(name,value); + graphcanvas.dirty_canvas = true; + }); + } + + panel.addSeparator(); + + if(node.onShowCustomPanelInfo) + node.onShowCustomPanelInfo(panel); + + /* + panel.addHTML("

Connections

"); + var connection_containers = panel.addHTML("
","connections"); + var inputs = connection_containers.querySelector(".inputs"); + var outputs = connection_containers.querySelector(".outputs"); + */ + + panel.addButton("Delete",function(){ + if(node.block_delete) + return; + node.graph.remove(node); + panel.close(); + }).classList.add("delete"); + } + + function inner_showCodePad( node, propname ) + { + panel.style.top = "calc( 50% - 250px)"; + panel.style.left = "calc( 50% - 400px)"; + panel.style.width = "800px"; + panel.style.height = "500px"; + + if(window.CodeFlask) //disabled for now + { + panel.content.innerHTML = "
"; + var flask = new CodeFlask( "div.code", { language: 'js' }); + flask.updateCode(node.properties[propname]); + flask.onUpdate( function(code) { + node.setProperty(propname, code); + }); + } + else + { + panel.content.innerHTML = ""; + var textarea = panel.content.querySelector("textarea"); + textarea.value = node.properties[propname]; + textarea.addEventListener("keydown", function(e){ + //console.log(e); + if(e.code == "Enter" && e.ctrlKey ) + { + console.log("Assigned"); + node.setProperty(propname, textarea.value); + } + }); + textarea.style.height = "calc(100% - 40px)"; + } + var assign = that.createButton( "Assign", null, function(){ + node.setProperty(propname, textarea.value); + }); + panel.content.appendChild(assign); + var button = that.createButton( "Close", null, function(){ + panel.style.height = ""; + inner_refresh(); + }); + button.style.float = "right"; + panel.content.appendChild(button); + } + + inner_refresh(); + + this.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showSubgraphPropertiesDialog = function(node) + { + console.log("showing subgraph properties dialog"); + + var old_panel = this.canvas.parentNode.querySelector(".subgraph_dialog"); + if(old_panel) + old_panel.close(); + + var panel = this.createPanel("Subgraph Inputs",{closable:true, width: 500}); + panel.node = node; + panel.classList.add("subgraph_dialog"); + + function inner_refresh() + { + panel.clear(); + + //show currents + if(node.inputs) + for(var i = 0; i < node.inputs.length; ++i) + { + var input = node.inputs[i]; + var html = " "; + var elem = panel.addHTML(html,"subgraph_property"); + elem.dataset["name"] = input.name; + elem.dataset["slot"] = i; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; + elem.querySelector("button").addEventListener("click",function(e){ + node.removeInput( Number( this.parentNode.dataset["slot"] ) ); + inner_refresh(); + }); + } + } + + //add extra + var html = " + NameType"; + var elem = panel.addHTML(html,"subgraph_property extra", true); + elem.querySelector("button").addEventListener("click", function(e){ + var elem = this.parentNode; + var name = elem.querySelector(".name").value; + var type = elem.querySelector(".type").value; + if(!name || node.findInputSlot(name) != -1) + return; + node.addInput(name,type); + elem.querySelector(".name").value = ""; + elem.querySelector(".type").value = ""; + inner_refresh(); + }); + + inner_refresh(); + this.canvas.parentNode.appendChild(panel); + return panel; + } + + LGraphCanvas.prototype.checkPanels = function() + { + if(!this.canvas) + return; + var panels = this.canvas.parentNode.querySelectorAll(".litegraph.dialog"); + for(var i = 0; i < panels.length; ++i) + { + var panel = panels[i]; + if( !panel.node ) + continue; + if( !panel.node.graph || panel.graph != this.graph ) + panel.close(); + } + } + LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { node.collapse(); }; @@ -10056,12 +10452,11 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeToSubgraph }); - if (node.removable !== false) { - options.push(null, { - content: "Remove", - callback: LGraphCanvas.onMenuNodeRemove - }); - } + options.push(null, { + content: "Remove", + disabled: !(node.removable !== false && !node.block_delete ), + callback: LGraphCanvas.onMenuNodeRemove + }); if (node.graph && node.graph.onGetNodeMenuOptions) { node.graph.onGetNodeMenuOptions(options, node); diff --git a/src/nodes/base.js b/src/nodes/base.js index 5e4c429d4..e0f4050cd 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -52,6 +52,7 @@ return [["enabled", "boolean"]]; }; + /* Subgraph.prototype.onDrawTitle = function(ctx) { if (this.flags.collapsed) { return; @@ -68,6 +69,7 @@ ctx.lineTo(x + w * 0.5, -w * 0.3); ctx.fill(); }; + */ Subgraph.prototype.onDblClick = function(e, pos, graphcanvas) { var that = this; @@ -76,6 +78,7 @@ }, 10); }; + /* Subgraph.prototype.onMouseDown = function(e, pos, graphcanvas) { if ( !this.flags.collapsed && @@ -88,6 +91,7 @@ }, 10); } }; + */ Subgraph.prototype.onAction = function(action, param) { this.subgraph.onAction(action, param); diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index 725f9ad65..3c24ddf69 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -660,7 +660,7 @@ u_texture: 0, u_textureB: 1, value: value, - texSize: [width, height], + texSize: [width, height,1/width,1/height], time: time }) .draw(mesh); @@ -675,7 +675,7 @@ uniform sampler2D u_texture;\n\ uniform sampler2D u_textureB;\n\ varying vec2 v_coord;\n\ - uniform vec2 texSize;\n\ + uniform vec4 texSize;\n\ uniform float time;\n\ uniform float value;\n\ \n\ @@ -712,6 +712,20 @@ LGraphTextureOperation.registerPreset("displace","texture2D(u_texture, uv + (colorB.xy - vec2(0.5)) * value).xyz"); LGraphTextureOperation.registerPreset("grayscale","vec3(color.x + color.y + color.z) * value / 3.0"); LGraphTextureOperation.registerPreset("saturation","mix( vec3(color.x + color.y + color.z) / 3.0, color, value )"); + LGraphTextureOperation.registerPreset("normalmap","\n\ + float z0 = texture2D(u_texture, uv + vec2(-texSize.z, -texSize.w) ).x;\n\ + float z1 = texture2D(u_texture, uv + vec2(0.0, -texSize.w) ).x;\n\ + float z2 = texture2D(u_texture, uv + vec2(texSize.z, -texSize.w) ).x;\n\ + float z3 = texture2D(u_texture, uv + vec2(-texSize.z, 0.0) ).x;\n\ + float z4 = color.x;\n\ + float z5 = texture2D(u_texture, uv + vec2(texSize.z, 0.0) ).x;\n\ + float z6 = texture2D(u_texture, uv + vec2(-texSize.z, texSize.w) ).x;\n\ + float z7 = texture2D(u_texture, uv + vec2(0.0, texSize.w) ).x;\n\ + float z8 = texture2D(u_texture, uv + vec2(texSize.z, texSize.w) ).x;\n\ + vec3 normal = vec3( z2 + 2.0*z4 + z7 - z0 - 2.0*z3 - z5, z5 + 2.0*z6 + z7 -z0 - 2.0*z1 - z2, 1.0 );\n\ + normal.xy *= value;\n\ + result.xyz = normalize(normal) * 0.5 + vec3(0.5);\n\ + "); LGraphTextureOperation.registerPreset("threshold","vec3(color.x > colorB.x * value ? 1.0 : 0.0,color.y > colorB.y * value ? 1.0 : 0.0,color.z > colorB.z * value ? 1.0 : 0.0)"); //webglstudio stuff... @@ -744,7 +758,7 @@ }; this.properties.code = LGraphTextureShader.pixel_shader; - this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec2.create(), time: 0 }; + this._uniforms = { u_value: 1, u_color: vec4.create(), in_texture: 0, texSize: vec4.create(), time: 0 }; } LGraphTextureShader.title = "Shader"; @@ -910,6 +924,8 @@ } uniforms.texSize[0] = w; uniforms.texSize[1] = h; + uniforms.texSize[2] = 1/w; + uniforms.texSize[3] = 1/h; uniforms.time = this.graph.getTime(); uniforms.u_value = this.properties.u_value; uniforms.u_color.set( this.properties.u_color ); @@ -930,7 +946,7 @@ \n\ varying vec2 v_coord;\n\ uniform float time; //time in seconds\n\ -uniform vec2 texSize; //tex resolution\n\ +uniform vec4 texSize; //tex resolution\n\ uniform float u_value;\n\ uniform vec4 u_color;\n\n\ void main() {\n\ @@ -1580,6 +1596,69 @@ void main() {\n\ LGraphTextureDownsample ); + + + function LGraphTextureResize() { + this.addInput("Texture", "Texture"); + this.addOutput("", "Texture"); + this.properties = { + size: [512,512], + generate_mipmaps: false, + precision: LGraphTexture.DEFAULT + }; + } + + LGraphTextureResize.title = "Resize"; + LGraphTextureResize.desc = "Resize Texture"; + LGraphTextureResize.widgets_info = { + iterations: { type: "number", step: 1, precision: 0, min: 0 }, + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureResize.prototype.onExecute = function() { + var tex = this.getInputData(0); + if (!tex && !this._temp_texture) { + return; + } + + if (!this.isOutputConnected(0)) { + return; + } //saves work + + //we do not allow any texture different than texture 2D + if (!tex || tex.texture_type !== GL.TEXTURE_2D) { + return; + } + + var width = this.properties.size[0] | 0; + var height = this.properties.size[1] | 0; + if(width == 0) + width = tex.width; + if(height == 0) + height = tex.height; + var type = tex.type; + if (this.properties.precision === LGraphTexture.LOW) { + type = gl.UNSIGNED_BYTE; + } else if (this.properties.precision === LGraphTexture.HIGH) { + type = gl.HIGH_PRECISION_FORMAT; + } + + if( !this._texture || this._texture.width != width || this._texture.height != height || this._texture.type != type ) + this._texture = new GL.Texture( width, height, { type: type } ); + + tex.copyTo( this._texture ); + + if (this.properties.generate_mipmaps) { + this._texture.bind(0); + gl.generateMipmap(this._texture.texture_type); + this._texture.unbind(0); + } + + this.setOutputData(0, this._texture); + }; + + LiteGraph.registerNodeType( "texture/resize", LGraphTextureResize ); + // Texture Average ***************************************** function LGraphTextureAverage() { this.addInput("Texture", "Texture"); @@ -2247,7 +2326,7 @@ void main() {\n\ this.addInput("Texture", "Texture"); this.addInput("Atlas", "Texture"); this.addOutput("", "Texture"); - this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, texture: null }; + this.properties = { enabled: true, num_row_symbols: 4, symbol_size: 16, brightness: 1, colorize: false, filter: false, invert: false, precision: LGraphTexture.DEFAULT, generate_mipmaps: false, texture: null }; if (!LGraphTextureEncode._shader) { LGraphTextureEncode._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEncode.pixel_shader ); @@ -2323,6 +2402,12 @@ void main() {\n\ tex.toViewport(LGraphTextureEncode._shader, uniforms); }); + if (this.properties.generate_mipmaps) { + this._tex.bind(0); + gl.generateMipmap(this._tex.texture_type); + this._tex.unbind(0); + } + this.setOutputData(0, this._tex); }; diff --git a/src/nodes/shaders.js b/src/nodes/shaders.js new file mode 100644 index 000000000..f5b22969c --- /dev/null +++ b/src/nodes/shaders.js @@ -0,0 +1,429 @@ +(function(global) { + var LiteGraph = global.LiteGraph; + var LGraphCanvas = global.LGraphCanvas; + + if (typeof GL == "undefined") + return; + + + //common actions to all shader node classes + function setShaderNode( node_ctor ) + { + node_ctor.color = "#345"; + } + + function getShaderInputLinkName( node, slot ) + { + if(!node.inputs) + return null; + var link = node.getInputLink( slot ); + if( !link ) + return null; + var origin_node = node.graph.getNodeById( link.origin_id ); + if( !origin_node ) + return null; + if(origin_node.getOutputVarName) + return origin_node.getOutputVarName(link.origin_slot); + //generate + return "link_" + origin_node.id + "_" + link.origin_slot; + } + + function getShaderOutputLinkName( node, slot ) + { + return "link_" + node.id + "_" + slot; + } + + //used to host a shader body ******************* + function LGShaderContext() + { + this.vs_template = ""; + this.fs_template = ""; + this._uniforms = {}; + this._codeparts = {}; + } + + LGShaderContext.valid_types = ["float","vec2","vec3","vec4","sampler2D","mat3","mat4","int","boolean"]; + + LGShaderContext.prototype.clear = function() + { + this._uniforms = {}; + this._codeparts = {}; + } + + LGShaderContext.prototype.addUniform = function( name, type ) + { + this._uniforms[ name ] = type; + } + + LGShaderContext.prototype.addCode = function( hook, code ) + { + if(!this._codeparts[ hook ]) + this._codeparts[ hook ] = code + "\n"; + else + this._codeparts[ hook ] += code + "\n"; + } + + //generates the shader code from the template and the + LGShaderContext.prototype.computeShader = function( shader ) + { + var uniforms = ""; + for(var i in this._uniforms) + uniforms += "uniform " + this._uniforms[i] + " " + i + ";\n"; + + var parts = this._codeparts; + parts.uniforms = uniforms; + + var vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, parts ); + var fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, parts ); + + try + { + if(shader) + shader.updateShader( vs_code, fs_code ); + else + shader = new GL.Shader( vs_code, fs_code ); + return shader; + } + catch (err) + { + return null; + } + + return null;//never here + } + + // LGraphShaderGraph ***************************** + // applies a shader graph to texture, it can be uses as an example + + function LGraphShaderGraph() { + this.addOutput("out", "texture"); + this.properties = { width: 0, height: 0, alpha: false, precision: 0 }; + + this.subgraph = new LiteGraph.LGraph(); + this.subgraph._subgraph_node = this; + this.subgraph._is_subgraph = true; + + var subnode = LiteGraph.createNode("shader/fragcolor"); + this.subgraph.pos = [300,100]; + this.subgraph.add( subnode ); + + this.size = [180,60]; + this.redraw_on_mouse = true; //force redraw + + this._uniforms = {}; + this._shader = null; + this._context = new LGShaderContext(); + this._context.vs_template = GL.Shader.SCREEN_VERTEX_SHADER; + this._context.fs_template = LGraphShaderGraph.template; + } + + 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\ + "; + + LGraphShaderGraph.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphShaderGraph.title = "ShaderGraph"; + LGraphShaderGraph.desc = "Builds a shader using a graph"; + + LGraphShaderGraph.prototype.onSerialize = function(o) + { + o.subgraph = this.subgraph.serialize(); + } + + LGraphShaderGraph.prototype.onConfigure = function(o) + { + this.subgraph.configure(o.subgraph); + } + + LGraphShaderGraph.prototype.onExecute = function() { + if (!this.isOutputConnected(0)) + return; + + var w = this.properties.width | 0; + var h = this.properties.height | 0; + if (w == 0) { + w = gl.viewport_data[2]; + } //0 means default + if (h == 0) { + h = gl.viewport_data[3]; + } //0 means default + var type = LGraphTexture.getTextureType(this.properties.precision); + + var texture = this._texture; + if ( !texture || texture.width != w || texture.height != h || texture.type != type ) { + texture = this._texture = new GL.Texture(w, h, { + type: type, + format: this.alpha ? gl.RGBA : gl.RGB, + filter: gl.LINEAR + }); + } + + var shader = this.getShader(); + if(!shader) + return; + + var uniforms = this._uniforms; + + var tex_slot = 0; + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var input = this.inputs[i]; + var data = this.getInputData(i); + if(input.type == "texture") + { + if(!data) + data = GL.Texture.getWhiteTexture(); + data = data.bind(tex_slot++); + } + + if(data != null) + uniforms[ "u_" + input.name ] = data; + } + + var mesh = GL.Mesh.getScreenQuad(); + + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.BLEND ); + + texture.drawTo(function(){ + shader.uniforms( uniforms ); + shader.draw( mesh ); + }); + + //use subgraph output + this.setOutputData(0, texture ); + }; + + LGraphShaderGraph.prototype.onInputAdded = function( slot_info ) + { + var subnode = LiteGraph.createNode("shader/uniform"); + subnode.properties.name = slot_info.name; + subnode.properties.type = slot_info.type; + this.subgraph.add( subnode ); + } + + LGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info ) + { + var nodes = this.subgraph.findNodesByType("shader/uniform"); + for(var i = 0; i < nodes.length; ++i) + { + var node = nodes[i]; + if(node.properties.name == slot_info.name ) + this.subgraph.remove( node ); + } + } + + LGraphShaderGraph.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 + 10]; + } + + LGraphShaderGraph.prototype.getShader = function() + { + //if subgraph not changed? + if(this._shader && this._shader._version == this.subgraph._version) + return this._shader; + + //prepare context + this._context.clear(); + + //gets code from graph + this.subgraph.sendEventToAllNodes("onGetCode", this._context); + + //compile shader + var shader = this._context.computeShader(); + if(!shader) + { + this.boxcolor = "red"; + return this._shader; + } + else + this.boxcolor = null; + + this._shader = shader; + shader._version = this.subgraph._version; + return shader; + } + + LGraphShaderGraph.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 ); + } + + LGraphShaderGraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + if(localpos[1] > y) + { + graphcanvas.showSubgraphPropertiesDialog(this); + } + } + + LiteGraph.registerNodeType( "texture/shaderGraph", LGraphShaderGraph ); + + //Shader Nodes *************************** + + //applies a shader graph to a code + function LGraphShaderUniform() { + this.addOutput("out", ""); + this.properties = { name: "", type: "" }; + } + + LGraphShaderUniform.title = "Uniform"; + LGraphShaderUniform.desc = "Input data for the shader"; + + LGraphShaderUniform.prototype.getTitle = function() + { + return "uniform " + this.properties.name; + } + + LGraphShaderUniform.prototype.onGetCode = function( context ) + { + var type = this.properties.type; + if( !type ) + return; + if(type == "number") + type = "float"; + else if(type == "texture") + type = "sampler2D"; + if ( LGShaderContext.valid_types.indexOf(type) == -1 ) + return; + context.addUniform( "u_" + this.properties.name, type ); + } + + LGraphShaderUniform.prototype.getOutputVarName = function(slot) + { + return "u_" + this.properties.name; + } + + setShaderNode( LGraphShaderUniform ); + + LiteGraph.registerNodeType( "shader/uniform", LGraphShaderUniform ); + + + + function LGraphShaderAttribute() { + this.addOutput("out", "vec2"); + this.properties = { name: "coord", type: "vec2" }; + } + + LGraphShaderAttribute.title = "Attribute"; + LGraphShaderAttribute.desc = "Input data from mesh attribute"; + + LGraphShaderAttribute.prototype.getTitle = function() + { + return "att. " + this.properties.name; + } + + LGraphShaderAttribute.prototype.onGetCode = function( context ) + { + var type = this.properties.type; + if( !type || LGShaderContext.valid_types.indexOf(type) == -1 ) + return; + if(type == "number") + type = "float"; + if( this.properties.name != "coord") + context.addCode( "varying", " varying " + type +" v_" + this.properties.name ); + } + + LGraphShaderAttribute.prototype.getOutputVarName = function(slot) + { + return "v_" + this.properties.name; + } + + setShaderNode( LGraphShaderAttribute ); + + LiteGraph.registerNodeType( "shader/attribute", LGraphShaderAttribute ); + + + function LGraphShaderSampler2D() { + this.addInput("tex", "sampler2D"); + this.addInput("uv", "vec2"); + this.addOutput("rgba", "vec4"); + } + + LGraphShaderSampler2D.title = "Sampler2D"; + LGraphShaderSampler2D.desc = "Reads a pixel from a texture"; + + LGraphShaderSampler2D.prototype.onGetCode = function( context ) + { + var texname = getShaderInputLinkName( this, 0 ); + var code; + if(texname) + { + var uvname = getShaderInputLinkName( this, 1 ) || "v_coord"; + code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = texture2D("+texname+","+uvname+");"; + } + else + code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = vec4(0.0);"; + context.addCode( "fs_code", code ); + } + + setShaderNode( LGraphShaderSampler2D ); + + LiteGraph.registerNodeType( "shader/sampler2D", LGraphShaderSampler2D ); + + //********************************* + + function LGraphShaderFragColor() { + this.addInput("color", "float,vec2,vec3,vec4"); + this.block_delete = true; + } + + LGraphShaderFragColor.title = "FragColor"; + LGraphShaderFragColor.desc = "Pixel final color"; + + LGraphShaderFragColor.prototype.onGetCode = function( context ) + { + var link_name = getShaderInputLinkName( this, 0 ); + if(!link_name) + return; + + var code = link_name; + var type = this.getInputDataType(0); + if(type == "float") + code = "vec4(" + code + ");"; + else if(type == "vec2") + code = "vec4(" + code + ",0.0,1.0);"; + else if(type == "vec3") + code = "vec4(" + code + ",1.0);"; + + context.addCode("fs_code", "color = " + code + ";\n"); + } + + setShaderNode( LGraphShaderFragColor ); + + LiteGraph.registerNodeType( "shader/fragcolor", LGraphShaderFragColor ); + +})(this); \ No newline at end of file