diff --git a/README.md b/README.md index 7bf320a9f..ef62193ab 100755 --- a/README.md +++ b/README.md @@ -1,4 +1,59 @@ -# litegraph.js +# EXPERIMENTS WITH litegraph.js + +A fork of the ecceptional litegraph.js library. This is unofficial and unsupported. + +Here there are pushed a couple experiments, nothing serious, take it for what is it. Don't use it at others risk. + +MODS + + +- auto connect + - drag into node + - auto select first available slot by type + - drag from IN to OUT) + - drag to create a new node + +- [use with SHIFT when releasing connection to SEARCH!], or double click anywhere + +- filter nodes in search by types (filtering/proposing current type, or generic types, and 'wrong' types) + +- ContrlZ, ControlY and history basics + +- added "on request" mode: executed when an action or a trigger is executed + +- BIG MOD: + - ancestors calculation and execution: every node exeution check if his ancestors are "updated" (action lines are not considered) + - every action generate an ID that is stored by each node executed by it (and every frame is an action too) + - some event node creates new "IDs" when needed (when we want a behaviour that updates future nodes ancestors) + + +- generic fixes + +- script node fix (code widget) + +- fix auto create oin-trigger slot + +- dialogs hide on mouseleave fix + +- "boxcolor" color on execute and action, applied to all nodes (like the lovely timer node does) + +- close node property panel with Esc + +- edit title, color and mode in node property panel + +- allow multiline in textarea by shift-enter + +- new logic nodes (and, or, not), new generic compare node (num, string, obj) + +- basics of Html, cdnLibraries, openCv, and test nodes + + + +----------------------------------------- +Original README.md +----------------------------------------- + +# [litegraph.js](https://github.com/jagenjo/litegraph.js) A library in Javascript to create graphs in the browser similar to Unreal Blueprints. Nodes can be programmed easily and it includes an editor to construct and tests the graphs. diff --git a/build/litegraph.js b/build/litegraph.js index 2abada2a5..2814d16ce 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -24,6 +24,7 @@ NODE_WIDGET_HEIGHT: 20, NODE_WIDTH: 140, NODE_MIN_WIDTH: 50, + NODE_MIN_SIZE:[50, 25], NODE_COLLAPSED_RADIUS: 10, NODE_COLLAPSED_WIDTH: 80, NODE_TITLE_COLOR: "#999", @@ -52,6 +53,7 @@ DEFAULT_POSITION: [100, 100], //default node position VALID_SHAPES: ["default", "box", "round", "card"], //,"circle" + //shapes are used for nodes but also for slots BOX_SHAPE: 1, ROUND_SHAPE: 2, @@ -73,7 +75,8 @@ ON_EVENT: 1, NEVER: 2, ON_TRIGGER: 3, - + ON_REQUEST: 4, // used from event-based nodes, where ancestors are recursively executed on needed + UP: 1, DOWN: 2, LEFT: 3, @@ -366,11 +369,17 @@ var classobj = Function(code); classobj.title = name.split("/").pop(); classobj.desc = "Generated from " + func.name; + + /*console.debug("wrapped "+func.name+" :: // atlasan debug REMOVE + console.debug(classobj); + console.debug(code);*/ + classobj.prototype.onExecute = function onExecute() { for (var i = 0; i < params.length; ++i) { params[i] = this.getInputData(i); } var r = func.apply(this, params); + //console.debug("wrapped res "+r); this.setOutputData(0, r); }; this.registerNodeType(name, classobj); @@ -456,6 +465,8 @@ if (!node.size) { node.size = node.computeSize(); //call onresize? + }else{ + node.size_basic = node.size; } if (!node.pos) { node.pos = LiteGraph.DEFAULT_POSITION.concat(); @@ -781,6 +792,7 @@ LGraph.supported_types = ["number", "string", "boolean"]; //used to know which types of connections support this graph (some graphs do not allow certain types) + // atlasan refactort: this is never used uh ? LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; }; @@ -832,6 +844,9 @@ this.vars = {}; this.extra = {}; //to store custom data + // apply default config + this.configApplyDefaults(); + //timing this.globaltime = 0; this.runningtime = 0; @@ -857,6 +872,29 @@ this.sendActionToCanvas("clear"); }; + /** + * Apply config values to LGraph config object + * @method configApply + * @param {object} opts options to merge + */ + LGraph.prototype.configApply = function(opts) { + /* + align_to_grid + links_ontop + */ + this.config = Object.assign(this.config,opts); + } + + /** + * Apply config values to LGraph config object + * @method configApply + * @param {object} opts options to merge + */ + LGraph.prototype.configApplyDefaults = function() { + var opts = LiteGraph.graphDefaultConfig; + this.configApply(opts); + } + /** * Attach Canvas to this graph * @method attachCanvas @@ -864,7 +902,8 @@ */ LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { + //if (graphcanvas.constructor != LGraphCanvas) { + if ( ! graphcanvas.constructor.prototype instanceof LGraphCanvas ) { throw "attachCanvas expects a LGraphCanvas instance"; } if (graphcanvas.graph && graphcanvas.graph != this) { @@ -1024,7 +1063,8 @@ for (var j = 0; j < limit; ++j) { var node = nodes[j]; if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); + //node.onExecute(); + node.execute(); } } @@ -1227,25 +1267,99 @@ * @method getAncestors * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution */ - LGraph.prototype.getAncestors = function(node) { + LGraph.prototype.getAncestors = function(node, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { modesSkip: [] + ,modesOnly: [] + ,typesSkip: [] + ,typesOnly: [] + }; + var opts = Object.assign(optsDef,optsIn); + var ancestors = []; + var ancestorsIds = []; var pending = [node]; var visited = {}; + //console.log("---getAncestors--- for "+node.id+":"+node.order); + while (pending.length) { + // get next var current = pending.shift(); - if (!current.inputs) { + if (!current) { continue; } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); + //console.log("checking ancestor for "+current.id+":"+current.order); + if (visited[current.id]){ + //console.log("already "+current.id+":"+current.order); + continue; + } + // mark as visited + visited[current.id] = true; + + // add to ancestors + if (current.id != node.id) { + + // mode check + if (opts.modesSkip && opts.modesSkip.length){ + if (opts.modesSkip.indexOf(current.mode) != -1){ + //console.log("mode skip "+current.id+":"+current.order+" :: "+current.mode); + continue; + } + } + if (opts.modesOnly && opts.modesOnly.length){ + if (opts.modesOnly.indexOf(current.mode) == -1){ + //console.log("mode only "+current.id+":"+current.order+" :: "+current.mode); + continue; + } + } + + if (ancestorsIds.indexOf(current.id) == -1) { + ancestors.push(current); + ancestorsIds.push(current.id); + //console.log("push current "+current.id+":"+current.order); + }else{ + //console.log("already push "+current.id+":"+current.order); + } + + }else{ + //console.log("current == node "+current.id+":"+current.order+" -- "+node.id+":"+node.order); + } + + // get its inputs + if (!current.inputs){ + continue; } - for (var i = 0; i < current.inputs.length; ++i) { var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); + if (!input) continue; + var inputType = current.inputs[i].type; + + // type check + if (opts.typesSkip && opts.typesSkip.length){ + if (opts.typesSkip.indexOf(inputType) != -1){ + //console.log("type skip "+input.id+":"+input.order+" :: "+inputType); + continue; + }else{ + //console.log("type ok? "+input.id+":"+input.order+" :: "+inputType+" : "+opts.typesSkip.indexOf(inputType)); + } + } + if (opts.typesOnly && opts.typesOnly.length){ + if (opts.typesOnly.indexOf(input.mode) == -1){ + //console.log("type only "+input.id+":"+input.order+" :: "+inputType); + continue; + } + } + + //console.log("input "+i+" "+input.id+":"+input.order); + // push em in + if (ancestorsIds.indexOf(input.id) == -1) { + if(!visited[input.id]){ + pending.push(input); + //console.log("push input "+input.id+":"+input.order); + }else{ + //console.log("already input "+input.id+":"+input.order); + } } } } @@ -1387,7 +1501,12 @@ * @param {LGraphNode} node the instance of the node */ - LGraph.prototype.add = function(node, skip_compute_order) { + LGraph.prototype.add = function(node, skip_compute_order, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { doProcessChange: true + ,doCalcSize: true + }; + var opts = Object.assign(optsDef,optsIn); if (!node) { return; } @@ -1398,15 +1517,13 @@ this.setDirtyCanvas(true); this.change(); node.graph = this; - this._version++; + this.onGraphChanged({action: "groupAdd", doSave: opts.doProcessChange}); // this._version++; return; } //nodes if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); + console.warn("LiteGraph: there is already a node with this ID, changing it"); node.id = ++this.last_node_id; } @@ -1422,7 +1539,8 @@ } node.graph = this; - this._version++; + + this.onGraphChanged({action: "nodeAdd", doSave: opts.doProcessChange}); // this._version++; this._nodes.push(node); this._nodes_by_id[node.id] = node; @@ -1443,6 +1561,10 @@ this.onNodeAdded(node); } + if (opts.doCalcSize){ + node.setSize( node.computeSize() ); + } + this.setDirtyCanvas(true); this.change(); @@ -1456,13 +1578,15 @@ */ LGraph.prototype.remove = function(node) { + + // group ? if (node.constructor === LiteGraph.LGraphGroup) { var index = this._groups.indexOf(node); if (index != -1) { this._groups.splice(index, 1); } node.graph = null; - this._version++; + this.onGraphChanged({action: "groupRemove"}); // this._version++; this.setDirtyCanvas(true, true); this.change(); return; @@ -1483,7 +1607,7 @@ for (var i = 0; i < node.inputs.length; i++) { var slot = node.inputs[i]; if (slot.link != null) { - node.disconnectInput(i); + node.disconnectInput(i, {doProcessChange: false}); } } } @@ -1493,7 +1617,7 @@ for (var i = 0; i < node.outputs.length; i++) { var slot = node.outputs[i]; if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); + node.disconnectOutput(i, false, {doProcessChange: false}); } } } @@ -1506,7 +1630,7 @@ } node.graph = null; - this._version++; + this.onGraphChanged({action: "nodeRemove"}); // this._version++; //remove from canvas render if (this.list_of_graphcanvas) { @@ -1733,9 +1857,9 @@ this.beforeChange(); this.inputs[name] = { name: name, type: type, value: value }; - this._version++; + this.onGraphChanged({action: "addInput"}); // this._version++; this.afterChange(); - + if (this.onInputAdded) { this.onInputAdded(name, type); } @@ -1795,7 +1919,7 @@ this.inputs[name] = this.inputs[old_name]; delete this.inputs[old_name]; - this._version++; + this.onGraphChanged({action: "renameInput"}); // this._version++; if (this.onInputRenamed) { this.onInputRenamed(old_name, name); @@ -1826,7 +1950,7 @@ } this.inputs[name].type = type; - this._version++; + this.onGraphChanged({action: "changeInputType"}); // this._version++; if (this.onInputTypeChanged) { this.onInputTypeChanged(name, type); } @@ -1844,7 +1968,7 @@ } delete this.inputs[name]; - this._version++; + this.onGraphChanged({action: "graphRemoveInput"}); // this._version++; if (this.onInputRemoved) { this.onInputRemoved(name); @@ -1865,7 +1989,7 @@ */ LGraph.prototype.addOutput = function(name, type, value) { this.outputs[name] = { name: name, type: type, value: value }; - this._version++; + this.onGraphChanged({action: "addOutput"}); // this._version++; if (this.onOutputAdded) { this.onOutputAdded(name, type); @@ -1922,7 +2046,7 @@ this.outputs[name] = this.outputs[old_name]; delete this.outputs[old_name]; - this._version++; + this.onGraphChanged({action: "renameOutput"}); // this._version++; if (this.onOutputRenamed) { this.onOutputRenamed(old_name, name); @@ -1953,7 +2077,7 @@ } this.outputs[name].type = type; - this._version++; + this.onGraphChanged({action: "changeOutputType"}); // this._version++; if (this.onOutputTypeChanged) { this.onOutputTypeChanged(name, type); } @@ -1969,7 +2093,7 @@ return false; } delete this.outputs[name]; - this._version++; + this.onGraphChanged({action: "removeOutput"}); // this._version++; if (this.onOutputRemoved) { this.onOutputRemoved(name); @@ -2016,7 +2140,7 @@ if (this.onConnectionChange) { this.onConnectionChange(node); } - this._version++; + this.onGraphChanged({action: "connectionChange", doSave: false}); // this._version++; this.sendActionToCanvas("onConnectionChange"); }; @@ -2070,19 +2194,196 @@ this.sendActionToCanvas("setDirty", [fg, bg]); }; + /** + * Ment to serve the history-saving mechanism + * @method onGraphSaved + * @param {object} optsIn options + */ + LGraph.prototype.onGraphSaved = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + this.savedVersion = this._version; + }; + + /** + * Ment to serve the history-saving mechanism + * @method onGraphSaved + * @param {object} optsIn options + */ + LGraph.prototype.onGraphLoaded = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + this.savedVersion = this._version; + }; + + /** + * Ment to be the history and prevent-exit mechanism, call to change _version + * @method onGraphChanged + * @param {object} optsIn options + */ + LGraph.prototype.onGraphChanged = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { action: "" + ,doSave: true + ,doSaveGraph: true + }; + var opts = Object.assign(optsDef,optsIn); + + this._version++; + + /*if(opts.action){ // atlasan DEBUG REMOVE + console.debug("ACT: "+opts.action); + }else{ + console.debug("ACT_noAction: "+opts); + } + if(opts.doSave){ // atlasan DEBUG REMOVE + console.debug("onGraphChanged SAVE :: "+opts.action); + }*/ + + if(opts.doSave){ + + var oHistory = { actionName: opts.action }; + if(opts.doSaveGraph){ + oHistory = Object.assign(oHistory + ,{ graphSave: this.serialize() // this is a heavy method, but the alternative is way more complex: every action has to have its contrary + }); + } + + var obH = this.history; + + // check if pointer has gone back: remove newest + while(obH.actionHistoryPtr < obH.actionHistoryVersions.length-1){ + //console.debug("popping: gone back? "+(obH.actionHistoryPtr+" < "+(obH.actionHistoryVersions.length-1))); // atlasan DEBUG REMOVE + obH.actionHistoryVersions.pop(); + } + // check if maximum saves + if(obH.actionHistoryVersions.length>=LiteGraph.actionHistoryMaxSave){ + var olderSave = obH.actionHistoryVersions.shift(); + //console.debug("maximum saves reached: "+obH.actionHistoryVersions.length+", remove older: "+olderSave); // atlasan DEBUG REMOVE + obH.actionHistory[olderSave] = false; // unset + } + + // update pointer + obH.actionHistoryPtr = obH.actionHistoryVersions.length; + obH.actionHistoryVersions.push(obH.actionHistoryPtr); + + // save to pointer + obH.actionHistory[obH.actionHistoryPtr] = oHistory; + + }else{ + // console.debug("action dont save"); // atlasan DEBUG REMOVE + } + + }; + + /** + * Go back in action history + * @method actionHistoryBack + * @param {object} optsIn options + */ + LGraph.prototype.actionHistoryBack = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + var obH = this.history; + + if (obH.actionHistoryPtr != undefined && obH.actionHistoryPtr >= 0){ + obH.actionHistoryPtr--; + if (!this.actionHistoryLoad({iVersion: obH.actionHistoryPtr})){ + //console.warn("historyLoad failed, restore pointer? "+obH.actionHistoryPtr); + // history not found? + obH.actionHistoryPtr++; + return false; + }else{ + //console.debug("history step back: "+obH.actionHistoryPtr); // atlasan DEBUG REMOVE + //console.debug(this.history); + return true; + } + }else{ + console.debug("history is already at older state"); + return false; + } + }; + + /** + * Go forward in action history + * @method actionHistoryForward + * @param {object} optsIn options + */ + LGraph.prototype.actionHistoryForward = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + var obH = this.history; + + if (obH.actionHistoryPtr w.options.max ) { w.value = w.options.max; } - } else if (delta) { //clicked in arrow, used for combos + } else if (delta) { //clicked in arrow, used for combos var index = -1; this.last_mouseclick = 0; //avoids dobl click event if(values.constructor === Object) @@ -9966,7 +10424,7 @@ LGraphNode.prototype.executeAction = function(action) w.value = values[index]; else w.value = index; - } else { //combo clicked + } else { //combo clicked var text_values = values != values_list ? Object.values(values) : values; var menu = new LiteGraph.ContextMenu(text_values, { scale: Math.max(1, this.ds.scale), @@ -10118,8 +10576,19 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.resize = function(width, height) { if (!width && !height) { var parent = this.canvas.parentNode; + width = parent.offsetWidth; height = parent.offsetHeight; + + /*var computed = window.getComputedStyle(parent); + var padlft = parseFloat(computed.getPropertyValue('padding-left')); + var padrgt = parseFloat(computed.getPropertyValue('padding-right')); + var padtop = parseFloat(computed.getPropertyValue('padding-top')); + var padbtm = parseFloat(computed.getPropertyValue('padding-bottom')); + + // set canvas dimension + width = parent.clientWidth - padlft - padrgt; + height = parent.clientHeight - padtop - padbtm;*/ } if (this.canvas.width == width && this.canvas.height == height) { @@ -11411,6 +11880,7 @@ LGraphNode.prototype.executeAction = function(action) } graphcanvas.graph.beforeChange(); + var node = LiteGraph.createNode(name); if (node) { node.pos = graphcanvas.convertEventToCanvasOffset( @@ -11790,6 +12260,7 @@ LGraphNode.prototype.executeAction = function(action) dialog.modified(); setValue(!!input.checked); }); + } } else { input = dialog.querySelector("input"); @@ -11885,7 +12356,7 @@ LGraphNode.prototype.executeAction = function(action) offsetx += this.canvas.width * 0.5; offsety += this.canvas.height * 0.5; } - + dialog.style.left = offsetx + "px"; dialog.style.top = offsety + "px"; @@ -12369,6 +12840,7 @@ LGraphNode.prototype.executeAction = function(action) node.graph.remove(node); panel.close(); }).classList.add("delete"); + } panel.inner_showCodePad = function( propname ) @@ -12933,6 +13405,7 @@ LGraphNode.prototype.executeAction = function(action) options[1].disabled = false; } } + if (this.allow_addOutSlot_onExecuted) options[1].disabled = false; if (node.getExtraMenuOptions) { var extra = node.getExtraMenuOptions(this, options); @@ -13019,9 +13492,10 @@ LGraphNode.prototype.executeAction = function(action) } else { if ( slot && - slot.output && - slot.output.links && - slot.output.links.length + ( + (slot.output && slot.output.links && slot.output.links.length) + || (slot.input && slot.input.link) + ) ) { menu_info.push({ content: "Disconnect Links", slot: slot }); } @@ -13038,14 +13512,16 @@ LGraphNode.prototype.executeAction = function(action) } } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { + + var slotOb = slot.input || slot.output; + options.title = slotOb.type || "*"; + if (slotOb.type == LiteGraph.ACTION) { options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { + }else if (slotOb.type == LiteGraph.EVENT) { options.title = "Event"; } + // if(slotOb.name) options.title = slotOb.name + ": " + options.title; + } else { if (node) { //on node @@ -16248,6 +16724,362 @@ if (typeof exports != "undefined") { })(this); + //Show value inside the debug console + function LogEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + } + + LogEvent.title = "Log Event"; + LogEvent.desc = "Log event in console"; + + LogEvent.prototype.onAction = function(action, param, options) { + console.log(action, param); + }; + + LiteGraph.registerNodeType("events/log", LogEvent); + + //convert to Event if the value is true + function TriggerEvent() { + this.size = [60, 30]; + this.addInput("if", ""); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.properties = { only_on_change: true }; + this.prev = 0; + } + + TriggerEvent.title = "TriggerEvent"; + TriggerEvent.desc = "Triggers event if input evaluates to true"; + + TriggerEvent.prototype.onExecute = function( param, options) { + var v = this.getInputData(0); + var changed = (v != this.prev); + if(this.prev === 0) + changed = false; + var must_resend = (changed && this.properties.only_on_change) || (!changed && !this.properties.only_on_change); + if(v && must_resend ) + this.triggerSlot(0, param, null, options); + if(!v && must_resend) + this.triggerSlot(2, param, null, options); + if(changed) + this.triggerSlot(1, param, null, options); + this.prev = v; + }; + + LiteGraph.registerNodeType("events/trigger", TriggerEvent); + + //Sequencer for events + function Sequencer() { + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addInput("", LiteGraph.ACTION); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.addOutput("", LiteGraph.EVENT); + this.size = [120, 30]; + this.flags = { horizontal: true, render_box: false }; + } + + Sequencer.title = "Sequencer"; + Sequencer.desc = "Trigger events when an event arrives"; + + Sequencer.prototype.getTitle = function() { + return ""; + }; + + Sequencer.prototype.onAction = function(action, param, options) { + if (this.outputs) { + options = options || {}; + for (var i = 0; i < this.outputs.length; ++i) { + options.action_call = options.action_call?options.action_call+"_seq_"+i:this.id+"_"+(action?action:"action")+"_seq_"+i+"_"+Math.floor(Math.random()*9999); + this.triggerSlot(i, param, null, options); + } + } + }; + + LiteGraph.registerNodeType("events/sequencer", Sequencer); + + //Filter events + function FilterEvent() { + this.size = [60, 30]; + this.addInput("event", LiteGraph.ACTION); + this.addOutput("event", LiteGraph.EVENT); + this.properties = { + equal_to: "", + has_property: "", + property_equal_to: "" + }; + } + + FilterEvent.title = "Filter Event"; + FilterEvent.desc = "Blocks events that do not match the filter"; + + FilterEvent.prototype.onAction = function(action, param, options) { + if (param == null) { + return; + } + + if (this.properties.equal_to && this.properties.equal_to != param) { + return; + } + + if (this.properties.has_property) { + var prop = param[this.properties.has_property]; + if (prop == null) { + return; + } + + if ( + this.properties.property_equal_to && + this.properties.property_equal_to != prop + ) { + return; + } + } + + this.triggerSlot(0, param, null, options); + }; + + LiteGraph.registerNodeType("events/filter", FilterEvent); + + + function EventBranch() { + this.addInput("in", LiteGraph.ACTION); + this.addInput("cond", "boolean"); + this.addOutput("true", LiteGraph.EVENT); + this.addOutput("false", LiteGraph.EVENT); + this.size = [120, 60]; + this._value = false; + } + + EventBranch.title = "Branch"; + EventBranch.desc = "If condition is true, outputs triggers true, otherwise false"; + + EventBranch.prototype.onExecute = function() { + this._value = this.getInputData(1); + } + + EventBranch.prototype.onAction = function(action, param, options) { + this._value = this.getInputData(1); + this.triggerSlot(this._value ? 0 : 1, param, null, options); + } + + LiteGraph.registerNodeType("events/branch", EventBranch); + + //Show value inside the debug console + function EventCounter() { + this.addInput("inc", LiteGraph.ACTION); + this.addInput("dec", LiteGraph.ACTION); + this.addInput("reset", LiteGraph.ACTION); + this.addOutput("change", LiteGraph.EVENT); + this.addOutput("num", "number"); + this.addProperty("doCountExecution", false, "boolean", {name: "Count Executions"}); + this.addWidget("toggle","Count Exec.",this.properties.doCountExecution,"doCountExecution"); + this.num = 0; + } + + EventCounter.title = "Counter"; + EventCounter.desc = "Counts events"; + + EventCounter.prototype.getTitle = function() { + if (this.flags.collapsed) { + return String(this.num); + } + return this.title; + }; + + EventCounter.prototype.onAction = function(action, param, options) { + var v = this.num; + if (action == "inc") { + this.num += 1; + } else if (action == "dec") { + this.num -= 1; + } else if (action == "reset") { + this.num = 0; + } + if (this.num != v) { + this.trigger("change", this.num); + } + }; + + EventCounter.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + ctx.fillStyle = "#AAA"; + ctx.font = "20px Arial"; + ctx.textAlign = "center"; + ctx.fillText(this.num, this.size[0] * 0.5, this.size[1] * 0.5); + }; + + EventCounter.prototype.onExecute = function() { + if(this.properties.doCountExecution){ + this.num += 1; + } + this.setOutputData(1, this.num); + }; + + LiteGraph.registerNodeType("events/counter", EventCounter); + + //Show value inside the debug console + function DelayEvent() { + this.size = [60, 30]; + this.addProperty("time_in_ms", 1000); + this.addInput("event", LiteGraph.ACTION); + this.addOutput("on_time", LiteGraph.EVENT); + + this._pending = []; + } + + DelayEvent.title = "Delay"; + DelayEvent.desc = "Delays one event"; + + DelayEvent.prototype.onAction = function(action, param, options) { + var time = this.properties.time_in_ms; + if (time <= 0) { + this.trigger(null, param, options); + } else { + this._pending.push([time, param]); + } + }; + + DelayEvent.prototype.onExecute = function(param, options) { + var dt = this.graph.elapsed_time * 1000; //in ms + + if (this.isInputConnected(1)) { + this.properties.time_in_ms = this.getInputData(1); + } + + for (var i = 0; i < this._pending.length; ++i) { + var actionPass = this._pending[i]; + actionPass[0] -= dt; + if (actionPass[0] > 0) { + continue; + } + + //remove + this._pending.splice(i, 1); + --i; + + //trigger + this.trigger(null, actionPass[1], options); + } + }; + + DelayEvent.prototype.onGetInputs = function() { + return [["event", LiteGraph.ACTION], ["time_in_ms", "number"]]; + }; + + LiteGraph.registerNodeType("events/delay", DelayEvent); + + //Show value inside the debug console + function TimerEvent() { + this.addProperty("interval", 1000); + this.addProperty("event", "tick"); + this.addOutput("on_tick", LiteGraph.EVENT); + this.time = 0; + this.last_interval = 1000; + this.triggered = false; + } + + TimerEvent.title = "Timer"; + TimerEvent.desc = "Sends an event every N milliseconds"; + + TimerEvent.prototype.onStart = function() { + this.time = 0; + }; + + TimerEvent.prototype.getTitle = function() { + return "Timer: " + this.last_interval.toString() + "ms"; + }; + + TimerEvent.on_color = "#AAA"; + TimerEvent.off_color = "#222"; + + TimerEvent.prototype.onDrawBackground = function() { + this.boxcolor = this.triggered + ? TimerEvent.on_color + : TimerEvent.off_color; + this.triggered = false; + }; + + TimerEvent.prototype.onExecute = function() { + var dt = this.graph.elapsed_time * 1000; //in ms + + var trigger = this.time == 0; + + this.time += dt; + this.last_interval = Math.max( + 1, + this.getInputOrProperty("interval") | 0 + ); + + if ( + !trigger && + (this.time < this.last_interval || isNaN(this.last_interval)) + ) { + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, false); + } + return; + } + + this.triggered = true; + this.time = this.time % this.last_interval; + this.trigger("on_tick", this.properties.event); + if (this.inputs && this.inputs.length > 1 && this.inputs[1]) { + this.setOutputData(1, true); + } + }; + + TimerEvent.prototype.onGetInputs = function() { + return [["interval", "number"]]; + }; + + TimerEvent.prototype.onGetOutputs = function() { + return [["tick", "boolean"]]; + }; + + LiteGraph.registerNodeType("events/timer", TimerEvent); + + function DataStore() { + this.addInput("data", 0); + this.addInput("assign", LiteGraph.ACTION); + this.addOutput("data", 0); + this._last_value = null; + this.properties = { data: null, serialize: true }; + var that = this; + this.addWidget("button","store","",function(){ + that.properties.data = that._last_value; + }); + } + + DataStore.title = "Data Store"; + DataStore.desc = "Stores data and only changes when event is received"; + + DataStore.prototype.onExecute = function() + { + this._last_value = this.getInputData(0); + this.setOutputData(0, this.properties.data ); + } + + DataStore.prototype.onAction = function(action, param, options) { + this.properties.data = this._last_value; + }; + + DataStore.prototype.onSerialize = function(o) + { + if(o.data == null) + return; + if(this.properties.serialize == false || (o.data.constructor !== String && o.data.constructor !== Number && o.data.constructor !== Boolean && o.data.constructor !== Array && o.data.constructor !== Object )) + o.data = null; + } + + LiteGraph.registerNodeType("basic/data_store", DataStore); +})(this); + //widgets (function(global) { var LiteGraph = global.LiteGraph; @@ -16890,6 +17722,7 @@ if (typeof exports != "undefined") { glowSize: 0, decimals: 1 }; + // not optimal in this case: // this.txtValue = this.addWidget("text","value", "multiline", "value", { multiline:true } ); } WidgetText.title = "Text"; @@ -17047,359 +17880,8 @@ if (typeof exports != "undefined") { LiteGraph.registerNodeType("widget/panel", WidgetPanel); })(this); -(function(global) { - var LiteGraph = global.LiteGraph; - - function GamepadInput() { - this.addOutput("left_x_axis", "number"); - this.addOutput("left_y_axis", "number"); - this.addOutput("button_pressed", LiteGraph.EVENT); - this.properties = { gamepad_index: 0, threshold: 0.1 }; - - this._left_axis = new Float32Array(2); - this._right_axis = new Float32Array(2); - this._triggers = new Float32Array(2); - this._previous_buttons = new Uint8Array(17); - this._current_buttons = new Uint8Array(17); - } - - GamepadInput.title = "Gamepad"; - GamepadInput.desc = "gets the input of the gamepad"; - - GamepadInput.CENTER = 0; - GamepadInput.LEFT = 1; - GamepadInput.RIGHT = 2; - GamepadInput.UP = 4; - GamepadInput.DOWN = 8; - - GamepadInput.zero = new Float32Array(2); - GamepadInput.buttons = [ - "a", - "b", - "x", - "y", - "lb", - "rb", - "lt", - "rt", - "back", - "start", - "ls", - "rs", - "home" - ]; - - GamepadInput.prototype.onExecute = function() { - //get gamepad - var gamepad = this.getGamepad(); - var threshold = this.properties.threshold || 0.0; - - if (gamepad) { - this._left_axis[0] = - Math.abs(gamepad.xbox.axes["lx"]) > threshold - ? gamepad.xbox.axes["lx"] - : 0; - this._left_axis[1] = - Math.abs(gamepad.xbox.axes["ly"]) > threshold - ? gamepad.xbox.axes["ly"] - : 0; - this._right_axis[0] = - Math.abs(gamepad.xbox.axes["rx"]) > threshold - ? gamepad.xbox.axes["rx"] - : 0; - this._right_axis[1] = - Math.abs(gamepad.xbox.axes["ry"]) > threshold - ? gamepad.xbox.axes["ry"] - : 0; - this._triggers[0] = - Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold - ? gamepad.xbox.axes["ltrigger"] - : 0; - this._triggers[1] = - Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold - ? gamepad.xbox.axes["rtrigger"] - : 0; - } - - if (this.outputs) { - for (var i = 0; i < this.outputs.length; i++) { - var output = this.outputs[i]; - if (!output.links || !output.links.length) { - continue; - } - var v = null; - - if (gamepad) { - switch (output.name) { - case "left_axis": - v = this._left_axis; - break; - case "right_axis": - v = this._right_axis; - break; - case "left_x_axis": - v = this._left_axis[0]; - break; - case "left_y_axis": - v = this._left_axis[1]; - break; - case "right_x_axis": - v = this._right_axis[0]; - break; - case "right_y_axis": - v = this._right_axis[1]; - break; - case "trigger_left": - v = this._triggers[0]; - break; - case "trigger_right": - v = this._triggers[1]; - break; - case "a_button": - v = gamepad.xbox.buttons["a"] ? 1 : 0; - break; - case "b_button": - v = gamepad.xbox.buttons["b"] ? 1 : 0; - break; - case "x_button": - v = gamepad.xbox.buttons["x"] ? 1 : 0; - break; - case "y_button": - v = gamepad.xbox.buttons["y"] ? 1 : 0; - break; - case "lb_button": - v = gamepad.xbox.buttons["lb"] ? 1 : 0; - break; - case "rb_button": - v = gamepad.xbox.buttons["rb"] ? 1 : 0; - break; - case "ls_button": - v = gamepad.xbox.buttons["ls"] ? 1 : 0; - break; - case "rs_button": - v = gamepad.xbox.buttons["rs"] ? 1 : 0; - break; - case "hat_left": - v = gamepad.xbox.hatmap & GamepadInput.LEFT; - break; - case "hat_right": - v = gamepad.xbox.hatmap & GamepadInput.RIGHT; - break; - case "hat_up": - v = gamepad.xbox.hatmap & GamepadInput.UP; - break; - case "hat_down": - v = gamepad.xbox.hatmap & GamepadInput.DOWN; - break; - case "hat": - v = gamepad.xbox.hatmap; - break; - case "start_button": - v = gamepad.xbox.buttons["start"] ? 1 : 0; - break; - case "back_button": - v = gamepad.xbox.buttons["back"] ? 1 : 0; - break; - case "button_pressed": - for ( - var j = 0; - j < this._current_buttons.length; - ++j - ) { - if ( - this._current_buttons[j] && - !this._previous_buttons[j] - ) { - this.triggerSlot( - i, - GamepadInput.buttons[j] - ); - } - } - break; - default: - break; - } - } else { - //if no gamepad is connected, output 0 - switch (output.name) { - case "button_pressed": - break; - case "left_axis": - case "right_axis": - v = GamepadInput.zero; - break; - default: - v = 0; - } - } - this.setOutputData(i, v); - } - } - }; - - GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; - GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; - - GamepadInput.prototype.getGamepad = function() { - var getGamepads = - navigator.getGamepads || - navigator.webkitGetGamepads || - navigator.mozGetGamepads; - if (!getGamepads) { - return null; - } - var gamepads = getGamepads.call(navigator); - var gamepad = null; - - this._previous_buttons.set(this._current_buttons); - - //pick the first connected - for (var i = this.properties.gamepad_index; i < 4; i++) { - if (!gamepads[i]) { - continue; - } - gamepad = gamepads[i]; - - //xbox controller mapping - var xbox = this.xbox_mapping; - if (!xbox) { - xbox = this.xbox_mapping = { - axes: [], - buttons: {}, - hat: "", - hatmap: GamepadInput.CENTER - }; - } - - xbox.axes["lx"] = gamepad.axes[0]; - xbox.axes["ly"] = gamepad.axes[1]; - xbox.axes["rx"] = gamepad.axes[2]; - xbox.axes["ry"] = gamepad.axes[3]; - xbox.axes["ltrigger"] = gamepad.buttons[6].value; - xbox.axes["rtrigger"] = gamepad.buttons[7].value; - xbox.hat = ""; - xbox.hatmap = GamepadInput.CENTER; - - for (var j = 0; j < gamepad.buttons.length; j++) { - this._current_buttons[j] = gamepad.buttons[j].pressed; - - if(j < 12) - { - xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; - if(gamepad.buttons[j].was_pressed) - this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); - } - else //mapping of XBOX - switch ( j ) //I use a switch to ensure that a player with another gamepad could play - { - case 12: - if (gamepad.buttons[j].pressed) { - xbox.hat += "up"; - xbox.hatmap |= GamepadInput.UP; - } - break; - case 13: - if (gamepad.buttons[j].pressed) { - xbox.hat += "down"; - xbox.hatmap |= GamepadInput.DOWN; - } - break; - case 14: - if (gamepad.buttons[j].pressed) { - xbox.hat += "left"; - xbox.hatmap |= GamepadInput.LEFT; - } - break; - case 15: - if (gamepad.buttons[j].pressed) { - xbox.hat += "right"; - xbox.hatmap |= GamepadInput.RIGHT; - } - break; - case 16: - xbox.buttons["home"] = gamepad.buttons[j].pressed; - break; - default: - } - } - gamepad.xbox = xbox; - return gamepad; - } - }; - - GamepadInput.prototype.onDrawBackground = function(ctx) { - if (this.flags.collapsed) { - return; - } - - //render gamepad state? - var la = this._left_axis; - var ra = this._right_axis; - ctx.strokeStyle = "#88A"; - ctx.strokeRect( - (la[0] + 1) * 0.5 * this.size[0] - 4, - (la[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - ctx.strokeStyle = "#8A8"; - ctx.strokeRect( - (ra[0] + 1) * 0.5 * this.size[0] - 4, - (ra[1] + 1) * 0.5 * this.size[1] - 4, - 8, - 8 - ); - var h = this.size[1] / this._current_buttons.length; - ctx.fillStyle = "#AEB"; - for (var i = 0; i < this._current_buttons.length; ++i) { - if (this._current_buttons[i]) { - ctx.fillRect(0, h * i, 6, h); - } - } - }; - - GamepadInput.prototype.onGetOutputs = function() { - return [ - ["left_axis", "vec2"], - ["right_axis", "vec2"], - ["left_x_axis", "number"], - ["left_y_axis", "number"], - ["right_x_axis", "number"], - ["right_y_axis", "number"], - ["trigger_left", "number"], - ["trigger_right", "number"], - ["a_button", "number"], - ["b_button", "number"], - ["x_button", "number"], - ["y_button", "number"], - ["lb_button", "number"], - ["rb_button", "number"], - ["ls_button", "number"], - ["rs_button", "number"], - ["start_button", "number"], - ["back_button", "number"], - ["a_button_event", LiteGraph.EVENT ], - ["b_button_event", LiteGraph.EVENT ], - ["x_button_event", LiteGraph.EVENT ], - ["y_button_event", LiteGraph.EVENT ], - ["lb_button_event", LiteGraph.EVENT ], - ["rb_button_event", LiteGraph.EVENT ], - ["ls_button_event", LiteGraph.EVENT ], - ["rs_button_event", LiteGraph.EVENT ], - ["start_button_event", LiteGraph.EVENT ], - ["back_button_event", LiteGraph.EVENT ], - ["hat_left", "number"], - ["hat_right", "number"], - ["hat_up", "number"], - ["hat_down", "number"], - ["hat", "number"], - ["button_pressed", LiteGraph.EVENT] - ]; - }; - - LiteGraph.registerNodeType("input/gamepad", GamepadInput); -})(this); +(function(global) { + var LiteGraph = global.LiteGraph; (function(global) { var LiteGraph = global.LiteGraph; @@ -19305,156 +19787,1830 @@ if (typeof exports != "undefined") { })(this); -//basic nodes -(function(global) { - var LiteGraph = global.LiteGraph; - - function toString(a) { - if(a && a.constructor === Object) - { - try - { - return JSON.stringify(a); - } - catch (err) - { - return String(a); - } - } - return String(a); - } - - LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "string"); - - function compare(a, b) { - return a == b; - } - - LiteGraph.wrapFunctionAsNode( - "string/compare", - compare, - ["string", "string"], - "boolean" - ); - - function concatenate(a, b) { - if (a === undefined) { - return b; - } - if (b === undefined) { - return a; - } - return a + b; - } - - LiteGraph.wrapFunctionAsNode( - "string/concatenate", - concatenate, - ["string", "string"], - "string" - ); - - function contains(a, b) { - if (a === undefined || b === undefined) { - return false; - } - return a.indexOf(b) != -1; - } - - LiteGraph.wrapFunctionAsNode( - "string/contains", - contains, - ["string", "string"], - "boolean" - ); - - function toUpperCase(a) { - if (a != null && a.constructor === String) { - return a.toUpperCase(); - } - return a; - } - - LiteGraph.wrapFunctionAsNode( - "string/toUpperCase", - toUpperCase, - ["string"], - "string" - ); - - function split(str, separator) { - if(separator == null) - separator = this.properties.separator; - if (str == null ) - return []; - if( str.constructor === String ) - return str.split(separator || " "); - else if( str.constructor === Array ) - { - var r = []; - for(var i = 0; i < str.length; ++i){ - if (typeof str[i] == "string") - r[i] = str[i].split(separator || " "); - } - return r; - } - return null; - } - - LiteGraph.wrapFunctionAsNode( - "string/split", - split, - ["string,array", "string"], - "array", - { separator: "," } - ); - - function toFixed(a) { - if (a != null && a.constructor === Number) { - return a.toFixed(this.properties.precision); - } - return a; - } - - LiteGraph.wrapFunctionAsNode( - "string/toFixed", - toFixed, - ["number"], - "string", - { precision: 0 } - ); - - - function StringToTable() { - this.addInput("", "string"); - this.addOutput("table", "table"); - this.addOutput("rows", "number"); - this.addProperty("value", ""); - this.addProperty("separator", ","); - this._table = null; - } - - StringToTable.title = "toTable"; - StringToTable.desc = "Splits a string to table"; - - StringToTable.prototype.onExecute = function() { - var input = this.getInputData(0); - if(!input) - return; - var separator = this.properties.separator || ","; - if(input != this._str || separator != this._last_separator ) - { - this._last_separator = separator; - this._str = input; - this._table = input.split("\n").map(function(a){ return a.trim().split(separator)}); - } - this.setOutputData(0, this._table ); - this.setOutputData(1, this._table ? this._table.length : 0 ); - }; - - LiteGraph.registerNodeType("string/toTable", StringToTable); - -})(this); +//basic nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + function toString(a) { + if(a && a.constructor === Object) + { + try + { + return JSON.stringify(a); + } + catch (err) + { + return String(a); + } + } + return String(a); + } + + LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "string"); + + function compare(a, b) { + return a == b; + } + + LiteGraph.wrapFunctionAsNode( + "string/compare", + compare, + ["string", "string"], + "boolean" + ); + + function concatenate(a, b) { + if (a === undefined) { + return b; + } + if (b === undefined) { + return a; + } + return a + b; + } + + LiteGraph.wrapFunctionAsNode( + "string/concatenate", + concatenate, + ["string", "string"], + "string" + ); + + function contains(a, b) { + if (a === undefined || b === undefined) { + return false; + } + return a.indexOf(b) != -1; + } + + LiteGraph.wrapFunctionAsNode( + "string/contains", + contains, + ["string", "string"], + "boolean" + ); + + function toUpperCase(a) { + if (a != null && a.constructor === String) { + return a.toUpperCase(); + } + return a; + } + + LiteGraph.wrapFunctionAsNode( + "string/toUpperCase", + toUpperCase, + ["string"], + "string" + ); + + function split(str, separator) { + if(separator == null) + separator = this.properties.separator; + if (str == null ) + return []; + if( str.constructor === String ) + return str.split(separator || " "); + else if( str.constructor === Array ) + { + var r = []; + for(var i = 0; i < str.length; ++i){ + if (typeof str[i] == "string") + r[i] = str[i].split(separator || " "); + } + return r; + } + return null; + } + + LiteGraph.wrapFunctionAsNode( + "string/split", + split, + ["string,array", "string"], + "array", + { separator: "," } + ); + + function toFixed(a) { + if (a != null && a.constructor === Number) { + return a.toFixed(this.properties.precision); + } + return a; + } + + LiteGraph.wrapFunctionAsNode( + "string/toFixed", + toFixed, + ["number"], + "string", + { precision: 0 } + ); + + + function StringToTable() { + this.addInput("", "string"); + this.addOutput("table", "table"); + this.addOutput("rows", "number"); + this.addProperty("value", ""); + this.addProperty("separator", ","); + this._table = null; + } + + StringToTable.title = "toTable"; + StringToTable.desc = "Splits a string to table"; + + StringToTable.prototype.onExecute = function() { + var input = this.getInputData(0); + if(!input) + return; + var separator = this.properties.separator || ","; + if(input != this._str || separator != this._last_separator ) + { + this._last_separator = separator; + this._str = input; + this._table = input.split("\n").map(function(a){ return a.trim().split(separator)}); + } + this.setOutputData(0, this._table ); + this.setOutputData(1, this._table ? this._table.length : 0 ); + }; + + LiteGraph.registerNodeType("string/toTable", StringToTable); + +})(this); + + this._left_axis = new Float32Array(2); + this._right_axis = new Float32Array(2); + this._triggers = new Float32Array(2); + this._previous_buttons = new Uint8Array(17); + this._current_buttons = new Uint8Array(17); + } + + GamepadInput.title = "Gamepad"; + GamepadInput.desc = "gets the input of the gamepad"; + + GamepadInput.CENTER = 0; + GamepadInput.LEFT = 1; + GamepadInput.RIGHT = 2; + GamepadInput.UP = 4; + GamepadInput.DOWN = 8; + + GamepadInput.zero = new Float32Array(2); + GamepadInput.buttons = [ + "a", + "b", + "x", + "y", + "lb", + "rb", + "lt", + "rt", + "back", + "start", + "ls", + "rs", + "home" + ]; + + GamepadInput.prototype.onExecute = function() { + //get gamepad + var gamepad = this.getGamepad(); + var threshold = this.properties.threshold || 0.0; + + if (gamepad) { + this._left_axis[0] = + Math.abs(gamepad.xbox.axes["lx"]) > threshold + ? gamepad.xbox.axes["lx"] + : 0; + this._left_axis[1] = + Math.abs(gamepad.xbox.axes["ly"]) > threshold + ? gamepad.xbox.axes["ly"] + : 0; + this._right_axis[0] = + Math.abs(gamepad.xbox.axes["rx"]) > threshold + ? gamepad.xbox.axes["rx"] + : 0; + this._right_axis[1] = + Math.abs(gamepad.xbox.axes["ry"]) > threshold + ? gamepad.xbox.axes["ry"] + : 0; + this._triggers[0] = + Math.abs(gamepad.xbox.axes["ltrigger"]) > threshold + ? gamepad.xbox.axes["ltrigger"] + : 0; + this._triggers[1] = + Math.abs(gamepad.xbox.axes["rtrigger"]) > threshold + ? gamepad.xbox.axes["rtrigger"] + : 0; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var v = null; + + if (gamepad) { + switch (output.name) { + case "left_axis": + v = this._left_axis; + break; + case "right_axis": + v = this._right_axis; + break; + case "left_x_axis": + v = this._left_axis[0]; + break; + case "left_y_axis": + v = this._left_axis[1]; + break; + case "right_x_axis": + v = this._right_axis[0]; + break; + case "right_y_axis": + v = this._right_axis[1]; + break; + case "trigger_left": + v = this._triggers[0]; + break; + case "trigger_right": + v = this._triggers[1]; + break; + case "a_button": + v = gamepad.xbox.buttons["a"] ? 1 : 0; + break; + case "b_button": + v = gamepad.xbox.buttons["b"] ? 1 : 0; + break; + case "x_button": + v = gamepad.xbox.buttons["x"] ? 1 : 0; + break; + case "y_button": + v = gamepad.xbox.buttons["y"] ? 1 : 0; + break; + case "lb_button": + v = gamepad.xbox.buttons["lb"] ? 1 : 0; + break; + case "rb_button": + v = gamepad.xbox.buttons["rb"] ? 1 : 0; + break; + case "ls_button": + v = gamepad.xbox.buttons["ls"] ? 1 : 0; + break; + case "rs_button": + v = gamepad.xbox.buttons["rs"] ? 1 : 0; + break; + case "hat_left": + v = gamepad.xbox.hatmap & GamepadInput.LEFT; + break; + case "hat_right": + v = gamepad.xbox.hatmap & GamepadInput.RIGHT; + break; + case "hat_up": + v = gamepad.xbox.hatmap & GamepadInput.UP; + break; + case "hat_down": + v = gamepad.xbox.hatmap & GamepadInput.DOWN; + break; + case "hat": + v = gamepad.xbox.hatmap; + break; + case "start_button": + v = gamepad.xbox.buttons["start"] ? 1 : 0; + break; + case "back_button": + v = gamepad.xbox.buttons["back"] ? 1 : 0; + break; + case "button_pressed": + for ( + var j = 0; + j < this._current_buttons.length; + ++j + ) { + if ( + this._current_buttons[j] && + !this._previous_buttons[j] + ) { + this.triggerSlot( + i, + GamepadInput.buttons[j] + ); + } + } + break; + default: + break; + } + } else { + //if no gamepad is connected, output 0 + switch (output.name) { + case "button_pressed": + break; + case "left_axis": + case "right_axis": + v = GamepadInput.zero; + break; + default: + v = 0; + } + } + this.setOutputData(i, v); + } + } + }; + + GamepadInput.mapping = {a:0,b:1,x:2,y:3,lb:4,rb:5,lt:6,rt:7,back:8,start:9,ls:10,rs:11 }; + GamepadInput.mapping_array = ["a","b","x","y","lb","rb","lt","rt","back","start","ls","rs"]; + + GamepadInput.prototype.getGamepad = function() { + var getGamepads = + navigator.getGamepads || + navigator.webkitGetGamepads || + navigator.mozGetGamepads; + if (!getGamepads) { + return null; + } + var gamepads = getGamepads.call(navigator); + var gamepad = null; + + this._previous_buttons.set(this._current_buttons); + + //pick the first connected + for (var i = this.properties.gamepad_index; i < 4; i++) { + if (!gamepads[i]) { + continue; + } + gamepad = gamepads[i]; + + //xbox controller mapping + var xbox = this.xbox_mapping; + if (!xbox) { + xbox = this.xbox_mapping = { + axes: [], + buttons: {}, + hat: "", + hatmap: GamepadInput.CENTER + }; + } + + xbox.axes["lx"] = gamepad.axes[0]; + xbox.axes["ly"] = gamepad.axes[1]; + xbox.axes["rx"] = gamepad.axes[2]; + xbox.axes["ry"] = gamepad.axes[3]; + xbox.axes["ltrigger"] = gamepad.buttons[6].value; + xbox.axes["rtrigger"] = gamepad.buttons[7].value; + xbox.hat = ""; + xbox.hatmap = GamepadInput.CENTER; + + for (var j = 0; j < gamepad.buttons.length; j++) { + this._current_buttons[j] = gamepad.buttons[j].pressed; + + if(j < 12) + { + xbox.buttons[ GamepadInput.mapping_array[j] ] = gamepad.buttons[j].pressed; + if(gamepad.buttons[j].was_pressed) + this.trigger( GamepadInput.mapping_array[j] + "_button_event" ); + } + else //mapping of XBOX + switch ( j ) //I use a switch to ensure that a player with another gamepad could play + { + case 12: + if (gamepad.buttons[j].pressed) { + xbox.hat += "up"; + xbox.hatmap |= GamepadInput.UP; + } + break; + case 13: + if (gamepad.buttons[j].pressed) { + xbox.hat += "down"; + xbox.hatmap |= GamepadInput.DOWN; + } + break; + case 14: + if (gamepad.buttons[j].pressed) { + xbox.hat += "left"; + xbox.hatmap |= GamepadInput.LEFT; + } + break; + case 15: + if (gamepad.buttons[j].pressed) { + xbox.hat += "right"; + xbox.hatmap |= GamepadInput.RIGHT; + } + break; + case 16: + xbox.buttons["home"] = gamepad.buttons[j].pressed; + break; + default: + } + } + gamepad.xbox = xbox; + return gamepad; + } + }; + + GamepadInput.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + //render gamepad state? + var la = this._left_axis; + var ra = this._right_axis; + ctx.strokeStyle = "#88A"; + ctx.strokeRect( + (la[0] + 1) * 0.5 * this.size[0] - 4, + (la[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + ctx.strokeStyle = "#8A8"; + ctx.strokeRect( + (ra[0] + 1) * 0.5 * this.size[0] - 4, + (ra[1] + 1) * 0.5 * this.size[1] - 4, + 8, + 8 + ); + var h = this.size[1] / this._current_buttons.length; + ctx.fillStyle = "#AEB"; + for (var i = 0; i < this._current_buttons.length; ++i) { + if (this._current_buttons[i]) { + ctx.fillRect(0, h * i, 6, h); + } + } + }; + + GamepadInput.prototype.onGetOutputs = function() { + return [ + ["left_axis", "vec2"], + ["right_axis", "vec2"], + ["left_x_axis", "number"], + ["left_y_axis", "number"], + ["right_x_axis", "number"], + ["right_y_axis", "number"], + ["trigger_left", "number"], + ["trigger_right", "number"], + ["a_button", "number"], + ["b_button", "number"], + ["x_button", "number"], + ["y_button", "number"], + ["lb_button", "number"], + ["rb_button", "number"], + ["ls_button", "number"], + ["rs_button", "number"], + ["start_button", "number"], + ["back_button", "number"], + ["a_button_event", LiteGraph.EVENT ], + ["b_button_event", LiteGraph.EVENT ], + ["x_button_event", LiteGraph.EVENT ], + ["y_button_event", LiteGraph.EVENT ], + ["lb_button_event", LiteGraph.EVENT ], + ["rb_button_event", LiteGraph.EVENT ], + ["ls_button_event", LiteGraph.EVENT ], + ["rs_button_event", LiteGraph.EVENT ], + ["start_button_event", LiteGraph.EVENT ], + ["back_button_event", LiteGraph.EVENT ], + ["hat_left", "number"], + ["hat_right", "number"], + ["hat_up", "number"], + ["hat_down", "number"], + ["hat", "number"], + ["button_pressed", LiteGraph.EVENT] + ]; + }; + + LiteGraph.registerNodeType("input/gamepad", GamepadInput); +})(this); + +(function(global) { + var LiteGraph = global.LiteGraph; + + //Converter + function Converter() { + this.addInput("in", "*"); + this.size = [80, 30]; + } + + Converter.title = "Converter"; + Converter.desc = "type A to type B"; + + Converter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + if (this.outputs) { + for (var i = 0; i < this.outputs.length; i++) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + + var result = null; + switch (output.name) { + case "number": + result = v.length ? v[0] : parseFloat(v); + break; + case "vec2": + case "vec3": + case "vec4": + var result = null; + var count = 1; + switch (output.name) { + case "vec2": + count = 2; + break; + case "vec3": + count = 3; + break; + case "vec4": + count = 4; + break; + } + + var result = new Float32Array(count); + if (v.length) { + for ( + var j = 0; + j < v.length && j < result.length; + j++ + ) { + result[j] = v[j]; + } + } else { + result[0] = parseFloat(v); + } + break; + } + this.setOutputData(i, result); + } + } + }; + + Converter.prototype.onGetOutputs = function() { + return [ + ["number", "number"], + ["vec2", "vec2"], + ["vec3", "vec3"], + ["vec4", "vec4"] + ]; + }; + + LiteGraph.registerNodeType("math/converter", Converter); + + //Bypass + function Bypass() { + this.addInput("in"); + this.addOutput("out"); + this.size = [80, 30]; + } + + Bypass.title = "Bypass"; + Bypass.desc = "removes the type"; + + Bypass.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/bypass", Bypass); + + function ToNumber() { + this.addInput("in"); + this.addOutput("out"); + } + + ToNumber.title = "to Number"; + ToNumber.desc = "Cast to number"; + + ToNumber.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, Number(v)); + }; + + LiteGraph.registerNodeType("math/to_number", ToNumber); + + function MathRange() { + this.addInput("in", "number", { locked: true }); + this.addOutput("out", "number", { locked: true }); + this.addOutput("clamped", "number", { locked: true }); + + this.addProperty("in", 0); + this.addProperty("in_min", 0); + this.addProperty("in_max", 1); + this.addProperty("out_min", 0); + this.addProperty("out_max", 1); + + this.size = [120, 50]; + } + + MathRange.title = "Range"; + MathRange.desc = "Convert a number from one range to another"; + + MathRange.prototype.getTitle = function() { + if (this.flags.collapsed) { + return (this._last_v || 0).toFixed(2); + } + return this.title; + }; + + MathRange.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var v = this.properties["in"]; + if (v === undefined || v === null || v.constructor !== Number) { + v = 0; + } + + var in_min = this.properties.in_min; + var in_max = this.properties.in_max; + var out_min = this.properties.out_min; + var out_max = this.properties.out_max; + /* + if( in_min > in_max ) + { + in_min = in_max; + in_max = this.properties.in_min; + } + if( out_min > out_max ) + { + out_min = out_max; + out_max = this.properties.out_min; + } + */ + + this._last_v = ((v - in_min) / (in_max - in_min)) * (out_max - out_min) + out_min; + this.setOutputData(0, this._last_v); + this.setOutputData(1, Math.clamp( this._last_v, out_min, out_max )); + }; + + MathRange.prototype.onDrawBackground = function(ctx) { + //show the current value + if (this._last_v) { + this.outputs[0].label = this._last_v.toFixed(3); + } else { + this.outputs[0].label = "?"; + } + }; + + MathRange.prototype.onGetInputs = function() { + return [ + ["in_min", "number"], + ["in_max", "number"], + ["out_min", "number"], + ["out_max", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/range", MathRange); + + function MathRand() { + this.addOutput("value", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.size = [80, 30]; + } + + MathRand.title = "Rand"; + MathRand.desc = "Random number"; + + MathRand.prototype.onExecute = function() { + if (this.inputs) { + for (var i = 0; i < this.inputs.length; i++) { + var input = this.inputs[i]; + var v = this.getInputData(i); + if (v === undefined) { + continue; + } + this.properties[input.name] = v; + } + } + + var min = this.properties.min; + var max = this.properties.max; + this._last_v = Math.random() * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathRand.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + MathRand.prototype.onGetInputs = function() { + return [["min", "number"], ["max", "number"]]; + }; + + LiteGraph.registerNodeType("math/rand", MathRand); + + //basic continuous noise + function MathNoise() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("min", 0); + this.addProperty("max", 1); + this.addProperty("smooth", true); + this.addProperty("seed", 0); + this.addProperty("octaves", 1); + this.addProperty("persistence", 0.8); + this.addProperty("speed", 1); + this.size = [90, 30]; + } + + MathNoise.title = "Noise"; + MathNoise.desc = "Random number with temporal continuity"; + MathNoise.data = null; + + MathNoise.getValue = function(f, smooth) { + if (!MathNoise.data) { + MathNoise.data = new Float32Array(1024); + for (var i = 0; i < MathNoise.data.length; ++i) { + MathNoise.data[i] = Math.random(); + } + } + f = f % 1024; + if (f < 0) { + f += 1024; + } + var f_min = Math.floor(f); + var f = f - f_min; + var r1 = MathNoise.data[f_min]; + var r2 = MathNoise.data[f_min == 1023 ? 0 : f_min + 1]; + if (smooth) { + f = f * f * f * (f * (f * 6.0 - 15.0) + 10.0); + } + return r1 * (1 - f) + r2 * f; + }; + + MathNoise.prototype.onExecute = function() { + var f = this.getInputData(0) || 0; + var iterations = this.properties.octaves || 1; + var r = 0; + var amp = 1; + var seed = this.properties.seed || 0; + f += seed; + var speed = this.properties.speed || 1; + var total_amp = 0; + for(var i = 0; i < iterations; ++i) + { + r += MathNoise.getValue(f * (1+i) * speed, this.properties.smooth) * amp; + total_amp += amp; + amp *= this.properties.persistence; + if(amp < 0.001) + break; + } + r /= total_amp; + var min = this.properties.min; + var max = this.properties.max; + this._last_v = r * (max - min) + min; + this.setOutputData(0, this._last_v); + }; + + MathNoise.prototype.onDrawBackground = function(ctx) { + //show the current value + this.outputs[0].label = (this._last_v || 0).toFixed(3); + }; + + LiteGraph.registerNodeType("math/noise", MathNoise); + + //generates spikes every random time + function MathSpikes() { + this.addOutput("out", "number"); + this.addProperty("min_time", 1); + this.addProperty("max_time", 2); + this.addProperty("duration", 0.2); + this.size = [90, 30]; + this._remaining_time = 0; + this._blink_time = 0; + } + + MathSpikes.title = "Spikes"; + MathSpikes.desc = "spike every random time"; + + MathSpikes.prototype.onExecute = function() { + var dt = this.graph.elapsed_time; //in secs + + this._remaining_time -= dt; + this._blink_time -= dt; + + var v = 0; + if (this._blink_time > 0) { + var f = this._blink_time / this.properties.duration; + v = 1 / (Math.pow(f * 8 - 4, 4) + 1); + } + + if (this._remaining_time < 0) { + this._remaining_time = + Math.random() * + (this.properties.max_time - this.properties.min_time) + + this.properties.min_time; + this._blink_time = this.properties.duration; + this.boxcolor = "#FFF"; + } else { + this.boxcolor = "#000"; + } + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/spikes", MathSpikes); + + //Math clamp + function MathClamp() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("min", 0); + this.addProperty("max", 1); + } + + MathClamp.title = "Clamp"; + MathClamp.desc = "Clamp number between min and max"; + //MathClamp.filter = "shader"; + + MathClamp.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + v = Math.max(this.properties.min, v); + v = Math.min(this.properties.max, v); + this.setOutputData(0, v); + }; + + MathClamp.prototype.getCode = function(lang) { + var code = ""; + if (this.isInputConnected(0)) { + code += + "clamp({{0}}," + + this.properties.min + + "," + + this.properties.max + + ")"; + } + return code; + }; + + LiteGraph.registerNodeType("math/clamp", MathClamp); + + //Math ABS + function MathLerp() { + this.properties = { f: 0.5 }; + this.addInput("A", "number"); + this.addInput("B", "number"); + + this.addOutput("out", "number"); + } + + MathLerp.title = "Lerp"; + MathLerp.desc = "Linear Interpolation"; + + MathLerp.prototype.onExecute = function() { + var v1 = this.getInputData(0); + if (v1 == null) { + v1 = 0; + } + var v2 = this.getInputData(1); + if (v2 == null) { + v2 = 0; + } + + var f = this.properties.f; + + var _f = this.getInputData(2); + if (_f !== undefined) { + f = _f; + } + + this.setOutputData(0, v1 * (1 - f) + v2 * f); + }; + + MathLerp.prototype.onGetInputs = function() { + return [["f", "number"]]; + }; + + LiteGraph.registerNodeType("math/lerp", MathLerp); + + //Math ABS + function MathAbs() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathAbs.title = "Abs"; + MathAbs.desc = "Absolute"; + + MathAbs.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.abs(v)); + }; + + LiteGraph.registerNodeType("math/abs", MathAbs); + + //Math Floor + function MathFloor() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFloor.title = "Floor"; + MathFloor.desc = "Floor number to remove fractional part"; + + MathFloor.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, Math.floor(v)); + }; + + LiteGraph.registerNodeType("math/floor", MathFloor); + + //Math frac + function MathFrac() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + } + + MathFrac.title = "Frac"; + MathFrac.desc = "Returns fractional part"; + + MathFrac.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + this.setOutputData(0, v % 1); + }; + + LiteGraph.registerNodeType("math/frac", MathFrac); + + //Math Floor + function MathSmoothStep() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.properties = { A: 0, B: 1 }; + } + + MathSmoothStep.title = "Smoothstep"; + MathSmoothStep.desc = "Smoothstep"; + + MathSmoothStep.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v === undefined) { + return; + } + + var edge0 = this.properties.A; + var edge1 = this.properties.B; + + // Scale, bias and saturate x to 0..1 range + v = Math.clamp((v - edge0) / (edge1 - edge0), 0.0, 1.0); + // Evaluate polynomial + v = v * v * (3 - 2 * v); + + this.setOutputData(0, v); + }; + + LiteGraph.registerNodeType("math/smoothstep", MathSmoothStep); + + //Math scale + function MathScale() { + this.addInput("in", "number", { label: "" }); + this.addOutput("out", "number", { label: "" }); + this.size = [80, 30]; + this.addProperty("factor", 1); + } + + MathScale.title = "Scale"; + MathScale.desc = "v * factor"; + + MathScale.prototype.onExecute = function() { + var value = this.getInputData(0); + if (value != null) { + this.setOutputData(0, value * this.properties.factor); + } + }; + + LiteGraph.registerNodeType("math/scale", MathScale); + + //Gate + function Gate() { + this.addInput("v","boolean"); + this.addInput("A"); + this.addInput("B"); + this.addOutput("out"); + } + + Gate.title = "Gate"; + Gate.desc = "if v is true, then outputs A, otherwise B"; + + Gate.prototype.onExecute = function() { + var v = this.getInputData(0); + this.setOutputData(0, this.getInputData( v ? 1 : 2 )); + }; + + LiteGraph.registerNodeType("math/gate", Gate); + + + //Math Average + function MathAverageFilter() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.size = [80, 30]; + this.addProperty("samples", 10); + this._values = new Float32Array(10); + this._current = 0; + } + + MathAverageFilter.title = "Average"; + MathAverageFilter.desc = "Average Filter"; + + MathAverageFilter.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + + var num_samples = this._values.length; + + this._values[this._current % num_samples] = v; + this._current += 1; + if (this._current > num_samples) { + this._current = 0; + } + + var avr = 0; + for (var i = 0; i < num_samples; ++i) { + avr += this._values[i]; + } + + this.setOutputData(0, avr / num_samples); + }; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (value < 1) { + value = 1; + } + this.properties.samples = Math.round(value); + var old = this._values; + + this._values = new Float32Array(this.properties.samples); + if (old.length <= this._values.length) { + this._values.set(old); + } else { + this._values.set(old.subarray(0, this._values.length)); + } + }; + + LiteGraph.registerNodeType("math/average", MathAverageFilter); + + //Math + function MathTendTo() { + this.addInput("in", "number"); + this.addOutput("out", "number"); + this.addProperty("factor", 0.1); + this.size = [80, 30]; + this._value = null; + } + + MathTendTo.title = "TendTo"; + MathTendTo.desc = "moves the output value always closer to the input"; + + MathTendTo.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var f = this.properties.factor; + if (this._value == null) { + this._value = v; + } else { + this._value = this._value * (1 - f) + v * f; + } + this.setOutputData(0, this._value); + }; + + LiteGraph.registerNodeType("math/tendTo", MathTendTo); + + //Math operation + function MathOperation() { + this.addInput("A", "number,array,object"); + this.addInput("B", "number"); + this.addOutput("=", "number"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "+", "enum", { values: MathOperation.values }); + this._func = function(A,B) { return A + B; }; + this._result = []; //only used for arrays + } + + MathOperation.values = ["+", "-", "*", "/", "%", "^", "max", "min"]; + + MathOperation.title = "Operation"; + MathOperation.desc = "Easy math operators"; + MathOperation["@OP"] = { + type: "enum", + title: "operation", + values: MathOperation.values + }; + MathOperation.size = [100, 60]; + + MathOperation.prototype.getTitle = function() { + if(this.properties.OP == "max" || this.properties.OP == "min") + return this.properties.OP + "(A,B)"; + return "A " + this.properties.OP + " B"; + }; + + MathOperation.prototype.setValue = function(v) { + if (typeof v == "string") { + v = parseFloat(v); + } + this.properties["value"] = v; + }; + + MathOperation.prototype.onPropertyChanged = function(name, value) + { + if (name != "OP") + return; + switch (this.properties.OP) { + case "+": this._func = function(A,B) { return A + B; }; break; + case "-": this._func = function(A,B) { return A - B; }; break; + case "x": + case "X": + case "*": this._func = function(A,B) { return A * B; }; break; + case "/": this._func = function(A,B) { return A / B; }; break; + case "%": this._func = function(A,B) { return A % B; }; break; + case "^": this._func = function(A,B) { return Math.pow(A, B); }; break; + case "max": this._func = function(A,B) { return Math.max(A, B); }; break; + case "min": this._func = function(A,B) { return Math.min(A, B); }; break; + default: + console.warn("Unknown operation: " + this.properties.OP); + this._func = function(A) { return A; }; + break; + } + } + + MathOperation.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if ( A != null ) { + if( A.constructor === Number ) + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B != null) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + var result; + if(A.constructor === Number) + { + result = 0; + result = this._func(A,B); + } + else if(A.constructor === Array) + { + result = this._result; + result.length = A.length; + for(var i = 0; i < A.length; ++i) + result[i] = this._func(A[i],B); + } + else + { + result = {}; + for(var i in A) + result[i] = this._func(A[i],B); + } + this.setOutputData(0, result); + }; + + MathOperation.prototype.onDrawBackground = function(ctx) { + if (this.flags.collapsed) { + return; + } + + ctx.font = "40px Arial"; + ctx.fillStyle = "#666"; + ctx.textAlign = "center"; + ctx.fillText( + this.properties.OP, + this.size[0] * 0.5, + (this.size[1] + LiteGraph.NODE_TITLE_HEIGHT) * 0.5 + ); + ctx.textAlign = "left"; + }; + + LiteGraph.registerNodeType("math/operation", MathOperation); + + LiteGraph.registerSearchboxExtra("math/operation", "MAX", { + properties: {OP:"max"}, + title: "MAX()" + }); + + LiteGraph.registerSearchboxExtra("math/operation", "MIN", { + properties: {OP:"min"}, + title: "MIN()" + }); + + + //Math compare + function MathCompare() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("A==B", "boolean"); + this.addOutput("A!=B", "boolean"); + this.addProperty("A", 0); + this.addProperty("B", 0); + } + + MathCompare.title = "Compare"; + MathCompare.desc = "compares between two values"; + + MathCompare.prototype.onExecute = function() { + var A = this.getInputData(0); + var B = this.getInputData(1); + if (A !== undefined) { + this.properties["A"] = A; + } else { + A = this.properties["A"]; + } + + if (B !== undefined) { + this.properties["B"] = B; + } else { + B = this.properties["B"]; + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + if (!output.links || !output.links.length) { + continue; + } + var value; + switch (output.name) { + case "A==B": + value = A == B; + break; + case "A!=B": + value = A != B; + break; + case "A>B": + value = A > B; + break; + case "A=B": + value = A >= B; + break; + } + this.setOutputData(i, value); + } + }; + + MathCompare.prototype.onGetOutputs = function() { + return [ + ["A==B", "boolean"], + ["A!=B", "boolean"], + ["A>B", "boolean"], + ["A=B", "boolean"], + ["A<=B", "boolean"] + ]; + }; + + LiteGraph.registerNodeType("math/compare", MathCompare); + + LiteGraph.registerSearchboxExtra("math/compare", "==", { + outputs: [["A==B", "boolean"]], + title: "A==B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "!=", { + outputs: [["A!=B", "boolean"]], + title: "A!=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", ">", { + outputs: [["A>B", "boolean"]], + title: "A>B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<", { + outputs: [["A=", { + outputs: [["A>=B", "boolean"]], + title: "A>=B" + }); + LiteGraph.registerSearchboxExtra("math/compare", "<=", { + outputs: [["A<=B", "boolean"]], + title: "A<=B" + }); + + function MathCondition() { + this.addInput("A", "number"); + this.addInput("B", "number"); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", ">", "enum", { values: MathCondition.values }); + this.addWidget("combo","Cond.",this.properties.OP,{ property: "OP", values: MathCondition.values } ); + + this.size = [80, 60]; + } + + MathCondition.values = [">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + MathCondition["@OP"] = { + type: "enum", + title: "operation", + values: MathCondition.values + }; + + MathCondition.title = "Condition"; + MathCondition.desc = "evaluates condition between A and B"; + + MathCondition.prototype.getTitle = function() { + return "A " + this.properties.OP + " B"; + }; + + MathCondition.prototype.onExecute = function() { + var A = this.getInputData(0); + if (A === undefined) { + A = this.properties.A; + } else { + this.properties.A = A; + } + + var B = this.getInputData(1); + if (B === undefined) { + B = this.properties.B; + } else { + this.properties.B = B; + } + + var result = true; + switch (this.properties.OP) { + case ">": + result = A > B; + break; + case "<": + result = A < B; + break; + case "==": + result = A == B; + break; + case "!=": + result = A != B; + break; + case "<=": + result = A <= B; + break; + case ">=": + result = A >= B; + break; + case "||": + result = A || B; + break; + case "&&": + result = A && B; + break; + } + + this.setOutputData(0, result); + this.setOutputData(1, !result); + }; + + LiteGraph.registerNodeType("math/condition", MathCondition); + + + function MathBranch() { + this.addInput("in", 0); + this.addInput("cond", "boolean"); + this.addOutput("true", 0); + this.addOutput("false", 0); + this.size = [80, 60]; + } + + MathBranch.title = "Branch"; + MathBranch.desc = "If condition is true, outputs IN in true, otherwise in false"; + + MathBranch.prototype.onExecute = function() { + var V = this.getInputData(0); + var cond = this.getInputData(1); + + if(cond) + { + this.setOutputData(0, V); + this.setOutputData(1, null); + } + else + { + this.setOutputData(0, null); + this.setOutputData(1, V); + } + } + + LiteGraph.registerNodeType("math/branch", MathBranch); + + + function MathAccumulate() { + this.addInput("inc", "number"); + this.addOutput("total", "number"); + this.addProperty("increment", 1); + this.addProperty("value", 0); + } + + MathAccumulate.title = "Accumulate"; + MathAccumulate.desc = "Increments a value every time"; + + MathAccumulate.prototype.onExecute = function() { + if (this.properties.value === null) { + this.properties.value = 0; + } + + var inc = this.getInputData(0); + if (inc !== null) { + this.properties.value += inc; + } else { + this.properties.value += this.properties.increment; + } + this.setOutputData(0, this.properties.value); + }; + + LiteGraph.registerNodeType("math/accumulate", MathAccumulate); + + //Math Trigonometry + function MathTrigonometry() { + this.addInput("v", "number"); + this.addOutput("sin", "number"); + + this.addProperty("amplitude", 1); + this.addProperty("offset", 0); + this.bgImageUrl = "nodes/imgs/icon-sin.png"; + } + + MathTrigonometry.title = "Trigonometry"; + MathTrigonometry.desc = "Sin Cos Tan"; + //MathTrigonometry.filter = "shader"; + + MathTrigonometry.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + v = 0; + } + var amplitude = this.properties["amplitude"]; + var slot = this.findInputSlot("amplitude"); + if (slot != -1) { + amplitude = this.getInputData(slot); + } + var offset = this.properties["offset"]; + slot = this.findInputSlot("offset"); + if (slot != -1) { + offset = this.getInputData(slot); + } + + for (var i = 0, l = this.outputs.length; i < l; ++i) { + var output = this.outputs[i]; + var value; + switch (output.name) { + case "sin": + value = Math.sin(v); + break; + case "cos": + value = Math.cos(v); + break; + case "tan": + value = Math.tan(v); + break; + case "asin": + value = Math.asin(v); + break; + case "acos": + value = Math.acos(v); + break; + case "atan": + value = Math.atan(v); + break; + } + this.setOutputData(i, amplitude * value + offset); + } + }; + + MathTrigonometry.prototype.onGetInputs = function() { + return [["v", "number"], ["amplitude", "number"], ["offset", "number"]]; + }; + + MathTrigonometry.prototype.onGetOutputs = function() { + return [ + ["sin", "number"], + ["cos", "number"], + ["tan", "number"], + ["asin", "number"], + ["acos", "number"], + ["atan", "number"] + ]; + }; + + LiteGraph.registerNodeType("math/trigonometry", MathTrigonometry); + + LiteGraph.registerSearchboxExtra("math/trigonometry", "SIN()", { + outputs: [["sin", "number"]], + title: "SIN()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "COS()", { + outputs: [["cos", "number"]], + title: "COS()" + }); + LiteGraph.registerSearchboxExtra("math/trigonometry", "TAN()", { + outputs: [["tan", "number"]], + title: "TAN()" + }); + + //math library for safe math operations without eval + function MathFormula() { + this.addInput("x", "number"); + this.addInput("y", "number"); + this.addOutput("", "number"); + this.properties = { x: 1.0, y: 1.0, formula: "x+y" }; + this.code_widget = this.addWidget( + "text", + "F(x,y)", + this.properties.formula, + function(v, canvas, node) { + node.properties.formula = v; + } + ); + this.addWidget("toggle", "allow", LiteGraph.allow_scripts, function(v) { + LiteGraph.allow_scripts = v; + }); + this._func = null; + } + + MathFormula.title = "Formula"; + MathFormula.desc = "Compute formula"; + MathFormula.size = [160, 100]; + + MathAverageFilter.prototype.onPropertyChanged = function(name, value) { + if (name == "formula") { + this.code_widget.value = value; + } + }; + + MathFormula.prototype.onExecute = function() { + if (!LiteGraph.allow_scripts) { + return; + } + + var x = this.getInputData(0); + var y = this.getInputData(1); + if (x != null) { + this.properties["x"] = x; + } else { + x = this.properties["x"]; + } + + if (y != null) { + this.properties["y"] = y; + } else { + y = this.properties["y"]; + } + + var f = this.properties["formula"]; + + var value; + try { + if (!this._func || this._func_code != this.properties.formula) { + this._func = new Function( + "x", + "y", + "TIME", + "return " + this.properties.formula + ); + this._func_code = this.properties.formula; + } + value = this._func(x, y, this.graph.globaltime); + this.boxcolor = null; + } catch (err) { + this.boxcolor = "red"; + } + this.setOutputData(0, value); + }; + + MathFormula.prototype.getTitle = function() { + return this._func_code || "Formula"; + }; + + MathFormula.prototype.onDrawBackground = function() { + var f = this.properties["formula"]; + if (this.outputs && this.outputs.length) { + this.outputs[0].label = f; + } + }; + + LiteGraph.registerNodeType("math/formula", MathFormula); + + function Math3DVec2ToXY() { + this.addInput("vec2", "vec2"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + } + + Math3DVec2ToXY.title = "Vec2->XY"; + Math3DVec2ToXY.desc = "vector 2 to components"; + + Math3DVec2ToXY.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + }; + + LiteGraph.registerNodeType("math3d/vec2-to-xy", Math3DVec2ToXY); + + function Math3DXYToVec2() { + this.addInputs([["x", "number"], ["y", "number"]]); + this.addOutput("vec2", "vec2"); + this.properties = { x: 0, y: 0 }; + this._data = new Float32Array(2); + } + + Math3DXYToVec2.title = "XY->Vec2"; + Math3DXYToVec2.desc = "components to vector2"; + + Math3DXYToVec2.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + + var data = this._data; + data[0] = x; + data[1] = y; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xy-to-vec2", Math3DXYToVec2); + + function Math3DVec3ToXYZ() { + this.addInput("vec3", "vec3"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + } + + Math3DVec3ToXYZ.title = "Vec3->XYZ"; + Math3DVec3ToXYZ.desc = "vector 3 to components"; + + Math3DVec3ToXYZ.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + }; + + LiteGraph.registerNodeType("math3d/vec3-to-xyz", Math3DVec3ToXYZ); + + function Math3DXYZToVec3() { + this.addInputs([["x", "number"], ["y", "number"], ["z", "number"]]); + this.addOutput("vec3", "vec3"); + this.properties = { x: 0, y: 0, z: 0 }; + this._data = new Float32Array(3); + } + + Math3DXYZToVec3.title = "XYZ->Vec3"; + Math3DXYZToVec3.desc = "components to vector3"; + + Math3DXYZToVec3.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xyz-to-vec3", Math3DXYZToVec3); + + function Math3DVec4ToXYZW() { + this.addInput("vec4", "vec4"); + this.addOutput("x", "number"); + this.addOutput("y", "number"); + this.addOutput("z", "number"); + this.addOutput("w", "number"); + } + + Math3DVec4ToXYZW.title = "Vec4->XYZW"; + Math3DVec4ToXYZW.desc = "vector 4 to components"; + + Math3DVec4ToXYZW.prototype.onExecute = function() { + var v = this.getInputData(0); + if (v == null) { + return; + } + + this.setOutputData(0, v[0]); + this.setOutputData(1, v[1]); + this.setOutputData(2, v[2]); + this.setOutputData(3, v[3]); + }; + + LiteGraph.registerNodeType("math3d/vec4-to-xyzw", Math3DVec4ToXYZW); + + function Math3DXYZWToVec4() { + this.addInputs([ + ["x", "number"], + ["y", "number"], + ["z", "number"], + ["w", "number"] + ]); + this.addOutput("vec4", "vec4"); + this.properties = { x: 0, y: 0, z: 0, w: 0 }; + this._data = new Float32Array(4); + } + + Math3DXYZWToVec4.title = "XYZW->Vec4"; + Math3DXYZWToVec4.desc = "components to vector4"; + + Math3DXYZWToVec4.prototype.onExecute = function() { + var x = this.getInputData(0); + if (x == null) { + x = this.properties.x; + } + var y = this.getInputData(1); + if (y == null) { + y = this.properties.y; + } + var z = this.getInputData(2); + if (z == null) { + z = this.properties.z; + } + var w = this.getInputData(3); + if (w == null) { + w = this.properties.w; + } + + var data = this._data; + data[0] = x; + data[1] = y; + data[2] = z; + data[3] = w; + + this.setOutputData(0, data); + }; + + LiteGraph.registerNodeType("math3d/xyzw-to-vec4", Math3DXYZWToVec4); + +})(this); (function(global) { var LiteGraph = global.LiteGraph; @@ -33627,369 +35783,9 @@ LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); LiteGraph.registerNodeType("audio/destination", LGAudioDestination); })(this); -//event related nodes -(function(global) { - var LiteGraph = global.LiteGraph; - - function LGWebSocket() { - this.size = [60, 20]; - this.addInput("send", LiteGraph.ACTION); - this.addOutput("received", LiteGraph.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = { - url: "", - room: "lgraph", //allows to filter messages, - only_send_changes: true - }; - this._ws = null; - this._last_sent_data = []; - this._last_received_data = []; - } - - LGWebSocket.title = "WebSocket"; - LGWebSocket.desc = "Send data through a websocket"; - - LGWebSocket.prototype.onPropertyChanged = function(name, value) { - if (name == "url") { - this.connectSocket(); - } - }; - - LGWebSocket.prototype.onExecute = function() { - if (!this._ws && this.properties.url) { - this.connectSocket(); - } - - if (!this._ws || this._ws.readyState != WebSocket.OPEN) { - return; - } - - var room = this.properties.room; - var only_changes = this.properties.only_send_changes; - - for (var i = 1; i < this.inputs.length; ++i) { - var data = this.getInputData(i); - if (data == null) { - continue; - } - var json; - try { - json = JSON.stringify({ - type: 0, - room: room, - channel: i, - data: data - }); - } catch (err) { - continue; - } - if (only_changes && this._last_sent_data[i] == json) { - continue; - } - - this._last_sent_data[i] = json; - this._ws.send(json); - } - - for (var i = 1; i < this.outputs.length; ++i) { - this.setOutputData(i, this._last_received_data[i]); - } - - if (this.boxcolor == "#AFA") { - this.boxcolor = "#6C6"; - } - }; - - LGWebSocket.prototype.connectSocket = function() { - var that = this; - var url = this.properties.url; - if (url.substr(0, 2) != "ws") { - url = "ws://" + url; - } - this._ws = new WebSocket(url); - this._ws.onopen = function() { - console.log("ready"); - that.boxcolor = "#6C6"; - }; - this._ws.onmessage = function(e) { - that.boxcolor = "#AFA"; - var data = JSON.parse(e.data); - if (data.room && data.room != that.properties.room) { - return; - } - if (data.type == 1) { - if ( - data.data.object_class && - LiteGraph[data.data.object_class] - ) { - var obj = null; - try { - obj = new LiteGraph[data.data.object_class](data.data); - that.triggerSlot(0, obj); - } catch (err) { - return; - } - } else { - that.triggerSlot(0, data.data); - } - } else { - that._last_received_data[data.channel || 0] = data.data; - } - }; - this._ws.onerror = function(e) { - console.log("couldnt connect to websocket"); - that.boxcolor = "#E88"; - }; - this._ws.onclose = function(e) { - console.log("connection closed"); - that.boxcolor = "#000"; - }; - }; - - LGWebSocket.prototype.send = function(data) { - if (!this._ws || this._ws.readyState != WebSocket.OPEN) { - return; - } - this._ws.send(JSON.stringify({ type: 1, msg: data })); - }; - - LGWebSocket.prototype.onAction = function(action, param) { - if (!this._ws || this._ws.readyState != WebSocket.OPEN) { - return; - } - this._ws.send({ - type: 1, - room: this.properties.room, - action: action, - data: param - }); - }; - - LGWebSocket.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - - LGWebSocket.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - - LiteGraph.registerNodeType("network/websocket", LGWebSocket); - - //It is like a websocket but using the SillyServer.js server that bounces packets back to all clients connected: - //For more information: https://github.com/jagenjo/SillyServer.js - - function LGSillyClient() { - //this.size = [60,20]; - this.room_widget = this.addWidget( - "text", - "Room", - "lgraph", - this.setRoom.bind(this) - ); - this.addWidget( - "button", - "Reconnect", - null, - this.connectSocket.bind(this) - ); - - this.addInput("send", LiteGraph.ACTION); - this.addOutput("received", LiteGraph.EVENT); - this.addInput("in", 0); - this.addOutput("out", 0); - this.properties = { - url: "tamats.com:55000", - room: "lgraph", - only_send_changes: true - }; - - this._server = null; - this.connectSocket(); - this._last_sent_data = []; - this._last_received_data = []; - - if(typeof(SillyClient) == "undefined") - console.warn("remember to add SillyClient.js to your project: https://tamats.com/projects/sillyserver/src/sillyclient.js"); - } - - LGSillyClient.title = "SillyClient"; - LGSillyClient.desc = "Connects to SillyServer to broadcast messages"; - - LGSillyClient.prototype.onPropertyChanged = function(name, value) { - if (name == "room") { - this.room_widget.value = value; - } - this.connectSocket(); - }; - - LGSillyClient.prototype.setRoom = function(room_name) { - this.properties.room = room_name; - this.room_widget.value = room_name; - this.connectSocket(); - }; - - //force label names - LGSillyClient.prototype.onDrawForeground = function() { - for (var i = 1; i < this.inputs.length; ++i) { - var slot = this.inputs[i]; - slot.label = "in_" + i; - } - for (var i = 1; i < this.outputs.length; ++i) { - var slot = this.outputs[i]; - slot.label = "out_" + i; - } - }; - - LGSillyClient.prototype.onExecute = function() { - if (!this._server || !this._server.is_connected) { - return; - } - - var only_send_changes = this.properties.only_send_changes; - - for (var i = 1; i < this.inputs.length; ++i) { - var data = this.getInputData(i); - var prev_data = this._last_sent_data[i]; - if (data != null) { - if (only_send_changes) - { - var is_equal = true; - if( data && data.length && prev_data && prev_data.length == data.length && data.constructor !== String) - { - for(var j = 0; j < data.length; ++j) - if( prev_data[j] != data[j] ) - { - is_equal = false; - break; - } - } - else if(this._last_sent_data[i] != data) - is_equal = false; - if(is_equal) - continue; - } - this._server.sendMessage({ type: 0, channel: i, data: data }); - if( data.length && data.constructor !== String ) - { - if( this._last_sent_data[i] ) - { - this._last_sent_data[i].length = data.length; - for(var j = 0; j < data.length; ++j) - this._last_sent_data[i][j] = data[j]; - } - else //create - { - if(data.constructor === Array) - this._last_sent_data[i] = data.concat(); - else - this._last_sent_data[i] = new data.constructor( data ); - } - } - else - this._last_sent_data[i] = data; //should be cloned - } - } - - for (var i = 1; i < this.outputs.length; ++i) { - this.setOutputData(i, this._last_received_data[i]); - } - - if (this.boxcolor == "#AFA") { - this.boxcolor = "#6C6"; - } - }; - - LGSillyClient.prototype.connectSocket = function() { - var that = this; - if (typeof SillyClient == "undefined") { - if (!this._error) { - console.error( - "SillyClient node cannot be used, you must include SillyServer.js" - ); - } - this._error = true; - return; - } - - this._server = new SillyClient(); - this._server.on_ready = function() { - console.log("ready"); - that.boxcolor = "#6C6"; - }; - this._server.on_message = function(id, msg) { - var data = null; - try { - data = JSON.parse(msg); - } catch (err) { - return; - } - - if (data.type == 1) { - //EVENT slot - if ( - data.data.object_class && - LiteGraph[data.data.object_class] - ) { - var obj = null; - try { - obj = new LiteGraph[data.data.object_class](data.data); - that.triggerSlot(0, obj); - } catch (err) { - return; - } - } else { - that.triggerSlot(0, data.data); - } - } //for FLOW slots - else { - that._last_received_data[data.channel || 0] = data.data; - } - that.boxcolor = "#AFA"; - }; - this._server.on_error = function(e) { - console.log("couldnt connect to websocket"); - that.boxcolor = "#E88"; - }; - this._server.on_close = function(e) { - console.log("connection closed"); - that.boxcolor = "#000"; - }; - - if (this.properties.url && this.properties.room) { - try { - this._server.connect(this.properties.url, this.properties.room); - } catch (err) { - console.error("SillyServer error: " + err); - this._server = null; - return; - } - this._final_url = this.properties.url + "/" + this.properties.room; - } - }; - - LGSillyClient.prototype.send = function(data) { - if (!this._server || !this._server.is_connected) { - return; - } - this._server.sendMessage({ type: 1, data: data }); - }; - - LGSillyClient.prototype.onAction = function(action, param) { - if (!this._server || !this._server.is_connected) { - return; - } - this._server.sendMessage({ type: 1, action: action, data: param }); - }; - - LGSillyClient.prototype.onGetInputs = function() { - return [["in", 0]]; - }; - - LGSillyClient.prototype.onGetOutputs = function() { - return [["out", 0]]; - }; - - LiteGraph.registerNodeType("network/sillyclient", LGSillyClient); -})(this); + LGraphFXLens.title = "Lens"; + LGraphFXLens.desc = "Camera Lens distortion"; + LGraphFXLens.widgets_info = { + precision: { widget: "combo", values: LGraphTexture.MODE_VALUES } + }; diff --git a/css/litegraph.css b/css/litegraph.css index 918858f41..1a6154e5a 100755 --- a/css/litegraph.css +++ b/css/litegraph.css @@ -199,6 +199,17 @@ margin-top: 2px; } +.litegraph.litesearchbox .close { + background-color: #000; + color: #FFF; + font: 14px; + margin-top: 6px; + min-height: 1.5em; + padding-left: 10px; + padding-right: 10px; + margin-right: 5px; +} + .litegraph.lite-search-item { font-family: Tahoma, sans-serif; background-color: rgba(0, 0, 0, 0.5); diff --git a/editor/editor_mobile.html b/editor/editor_mobile.html index da5b1e104..c065551d7 100644 --- a/editor/editor_mobile.html +++ b/editor/editor_mobile.html @@ -40,6 +40,9 @@ + + + @@ -119,12 +122,22 @@ jsConsole.log("Here is console.log!"); // map console log-debug to jsConsole - console.log = function(par){ - jsConsole.log(par); + var olConsoleLog = console.log; + console.log = function(par,par2,par3,par4,par5){ + jsConsole.log(par,par2,par3,par4,par5); + var objDiv = document.getElementById("console-container"); + objDiv.scrollTop = objDiv.scrollHeight; + + olConsoleLog(par,par2,par3,par4,par5); + } + var oldConsoleDebug = console.debug; + console.debug = function(par,par2,par3,par4,par5){ + jsConsole.log(par,par2,par3,par4,par5); var objDiv = document.getElementById("console-container"); objDiv.scrollTop = objDiv.scrollHeight; + + oldConsoleDebug(par,par2,par3,par4,par5); } - console.debug = console.log; console.log("going into html console"); diff --git a/editor/index.html b/editor/index.html index c43922b45..45eeaec10 100755 --- a/editor/index.html +++ b/editor/index.html @@ -38,6 +38,9 @@ + + + diff --git a/editor/js/defaults.js b/editor/js/defaults.js index 00bea24e3..6ef28bf02 100644 --- a/editor/js/defaults.js +++ b/editor/js/defaults.js @@ -16,6 +16,9 @@ LiteGraph.search_hide_on_mouse_leave = true; // [false on mobile] better true if LiteGraph.search_filter_enabled = true; // [true!] enable filtering slots type in the search widget; !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] LiteGraph.search_show_all_on_open = true; // [true!] opens the results list when opening the search widget +LiteGraph.show_node_tooltip = true; // [true!] show a tooltip with node property "tooltip" over the selected node +LiteGraph.show_node_tooltip_use_descr_property= false; // enabled tooltip from desc when property tooltip not set + LiteGraph.auto_load_slot_types = true; // [if want false; use true; run; get vars values to be statically set; than disable] nodes types and nodeclass association with node types need to be calculated; if dont want this; calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] /*// set these values if not using auto_load_slot_types LiteGraph.registered_slot_in_types = {}; // slot types for nodeclass @@ -28,4 +31,12 @@ LiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentually; one by one LiteGraph.middle_click_slot_add_default_node = true; //[true!] allows to create and connect a ndoe clicking with the third button (wheel) LiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults -LiteGraph.pointerevents_method = "mouse"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) \ No newline at end of file +LiteGraph.pointerevents_method = "mouse"; // "mouse"|"pointer" use mouse for compatibility issues, touch will work only with pointer + +LiteGraph.actionHistory_enabled = true; // [true!] cntrlZ, cntrlY +LiteGraph.actionHistoryMaxSave = 40; + +/* EXECUTING ACTIONS AFTER UPDATING VALUES - ANCESTORS */ +LiteGraph.refreshAncestorsOnTriggers = true; //[true!] +LiteGraph.refreshAncestorsOnActions = true; //[true!] +LiteGraph.ensureUniqueExecutionAndActionCall = true; //[true!] \ No newline at end of file diff --git a/editor/js/defaults_mobile.js b/editor/js/defaults_mobile.js index d4ef831c7..e7a3aaa14 100644 --- a/editor/js/defaults_mobile.js +++ b/editor/js/defaults_mobile.js @@ -16,6 +16,9 @@ LiteGraph.search_hide_on_mouse_leave = false; // [false on mobile] better true i LiteGraph.search_filter_enabled = true; // [true!] enable filtering slots type in the search widget; !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] LiteGraph.search_show_all_on_open = true; // [true!] opens the results list when opening the search widget +LiteGraph.show_node_tooltip = true; // [true!] show a tooltip with node property "tooltip" over the selected node +LiteGraph.show_node_tooltip_use_descr_property= false; // enabled tooltip from desc when property tooltip not set + LiteGraph.auto_load_slot_types = true; // [if want false; use true; run; get vars values to be statically set; than disable] nodes types and nodeclass association with node types need to be calculated; if dont want this; calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] /*// set these values if not using auto_load_slot_types LiteGraph.registered_slot_in_types = {}; // slot types for nodeclass @@ -28,4 +31,14 @@ LiteGraph.do_add_triggers_slots = true; // [true!] will create and connect event LiteGraph.allow_multi_output_for_events = false; // [false!] being events; it is strongly reccomended to use them sequentually; one by one LiteGraph.middle_click_slot_add_default_node = true; //[true!] allows to create and connect a ndoe clicking with the third button (wheel) LiteGraph.release_link_on_empty_shows_menu = true; //[true!] dragging a link to empty space will open a menu, add from list, search or defaults -LiteGraph.pointerevents_method = "pointer"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) \ No newline at end of file +LiteGraph.pointerevents_method = "pointer"; // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) + +LiteGraph.two_fingers_opens_menu = true; + +LiteGraph.actionHistory_enabled = true; // [true!] cntrlZ, cntrlY +LiteGraph.actionHistoryMaxSave = 40; + +/* EXECUTING ACTIONS AFTER UPDATING VALUES - ANCESTORS */ +LiteGraph.refreshAncestorsOnTriggers = true; //[true!] +LiteGraph.refreshAncestorsOnActions = true; //[true!] +LiteGraph.ensureUniqueExecutionAndActionCall = true; //[true!]; \ No newline at end of file diff --git a/editor/js/libs/sillyclient.js b/editor/js/libs/sillyclient.js new file mode 100644 index 000000000..8af107c33 --- /dev/null +++ b/editor/js/libs/sillyclient.js @@ -0,0 +1,392 @@ +//SillyClient.js allows to connect to SillyServer.js, more info in https://github.com/jagenjo/SillyServer.js/ +//Javi Agenjo 2015 + +function SillyClient() +{ + this.url = ""; + this.socket = null; + this.is_connected = false; + this.room = { name: "", clients:[], updated: false }; + this.clients = {}; + this.num_clients = 0; + this.info_transmitted = 0; + this.info_received = 0; + + this.feedback = false; //if you want message to bounce back to you + + this.user_id = 0; + this.user_name = "anonymous"; + + this.on_connect = null; //when connected + this.on_ready = null; //when we have an ID from the server + this.on_message = null; //when somebody sends a message + this.on_close = null; //when the server closes + this.on_user_connected = null; //new user connected + this.on_user_disconnected = null; //user leaves + this.on_error = null; //when cannot connect +} + +SillyClient.verbose = false; + +//Connects to server, you must specify server host (p.e: "tamats.com:55000") and room name +SillyClient.prototype.connect = function( url, room_name, on_connect, on_message, on_close ) +{ + room_name = room_name || ""; + var that = this; + this.url = url; + if(!url) + throw("You must specify the server URL of the SillyServer"); + + if(this.socket) + { + this.socket.onmessage = null; + this.socket.onclose = null; + this.socket.close(); + } + this.clients = {}; + + if(typeof(WebSocket) == "undefined") + WebSocket = window.MozWebSocket; + if(typeof(WebSocket) == "undefined") + { + alert("Websockets not supported by your browser, consider switching to the latest version of Firefox, Chrome or Safari."); + return; + } + + var params = ""; + if(this.feedback) + params = "?feedback=1"; + + var protocol = ""; + if( url.substr(0,3) != "ws:" && url.substr(0,4) != "wss:" ) + { + protocol = location.protocol == "http:" ? "ws://" : "wss://"; //default protocol + } + + var final_url = this._final_url = protocol + url + "/" + room_name + params; + + //connect + this.socket = new WebSocket( final_url ); + this.socket.binaryType = "arraybuffer"; + this.socket.onopen = function(){ + that.is_connected = true; + that.room = { + name: room_name, + clients: [] + }; + if(SillyClient.verbose) + console.log("SillyClient socket opened"); + if(on_connect && typeof(on_connect) == "function" ) + on_connect(); + if(that.on_connect) + that.on_connect(that); + } + + this.socket.onclose = function(e) { + if(SillyClient.verbose) + console.log("SillyClient socket has been closed: ", e); + if(that.socket != this) + return; + if(on_close) + on_close(); + if(that.on_close) + that.on_close( e ); + that.socket = null; + that.room = null; + that.is_connected = false; + }; + + this.socket.onmessage = function(msg){ + if(that.socket != this) + return; + + that.info_received += 1; + + if( msg.data.constructor === ArrayBuffer ) + { + var buffer = msg.data; + processArrayBuffer( buffer ); + } + else if( msg.data.constructor === String ) + { + var tokens = msg.data.split("|"); //author id | cmd | data + if(tokens.length < 3) + { + if(SillyClient.verbose) + console.log("Received: " + msg.data); //Awesome! + } + else + that.onServerEvent( tokens[0], tokens[1], msg.data.substr( tokens[0].length + tokens[1].length + 2, msg.data.length), on_message ); + } + else + console.warn("Unknown message type"); + } + + function processArrayBuffer( buffer ) + { + var buffer_array = new Uint8Array( buffer ); + var header = buffer_array.subarray(0,32); + var data = buffer.slice(32); + var header_str = SillyClient.arrayToString( new Uint8Array(header) ); + var tokens = header_str.split("|"); //author id | cmd | data + //author_id, cmd, data, on_message + that.onServerEvent( tokens[0], tokens[1], data, on_message ); + } + + this.socket.onerror = function(err){ + console.log("error: ", err ); + if(that.on_error) + that.on_error(err); + } + + return true; +} + +SillyClient.arrayToString = function(array) +{ + var str = ""; + for(var i = 0; i < array.length; i++) + str += String.fromCharCode(array[i]); + return str; +} + +//Close the connection with the server +SillyClient.prototype.close = function() +{ + if(!this.socket) + return; + + this.socket.close(); + this.socket = null; + this.clients = {}; + this.is_connected = false; +} + +//Process events +SillyClient.prototype.onServerEvent = function( author_id, cmd, data, on_message ) +{ + if (cmd == "MSG" || cmd == "DATA") //user message received + { + if(on_message) + on_message( author_id, data ); + if(this.on_message) + this.on_message( author_id, data ); + } + else if (cmd == "LOGIN") //new user entering + { + if(SillyClient.verbose) + console.log("User connected: " + data); + var name = "user_" + author_id.toString(); + if(!this.clients[ author_id ]) + { + this.clients[ author_id ] = { id: author_id, name: name }; + this.num_clients += 1; + } + if(author_id != this.user_id) + { + if(this.on_user_connected) //somebody else is connected + this.on_user_connected( author_id, data ); + } + } + else if (cmd == "LOGOUT") //user leaving + { + if(this.clients[author_id]) + { + if(SillyClient.verbose) + console.log("User disconnected: " + this.clients[ author_id ].name ); + delete this.clients[ author_id ]; + this.num_clients -= 1; + } + + if(this.on_user_disconnected) //somebody else is connected + this.on_user_disconnected( author_id ); + var pos = this.room.clients.indexOf( author_id ); + if(pos != -1) + this.room.clients.splice( pos, 1 ); + } + else if (cmd == "ID") //retrieve your user id + { + this.user_id = author_id; + this.user_name = "user_" + author_id.toString(); + this.clients[ author_id ] = { id: author_id, name: this.user_name }; + if(this.on_ready) + this.on_ready( author_id ); + } + else if (cmd == "INFO") //retrieve room info + { + var room_info = JSON.parse( data ); + this.room = room_info; + this.num_clients = room_info.clients.length; + for(var i = 0; i < room_info.clients.length; ++i) + { + var client_id = room_info.clients[i]; + this.clients[ client_id ] = { id: client_id, name: "user_" + client_id }; + } + + if(this.on_room_info) + this.on_room_info( room_info ); + } +} + +//target_ids is optional, if not specified the message is send to all +SillyClient.prototype.sendMessage = function( msg, target_ids ) +{ + if(msg === null) + return; + + if(msg.constructor === Object) + msg = JSON.stringify(msg); + + if(!this.socket || this.socket.readyState !== WebSocket.OPEN) + { + console.error("Not connected, cannot send info"); + return; + } + + //pack target info + if( target_ids ) + { + var target_str = "@" + (target_ids.constructor === Array ? target_ids.join(",") : target_ids) + "|"; + if(msg.constructor === String) + msg = target_str + msg; + else + throw("targeted not supported in binary messages"); + } + + this.socket.send(msg); + this.info_transmitted += 1; +} + +SillyClient.prototype.getBaseURL = function() +{ + var url = this.url; + var protocol = location.protocol + "//"; + if( url.indexOf("wss://") != -1) + { + protocol = "https://"; + url = url.substr(6); + } + var index = url.indexOf("/"); + var host = url.substr(0,index); + return protocol + host; +} + +//To store temporal information in the server +SillyClient.prototype.storeData = function(key, value, on_complete) +{ + if(!this.url) + throw("Cannot storeData if not connected to the server"); + var req = new XMLHttpRequest(); + var base_url = this.getBaseURL(); + req.open('GET', base_url + "/data?action=set&key="+key + ((value !== undefined && value !== null) ? "&value="+value : ""), true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error setting data: ", req.responseText ); + if(on_complete) + on_complete( JSON.parse(req.responseText) ); + } + }; + req.send(null); +} + +//To retrieve the temporal information from the server +SillyClient.prototype.loadData = function(key, on_complete) +{ + if(!this.url) + throw("Cannot loadData if not connected to the server"); + var req = new XMLHttpRequest(); + var base_url = this.getBaseURL(); + req.open('GET', base_url + "/data?action=get&key="+key, true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error setting data: ", req.responseText ); + var resp = JSON.parse(req.responseText); + if(on_complete) + on_complete( resp.data ); + } + }; + req.send(null); +} + +//Returns a report with information about clients connected and rooms open +SillyClient.prototype.getReport = function( on_complete ) +{ + var req = new XMLHttpRequest(); + var base_url = this.getBaseURL(); + req.open('GET', base_url + "/info", true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error getting report: ", req.responseText ); + var resp = JSON.parse(req.responseText); + if(on_complete) + on_complete( resp ); + } + }; + req.send(null); +} + +//Returns a report with information about clients connected and rooms open +SillyClient.getReport = function( url, on_complete ) +{ + var req = new XMLHttpRequest(); + var protocol = location.protocol + "//"; + if( url.indexOf("wss://") != -1) + { + protocol = "https://"; + url = url.substr(6); + } + var index = url.indexOf("/"); + var host = url.substr(0,index); + req.open('GET', protocol + host + "/info", true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error getting report: ", req.responseText ); + var resp = JSON.parse(req.responseText); + if(on_complete) + on_complete( resp ); + } + }; + req.send(null); +} + + +//Returns info about a room (which clients are connected now) +SillyClient.prototype.getRoomInfo = function( name, on_complete ) +{ + var req = new XMLHttpRequest(); + var base_url = this.getBaseURL(); + req.open('GET', base_url + "/room/" + name, true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error getting room info: ", req.responseText ); + var resp = JSON.parse(req.responseText); + if(on_complete) + on_complete( resp.data ); + } + }; + req.send(null); +} + +//Returns a list with all the open rooms that start with txt (txt must be at least 6 characters long) +SillyClient.prototype.findRooms = function( name_str, on_complete ) +{ + name_str = name_str || ""; + var req = new XMLHttpRequest(); + var base_url = this.getBaseURL(); + req.open('GET', base_url + "/find?name=" + name_str, true); + req.onreadystatechange = function (aEvt) { + if (req.readyState == 4) { + if(req.status != 200) + return console.error("Error getting room info: ", req.responseText ); + var resp = JSON.parse(req.responseText); + if(on_complete) + on_complete( resp.data ); + } + }; + req.send(null); +} \ No newline at end of file diff --git a/src/litegraph.js b/src/litegraph.js index 09fd1632f..90079131c 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -22,6 +22,7 @@ NODE_WIDGET_HEIGHT: 20, NODE_WIDTH: 140, NODE_MIN_WIDTH: 50, + NODE_MIN_SIZE:[50, 25], NODE_COLLAPSED_RADIUS: 10, NODE_COLLAPSED_WIDTH: 80, NODE_TITLE_COLOR: "#999", @@ -65,13 +66,14 @@ EVENT: -1, //for outputs ACTION: -1, //for inputs - NODE_MODES: ["Always", "On Event", "Never", "On Trigger"], // helper, will add "On Request" and more in the future + NODE_MODES: ["Always", "On Event", "Never", "On Trigger", "On Request"], NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode ALWAYS: 0, ON_EVENT: 1, NEVER: 2, ON_TRIGGER: 3, - + ON_REQUEST: 4, // used from event-based nodes, where ancestors are recursively executed on needed + UP: 1, DOWN: 2, LEFT: 3, @@ -116,6 +118,9 @@ search_filter_enabled: false, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types or manual set registered_slot_[in/out]_types and slot_types_[in/out] search_show_all_on_open: true, // [true!] opens the results list when opening the search widget + show_node_tooltip: false, // [true!] show a tooltip with node property "tooltip" over the selected node + show_node_tooltip_use_descr_property: false, // enabled tooltip from desc when property tooltip not set + auto_load_slot_types: false, // [if want false, use true, run, get vars values to be statically set, than disable] nodes types and nodeclass association with node types need to be calculated, if dont want this, calculate once and set registered_slot_[in/out]_types and slot_types_[in/out] // set these values if not using auto_load_slot_types @@ -126,6 +131,11 @@ slot_types_default_in: [], // specify for each IN slot type a(/many) deafult node(s), use single string, array, or object (with node, title, parameters, ..) like for search slot_types_default_out: [], // specify for each OUT slot type a(/many) deafult node(s), use single string, array, or object (with node, title, parameters, ..) like for search + graphDefaultConfig: { + align_to_grid: true, + links_ontop: false, + }, + alt_drag_do_clone_nodes: false, // [true!] very handy, ALT click to clone and drag the new node do_add_triggers_slots: false, // [true!] will create and connect event slots when using action/events connections, !WILL CHANGE node mode when using onTrigger (enable mode colors), onExecuted does not need this @@ -136,9 +146,62 @@ release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults + two_fingers_opens_menu: false, // [true!] using pointer event isPrimary, when is not simulate right click + pointerevents_method: "mouse", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) + actionHistory_enabled: false, // [true!] cntrlZ, cntrlY + actionHistoryMaxSave: 40, + + /* EXECUTING ACTIONS AFTER UPDATING VALUES - ANCESTORS */ + refreshAncestorsOnTriggers: false, //[true!] + refreshAncestorsOnActions: false, //[true!] + ensureUniqueExecutionAndActionCall: false, //[true!] the new tecnique.. let's make it working best of + /* DO NOT USE THESE + ensureNodeSingleExecution: false, // this will prevent nodes to be executed more than once for step (comparing graph.iteration) + ensureNodeSingleAction: false, // this will prevent nodes to be executed more than once for action call! + preventAncestorRecalculation: false, // when calculating the ancestors, set a flag to prevent recalculate the subtree*/ + + showCanvasOptions: true, // [false!] use with custom implementation + availableCanvasOptions: [ + "highquality_render", + "use_gradients", //set to true to render titlebar with gradients + //",editor_alpha = 1; //used for transition + "pause_rendering", + "clear_background", + + "read_only", //if set to true users cannot modify the graph + //",render_only_selected // refactor: not implemented + "live_mode", + "show_info", + "allow_dragcanvas", + "allow_dragnodes", + "allow_interaction", //allow to control widgets, buttons, collapse, etc + "allow_searchbox", + "move_destination_link_without_shift", //old: allow_reconnect_links //allows to change a connection, no need to hold shift + "set_canvas_dirty_on_mouse_event", //forces to redraw the canvas if the mouse does anything + "always_render_background", + "render_shadows", + "render_canvas_border", + "render_connections_shadows", //too much cpu + "render_connections_border", + "render_curved_connections", + "render_connection_arrows", + "render_collapsed_slots", + "render_execution_order", + "render_title_colored", + "render_link_tooltip", + + //"links_render_mode", //= LiteGraph.SPLINE_LINK; + + // TODO refactor: options object do need refactoring .. all the options are actually outside of it + "autoresize", //= options.autoresize; + "skip_render", //= options.skip_render; + "clip_all_nodes", //= options.clip_all_nodes; + "free_resize" + ], + /** * Register a node class so it can be listed when the user wants to create a new one * @method registerNodeType @@ -260,7 +323,6 @@ } } - // TODO one would want to know input and ouput :: this would allow trought registerNodeAndSlotType to get all the slots types //console.debug("Registering "+type); if (this.auto_load_slot_types) nodeTmp = new base_class(base_class.title || "tmpnode"); }, @@ -454,6 +516,8 @@ if (!node.size) { node.size = node.computeSize(); //call onresize? + }else{ + node.size_basic = node.size; } if (!node.pos) { node.pos = LiteGraph.DEFAULT_POSITION.concat(); @@ -830,6 +894,9 @@ this.vars = {}; this.extra = {}; //to store custom data + // apply default config + this.configApplyDefaults(); + //timing this.globaltime = 0; this.runningtime = 0; @@ -841,8 +908,18 @@ this.catch_errors = true; + // savings + this.history = { actionHistory: [] + ,actionHistoryVersions: [] + ,actionHistoryPtr: 0 + }; + /*this.actionHistory = []; + this.actionHistoryVersions = []; + this.actionHistoryPtr = 0;*/ + this.nodes_executing = []; this.nodes_actioning = []; + this.node_ancestorsCalculated = []; this.nodes_executedAction = []; //subgraph_data @@ -855,6 +932,29 @@ this.sendActionToCanvas("clear"); }; + /** + * Apply config values to LGraph config object + * @method configApply + * @param {object} opts options to merge + */ + LGraph.prototype.configApply = function(opts) { + /* + align_to_grid + links_ontop + */ + this.config = Object.assign(this.config,opts); + } + + /** + * Apply config values to LGraph config object + * @method configApply + * @param {object} opts options to merge + */ + LGraph.prototype.configApplyDefaults = function() { + var opts = LiteGraph.graphDefaultConfig; + this.configApply(opts); + } + /** * Attach Canvas to this graph * @method attachCanvas @@ -862,7 +962,8 @@ */ LGraph.prototype.attachCanvas = function(graphcanvas) { - if (graphcanvas.constructor != LGraphCanvas) { + //if (graphcanvas.constructor != LGraphCanvas) { + if ( ! graphcanvas instanceof LGraphCanvas ) { throw "attachCanvas expects a LGraphCanvas instance"; } if (graphcanvas.graph && graphcanvas.graph != this) { @@ -1022,7 +1123,8 @@ for (var j = 0; j < limit; ++j) { var node = nodes[j]; if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); + //wrap node.onExecute(); + node.doExecute(); } } @@ -1060,6 +1162,7 @@ this.last_update_time = now; this.nodes_executing = []; this.nodes_actioning = []; + this.node_ancestorsCalculated = []; this.nodes_executedAction = []; }; @@ -1225,25 +1328,99 @@ * @method getAncestors * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution */ - LGraph.prototype.getAncestors = function(node) { + LGraph.prototype.getAncestors = function(node, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { modesSkip: [] + ,modesOnly: [] + ,typesSkip: [] + ,typesOnly: [] + }; + var opts = Object.assign(optsDef,optsIn); + var ancestors = []; + var ancestorsIds = []; var pending = [node]; var visited = {}; + //console.log("---getAncestors--- for "+node.id+":"+node.order); + while (pending.length) { + // get next var current = pending.shift(); - if (!current.inputs) { + if (!current) { continue; } - if (!visited[current.id] && current != node) { - visited[current.id] = true; - ancestors.push(current); + //console.log("checking ancestor for "+current.id+":"+current.order); + if (visited[current.id]){ + //console.log("already "+current.id+":"+current.order); + continue; + } + // mark as visited + visited[current.id] = true; + + // add to ancestors + if (current.id != node.id) { + + // mode check + if (opts.modesSkip && opts.modesSkip.length){ + if (opts.modesSkip.indexOf(current.mode) != -1){ + //console.log("mode skip "+current.id+":"+current.order+" :: "+current.mode); + continue; + } + } + if (opts.modesOnly && opts.modesOnly.length){ + if (opts.modesOnly.indexOf(current.mode) == -1){ + //console.log("mode only "+current.id+":"+current.order+" :: "+current.mode); + continue; + } + } + + if (ancestorsIds.indexOf(current.id) == -1) { + ancestors.push(current); + ancestorsIds.push(current.id); + //console.log("push current "+current.id+":"+current.order); + }else{ + //console.log("already push "+current.id+":"+current.order); + } + + }else{ + //console.log("current == node "+current.id+":"+current.order+" -- "+node.id+":"+node.order); + } + + // get its inputs + if (!current.inputs){ + continue; } - for (var i = 0; i < current.inputs.length; ++i) { var input = current.getInputNode(i); - if (input && ancestors.indexOf(input) == -1) { - pending.push(input); + if (!input) continue; + var inputType = current.inputs[i].type; + + // type check + if (opts.typesSkip && opts.typesSkip.length){ + if (opts.typesSkip.indexOf(inputType) != -1){ + //console.log("type skip "+input.id+":"+input.order+" :: "+inputType); + continue; + }else{ + //console.log("type ok? "+input.id+":"+input.order+" :: "+inputType+" : "+opts.typesSkip.indexOf(inputType)); + } + } + if (opts.typesOnly && opts.typesOnly.length){ + if (opts.typesOnly.indexOf(input.mode) == -1){ + //console.log("type only "+input.id+":"+input.order+" :: "+inputType); + continue; + } + } + + //console.log("input "+i+" "+input.id+":"+input.order); + // push em in + if (ancestorsIds.indexOf(input.id) == -1) { + if(!visited[input.id]){ + pending.push(input); + //console.log("push input "+input.id+":"+input.order); + }else{ + //console.log("already input "+input.id+":"+input.order); + } } } } @@ -1385,7 +1562,12 @@ * @param {LGraphNode} node the instance of the node */ - LGraph.prototype.add = function(node, skip_compute_order) { + LGraph.prototype.add = function(node, skip_compute_order, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { doProcessChange: true + ,doCalcSize: true + }; + var opts = Object.assign(optsDef,optsIn); if (!node) { return; } @@ -1396,15 +1578,13 @@ this.setDirtyCanvas(true); this.change(); node.graph = this; - this._version++; + this.onGraphChanged({action: "groupAdd", doSave: opts.doProcessChange}); // this._version++; return; } //nodes if (node.id != -1 && this._nodes_by_id[node.id] != null) { - console.warn( - "LiteGraph: there is already a node with this ID, changing it" - ); + console.warn("LiteGraph: there is already a node with this ID, changing it"); node.id = ++this.last_node_id; } @@ -1420,7 +1600,8 @@ } node.graph = this; - this._version++; + + this.onGraphChanged({action: "nodeAdd", doSave: opts.doProcessChange}); // this._version++; this._nodes.push(node); this._nodes_by_id[node.id] = node; @@ -1441,6 +1622,10 @@ this.onNodeAdded(node); } + if (opts.doCalcSize){ + node.setSize( node.computeSize() ); + } + this.setDirtyCanvas(true); this.change(); @@ -1454,13 +1639,15 @@ */ LGraph.prototype.remove = function(node) { + + // group ? if (node.constructor === LiteGraph.LGraphGroup) { var index = this._groups.indexOf(node); if (index != -1) { this._groups.splice(index, 1); } node.graph = null; - this._version++; + this.onGraphChanged({action: "groupRemove"}); // this._version++; this.setDirtyCanvas(true, true); this.change(); return; @@ -1474,14 +1661,14 @@ return; } //cannot be removed - this.beforeChange(); //sure? - almost sure is wrong + //this.beforeChange(); //sure? - almost sure is wrong //disconnect inputs if (node.inputs) { for (var i = 0; i < node.inputs.length; i++) { var slot = node.inputs[i]; if (slot.link != null) { - node.disconnectInput(i); + node.disconnectInput(i, {doProcessChange: false}); } } } @@ -1491,7 +1678,7 @@ for (var i = 0; i < node.outputs.length; i++) { var slot = node.outputs[i]; if (slot.links != null && slot.links.length) { - node.disconnectOutput(i); + node.disconnectOutput(i, false, {doProcessChange: false}); } } } @@ -1504,7 +1691,7 @@ } node.graph = null; - this._version++; + this.onGraphChanged({action: "nodeRemove"}); // this._version++; //remove from canvas render if (this.list_of_graphcanvas) { @@ -1534,7 +1721,7 @@ this.sendActionToCanvas("checkPanels"); this.setDirtyCanvas(true, true); - this.afterChange(); //sure? - almost sure is wrong + //this.afterChange(); //sure? - almost sure is wrong this.change(); this.updateExecutionOrder(); @@ -1731,7 +1918,7 @@ this.beforeChange(); this.inputs[name] = { name: name, type: type, value: value }; - this._version++; + this.onGraphChanged({action: "addInput"}); // this._version++; this.afterChange(); if (this.onInputAdded) { @@ -1793,7 +1980,7 @@ this.inputs[name] = this.inputs[old_name]; delete this.inputs[old_name]; - this._version++; + this.onGraphChanged({action: "renameInput"}); // this._version++; if (this.onInputRenamed) { this.onInputRenamed(old_name, name); @@ -1824,7 +2011,7 @@ } this.inputs[name].type = type; - this._version++; + this.onGraphChanged({action: "changeInputType"}); // this._version++; if (this.onInputTypeChanged) { this.onInputTypeChanged(name, type); } @@ -1842,7 +2029,7 @@ } delete this.inputs[name]; - this._version++; + this.onGraphChanged({action: "graphRemoveInput"}); // this._version++; if (this.onInputRemoved) { this.onInputRemoved(name); @@ -1863,7 +2050,7 @@ */ LGraph.prototype.addOutput = function(name, type, value) { this.outputs[name] = { name: name, type: type, value: value }; - this._version++; + this.onGraphChanged({action: "addOutput"}); // this._version++; if (this.onOutputAdded) { this.onOutputAdded(name, type); @@ -1920,7 +2107,7 @@ this.outputs[name] = this.outputs[old_name]; delete this.outputs[old_name]; - this._version++; + this.onGraphChanged({action: "renameOutput"}); // this._version++; if (this.onOutputRenamed) { this.onOutputRenamed(old_name, name); @@ -1951,7 +2138,7 @@ } this.outputs[name].type = type; - this._version++; + this.onGraphChanged({action: "changeOutputType"}); // this._version++; if (this.onOutputTypeChanged) { this.onOutputTypeChanged(name, type); } @@ -1967,7 +2154,7 @@ return false; } delete this.outputs[name]; - this._version++; + this.onGraphChanged({action: "removeOutput"}); // this._version++; if (this.onOutputRemoved) { this.onOutputRemoved(name); @@ -2014,7 +2201,7 @@ if (this.onConnectionChange) { this.onConnectionChange(node); } - this._version++; + this.onGraphChanged({action: "connectionChange", doSave: false}); // this._version++; this.sendActionToCanvas("onConnectionChange"); }; @@ -2068,19 +2255,201 @@ this.sendActionToCanvas("setDirty", [fg, bg]); }; + /** + * Ment to serve the history-saving mechanism + * @method onGraphSaved + * @param {object} optsIn options + */ + LGraph.prototype.onGraphSaved = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + this.savedVersion = this._version; + }; + + /** + * Ment to serve the history-saving mechanism + * @method onGraphSaved + * @param {object} optsIn options + */ + LGraph.prototype.onGraphLoaded = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + this.savedVersion = this._version; + }; + + /** + * Ment to be the history and prevent-exit mechanism, call to change _version + * @method onGraphChanged + * @param {object} optsIn options + */ + LGraph.prototype.onGraphChanged = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { action: "" + ,doSave: true + ,doSaveGraph: true + }; + var opts = Object.assign(optsDef,optsIn); + + this._version++; + + if(opts.action){ + console.debug("ACT: "+opts.action); + }else{ + console.debug("ACT_noAction: "+opts); + } + if(opts.doSave){ + console.debug("onGraphChanged SAVE :: "+opts.action); // debug history + } + + if(opts.doSave && LiteGraph.actionHistory_enabled){ + + var oHistory = { actionName: opts.action }; + if(opts.doSaveGraph){ + oHistory = Object.assign(oHistory + ,{ graphSave: this.serialize() // this is a heavy method, but the alternative is way more complex: every action has to have its contrary + }); + } + + var obH = this.history; + + // check if pointer has gone back: remove newest + while(obH.actionHistoryPtr < obH.actionHistoryVersions.length-1){ + console.debug("popping: gone back? "+(obH.actionHistoryPtr+" < "+(obH.actionHistoryVersions.length-1))); // debug history + obH.actionHistoryVersions.pop(); + } + // check if maximum saves + if(obH.actionHistoryVersions.length>=LiteGraph.actionHistoryMaxSave){ + var olderSave = obH.actionHistoryVersions.shift(); + console.debug("maximum saves reached: "+obH.actionHistoryVersions.length+", remove older: "+olderSave); // debug history + obH.actionHistory[olderSave] = false; // unset + } + + // update pointer + obH.actionHistoryPtr = obH.actionHistoryVersions.length; + obH.actionHistoryVersions.push(obH.actionHistoryPtr); + + // save to pointer + obH.actionHistory[obH.actionHistoryPtr] = oHistory; + + console.debug("history saved: "+obH.actionHistoryPtr,oHistory.actionName); // debug history + + }else{ + // console.debug("action dont save"); + } + + }; + + /** + * Go back in action history + * @method actionHistoryBack + * @param {object} optsIn options + */ + LGraph.prototype.actionHistoryBack = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + var obH = this.history; + + if (obH.actionHistoryPtr != undefined && obH.actionHistoryPtr >= 0){ + obH.actionHistoryPtr--; + console.debug("history step back: "+obH.actionHistoryPtr); // debug history + if (!this.actionHistoryLoad({iVersion: obH.actionHistoryPtr})){ + console.warn("historyLoad failed, restore pointer? "+obH.actionHistoryPtr); // debug history + // history not found? + obH.actionHistoryPtr++; + return false; + }else{ + //console.debug("history loaded back: "+obH.actionHistoryPtr); // debug history + //console.debug(this.history); + return true; + } + }else{ + console.debug("history is already at older state"); + return false; + } + }; + + /** + * Go forward in action history + * @method actionHistoryForward + * @param {object} optsIn options + */ + LGraph.prototype.actionHistoryForward = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { + }; + var opts = Object.assign(optsDef,optsIn); + + var obH = this.history; + + if (obH.actionHistoryPtr= this.graph.iteration && this.exec_version !== undefined){ + //console.debug("!! NODE already EXECUTED THIS STEP !! "+this.exec_version); + return; + } + //console.debug("Actioned ? "+this.id+":"+this.order+" :: "+this.action_call); + if (LiteGraph.ensureUniqueExecutionAndActionCall){ + //if(this.action_call && options && options.action_call && this.action_call == options.action_call){ + if(this.graph.nodes_executedAction[this.id] && options && options.action_call && this.graph.nodes_executedAction[this.id] == options.action_call){ + //console.debug("!! NODE already ACTION THIS STEP !! "+options.action_call); + return; + } + } + this.graph.nodes_executing[this.id] = true; //.push(this.id); this.onExecute(param, options); + //console.debug(this.graph.nodes_executing.pop()+" << pop"); this.graph.nodes_executing[this.id] = false; //.pop(); // save execution/action ref @@ -3133,6 +3620,8 @@ this.execute_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); // callback }; + // retrocompatibility + LGraphNode.prototype.execute = LGraphNode.prototype.doExecute; /** * Triggers an action, wrapped by logics to control execution flow @@ -3147,10 +3636,25 @@ // enable this to give the event an ID if (!options.action_call) options.action_call = this.id+"_"+(action?action:"action")+"_"+Math.floor(Math.random()*9999); + if (LiteGraph.ensureNodeSingleAction){ + if (this.graph.nodes_actioning && this.graph.nodes_actioning[this.id] == options.action_call){ // == action){ + //console.debug("NODE already actioning! Prevent! "+this.id+":"+this.order+" :: "+options.action_call); + return; + } + } + //console.debug("Actioned ? "+this.id+":"+this.order+" :: "+this.action_call); + if (LiteGraph.ensureUniqueExecutionAndActionCall){ + //if(this.action_call && options && options.action_call && this.action_call == options.action_call){ + if(this.graph.nodes_executedAction[this.id] && options && options.action_call && this.graph.nodes_executedAction[this.id] == options.action_call){ + //console.debug("!! NODE already ACTION THIS STEP !! "+options.action_call); + return; + } + } this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); this.onAction(action, param, options); + //console.debug(this.graph.nodes_actioning.pop()+" << pop"); this.graph.nodes_actioning[this.id] = false; //.pop(); // save execution/action ref @@ -3207,7 +3711,13 @@ if (!links || !links.length) { return; } - + + // check for ancestors calls + if (this.graph && this.graph.ancestorsCall){ + //console.debug("ancestors call, prevent triggering slot "+slot+" on "+this.id+":"+this.order); + return; + } + if (this.graph) { this.graph._last_trigger_time = LiteGraph.getTime(); } @@ -3238,16 +3748,22 @@ { // generate unique trigger ID if not present if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); + if (LiteGraph.refreshAncestorsOnTriggers) node.refreshAncestors({action: "trigger", param: param, options: options}); if (node.onExecute) { // -- wrapping node.onExecute(param); -- node.doExecute(param, options); } - } - else if (node.onAction) { + /*else if (node.mode === LiteGraph.ON_EVENT) + { + // this probably expect to have onAction be SET + }*/ + }else if (node.onAction) { // generate unique action ID if not present if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); //pass the action name var target_connection = node.inputs[link_info.target_slot]; + //console.debug("ACTION: "+this.id+":"+this.order+" :: "+target_connection.name); + if (LiteGraph.refreshAncestorsOnActions) node.refreshAncestors({action: target_connection.name, param: param, options:options}); // wrap node.onAction(target_connection.name, param); node.actionDo(target_connection.name, param, options); } @@ -3494,8 +4010,8 @@ * @method removeInput * @param {number} slot */ - LGraphNode.prototype.removeInput = function(slot) { - this.disconnectInput(slot); + LGraphNode.prototype.removeInput = function(slot) { /* , optsIn */ + this.disconnectInput(slot); /* , optsIn */ var slot_info = this.inputs.splice(slot, 1); for (var i = slot; i < this.inputs.length; ++i) { if (!this.inputs[i]) { @@ -4032,6 +4548,7 @@ ,generalTypeInCase: true }; var opts = Object.assign(optsDef,optsIn); + target_slotType = target_slotType || "*"; if (target_node && target_node.constructor === Number) { target_node = this.graph.getNodeById(target_node); } @@ -4055,7 +4572,7 @@ } } // connect to the first free input slot if not found a specific type and this output is general - if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "")){ + if (opts.firstFreeIfOutputGeneralInCase && (target_slotType == 0 || target_slotType == "*" || target_slotType == "" || target_slotType == "undefined")){ var target_slot = target_node.findInputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); //console.debug("connect TO TheFirstFREE ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); if (target_slot >= 0){ @@ -4085,6 +4602,7 @@ ,generalTypeInCase: true }; var opts = Object.assign(optsDef,optsIn); + source_slotType = source_slotType || "*"; if (source_node && source_node.constructor === Number) { source_node = this.graph.getNodeById(source_node); } @@ -4110,7 +4628,7 @@ } } // connect to the first free output slot if not found a specific type and this input is general - if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "")){ + if (opts.firstFreeIfInputGeneralInCase && (source_slotType == 0 || source_slotType == "*" || source_slotType == "" || source_slotType == "undefined")){ var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); if (source_slot >= 0){ return source_node.connect(source_slot, this, slot); @@ -4251,7 +4769,7 @@ target_node.disconnectInput(target_slot, {doProcessChange: false}); changed = true; } - if (output.links !== null && output.links.length){ + if (output.links && output.links.length){ switch(output.type){ case LiteGraph.EVENT: if (!LiteGraph.allow_multi_output_for_events){ @@ -4285,9 +4803,7 @@ output.links.push(link_info.id); //connect in input target_node.inputs[target_slot].link = link_info.id; - if (this.graph) { - this.graph._version++; - } + if (this.onConnectionsChange) { this.onConnectionsChange( LiteGraph.OUTPUT, @@ -4323,6 +4839,8 @@ ); } + this.graph.onGraphChanged({action: "connect"}); //this.graph._version++; + this.setDirtyCanvas(false, true); this.graph.afterChange(); this.graph.connectionChange(this, link_info); @@ -4337,7 +4855,10 @@ * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected] * @return {boolean} if it was disconnected successfully */ - LGraphNode.prototype.disconnectOutput = function(slot, target_node) { + LGraphNode.prototype.disconnectOutput = function(slot, target_node, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { doProcessChange: true }; + var opts = Object.assign(optsDef,optsIn); if (slot.constructor === String) { slot = this.findOutputSlot(slot); if (slot == -1) { @@ -4379,7 +4900,7 @@ input.link = null; //remove there delete this.graph.links[link_id]; //remove the link from the links pool if (this.graph) { - this.graph._version++; + this.graph.onGraphChanged({action: "disconnectOutput", doSave: opts.doProcessChange}); //this.graph._version++; } if (target_node.onConnectionsChange) { target_node.onConnectionsChange( @@ -4434,7 +4955,7 @@ var target_node = this.graph.getNodeById(link_info.target_id); var input = null; if (this.graph) { - this.graph._version++; + this.graph.onGraphChanged({action: "disconnectOutput", doSave: opts.doProcessChange}); //this.graph._version++; } if (target_node) { input = target_node.inputs[link_info.target_slot]; @@ -4493,7 +5014,10 @@ * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) * @return {boolean} if it was disconnected successfully */ - LGraphNode.prototype.disconnectInput = function(slot) { + LGraphNode.prototype.disconnectInput = function(slot, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { doProcessChange: true }; + var opts = Object.assign(optsDef,optsIn); //seek for the output slot if (slot.constructor === String) { slot = this.findInputSlot(slot); @@ -4543,7 +5067,7 @@ delete this.graph.links[link_id]; //remove from the pool if (this.graph) { - this.graph._version++; + this.graph.onGraphChanged({action: "disconnectInput", doSave: opts.doProcessChange}); //this.graph._version++; } if (this.onConnectionsChange) { this.onConnectionsChange( @@ -4791,7 +5315,7 @@ LGraphNode.prototype.executeAction = function(action) * @method collapse **/ LGraphNode.prototype.collapse = function(force) { - this.graph._version++; + this.graph.onGraphChanged({action: "collapse"}); //this.graph._version++; if (this.constructor.collapsable === false && !force) { return; } @@ -4809,7 +5333,7 @@ LGraphNode.prototype.executeAction = function(action) **/ LGraphNode.prototype.pin = function(v) { - this.graph._version++; + this.graph.onGraphChanged({action: "pin"}); //this.graph._version++; if (v === undefined) { this.flags.pinned = !this.flags.pinned; } else { @@ -5160,7 +5684,11 @@ LGraphNode.prototype.executeAction = function(action) * @param {Object} options [optional] { skip_rendering, autoresize, viewport } */ function LGraphCanvas(canvas, graph, options) { - this.options = options = options || {}; + options = options || { skip_render: false + ,autoresize: false + ,clip_all_nodes: false + }; + this.options = options; //if(graph === undefined) // throw ("No graph assigned"); @@ -5195,6 +5723,13 @@ LGraphNode.prototype.executeAction = function(action) boolean: "#744",*/ }; + this.drag_mode = false; // never used ? + this.dragging_rectangle = null; + + this.filter = null; //allows to filter to only accept some type of nodes in a graph + + // options + // this.allow_addOutSlot_onExecuted = true; ::: using LiteGraph.do_add_triggers_slots instead this.highquality_render = true; this.use_gradients = false; //set to true to render titlebar with gradients this.editor_alpha = 1; //used for transition @@ -5202,21 +5737,14 @@ LGraphNode.prototype.executeAction = function(action) this.clear_background = true; this.read_only = false; //if set to true users cannot modify the graph - this.render_only_selected = true; + //this.render_only_selected = true; // refactor: not implemented this.live_mode = false; this.show_info = true; this.allow_dragcanvas = true; this.allow_dragnodes = true; this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc this.allow_searchbox = true; - this.allow_reconnect_links = false; //allows to change a connection with having to redo it again - this.align_to_grid = false; //snap to grid - - this.drag_mode = false; - this.dragging_rectangle = null; - - this.filter = null; //allows to filter to only accept some type of nodes in a graph - + this.move_destination_link_without_shift = false; //old: allow_reconnect_links //allows to change a connection, no need to hold shift 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; @@ -5231,7 +5759,13 @@ LGraphNode.prototype.executeAction = function(action) this.render_link_tooltip = true; this.links_render_mode = LiteGraph.SPLINE_LINK; - + + // TODO refactor: options object do need refactoring .. all the options are actually outside of it + this.autoresize = options.autoresize; + this.skip_render = options.skip_render; + this.clip_all_nodes = options.clip_all_nodes; + this.free_resize = options.free_resize; + 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 @@ -5272,11 +5806,10 @@ LGraphNode.prototype.executeAction = function(action) this.setCanvas(canvas,options.skip_events); this.clear(); - if (!options.skip_render) { + if (!this.skip_render && !options.skip_render) { this.startRendering(); } - this.autoresize = options.autoresize; } global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas; @@ -5286,7 +5819,10 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.link_type_colors = { "-1": LiteGraph.EVENT_LINK_COLOR, number: "#AAA", - node: "#DCA" + node: "#DCA", + string: "#77F", + boolean: "#F77" + // TODO make options for colors }; LGraphCanvas.gradients = {}; //cache of gradients @@ -5427,9 +5963,9 @@ LGraphNode.prototype.executeAction = function(action) this.centerOnNode(subgraph_node); this.selectNodes([subgraph_node]); } - // when close sub graph back to offset [0, 0] scale 1 - this.ds.offset = [0, 0] - this.ds.scale = 1 + // when close sub graph back to offset [0, 0] scale 1 + this.ds.offset = [0, 0]; + this.ds.scale = 1; }; /** @@ -5542,6 +6078,8 @@ LGraphNode.prototype.executeAction = function(action) var canvas = this.canvas; + canvas.lgraphcanvas = this; // place a pointer in the dom element to get the LGraphCanvas related + var ref_window = this.getCanvasWindow(); var document = ref_window.document; //hack used when moving canvas between windows @@ -5758,6 +6296,59 @@ LGraphNode.prototype.executeAction = function(action) this.last_mouseclick = 0; } + LGraphCanvas.prototype.processUserInputDown = function(e){ + + if (this.pointer_is_down && e.isPrimary!==undefined && !e.isPrimary){ + this.userInput_isNotPrimary = true; + //console.log("pointerevents: userInput_isNotPrimary start"); + }else{ + this.userInput_isNotPrimary = false; + } + + this.userInput_type = e.pointerType?e.pointerType:false; + this.userInput_id = e.pointerId?e.pointerId:false; + + if (e.pointerType){ + switch (e.pointerType) { + case "mouse": + break; + case "pen": + break; + case "touch": + + break; + default: + console.log("pointerType unknown " + ev.pointerType); + } + } + + if (e.button !== undefined){ + this.userInput_button = e.button; + //console.debug("input button ",e.button); + switch(e.button){ + case -1: // no changes since last event + case 0: // Left Mouse, Touch Contact, Pen contact + case 1: // Middle Mouse + case 2: // Right Mouse, Pen barrel button + case 3: // X1 (back) Mouse + case 4: // X2 (forward) Mouse + case 5: // Pen eraser button + default: // ?? move without touches + } + } + if (e.buttons !== undefined){ + this.userInput_button_s = e.buttons; + //console.debug("input button_S ",e.buttons); + } + + this.userInput_touches = (e.changedTouches!==undefined && e.changedTouches.length!==undefined) ? e.changedTouches : false; + if (this.userInput_touches && this.userInput_touches.length){ + console.debug("multiTouches",e.changedTouches); + } + + return this.processMouseDown(e); + } + LGraphCanvas.prototype.processMouseDown = function(e) { if( this.set_canvas_dirty_on_mouse_event ) @@ -5798,21 +6389,14 @@ LGraphNode.prototype.executeAction = function(action) var skip_dragging = false; var skip_action = false; var now = LiteGraph.getTime(); - var is_double_click = (now - this.last_mouseclick < 300) && (e.isPrimary!==undefined && e.isPrimary); + var is_double_click = (now - this.last_mouseclick < 300) && (e.isPrimary==undefined || e.isPrimary); this.mouse[0] = e.clientX; this.mouse[1] = e.clientY; this.graph_mouse[0] = e.canvasX; this.graph_mouse[1] = e.canvasY; this.last_click_position = [this.mouse[0],this.mouse[1]]; - if (this.pointer_is_down && e.isPrimary!==undefined && !e.isPrimary){ - this.pointer_is_double = true; - //console.log("pointerevents: pointer_is_double start"); - }else{ - this.pointer_is_double = false; - } this.pointer_is_down = true; - this.canvas.focus(); @@ -5825,7 +6409,7 @@ LGraphNode.prototype.executeAction = function(action) } //left button mouse / single finger - if (e.which == 1 && !this.pointer_is_double) + if (e.which == 1 && !this.userInput_isNotPrimary) { if (e.ctrlKey) { @@ -5972,8 +6556,8 @@ LGraphNode.prototype.executeAction = function(action) } if ( - this.allow_reconnect_links || - //this.move_destination_link_without_shift || + //this.allow_reconnect_links || + this.move_destination_link_without_shift || e.shiftKey ) { if (!LiteGraph.click_do_break_link_to){ @@ -6113,6 +6697,8 @@ LGraphNode.prototype.executeAction = function(action) this.showSearchBox(e); e.preventDefault(); e.stopPropagation(); + }else{ + //if (is_double_click) console.log("Dont search"); } clicking_canvas_bg = true; @@ -6189,7 +6775,7 @@ LGraphNode.prototype.executeAction = function(action) } } - } else if (e.which == 3 || this.pointer_is_double) { + } else if (e.which == 3 || (LiteGraph.two_fingers_opens_menu && this.userInput_isNotPrimary)) { // TODO check for fingers number //right button if (this.allow_interaction && !skip_action && !this.read_only){ @@ -6486,7 +7072,7 @@ LGraphNode.prototype.executeAction = function(action) if (this.resizing_node && !this.live_mode) { //convert mouse to node space var desired_size = [ e.canvasX - this.resizing_node.pos[0], e.canvasY - this.resizing_node.pos[1] ]; - var min_size = this.resizing_node.computeSize(); + var min_size = this.free_resize?LiteGraph.NODE_MIN_SIZE:this.resizing_node.computeSize(); //this.resizing_node.size_basic;// .computeSize(); desired_size[0] = Math.max( min_size[0], desired_size[0] ); desired_size[1] = Math.max( min_size[1], desired_size[1] ); this.resizing_node.setSize( desired_size ); @@ -6551,13 +7137,13 @@ LGraphNode.prototype.executeAction = function(action) //console.log("pointerevents: processMouseUp which: "+e.which); if (e.which == 1) { + //left button if( this.node_widget ) { this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); } - //left button this.node_widget = null; if (this.selected_group) { @@ -6643,6 +7229,12 @@ LGraphNode.prototype.executeAction = function(action) var connInOrOut = this.connecting_output || this.connecting_input; var connType = connInOrOut.type; + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + //node below mouse if (node) { @@ -6749,6 +7341,9 @@ LGraphNode.prototype.executeAction = function(action) } if( this.onNodeMoved ) this.onNodeMoved( this.node_dragged ); + + this.graph.onGraphChanged({action: "nodeDrag", doSave: true}); + this.graph.afterChange(this.node_dragged); this.node_dragged = null; } //no node being dragged @@ -7022,6 +7617,18 @@ LGraphNode.prototype.executeAction = function(action) //collapse //... + // control Z, control Y, ctlrZ, ctlrY + if (LiteGraph.actionHistory_enabled){ + if (e.keyCode == 89 && e.ctrlKey || (e.keyCode == 90 && e.ctrlKey && e.shiftKey)){ + // Y + this.graph.actionHistoryForward(); + }else if (e.keyCode == 90 && e.ctrlKey){ + // Z + this.graph.actionHistoryBack(); + } + } + // console.debug("keydown "+e.keyCode); // atlasan debug REMOVE + //TODO if (this.selected_nodes) { for (var i in this.selected_nodes) { @@ -7168,6 +7775,8 @@ LGraphNode.prototype.executeAction = function(action) this.selectNodes(nodes); + this.graph.onGraphChanged({action: "paste", doSave: true}); + this.graph.afterChange(); }; @@ -7261,10 +7870,11 @@ LGraphNode.prototype.executeAction = function(action) this.graph.beforeChange(); var node = LiteGraph.createNode(nodetype.type); node.pos = [e.canvasX, e.canvasY]; - this.graph.add(node); + this.graph.add(node, false, {doProcessChange: false}); if (node.onDropFile) { node.onDropFile(file); } + this.graph.onGraphChanged({action: "fileDrop", doSave: true}); this.graph.afterChange(); } } @@ -7456,6 +8066,15 @@ LGraphNode.prototype.executeAction = function(action) this.graph.afterChange(); }; + /** + * connect TWO nodes looking for matching types + * @method autoConnectNodes + **/ + // TODO implement: use connectByType (check all matching input-outputs?) + /*LGraphCanvas.prototype.autoConnectNodes = function(input_node, output_node){ + + }*/ + /** * centers the camera on a given node * @method centerOnNode @@ -7964,7 +8583,7 @@ LGraphNode.prototype.executeAction = function(action) this.graph.beforeChange(); var newnode = LiteGraph.createNode(type); if (newnode) { - subgraph.add(newnode); + subgraph.add(newnode, false, {doProcessChange: false} ); // TODO CHECK THIS this.block_click = false; this.last_click_position = null; this.selectNodes([newnode]); @@ -8034,7 +8653,7 @@ LGraphNode.prototype.executeAction = function(action) this.graph.beforeChange(); var newnode = LiteGraph.createNode(type); if (newnode) { - subgraph.add(newnode); + subgraph.add(newnode, false, {doProcessChange: false} ); // TODO CHECK THIS this.block_click = false; this.last_click_position = null; this.selectNodes([newnode]); @@ -8388,20 +9007,22 @@ LGraphNode.prototype.executeAction = function(action) } } - if (node.clip_area) { + if (node.clip_area || this.clip_all_nodes){ //Start clipping ctx.save(); ctx.beginPath(); if (shape == LiteGraph.BOX_SHAPE) { ctx.rect(0, 0, size[0], size[1]); + // TODO CHECK THIS :: ctx.rect(-6.5, -6.5-LiteGraph.NODE_TITLE_HEIGHT, size[0]+14, size[1]+13+LiteGraph.NODE_TITLE_HEIGHT); } else if (shape == LiteGraph.ROUND_SHAPE) { ctx.roundRect(0, 0, size[0], size[1], [10]); + // TODO CHECK THIS :: ctx.roundRect(-6.5, -6.5, size[0]+14, size[1]+13, 10); } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( size[0] * 0.5, size[1] * 0.5, size[0] * 0.5, - 0, + 0, // TODO CHECK THIS 0 + 6.5, Math.PI * 2 ); } @@ -8427,7 +9048,15 @@ LGraphNode.prototype.executeAction = function(action) if (node.onDrawForeground) { node.onDrawForeground(ctx, this, this.canvas); } - + + // node tooltip + if ( LiteGraph.show_node_tooltip + && node.mouseOver + && (node.is_selected && (!this.selected_nodes || Object.keys(this.selected_nodes).length <= 1)) + ){ + this.drawNodeTooltip(ctx,node); + } + //connection slots ctx.textAlign = horizontal ? "center" : "left"; ctx.font = this.inner_text_font; @@ -8476,15 +9105,19 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); + // TODO : changing this here is kind of dirty? place in addInput-addOutput instead? if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? + slot_shape = LiteGraph.GRID_SHAPE; + }else if (slot.name == "onTrigger" || slot.name == "onExecuted"){ + slot_shape = LiteGraph.ARROW_SHAPE; + }else if(slot_type === LiteGraph.EVENT){ + slot_shape = LiteGraph.BOX_SHAPE; } var doStroke = true; if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE + slot_shape === LiteGraph.BOX_SHAPE ) { if (horizontal) { ctx.rect( @@ -8526,7 +9159,9 @@ LGraphNode.prototype.executeAction = function(action) ctx.fill(); //render name - if (render_text) { + if (render_text + && !(slot.name == "onTrigger" || slot.name == "onExecuted") + ) { var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; @@ -8575,14 +9210,18 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); //ctx.rect( node.size[0] - 14,i*14,10,10); + // TODO : changing this here is kind of dirty? place in addInput-addOutput instead? if (slot_type == "array"){ slot_shape = LiteGraph.GRID_SHAPE; + }else if (slot.name == "onTrigger" || slot.name == "onExecuted"){ + slot_shape = LiteGraph.ARROW_SHAPE; + }else if(slot_type === LiteGraph.EVENT){ + slot_shape = LiteGraph.BOX_SHAPE; } var doStroke = true; if ( - slot_type === LiteGraph.EVENT || slot_shape === LiteGraph.BOX_SHAPE ) { if (horizontal) { @@ -8633,7 +9272,9 @@ LGraphNode.prototype.executeAction = function(action) ctx.stroke(); //render output name - if (render_text) { + if (render_text + && !(slot.name == "onTrigger" || slot.name == "onExecuted") + ) { var text = slot.label != null ? slot.label : slot.name; if (text) { ctx.fillStyle = LiteGraph.NODE_TEXT_COLOR; @@ -8745,13 +9386,70 @@ LGraphNode.prototype.executeAction = function(action) } } - if (node.clip_area) { + if (node.clip_area || this.clip_all_nodes){ ctx.restore(); } ctx.globalAlpha = 1.0; }; + LGraphCanvas.prototype.drawNodeTooltip = function( ctx, node ) + { + if(!node || !ctx){ + return; + } + var text = node.properties.tooltip!=undefined?node.properties.tooltip:""; + if (!text || text==""){ + if (LiteGraph.show_node_tooltip_use_descr_property && node.constructor.desc){ + text = node.constructor.desc; + } + } + text = (text+"").trim(); + if(!text || text == ""){ + return; + } + + var pos = [0,-LiteGraph.NODE_TITLE_HEIGHT]; //node.pos; + //text = text.substr(0,30); //avoid weird + //text = text + "\n" + text; + var size = node.flags.collapsed? [LiteGraph.NODE_COLLAPSED_WIDTH, LiteGraph.NODE_TITLE_HEIGHT] : node.size; + + // using a trick to save the calculated height of the tip the first time using trasparent, to than show it + // node.ttip_oTMultiRet is not set or false the first time + + ctx.font = "14px Courier New"; + var info = ctx.measureText(text); + var w = Math.max(node.size[0],160) + 20; //info.width + 20; + var h = node.ttip_oTMultiRet ? node.ttip_oTMultiRet.height + 15 : 21; + + ctx.globalAlpha = 0.7 * this.editor_alpha; + + ctx.shadowColor = node.ttip_oTMultiRet?"black":"transparent"; + ctx.shadowOffsetX = 2; + ctx.shadowOffsetY = 2; + ctx.shadowBlur = 3; + ctx.fillStyle = node.ttip_oTMultiRet?"#454":"transparent"; + ctx.beginPath(); + + ctx.roundRect( pos[0] - w*0.5 + size[0]/2, pos[1] - 15 - h, w, h, [3]); + ctx.moveTo( pos[0] - 10 + size[0]/2, pos[1] - 15 ); + ctx.lineTo( pos[0] + 10 + size[0]/2, pos[1] - 15 ); + ctx.lineTo( pos[0] + size[0]/2, pos[1] - 5 ); + ctx.fill(); + ctx.shadowColor = "transparent"; + ctx.textAlign = "center"; + ctx.fillStyle = node.ttip_oTMultiRet?"#CEC":"transparent"; + + ctx.globalAlpha = this.editor_alpha; + + // ctx.fillText(text, pos[0] + size[0]/2, pos[1] - 15 - h * 0.3); + var oTMultiRet = LiteGraph.canvasFillTextMultiline(ctx, text, pos[0] + size[0]/2, pos[1] - (h), w, 14); + + node.ttip_oTMultiRet = oTMultiRet; + + ctx.closePath(); + } + //used by this.over_link_center LGraphCanvas.prototype.drawLinkTooltip = function( ctx, link ) { @@ -10023,8 +10721,8 @@ LGraphNode.prototype.executeAction = function(action) } break; default: - if (w.mouse) { - this.dirty_canvas = w.mouse(event, [x, y], node); + if (w.mouse) { // could have a better name this widget callback, right? + this.dirty_canvas = w.mouse(event, [x, y], node); // should update only with true right? (false would eventually not make it dirty?) } break; } //end switch @@ -10364,6 +11062,11 @@ LGraphNode.prototype.executeAction = function(action) var retEntries = node.onMenuNodeInputs(entries); if(retEntries) entries = retEntries; } + if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted + if (node.findInputSlot("onTrigger") == -1){ + entries.push({content: "On Trigger", value: ["onTrigger", LiteGraph.EVENT, {nameLocked: true, removable: true}], className: "event"}); //, opts: {} + } + } if (!entries.length) { console.log("no input entries"); @@ -10392,8 +11095,11 @@ LGraphNode.prototype.executeAction = function(action) if (v.value) { node.graph.beforeChange(); - node.addInput(v.value[0], v.value[1], v.value[2]); - + + var slotOpts = {}; // TODO CHECK THIS :: can be removed: removabled:true? .. optional: true? + if (v.value[2]) slotOpts = Object.assign(slotOpts, v.value[2]); + + node.addInput(v.value[0], v.value[1], slotOpts); if (node.onNodeInputAdd) { // callback to the node when adding a slot node.onNodeInputAdd(v.value); } @@ -10462,7 +11168,7 @@ LGraphNode.prototype.executeAction = function(action) } if (LiteGraph.do_add_triggers_slots){ //canvas.allow_addOutSlot_onExecuted if (node.findOutputSlot("onExecuted") == -1){ - entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true}], className: "event"}); //, opts: {} + entries.push({content: "On Executed", value: ["onExecuted", LiteGraph.EVENT, {nameLocked: true, removable: true}], className: "event"}); //, opts: {} } } // add callback for modifing the menu elements onMenuNodeOutputs @@ -10519,9 +11225,12 @@ LGraphNode.prototype.executeAction = function(action) return false; } else { node.graph.beforeChange(); - node.addOutput(v.value[0], v.value[1], v.value[2]); - - if (node.onNodeOutputAdd) { // a callback to the node when adding a slot + + var slotOpts = {}; // TODO CHECK THIS :: can be removed: removabled:true? .. optional: true? + if (v.value[2]) slotOpts = Object.assign(slotOpts, v.value[2]); + + node.addOutput(v.value[0], v.value[1], slotOpts); + if (node.onNodeOutputAdd) { // callback to the node when adding a slot node.onNodeOutputAdd(v.value); } node.setDirtyCanvas(true, true); @@ -11170,6 +11879,7 @@ LGraphNode.prototype.executeAction = function(action) ,hide_on_mouse_leave: LiteGraph.search_hide_on_mouse_leave ,show_all_if_empty: true ,show_all_on_open: LiteGraph.search_show_all_on_open + ,show_close_button: true }; options = Object.assign(def_options, options || {}); @@ -11188,6 +11898,9 @@ LGraphNode.prototype.executeAction = function(action) dialog.innerHTML += ""; dialog.innerHTML += ""; } + if(options.show_close_button){ + dialog.innerHTML += ""; + } dialog.innerHTML += "
"; if( root_document.fullscreenElement ) @@ -11365,17 +12078,26 @@ LGraphNode.prototype.executeAction = function(action) } } + if(options.show_close_button){ + var button = dialog.querySelector(".close"); + button.addEventListener("click", dialog.close); + } + //compute best position var rect = canvas.getBoundingClientRect(); + //To avoid out of screen problems var left = ( event ? event.clientX : (rect.left + rect.width * 0.5) ) - 80; var top = ( event ? event.clientY : (rect.top + rect.height * 0.5) ) - 20; + if (rect.width - left < 470) left = rect.width - 470; + if (rect.height - top < 220) top = rect.height - 220; + if (left < rect.left + 20) left = rect.left + 20; + if (top < rect.top + 20) top = rect.top + 20; dialog.style.left = left + "px"; dialog.style.top = top + "px"; - //To avoid out of screen problems - if(event.layerY > (rect.height - 200)) - helper.style.maxHeight = (rect.height - event.layerY - 20) + "px"; + /*if(event.layerY > (rect.height - 200)) + helper.style.maxHeight = (rect.height - event.layerY - 20) + "px";*/ /* var offsetx = -20; @@ -11414,7 +12136,7 @@ LGraphNode.prototype.executeAction = function(action) node.pos = graphcanvas.convertEventToCanvasOffset( event ); - graphcanvas.graph.add(node, false); + graphcanvas.graph.add(node, false, {doProcessChange: false}); } if (extra && extra.data) { @@ -11450,6 +12172,8 @@ LGraphNode.prototype.executeAction = function(action) } + graphcanvas.graph.onGraphChanged({action: "nodeAdd", doSave: true}); + // join node after inserting if (options.node_from){ var iS = false; @@ -11889,7 +12613,7 @@ LGraphNode.prototype.executeAction = function(action) this.canvas.parentNode.appendChild(dialog); - // acheck for input and use default behaviour: save on enter, close on esc + // check for input and use default behaviour: save on enter, close on esc if (options.checkForInput){ var aI = []; var focused = false; @@ -12808,11 +13532,7 @@ LGraphNode.prototype.executeAction = function(action) brown: { color: "#332922", bgcolor: "#593930", groupcolor: "#b06634" }, green: { color: "#232", bgcolor: "#353", groupcolor: "#8A8" }, blue: { color: "#223", bgcolor: "#335", groupcolor: "#88A" }, - pale_blue: { - color: "#2a363b", - bgcolor: "#3f5159", - groupcolor: "#3f789e" - }, + pale_blue: { color: "#2a363b", bgcolor: "#3f5159", groupcolor: "#3f789e" }, cyan: { color: "#233", bgcolor: "#355", groupcolor: "#8AA" }, purple: { color: "#323", bgcolor: "#535", groupcolor: "#a1309b" }, yellow: { color: "#432", bgcolor: "#653", groupcolor: "#b58b2a" }, @@ -12832,12 +13552,12 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuAdd }, { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - //{ content: "Arrange", callback: that.graph.arrange }, - //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } + { content: "Arrange", callback: that.graph.arrange }, + {content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; - /*if (LiteGraph.showCanvasOptions){ + /*atlasan*/if (LiteGraph.showCanvasOptions){ options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); - }*/ + }/**/ if (this._graph_stack && this._graph_stack.length > 0) { options.push(null, { @@ -12931,6 +13651,7 @@ LGraphNode.prototype.executeAction = function(action) options[1].disabled = false; } } + if (LiteGraph.do_add_triggers_slots) options[1].disabled = false; //this.allow_addOutSlot_onExecuted if (node.getExtraMenuOptions) { var extra = node.getExtraMenuOptions(this, options); @@ -13034,16 +13755,36 @@ LGraphNode.prototype.executeAction = function(action) if (!_slot.nameLocked){ menu_info.push({ content: "Rename Slot", slot: slot }); } + /*if (LiteGraph.canRemoveSlots){ + if (_slot.optional || !LiteGraph.canRemoveSlots_onlyOptional){ + menu_info.push( + _slot.locked + ? "Cannot remove" + : { content: "Remove Slot", slot: slot } + ); + } + } + if (LiteGraph.canRenameSlots){ + if (_slot.optional || !LiteGraph.canRenameSlots_onlyOptional){ + menu_info.push( + _slot.nameLocked + ? "Cannot rename" + : { content: "Rename Slot", slot: slot } + ); + } + }*/ } - options.title = - (slot.input ? slot.input.type : slot.output.type) || "*"; - if (slot.input && slot.input.type == LiteGraph.ACTION) { + + var slotOb = slot.input || slot.output; + options.title = slotOb.type || "*"; + if (slotOb.type == LiteGraph.ACTION) { options.title = "Action"; - } - if (slot.output && slot.output.type == LiteGraph.EVENT) { + }else if (slotOb.type == LiteGraph.EVENT) { options.title = "Event"; } + // if(slotOb.name) options.title = slotOb.name + ": " + options.title; + } else { if (node) { //on node @@ -13256,6 +13997,35 @@ LGraphNode.prototype.executeAction = function(action) } LiteGraph.colorToString = colorToString; + function canvasFillTextMultiline(context, text, x, y, maxWidth, lineHeight) { + var words = (text+"").trim().split(' '); + var line = ''; + var ret = {lines: [], maxW: 0, height:0}; + if (words.length>1){ + for(var n = 0; n < words.length; n++) { + var testLine = line + words[n] + ' '; + var metrics = context.measureText(testLine); + var testWidth = metrics.width; + if (testWidth > maxWidth && n > 0) { + context.fillText(line, x, y+(lineHeight*ret.lines.length)); + line = words[n] + ' '; + //y += lineHeight; + ret.max = testWidth; + ret.lines.push(line); + }else{ + line = testLine; + } + } + }else{ + line = words[0]; + } + context.fillText(line, x, y+(lineHeight*ret.lines.length)); + ret.lines.push(line); + ret.height = lineHeight*ret.lines.length || lineHeight; + return ret; + } + LiteGraph.canvasFillTextMultiline = canvasFillTextMultiline; + function isInsideRectangle(x, y, left, top, width, height) { if (left < x && left + width > x && top < y && top + height > y) { return true; @@ -13372,6 +14142,8 @@ LGraphNode.prototype.executeAction = function(action) this.options = options; var that = this; + this.menu_elements = []; + //to link a menu with its parent if (options.parentMenu) { if (options.parentMenu.constructor !== this.constructor) { @@ -13432,7 +14204,6 @@ LGraphNode.prototype.executeAction = function(action) }, true ); - LiteGraph.pointerListenerAdd(root,"down", function(e) { //console.log("pointerevents: ContextMenu down"); @@ -13478,7 +14249,7 @@ LGraphNode.prototype.executeAction = function(action) name = name.content === undefined ? String(name) : name.content; } var value = values[i]; - this.addItem(name, value, options); + this.menu_elements.push(this.addItem(name, value, options)); num++; } @@ -13512,6 +14283,78 @@ LGraphNode.prototype.executeAction = function(action) root_document = document; } + + if(root_document){ + root_document.addEventListener( + "keydown", + function(e) { + // console.debug("keyPressInsideContext",e,that,this,options); + // if(options.keyfilter){ + if(!that.allOptions){ + that.allOptions = that.menu_elements; //combo_options; + that.currentOptions = []; + } + if(!that.filteringText){ + that.filteringText = ""; + } + if(e.key){ + var kdone = false; + switch(e.key){ + case "Backspace": + if(that.filteringText.length){ + that.filteringText = that.filteringText.substring(0,that.filteringText.length-1); + kdone = true; + } + break; + } + if(!kdone && e.key.length == 1){ + that.filteringText += e.key; + } + } + if(that.filteringText && that.filteringText!==""){ + var aFilteredOpts = []; + for(var iO in that.allOptions){ + if(that.allOptions[iO].textContent){ //.startWith(that.filteringText)){ + if(that.allOptions[iO].textContent.startsWith(that.filteringText)){ + aFilteredOpts.push(that.allOptions[iO]); + that.allOptions[iO].style.display = "block"; + // }else if(that.allOptions[iO].textContent.includes(that.filteringText)){ + // aFilteredOpts.push(that.allOptions[iO]); + // that.allOptions[iO].style.fontStyle = "italic"; + }else{ + that.allOptions[iO].style.display = "none"; + } + } + } + }else{ + aFilteredOpts = that.allOptions; //combo_options + for(var iO in that.allOptions){ + that.allOptions[iO].style.display = "block"; + that.allOptions[iO].style.fontStyle = ""; + } + } + // height reset + + var body_rect = document.body.getBoundingClientRect(); + var root_rect = root.getBoundingClientRect(); + root.style.top = that.top_original + "px"; + // if (body_rect.height && top > body_rect.height - root_rect.height - 10) { + // var new_top = body_rect.height - root_rect.height - 10; + // root.style.top = this.top_original + "px"; + // } + + console.debug("filtered for ",that.filteringText); + //e.preventDefault(); + //return false; + // } + }, + true + ); + }else{ + console.warning("NO root_document to add context menu and event",root_document,options); + } + + if( root_document.fullscreenElement ) root_document.fullscreenElement.appendChild(root); else @@ -13520,12 +14363,14 @@ LGraphNode.prototype.executeAction = function(action) //compute best position var left = options.left || 0; var top = options.top || 0; + this.top_original = top; if (options.event) { left = options.event.clientX - 10; top = options.event.clientY - 10; if (options.title) { top -= 20; } + this.top_original = top; if (options.parentMenu) { var rect = options.parentMenu.root.getBoundingClientRect(); @@ -14068,6 +14913,8 @@ LGraphNode.prototype.executeAction = function(action) } // only pointerevents case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": + // and touch events + case "start": case "end": { if (sMethod!="mouse"){ return oDOM.addEventListener(sMethod+sEvent, fCall, capture); diff --git a/src/nodes/events.js b/src/nodes/events.js index 4c8e51e20..9d8394e25 100644 --- a/src/nodes/events.js +++ b/src/nodes/events.js @@ -24,7 +24,7 @@ this.addOutput("true", LiteGraph.EVENT); this.addOutput("change", LiteGraph.EVENT); this.addOutput("false", LiteGraph.EVENT); - this.properties = { only_on_change: true }; + this.properties = { only_on_change: true, tooltip:"Triggers event if input evaluates to true" }; this.prev = 0; } diff --git a/src/nodes/html.js b/src/nodes/html.js new file mode 100644 index 000000000..e0b34759e --- /dev/null +++ b/src/nodes/html.js @@ -0,0 +1,298 @@ +(function(global) { + var LiteGraph = global.LiteGraph; + + function DOMSelector() { + this.addInput("selector", "string"); + this.addOutput("result", "htmlelement"); + this.properties = { }; + } + DOMSelector.title = "DOMSelector"; + DOMSelector.desc = "Execute a selection query on the document returning the corresponging DOM element"; + DOMSelector.prototype.onExecute = function(param){ + var sSel = this.getInputData(0); + var res = null; + if (sSel){ + res = document.querySelector(sSel); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/dom_selector", DOMSelector); + + function DOMSelectorAll() { + this.addInput("selector", "string"); + this.addOutput("result", "array"); + this.properties = { }; + } + DOMSelectorAll.title = "DOMSelectorAll"; + DOMSelectorAll.desc = "Execute a selection query (for MULTI) on the document returning the corresponging DOM elements"; + DOMSelectorAll.prototype.onExecute = function(param){ + var sSel = this.getInputData(0); + var res = null; + if (sSel){ + res = document.querySelectorAll(sSel); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/dom_selector_all", DOMSelectorAll); + + function HtmlEventListener() { + this.addInput("element", "htmlelement"); + this.addInput("add_listener", LiteGraph.ACTION); + this.addOutput("listener", "htmlelement_listener"); + this.addOutput("on_event", LiteGraph.EVENT); + this.addOutput("last_event", ""); + this.addOutput("current_event", ""); + this.addProperty("eventType", ""); + this.addWidget("combo","eventType",this.properties["eventType"],"eventType",{values:["click","dblclick", "mouseover","mousedown","mouseup","mousemove","mouseout","keydown","keyup","keypress","load","unload","mousewheel","contextmenu", "focus","change","blur","pointerdown","pointerup","pointermove","pointerover","pointerout","pointerenter","pointerleave","pointercancel","gotpointercapture","lostpointercapture", "touchstart","touchmove","touchend","touchcancel","submit","scroll","resize","hashchange"]}); + //this.properties = {eventType: "" }; + this.mode = LiteGraph.ON_ACTION; + } + HtmlEventListener.title = "HTML Listener"; + HtmlEventListener.desc = "Add an event listener on an html element"; + HtmlEventListener.prototype.onExecute = function(param, options){ + // no code? + if (this.mode == LiteGraph.ON_TRIGGER){ + action = this.id+"_"+(action?action:"action")+"_exectoact_"+Math.floor(Math.random()*9999); + this.onAction(action, param, options); + } + else this.setOutputData(3,null); + } + HtmlEventListener.prototype.onAction = function(action, param, options){ + var sSel = this.getInputData(0); + var eventType = this.getInputOrProperty("eventType"); + var res = null; + if (sSel && eventType && sSel.addEventListener){ + switch(action){ + case "add_listener": + default: + if ( ! sSel.attributes["data-listener-"+eventType] ){ + var that = this; + var fEv = function(e){ + that.setOutputData(2,e); + that.setOutputData(3,e); + that.triggerSlot(1); + } + sSel.addEventListener(eventType, fEv); + sSel.attributes["data-listener-"+eventType] = fEv; + }else{ + var fEv = sSel.attributes["data-listener-"+eventType]; + } + res = {element: sSel, function: fEv, event: eventType}; + break; + } + }else{ + console.log("no el to add event"); + //this.setOutputData(2,null); // clean ? + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/event_listener", HtmlEventListener); + + + function HtmlEventListenerRemove() { + this.addInput("listener", "htmlelement_listener"); + this.addInput("remove_listener", LiteGraph.ACTION); + this.addOutput("result","boolean"); + this.mode = LiteGraph.ON_ACTION; + } + HtmlEventListenerRemove.title = "HTML Remove Listener"; + HtmlEventListenerRemove.desc = "Remove an event listener by passing his reference"; + HtmlEventListenerRemove.prototype.onExecute = function(param, options){ + // no code? + if (this.mode == LiteGraph.ON_TRIGGER){ + action = this.id+"_"+(action?action:"action")+"_exectoact_"+Math.floor(Math.random()*9999); + this.onAction(action, param, options); + } + } + HtmlEventListenerRemove.prototype.onAction = function(action, param, options){ + var oLis = this.getInputData(0); + var res = false; + if (oLis && oLis.element && oLis.function && oLis.event && oLis.element.removeEventListener){ + oLis.element.attributes["data-listener-"+oLis.event] = false; + oLis.element.removeEventListener(oLis.event, oLis.function); + res = true; + }else{ + console.log("bad element to remove listener"); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/event_listener_remove", HtmlEventListenerRemove); + + + function HtmlValue() { + this.addInput("element", "htmlelement"); + //this.addInput("get", LiteGraph.ACTION); + this.addOutput("result","string"); + //this.mode = LiteGraph.ON_ACTION; + } + HtmlValue.title = "HTML GET Value"; + HtmlValue.desc = "Get the value (or the text content) of an HTML element"; + HtmlValue.prototype.onExecute = function(param, options){ + var el = this.getInputData(0); + var res = false; + if (el){ + if(typeof el == "object"){ + if (typeof el.value != "undefined"){ + res = el.value; + }else if(typeof el.checked != "undefined"){ // el.constructor.name == "HTMLInputElement" && .. + res = el.checked?true:false; + }else if(typeof el.textContent != "undefined"){ + res = el.textContent; + }else{ + res = ""; + } + /*switch(el.constructor.name){ + }*/ + } + }else{ + //console.log("no element to get value"); + } + this.setOutputData(0,res); + } + //HtmlEventListenerRemove.prototype.onAction = function(action, param, options){} + LiteGraph.registerNodeType("html/element_value", HtmlValue); + + + function HtmlValueSet() { + this.addInput("element", "htmlelement"); + //this.addInput("set", LiteGraph.ACTION); + this.addInput("value", "string"); + this.addOutput("result","boolean"); + this.addProperty("value",""); + //this.mode = LiteGraph.ON_ACTION; + } + HtmlValueSet.title = "HTML SET Value"; + HtmlValueSet.desc = "Set the value (or the text content) of an HTML element"; + HtmlValueSet.prototype.onExecute = function(param, options){ + //if (this.mode == LiteGraph.ON_TRIGGER) this.onAction(action, param, options); + var el = this.getInputData(0); + var sVal = this.getInputOrProperty("value"); //getInputData(1); + var res = false; + if (el){ + if(typeof el == "object"){ + if (typeof el.value != "undefined"){ + el.value = sVal+""; + res = true; + }else if(typeof el.checked != "undefined"){ + el.checked = sVal?true:false; + res = true; + }else if(typeof el.textContent != "undefined"){ + el.textContent = sVal+""; + res = true; + }else{ + console.log("unkonwn element to set value"); + } + /*switch(el.constructor.name){ + }*/ + } + }else{ + //console.log("no element to set value"); + } + this.setOutputData(0,res); + } + /*HtmlValueSet.prototype.onAction = function(action, param, options){ + + }*/ + LiteGraph.registerNodeType("html/element_value_set", HtmlValueSet); + + + function HtmlCreateElement() { + this.addInput("create", LiteGraph.ACTION); + this.addInput("type", "string"); + this.addInput("id", "string"); + this.addInput("class", "string"); + this.addOutput("element","htmlelement"); + this.addProperty("type", ""); + this.addProperty("id", ""); + this.addProperty("class", ""); + this.addWidget("combo","type",this.properties["type"],"type",{values:["div","a","span","input","form","br","hr","table","th","tr","td","h1","h2","h3","h4","h5","h6"]}); + this.mode = LiteGraph.ON_ACTION; + } + HtmlCreateElement.title = "HTML Create El"; + HtmlCreateElement.desc = "Create an HTML element"; + HtmlCreateElement.prototype.onExecute = function(param, options){ + // no code? + if (this.mode == LiteGraph.ON_TRIGGER){ + action = this.id+"_"+(action?action:"action")+"_exectoact_"+Math.floor(Math.random()*9999); + this.onAction(action, param, options); + } + } + HtmlCreateElement.prototype.onAction = function(action, param, options){ + var sType = this.getInputOrProperty("type"); //this.getInputData(1); + var sId = this.getInputOrProperty("id"); //getInputData(2); + var sClass = this.getInputOrProperty("class"); //getInputData(3); + var res = null; + if (sType){ + var el = document.createElement(sType); + if (el){ + if (sId) el.id = sId+""; + if (sClass) el.className = sClass+""; + res = el; + } + }else{ + //console.log("no type to create"); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/create_element", HtmlCreateElement); + + + function HtmlAppendChild() { + this.addInput("parent", "htmlelement"); + this.addInput("child", "htmlelement"); + this.addInput("add", LiteGraph.ACTION); + this.addOutput("result",""); + this.mode = LiteGraph.ON_ACTION; + } + HtmlAppendChild.title = "HTML Append Child"; + HtmlAppendChild.desc = "Append an HTML element to another"; + HtmlAppendChild.prototype.onExecute = function(param, options){ + // no code? + if (this.mode == LiteGraph.ON_TRIGGER){ + action = this.id+"_"+(action?action:"action")+"_exectoact_"+Math.floor(Math.random()*9999); + this.onAction(action, param, options); + } + } + HtmlAppendChild.prototype.onAction = function(action, param, options){ + var parent = this.getInputData(0); + var child = this.getInputData(1); + var res = null; + if (parent && child && parent.appendChild){ + res = parent.appendChild(child)?true:false; + }else{ + //console.log("no type to create"); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/append_child", HtmlAppendChild); + + + function HtmlRemoveElement() { + this.addInput("element", "htmlelement"); + this.addInput("remove", LiteGraph.ACTION); + this.addOutput("result",""); + this.mode = LiteGraph.ON_ACTION; + } + HtmlRemoveElement.title = "HTML Remove element"; + HtmlRemoveElement.desc = "Remove an HTML element"; + HtmlRemoveElement.prototype.onExecute = function(param, options){ + // no code? + if (this.mode == LiteGraph.ON_TRIGGER){ + action = this.id+"_"+(action?action:"action")+"_exectoact_"+Math.floor(Math.random()*9999); + this.onAction(action, param, options); + } + } + HtmlRemoveElement.prototype.onAction = function(action, param, options){ + var element = this.getInputData(0); + var res = null; + if (element && element.remove){ + res = element.remove()?true:false; + }else{ + //console.log("no type to create"); + } + this.setOutputData(0,res); + } + LiteGraph.registerNodeType("html/remove_element", HtmlRemoveElement); + + +})(this); \ No newline at end of file diff --git a/src/nodes/objects.js b/src/nodes/objects.js new file mode 100644 index 000000000..7b204e7ba --- /dev/null +++ b/src/nodes/objects.js @@ -0,0 +1,234 @@ +//event related nodes +(function(global) { + var LiteGraph = global.LiteGraph; + + + function mMETHOD(){ + this.properties = { }; + // this.addInput("onTrigger", LiteGraph.ACTION); + // this.addInput("condition", "boolean"); + // this.addOutput("true", LiteGraph.EVENT); + // this.addOutput("false", LiteGraph.EVENT); + this.mode = LiteGraph.ON_TRIGGER; + } + mMETHOD.title = "Branch"; + mMETHOD.desc = "Branch execution on condition"; + mMETHOD.prototype.onExecute = function(param, options) { + // this.triggerSlot(0); + }; + mMETHOD.prototype.onAction = function(action, param, options){ + }; + mMETHOD.prototype.onGetInputs = function() { + //return [["in", 0]]; + }; + mMETHOD.prototype.onGetOutputs = function() { + //return [["out", 0]]; + }; + LiteGraph.registerNodeType("basic/egnode", mMETHOD); + + // -------------------------- + + function objProperties(){ + + this.addInput("obj", "object"); + // this.addInput("condition", "boolean"); + + this.addOutput("properties", "array"); + // this.addOutput("false", LiteGraph.EVENT); + + // this.mode = LiteGraph.ON_TRIGGER; + //this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); + //this.widgets_up = true; + //this.size = [140, 30]; + this._value = null; + this._properties = []; + } + objProperties.title = "OBJ props"; + objProperties.desc = "Properties for objects"; + objProperties.prototype.onExecute = function(param, options) { + var data = this.getInputData(0); + if (data != null) { + this._value = data; + try{ + this._properties = Object.keys(this._value); + }catch(e){ + } + this.setOutputData(0, this._properties); + } + }; + objProperties.prototype.onAction = function(action, param, options){ + // should probably execute on action + }; + objProperties.prototype.onGetInputs = function() { + //return [["in", 0]]; + }; + objProperties.prototype.onGetOutputs = function() { + //return [["out", 0]]; + }; + objProperties.prototype.getTitle = function() { + if (this.flags.collapsed) { + } + return this.title; + }; + objProperties.prototype.onPropertyChanged = function(name, value) { + //this.widget.value = value; + }; + LiteGraph.registerNodeType("objects/properties", objProperties); + + // -------------------------- + + // node events + /* + onWidgetChanged + */ + + + // widgets + /* + + this.widg_prop = this.addWidget("property","prop.","",this.setValue.bind(this) ); + this.widg_prop = this.addWidget("combo","prop.",this.properties.prop,{ property: "prop", values: [] }); //,this.setValue.bind(this) ); + + // to put it before inputs + this.widgets_up = true; + + // remove or update does not exists :: should save index to do it :: this.removeWidget(); + // to clear + this.widgets = []; + // readd if needed + this.widg_prop = this.addWidget(); + + // can specify draw function + obWidget.draw = function(ctx, node, widget_width, y, H){ + + } + // can override Y placement + obWidget.computeSize = function(width){ + return Y; + } + + obWidget.mouse = function(){ + return b_isDirtyCanvas; // can specify if canvas should get dirty + } + + obWidget.callback = function(value, canvas, node, pos, event){ + + } + + */ + + // -------------------------- + + + function objPropertyWidget(){ + + this.addInput("obj", "object"); + // this.addInput("condition", "boolean"); + + this.addOutput("value", "*"); + // this.addOutput("false", LiteGraph.EVENT); + + this.addProperty("prop", 0); + + this.mode = LiteGraph.ON_REQUEST; // to be optimized, could run always + //this.widg_prop = this.addWidget("property","prop.","",this.setValue.bind(this) ); + this.widg_prop = this.addWidget("combo","prop.",this.properties.prop,{ property: "prop", values: [] }); //,this.setValue.bind(this) ); + //this.widgets_up = true; + //this.size = [140, 30]; + + this._obin = null; + this._value = null; + this._properties = []; + } + objPropertyWidget.title = "Obj Prop widget"; + objPropertyWidget.desc = "Choose a property for an object"; + objPropertyWidget.prototype.setValue = function(v) { + this.properties.prop = v; + this.widg_prop.value = v; + }; + objPropertyWidget.prototype.updateFromInput = function(v) { + var data = this.getInputData(0); + if (data != null) { + this._obin = data; + if(this._obin){ //} && typeof this._obin == "Object"){ + try{ + this._properties = Object.keys(this._obin); + if(this._properties && this._properties.sort) this._properties = this._properties.sort(); + }catch(e){ + } + if(this._properties){ + //this.removeWidget(); + this.widgets = []; + this.widg_prop = this.addWidget("combo","prop.",this.properties.prop,{ property: "prop", values: this._properties }); + } + if(typeof this._obin[this.properties.prop] !== "undefined"){ + this._value = this._obin[this.properties.prop]; + }else{ + this._value = null; + } + }else{ + this._value = null; + this._properties = []; + } + } + if(!this.widg_prop.options) this.widg_prop.options = {}; + this.widg_prop.options.values = this._properties; + this.setOutputData(0, this._value); + }; + objPropertyWidget.prototype.onExecute = function(param, options) { + // var data = this.getInputData(0); + // if (data != null) { + // this._obin = data; + // if(this._obin){ //} && typeof this._obin == "Object"){ + // try{ + // this._properties = Object.keys(this._obin); + // }catch(e){ + // } + // if(typeof this._obin[this.properties.prop] !== "undefined"){ + // this._value = this._obin[this.properties.prop]; + // }else{ + // this._value = null; + // } + // }else{ + // this._value = null; + // this._properties = []; + // } + // } + // if(!this.widg_prop.options) this.widg_prop.options = {}; + // this.widg_prop.options.values = this._properties; + // this.setOutputData(0, this._value); + this.updateFromInput(); + }; + objPropertyWidget.prototype.onAction = function(action, param, options){ + // should probably execute on action + this.updateFromInput(); + }; + objPropertyWidget.prototype.onGetInputs = function() { + //return [["in", 0]]; + }; + objPropertyWidget.prototype.onGetOutputs = function() { + //return [["out", 0]]; + }; + objPropertyWidget.prototype.getTitle = function() { + if (this.flags.collapsed) { + return this.properties.prop; + } + return this.title; + }; + objPropertyWidget.prototype.onPropertyChanged = function(name, value) { + if(name == "value"){ + this.widg_prop.value = value; + } + }; + objPropertyWidget.prototype.getExtraMenuOptions = function(canvas, options){ + return [{ + content: "Console DBG", //has_submenu: false, + callback: function(menuitO,obX,ev,htmO,nodeX){ + console.debug(nodeX.widg_prop); + console.debug(nodeX); + } + }]; + } + LiteGraph.registerNodeType("objects/property_widget", objPropertyWidget); + +})(this); \ No newline at end of file diff --git a/src/nodes/others.js b/src/nodes/others.js index cca0e6f2b..6679156a9 100644 --- a/src/nodes/others.js +++ b/src/nodes/others.js @@ -14,9 +14,17 @@ LiteGraph.slot_types_default_in["vec3"] = "math3d/xyz-to-vec3"; LiteGraph.slot_types_default_in["vec4"] = "math3d/xyzw-to-vec4"; + LiteGraph.slot_types_default_in["audio"] = ["audio/source","audio/media_source"]; + LiteGraph.slot_types_default_in["canvas"] = "graphics/canvas"; + LiteGraph.slot_types_default_in["geometry"] = ["geometry/polygon","geometry/eval"]; + LiteGraph.slot_types_default_in["image"] = ["graphics/image","graphics/video","graphics/webcam"]; + LiteGraph.slot_types_default_in["mat4"] = "math3d/mat4"; + LiteGraph.slot_types_default_in["quat"] = ["math3d/quaternion","math3d/rotation"]; + LiteGraph.slot_types_default_in["table"] = "string/toTable"; + /* out types :: run in console :: var s=""; LiteGraph.slot_types_out.forEach(function(el){s+=el+"\n";}); console.log(s); */ if(typeof LiteGraph.slot_types_default_out == "undefined") LiteGraph.slot_types_default_out = {}; - LiteGraph.slot_types_default_out["_event_"] = ["logic/IF","events/sequencer","events/log","events/counter"]; + LiteGraph.slot_types_default_out["_event_"] = ["logic/IF","events/sequence","events/log","events/counter"]; LiteGraph.slot_types_default_out["array"] = ["basic/watch","basic/set_array","basic/array[]"]; LiteGraph.slot_types_default_out["boolean"] = ["logic/IF","basic/watch","math/branch","math/gate"]; LiteGraph.slot_types_default_out["number"] = ["basic/watch" @@ -33,5 +41,13 @@ LiteGraph.slot_types_default_out["vec2"] = "math3d/vec2-to-xy"; LiteGraph.slot_types_default_out["vec3"] = "math3d/vec3-to-xyz"; LiteGraph.slot_types_default_out["vec4"] = "math3d/vec4-to-xyzw"; - + + LiteGraph.slot_types_default_out["audio"] = ["audio/destination","audio/mixer","audio/analyser"]; + LiteGraph.slot_types_default_out["canvas"] = ["graphics/frame","graphics/drawImage"]; + LiteGraph.slot_types_default_out["geometry"] = ["geometry/points_to_instances","geometry/extrude","geometry/eval","geometry/connectPoints","geometry/transform"]; + LiteGraph.slot_types_default_out["image"] = ["graphics/frame","graphics/drawImage"]; + //LiteGraph.slot_types_default_out["mat4"] = "geometry/transform"; + LiteGraph.slot_types_default_out["quat"] = ["math3d/rotate_vec3","math3d/rotation"]; + LiteGraph.slot_types_default_out["table"] = "string/toTable"; + })(this); \ No newline at end of file