diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css index 4b1239602..130ac0b97 100755 --- a/css/litegraph-editor.css +++ b/css/litegraph-editor.css @@ -1,213 +1,214 @@ -.litegraph-editor { - width: 100%; - height: 100%; - margin: 0; - padding: 0; - - background-color: #333; - color: #eee; - font: 14px Tahoma; - - position: relative; -} - -.litegraph-editor h1 { - font-family: "Metro Light", Tahoma; - color: #ddd; - font-size: 28px; - padding-left: 10px; - /*text-shadow: 0 1px 1px #333, 0 -1px 1px #777;*/ - margin: 0; - font-weight: normal; -} - -.litegraph-editor h1 span { - font-family: "Arial"; - font-size: 14px; - font-weight: normal; - color: #aaa; -} - -.litegraph-editor h2 { - font-family: "Metro Light"; - padding: 5px; - margin-left: 10px; -} - -.litegraph-editor * { - box-sizing: border-box; - -moz-box-sizing: border-box; -} - -.litegraph-editor .content { - position: relative; - width: 100%; - height: calc(100% - 80px); - background-color: #1a1a1a; -} - -.litegraph-editor .header, -.litegraph-editor .footer { - position: relative; - height: 40px; - background-color: #333; - /*border-radius: 10px 10px 0 0;*/ -} - -.litegraph-editor .tools, -.litegraph-editor .tools-left, -.litegraph-editor .tools-right { - position: absolute; - top: 2px; - right: 0px; - vertical-align: top; - - margin: 2px 5px 0 0px; -} - -.litegraph-editor .tools-left { - right: auto; - left: 4px; -} - -.litegraph-editor .footer { - height: 40px; - position: relative; - /*border-radius: 0 0 10px 10px;*/ -} - -.litegraph-editor .miniwindow { - background-color: #333; - border: 1px solid #111; -} - -.litegraph-editor .miniwindow .corner-button { - position: absolute; - top: 2px; - right: 2px; - font-family: "Tahoma"; - font-size: 14px; - color: #aaa; - cursor: pointer; -} - -/* BUTTONS **********************/ - -.litegraph-editor .btn { - /*font-family: "Metro Light";*/ - color: #ccc; - font-size: 20px; - min-width: 30px; - /*border-radius: 0.3em;*/ - border: 0 solid #666; - background-color: #3f3f3f; - /*box-shadow: 0 0 3px black;*/ - padding: 4px 10px; - cursor: pointer; - transition: all 1s; - -moz-transition: all 1s; - -webkit-transition: all 0.4s; -} - -.litegraph-editor button:hover { - background-color: #999; - color: #fff; - transition: all 1s; - -moz-transition: all 1s; - -webkit-transition: all 0.4s; -} - -.litegraph-editor button:active { - background-color: white; -} - -.litegraph-editor button.fixed { - position: absolute; - top: 5px; - right: 5px; - font-size: 1.2em; -} - -.litegraph-editor button img { - margin: -4px; - vertical-align: top; - opacity: 0.8; - transition: all 1s; -} - -.litegraph-editor button:hover img { - opacity: 1; -} - -.litegraph-editor .header button { - height: 32px; - vertical-align: top; -} - -.litegraph-editor .footer button { - /*font-size: 16px;*/ -} - -.litegraph-editor .toolbar-widget { - display: inline-block; -} - -.litegraph-editor .editor-area { - width: 100%; - height: 100%; -} - -/* METER *********************/ - -.litegraph-editor .loadmeter { - font-family: "Tahoma"; - color: #aaa; - font-size: 12px; - border-radius: 2px; - width: 130px; - vertical-align: top; -} - -.litegraph-editor .strong { - vertical-align: top; - padding: 3px; - width: 30px; - display: inline-block; - line-height: 8px; -} - -.litegraph-editor .cpuload .bgload, -.litegraph-editor .gpuload .bgload { - display: inline-block; - width: 90px; - height: 15px; - background-image: url("../editor/imgs/load-progress-empty.png"); -} - -.litegraph-editor .cpuload .fgload, -.litegraph-editor .gpuload .fgload { - display: inline-block; - width: 4px; - height: 15px; - max-width: 90px; - background-image: url("../editor/imgs/load-progress-full.png"); -} - -.litegraph-editor textarea.code, .litegraph-editor div.code { - height: 100%; - width: 100%; - background-color: black; - padding: 4px; - font: 16px monospace; - overflow: auto; - resize: none; - outline: none; -} - -.litegraph-editor .codeflask { - background-color: #2a2a2a; -} - -.litegraph-editor .codeflask textarea { - opacity: 0; +.litegraph-editor { + width: 100%; + height: 100%; + margin: 0; + padding: 0; + + background-color: #333; + color: #eee; + font: 14px Tahoma; + + position: relative; +} + +.litegraph-editor h1 { + font-family: "Metro Light", Tahoma; + color: #ddd; + font-size: 28px; + padding-left: 10px; + /*text-shadow: 0 1px 1px #333, 0 -1px 1px #777;*/ + margin: 0; + font-weight: normal; +} + +.litegraph-editor h1 span { + font-family: "Arial"; + font-size: 14px; + font-weight: normal; + color: #aaa; +} + +.litegraph-editor h2 { + font-family: "Metro Light"; + padding: 5px; + margin-left: 10px; +} + +.litegraph-editor * { + box-sizing: border-box; + -moz-box-sizing: border-box; +} + +.litegraph-editor .content { + position: relative; + width: 100%; + height: calc(100% - 80px); + background-color: #1a1a1a; +} + +.litegraph-editor .header, +.litegraph-editor .footer { + position: relative; + height: 40px; + background-color: #333; + /*border-radius: 10px 10px 0 0;*/ +} + +.litegraph-editor .tools, +.litegraph-editor .tools-left, +.litegraph-editor .tools-right { + position: absolute; + top: 2px; + right: 0px; + vertical-align: top; + + margin: 2px 5px 0 0px; +} + +.litegraph-editor .tools-left { + right: auto; + left: 4px; +} + +.litegraph-editor .footer { + height: 40px; + position: relative; + /*border-radius: 0 0 10px 10px;*/ +} + +.litegraph-editor .miniwindow { + background-color: #333; + border: 1px solid #111; +} + +.litegraph-editor .miniwindow .corner-button { + position: absolute; + top: 2px; + right: 2px; + font-family: "Tahoma"; + font-size: 14px; + color: #aaa; + cursor: pointer; +} + +/* BUTTONS **********************/ + +.litegraph-editor .btn { + /*font-family: "Metro Light";*/ + color: #ccc; + font-size: 20px; + min-width: 30px; + /*border-radius: 0.3em;*/ + border: 0 solid #666; + background-color: #3f3f3f; + /*box-shadow: 0 0 3px black;*/ + padding: 4px 10px; + cursor: pointer; + transition: all 1s; + -moz-transition: all 1s; + -webkit-transition: all 0.4s; +} + +.litegraph-editor button:hover { + background-color: #999; + color: #fff; + transition: all 1s; + -moz-transition: all 1s; + -webkit-transition: all 0.4s; +} + +.litegraph-editor button:active { + background-color: white; +} + +.litegraph-editor button.fixed { + position: absolute; + top: 5px; + right: 5px; + font-size: 1.2em; +} + +.litegraph-editor button img { + margin: -4px; + vertical-align: top; + opacity: 0.8; + transition: all 1s; +} + +.litegraph-editor button:hover img { + opacity: 1; +} + +.litegraph-editor .header button { + height: 32px; + vertical-align: top; +} + +.litegraph-editor .footer button { + /*font-size: 16px;*/ +} + +.litegraph-editor .toolbar-widget { + display: inline-block; +} + +.litegraph-editor .editor-area { + width: 100%; + height: 100%; +} + +/* METER *********************/ + +.litegraph-editor .loadmeter { + font-family: "Tahoma"; + color: #aaa; + font-size: 12px; + border-radius: 2px; + width: 130px; + vertical-align: top; +} + +.litegraph-editor .strong { + vertical-align: top; + padding: 3px; + width: 30px; + display: inline-block; + line-height: 8px; +} + +.litegraph-editor .cpuload .bgload, +.litegraph-editor .gpuload .bgload { + display: inline-block; + width: 90px; + height: 15px; + background-image: url("../editor/imgs/load-progress-empty.png"); +} + +.litegraph-editor .cpuload .fgload, +.litegraph-editor .gpuload .fgload { + display: inline-block; + width: 4px; + height: 15px; + max-width: 90px; + background-image: url("../editor/imgs/load-progress-full.png"); +} + +.litegraph-editor textarea.code, .litegraph-editor div.code { + height: 100%; + width: 100%; + background-color: black; + padding: 4px; + font: 16px monospace; + overflow: auto; + resize: none; + outline: none; + color: #DDD; +} + +.litegraph-editor .codeflask { + background-color: #2a2a2a; +} + +.litegraph-editor .codeflask textarea { + opacity: 0; } \ No newline at end of file diff --git a/css/litegraph.css b/css/litegraph.css index b32be79bf..918858f41 100755 --- a/css/litegraph.css +++ b/css/litegraph.css @@ -206,6 +206,20 @@ padding-top: 2px; } +.litegraph.lite-search-item.not_in_filter{ + /*background-color: rgba(50, 50, 50, 0.5);*/ + /*color: #999;*/ + color: #B99; + font-style: italic; +} + +.litegraph.lite-search-item.generic_type{ + /*background-color: rgba(50, 50, 50, 0.5);*/ + /*color: #DD9;*/ + color: #999; + font-style: italic; +} + .litegraph.lite-search-item:hover, .litegraph.lite-search-item.selected { cursor: pointer; @@ -235,6 +249,18 @@ top: 10px; height: calc( 100% - 20px ); margin: auto; + max-width: 50%; +} + +.litegraph .dialog.centered { + top: 50px; + left: 50%; + position: absolute; + transform: translateX(-50%); + min-width: 600px; + min-height: 300px; + height: calc( 100% - 100px ); + margin: auto; } .litegraph .dialog .close { @@ -264,13 +290,14 @@ display: inline-block; } -.litegraph .dialog .dialog-content { +.litegraph .dialog .dialog-content, .litegraph .dialog .dialog-alt-content { height: calc(100% - 90px); width: 100%; min-height: 100px; display: inline-block; color: #AAA; /*background-color: black;*/ + overflow: auto; } .litegraph .dialog .dialog-content h3 { @@ -315,14 +342,23 @@ padding: 4px; } +.litegraph .dialog .property:hover { + background: #545454; +} + .litegraph .dialog .property_name { color: #737373; display: inline-block; text-align: left; vertical-align: top; - width: 120px; + width: 160px; padding-left: 4px; overflow: hidden; + margin-right: 6px; +} + +.litegraph .dialog .property:hover .property_name { + color: white; } .litegraph .dialog .property_value { @@ -330,8 +366,11 @@ text-align: right; color: #AAA; background-color: #1A1A1A; - width: calc( 100% - 122px ); + /*width: calc( 100% - 122px );*/ + max-width: calc( 100% - 162px ); + min-width: 200px; max-height: 300px; + min-height: 20px; padding: 4px; padding-right: 12px; overflow: hidden; @@ -345,6 +384,16 @@ .litegraph .dialog .property.boolean .property_value { padding-right: 30px; + color: #A88; + /*width: auto; + float: right;*/ +} + +.litegraph .dialog .property.boolean.bool-on .property_name{ + color: #8A8; +} +.litegraph .dialog .property.boolean.bool-on .property_value{ + color: #8A8; } .litegraph .dialog .btn { @@ -515,7 +564,7 @@ position: absolute; top: 10px; left: 10px; - /*min-height: 2em;*/ + min-height: 2em; background-color: #333; font-size: 1.2em; box-shadow: 0 0 10px black !important; diff --git a/editor/editor_mobile.html b/editor/editor_mobile.html new file mode 100644 index 000000000..da5b1e104 --- /dev/null +++ b/editor/editor_mobile.html @@ -0,0 +1,144 @@ + + + + + LiteGraph + + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/index.html b/editor/index.html index 01dce5054..c43922b45 100755 --- a/editor/index.html +++ b/editor/index.html @@ -1,44 +1,47 @@ - - - - LiteGraph - - - - - - - - -
-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + LiteGraph + + + + + + + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/editor/js/code.js b/editor/js/code.js index 28c8dc442..fe9e89076 100644 --- a/editor/js/code.js +++ b/editor/js/code.js @@ -1,6 +1,7 @@ var webgl_canvas = null; LiteGraph.node_images_path = "../nodes_data/"; + var editor = new LiteGraph.Editor("main",{miniwindow:false}); window.graphcanvas = editor.graphcanvas; window.graph = editor.graph; @@ -19,8 +20,10 @@ LiteGraph.allow_scripts = true; //create scene selector var elem = document.createElement("span"); +elem.id = "LGEditorTopBarSelector"; elem.className = "selector"; -elem.innerHTML = "Demo | "; +elem.innerHTML = ""; +elem.innerHTML += "Demo | "; editor.tools.appendChild(elem); var select = elem.querySelector("select"); select.addEventListener("change", function(e){ @@ -167,4 +170,4 @@ function enableWebGL() gl.viewport(0,0,gl.canvas.width, gl.canvas.height ); } } -} +} \ No newline at end of file diff --git a/editor/js/defaults.js b/editor/js/defaults.js new file mode 100644 index 000000000..3709e645a --- /dev/null +++ b/editor/js/defaults.js @@ -0,0 +1,31 @@ + +LiteGraph.debug = false; +LiteGraph.catch_exceptions = true; +LiteGraph.throw_errors = true; +LiteGraph.allow_scripts = false; //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration); which could lead to exploits + +LiteGraph.searchbox_extras = {}; //used to add extra features to the search box +LiteGraph.auto_sort_node_types = true; // [true!] If set to true; will automatically sort node types / categories in the context menus +LiteGraph.node_box_coloured_when_on = true; // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action); visual feedback +LiteGraph.node_box_coloured_by_mode = true; // [true!] nodebox based on node mode; visual feedback +LiteGraph.dialog_close_on_mouse_leave = true; // [false on mobile] better true if not touch device; +LiteGraph.dialog_close_on_mouse_leave_delay = 500; +LiteGraph.shift_click_do_break_link_from = false; // [false!] prefer false if results too easy to break links +LiteGraph.click_do_break_link_to = false; // [false!]prefer false; way too easy to break links +LiteGraph.search_hide_on_mouse_leave = true; // [false on mobile] better true if not touch device; +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.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 +LiteGraph.registered_slot_out_types = {}; // slot types for nodeclass +LiteGraph.slot_types_in = []; // slot types IN +LiteGraph.slot_types_out = []; // slot types OUT*/ + +LiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node +LiteGraph.do_add_triggers_slots = true; // [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 +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 diff --git a/editor/js/defaults_mobile.js b/editor/js/defaults_mobile.js new file mode 100644 index 000000000..d4ef831c7 --- /dev/null +++ b/editor/js/defaults_mobile.js @@ -0,0 +1,31 @@ + +LiteGraph.debug = false; +LiteGraph.catch_exceptions = true; +LiteGraph.throw_errors = true; +LiteGraph.allow_scripts = false; //if set to true some nodes like Formula would be allowed to evaluate code that comes from unsafe sources (like node configuration); which could lead to exploits + +LiteGraph.searchbox_extras = {}; //used to add extra features to the search box +LiteGraph.auto_sort_node_types = true; // [true!] If set to true; will automatically sort node types / categories in the context menus +LiteGraph.node_box_coloured_when_on = true; // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action); visual feedback +LiteGraph.node_box_coloured_by_mode = true; // [true!] nodebox based on node mode; visual feedback +LiteGraph.dialog_close_on_mouse_leave = false; // [false on mobile] better true if not touch device; +LiteGraph.dialog_close_on_mouse_leave_delay = 500; +LiteGraph.shift_click_do_break_link_from = false; // [false!] prefer false if results too easy to break links +LiteGraph.click_do_break_link_to = false; // [false!]prefer false; way too easy to break links +LiteGraph.search_hide_on_mouse_leave = false; // [false on mobile] better true if not touch device; +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.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 +LiteGraph.registered_slot_out_types = {}; // slot types for nodeclass +LiteGraph.slot_types_in = []; // slot types IN +LiteGraph.slot_types_out = []; // slot types OUT*/ + +LiteGraph.alt_drag_do_clone_nodes = true; // [true!] very handy; ALT click to clone and drag the new node +LiteGraph.do_add_triggers_slots = true; // [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 +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 diff --git a/src/litegraph.js b/src/litegraph.js old mode 100755 new mode 100644 index a9fa0061c..f372fb65c --- a/src/litegraph.js +++ b/src/litegraph.js @@ -56,6 +56,7 @@ CIRCLE_SHAPE: 3, CARD_SHAPE: 4, ARROW_SHAPE: 5, + GRID_SHAPE: 6, // intended for slot arrays //enums INPUT: 1, @@ -64,6 +65,8 @@ 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_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode ALWAYS: 0, ON_EVENT: 1, NEVER: 2, @@ -75,6 +78,7 @@ RIGHT: 4, CENTER: 5, + LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper STRAIGHT_LINK: 0, LINEAR_LINK: 1, SPLINE_LINK: 2, @@ -97,11 +101,44 @@ Globals: {}, //used to store vars between graphs searchbox_extras: {}, //used to add extra features to the search box - auto_sort_node_types: false, // If set to true, will automatically sort node types / categories in the context menus + auto_sort_node_types: false, // [true!] If set to true, will automatically sort node types / categories in the context menus + + node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback + node_box_coloured_by_mode: false, // [true!] nodebox based on node mode, visual feedback - pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues + dialog_close_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + dialog_close_on_mouse_leave_delay: 500, + + shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - implement with ALT or TODO custom keys + click_do_break_link_to: false, // [false!]prefer false, way too easy to break links + + search_hide_on_mouse_leave: true, // [false on mobile] better true if not touch device, TODO add an helper/listener to close if false + 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 + + 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 + registered_slot_in_types: {}, // slot types for nodeclass + registered_slot_out_types: {}, // slot types for nodeclass + slot_types_in: [], // slot types IN + slot_types_out: [], // slot types OUT + 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 + + 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 + + allow_multi_output_for_events: true, // [false!] being events, it is strongly reccomended to use them sequentually, one by one + + middle_click_slot_add_default_node: false, //[true!] allows to create and connect a ndoe clicking with the third button (wheel) + + release_link_on_empty_shows_menu: false, //[true!] dragging a link to empty space will open a menu, add from list, search or defaults + + pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) - + /** * Register a node class so it can be listed when the user wants to create a new one * @method registerNodeType @@ -222,6 +259,10 @@ this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; } } + + // 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"); }, /** @@ -238,6 +279,50 @@ delete this.Nodes[base_class.constructor.name]; }, + /** + * Save a slot type and his node + * @method registerSlotType + * @param {String|Object} type name of the node or the node constructor itself + * @param {String} slot_type name of the slot type (variable type), eg. string, number, array, boolean, .. + */ + registerNodeAndSlotType: function(type,slot_type,out){ + out = out || false; + var base_class = type.constructor === String && this.registered_node_types[type] !== "anonymous" ? this.registered_node_types[type] : type; + + var sCN = base_class.constructor.type; + + if (typeof slot_type == "string"){ + var aTypes = slot_type.split(","); + }else if (slot_type == this.EVENT || slot_type == this.ACTION){ + var aTypes = ["_event_"]; + }else{ + var aTypes = ["*"]; + } + + for (var i = 0; i < aTypes.length; ++i) { + var sT = aTypes[i]; //.toLowerCase(); + if (sT === ""){ + sT = "*"; + } + var registerTo = out ? "registered_slot_out_types" : "registered_slot_in_types"; + if (typeof this[registerTo][sT] == "undefined") this[registerTo][sT] = {nodes: []}; + this[registerTo][sT].nodes.push(sCN); + + // check if is a new type + if (!out){ + if (!this.slot_types_in.includes(sT.toLowerCase())){ + this.slot_types_in.push(sT.toLowerCase()); + this.slot_types_in.sort(); + } + }else{ + if (!this.slot_types_out.includes(sT.toLowerCase())){ + this.slot_types_out.push(sT.toLowerCase()); + this.slot_types_out.sort(); + } + } + } + }, + /** * Create a new nodetype by passing a function, it wraps it with a proper class and generates inputs according to the parameters of the function. * Useful to wrap simple methods that do not require properties, and that only process some input to generate an output. @@ -384,6 +469,11 @@ } } + // callback + if ( node.onNodeCreated ) { + node.onNodeCreated(); + } + return node; }, @@ -521,11 +611,13 @@ * @return {Boolean} true if they can be connected */ isValidConnection: function(type_a, type_b) { + if (type_a=="" || type_a==="*") type_a = 0; + if (type_b=="" || type_b==="*") type_b = 0; if ( - !type_a || //generic output - !type_b || //generic input - type_a == type_b || //same type (is valid for triggers) - (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) + !type_a //generic output + || !type_b // generic input + || type_a == type_b //same type (is valid for triggers) + || (type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION) ) { return true; } @@ -546,7 +638,8 @@ var supported_types_b = type_b.split(","); for (var i = 0; i < supported_types_a.length; ++i) { for (var j = 0; j < supported_types_b.length; ++j) { - if (supported_types_a[i] == supported_types_b[j]) { + if(this.isValidConnection(supported_types_a[i],supported_types_b[j])){ + //if (supported_types_a[i] == supported_types_b[j]) { return true; } } @@ -748,6 +841,10 @@ this.catch_errors = true; + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + //subgraph_data this.inputs = {}; this.outputs = {}; @@ -904,7 +1001,8 @@ for (var j = 0; j < limit; ++j) { var node = nodes[j]; if (node.mode == LiteGraph.ALWAYS && node.onExecute) { - node.onExecute(); //hard to send elapsed time + //wrap node.onExecute(); + node.doExecute(); } } @@ -960,6 +1058,9 @@ this.iteration += 1; this.elapsed_time = (now - this.last_update_time) * 0.001; this.last_update_time = now; + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; }; /** @@ -1373,7 +1474,7 @@ return; } //cannot be removed - this.beforeChange(); //sure? + this.beforeChange(); //sure? - almost sure is wrong //disconnect inputs if (node.inputs) { @@ -1433,7 +1534,7 @@ this.sendActionToCanvas("checkPanels"); this.setDirtyCanvas(true, true); - this.afterChange(); //sure? + this.afterChange(); //sure? - almost sure is wrong this.change(); this.updateExecutionOrder(); @@ -1528,13 +1629,19 @@ */ LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) { nodes_list = nodes_list || this._nodes; + var nRet = null; for (var i = nodes_list.length - 1; i >= 0; i--) { var n = nodes_list[i]; if (n.isPointInside(x, y, margin)) { - return n; + // check for lesser interest nodes (TODO check for overlapping, use the top) + /*if (typeof n == "LGraphGroup"){ + nRet = n; + }else{*/ + return n; + /*}*/ } } - return null; + return nRet; }; /** @@ -1586,7 +1693,7 @@ // ********** GLOBALS ***************** - LGraph.prototype.onAction = function(action, param) { + LGraph.prototype.onAction = function(action, param, options) { this._input_nodes = this.findNodesByClass( LiteGraph.GraphInput, this._input_nodes @@ -1596,7 +1703,8 @@ if (node.properties.name != action) { continue; } - node.onAction(action, param); + //wrap node.onAction(action, param); + node.actionDo(action, param, options); break; } }; @@ -2938,13 +3046,130 @@ return r; }; + LGraphNode.prototype.addOnTriggerInput = function(){ + var trigS = this.findInputSlot("onTrigger"); + if (trigS == -1){ //!trigS || + var input = this.addInput("onTrigger", LiteGraph.EVENT, {optional: true, nameLocked: true}); + return this.findInputSlot("onTrigger"); + } + return trigS; + } + + LGraphNode.prototype.addOnExecutedOutput = function(){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS == -1){ //!trigS || + var output = this.addOutput("onExecuted", LiteGraph.ACTION, {optional: true, nameLocked: true}); + return this.findOutputSlot("onExecuted"); + } + return trigS; + } + + LGraphNode.prototype.onAfterExecuteNode = function(param, options){ + var trigS = this.findOutputSlot("onExecuted"); + if (trigS != -1){ + + //console.debug(this.id+":"+this.order+" triggering slot onAfterExecute"); + //console.debug(param); + //console.debug(options); + this.triggerSlot(trigS, param, null, options); + + } + } + + LGraphNode.prototype.changeMode = function(modeTo){ + switch(modeTo){ + case LiteGraph.ON_EVENT: + // this.addOnExecutedOutput(); + break; + + case LiteGraph.ON_TRIGGER: + this.addOnTriggerInput(); + this.addOnExecutedOutput(); + break; + + case LiteGraph.NEVER: + break; + + case LiteGraph.ALWAYS: + break; + + case LiteGraph.ON_REQUEST: + break; + + default: + return false; + break; + } + this.mode = modeTo; + return true; + }; + + /** + * Triggers the node code execution, place a boolean/counter to mark the node as being executed + * @method execute + * @param {*} param + * @param {*} options + */ + LGraphNode.prototype.doExecute = function(param, options) { + options = options || {}; + if (this.onExecute){ + + // enable this to give the event an ID + if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); + + this.graph.nodes_executing[this.id] = true; //.push(this.id); + + this.onExecute(param, options); + + this.graph.nodes_executing[this.id] = false; //.pop(); + + // save execution/action ref + this.exec_version = this.graph.iteration; + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; + } + } + 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 + }; + + /** + * Triggers an action, wrapped by logics to control execution flow + * @method actionDo + * @param {String} action name + * @param {*} param + */ + LGraphNode.prototype.actionDo = function(action, param, options) { + options = options || {}; + if (this.onAction){ + + // 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); + + this.graph.nodes_actioning[this.id] = (action?action:"actioning"); //.push(this.id); + + this.onAction(action, param, options); + + this.graph.nodes_actioning[this.id] = false; //.pop(); + + // save execution/action ref + if(options && options.action_call){ + this.action_call = options.action_call; // if (param) + this.graph.nodes_executedAction[this.id] = options.action_call; + } + } + this.action_triggered = 2; // the nFrames it will be used (-- each step), means "how old" is the event + if(this.onAfterExecuteNode) this.onAfterExecuteNode(param, options); + }; + /** * Triggers an event in this node, this will trigger any output with the same name * @method trigger * @param {String} event name ( "on_play", ... ) if action is equivalent to false then the event is send to all * @param {*} param */ - LGraphNode.prototype.trigger = function(action, param) { + LGraphNode.prototype.trigger = function(action, param, options) { if (!this.outputs || !this.outputs.length) { return; } @@ -2956,18 +3181,19 @@ var output = this.outputs[i]; if ( !output || output.type !== LiteGraph.EVENT || (action && output.name != action) ) continue; - this.triggerSlot(i, param); + this.triggerSlot(i, param, null, options); } }; /** - * Triggers an slot event in this node + * Triggers a slot event in this node: cycle output slots and launch execute/action on connected nodes * @method triggerSlot * @param {Number} slot the index of the output slot * @param {*} param * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot */ - LGraphNode.prototype.triggerSlot = function(slot, param, link_id) { + LGraphNode.prototype.triggerSlot = function(slot, param, link_id, options) { + options = options || {}; if (!this.outputs) { return; } @@ -3010,12 +3236,20 @@ if (node.mode === LiteGraph.ON_TRIGGER) { + // generate unique trigger ID if not present + if (!options.action_call) options.action_call = this.id+"_trigg_"+Math.floor(Math.random()*9999); if (node.onExecute) { - node.onExecute(param); + // -- wrapping node.onExecute(param); -- + node.doExecute(param, options); } } else if (node.onAction) { - node.onAction(target_connection.name, param); + // 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]; + // wrap node.onAction(target_connection.name, param); + node.actionDo(target_connection.name, param, options); } } }; @@ -3124,6 +3358,9 @@ if (this.onOutputAdded) { this.onOutputAdded(o); } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,type,true); + this.setSize( this.computeSize() ); this.setDirtyCanvas(true, true); return o; @@ -3151,6 +3388,9 @@ if (this.onOutputAdded) { this.onOutputAdded(o); } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); + } this.setSize( this.computeSize() ); @@ -3211,7 +3451,9 @@ if (this.onInputAdded) { this.onInputAdded(o); - } + } + + LiteGraph.registerNodeAndSlotType(this,type); this.setDirtyCanvas(true, true); return o; @@ -3239,6 +3481,8 @@ if (this.onInputAdded) { this.onInputAdded(o); } + + LiteGraph.registerNodeAndSlotType(this,info[1]); } this.setSize( this.computeSize() ); @@ -3309,7 +3553,6 @@ rows = Math.max(rows, 1); var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size - var font_size = font_size; var title_width = compute_text_size(this.title); var input_width = 0; var output_width = 0; @@ -3610,15 +3853,16 @@ * returns the input slot with a given name (used for dynamic slots), -1 if not found * @method findInputSlot * @param {string} name the name of the slot - * @return {number} the slot (-1 if not found) + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) */ - LGraphNode.prototype.findInputSlot = function(name) { + LGraphNode.prototype.findInputSlot = function(name, returnObj) { if (!this.inputs) { return -1; } for (var i = 0, l = this.inputs.length; i < l; ++i) { if (name == this.inputs[i].name) { - return i; + return !returnObj ? i : this.inputs[i]; } } return -1; @@ -3628,20 +3872,259 @@ * returns the output slot with a given name (used for dynamic slots), -1 if not found * @method findOutputSlot * @param {string} name the name of the slot - * @return {number} the slot (-1 if not found) + * @param {boolean} returnObj if the obj itself wanted + * @return {number_or_object} the slot (-1 if not found) */ - LGraphNode.prototype.findOutputSlot = function(name) { + LGraphNode.prototype.findOutputSlot = function(name, returnObj) { + returnObj = returnObj || false; if (!this.outputs) { return -1; } for (var i = 0, l = this.outputs.length; i < l; ++i) { if (name == this.outputs[i].name) { - return i; + return !returnObj ? i : this.outputs[i]; + } + } + return -1; + }; + + // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options + + /** + * returns the first free input slot + * @method findInputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findInputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = {returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.inputs) { + return -1; + } + for (var i = 0, l = this.inputs.length; i < l; ++i) { + if (this.inputs[i].link && this.inputs[i].link != null) { + continue; + } + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.inputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.inputs[i]; + } + return -1; + }; + + /** + * returns the first output slot free + * @method findOutputSlotFree + * @param {object} options + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findOutputSlotFree = function(optsIn) { + var optsIn = optsIn || {}; + var optsDef = { returnObj: false + ,typesNotAccepted: [] + }; + var opts = Object.assign(optsDef,optsIn); + if (!this.outputs) { + return -1; + } + for (var i = 0, l = this.outputs.length; i < l; ++i) { + if (this.outputs[i].links && this.outputs[i].links != null) { + continue; + } + if (opts.typesNotAccepted && opts.typesNotAccepted.includes && opts.typesNotAccepted.includes(this.outputs[i].type)){ + continue; + } + return !opts.returnObj ? i : this.outputs[i]; + } + return -1; + }; + + /** + * findSlotByType for INPUTS + */ + LGraphNode.prototype.findInputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(true, type, returnObj, preferFreeSlot, doNotUseOccupied); + }; + + /** + * findSlotByType for OUTPUTS + */ + LGraphNode.prototype.findOutputSlotByType = function(type, returnObj, preferFreeSlot, doNotUseOccupied) { + return this.findSlotByType(false, type, returnObj, preferFreeSlot, doNotUseOccupied); + }; + + /** + * returns the output (or input) slot with a given type, -1 if not found + * @method findSlotByType + * @param {boolean} input uise inputs instead of outputs + * @param {string} type the type of the slot + * @param {boolean} returnObj if the obj itself wanted + * @param {boolean} preferFreeSlot if we want a free slot (if not found, will return the first of the type anyway) + * @return {number_or_object} the slot (-1 if not found) + */ + LGraphNode.prototype.findSlotByType = function(input, type, returnObj, preferFreeSlot, doNotUseOccupied) { + input = input || false; + returnObj = returnObj || false; + preferFreeSlot = preferFreeSlot || false; + doNotUseOccupied = doNotUseOccupied || false; + var aSlots = input ? this.inputs : this.outputs; + if (!aSlots) { + return -1; + } + // !! empty string type is considered 0, * !! + if (type == "" || type == "*") type = 0; + for (var i = 0, l = aSlots.length; i < l; ++i) { + var tFound = false; + var aSource = (type+"").toLowerCase().split(","); + var aDest = aSlots[i].type=="0"||aSlots[i].type=="*"?"0":aSlots[i].type; + aDest = (aDest+"").toLowerCase().split(","); + for(sI=0;sI= 0 && target_slot !== null){ + //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) + return this.connect(slot, target_node, target_slot); + }else{ + //console.log("type "+target_slotType+" not found or not free?") + if (opts.createEventInCase && target_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onTrigger IN SLOT + //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); + return this.connect(slot, target_node, -1); + } + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var target_slot = target_node.findInputSlotByType(0, false, true, true); + //console.debug("connect TO a general type (*, 0), if not found the specific type ",target_slotType," to ",target_node,"RES_SLOT:",target_slot); + if (target_slot >= 0){ + return this.connect(slot, target_node, target_slot); + } + } + // 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 == "")){ + 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){ + return this.connect(slot, target_node, target_slot); + } + } + + console.debug("no way to connect type: ",target_slotType," to targetNODE ",target_node); + //TODO filter + + return null; + } + } + + /** + * connect this node input to the output of another node BY TYPE + * @method connectByType + * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot) + * @param {LGraphNode} node the target node + * @param {string} target_type the output slot type of the target node + * @return {Object} the link_info is created, otherwise null + */ + LGraphNode.prototype.connectByTypeOutput = function(slot, source_node, source_slotType, optsIn) { + var optsIn = optsIn || {}; + var optsDef = { createEventInCase: true + ,firstFreeIfInputGeneralInCase: true + ,generalTypeInCase: true + }; + var opts = Object.assign(optsDef,optsIn); + if (source_node && source_node.constructor === Number) { + source_node = this.graph.getNodeById(source_node); + } + source_slot = source_node.findOutputSlotByType(source_slotType, false, true); + if (source_slot >= 0 && source_slot !== null){ + //console.debug("CONNbyTYPE OUT! type "+source_slotType+" for "+source_slot) + return source_node.connect(source_slot, this, slot); + }else{ + + // connect to the first general output slot if not found a specific type and + if (opts.generalTypeInCase){ + var source_slot = source_node.findOutputSlotByType(0, false, true, true); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ + // WILL CREATE THE onExecuted OUT SLOT + if (LiteGraph.do_add_triggers_slots){ + var source_slot = source_node.addOnExecutedOutput(); + return source_node.connect(source_slot, this, slot); + } + } + // 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 == "")){ + var source_slot = source_node.findOutputSlotFree({typesNotAccepted: [LiteGraph.EVENT] }); + if (source_slot >= 0){ + return source_node.connect(source_slot, this, slot); + } + } + + console.debug("no way to connect byOUT type: ",source_slotType," to sourceNODE ",source_node); + //TODO filter + + //console.log("type OUT! "+source_slotType+" not found or not free?") + return null; + } + } + /** * connect this node output to the input of another node * @method connect @@ -3701,14 +4184,16 @@ return null; } } else if (target_slot === LiteGraph.EVENT) { - //search for first slot with event? - /* - //create input for trigger - var input = target_node.addInput("onTrigger", LiteGraph.EVENT ); - target_slot = target_node.inputs.length - 1; //last one is the one created - target_node.mode = LiteGraph.ON_TRIGGER; - */ - return null; + + if (LiteGraph.do_add_triggers_slots){ + //search for first slot with event? :: NO this is done outside + //console.log("Connect: Creating triggerEvent"); + // force mode + target_node.changeMode(LiteGraph.ON_TRIGGER); + target_slot = target_node.findInputSlot("onTrigger"); + }else{ + return null; // -- break -- + } } else if ( !target_node.inputs || target_slot >= target_node.inputs.length @@ -3721,45 +4206,69 @@ var changed = false; - //if there is something already plugged there, disconnect - if (target_node.inputs[target_slot].link != null) { - this.graph.beforeChange(); - target_node.disconnectInput(target_slot); - changed = true; - } - - //why here?? - //this.setDirtyCanvas(false,true); - //this.graph.connectionChange( this ); - - var output = this.outputs[slot]; - - //allows nodes to block connection - if (target_node.onConnectInput) { - if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { - return null; - } - } - var input = target_node.inputs[target_slot]; var link_info = null; + var output = this.outputs[slot]; + + if (!this.outputs[slot]){ + /*console.debug("Invalid slot passed: "+slot); + console.debug(this.outputs);*/ + return null; + } - //this slots cannot be connected (different types) - if (!LiteGraph.isValidConnection(output.type, input.type)) + // allow target node to change slot + if (target_node.onBeforeConnectInput) { + // This way node can choose another slot (or make a new one?) + target_slot = target_node.onBeforeConnectInput(target_slot); //callback + } + + //check target_slot and check connection types + if (target_slot===false || target_slot===null || !LiteGraph.isValidConnection(output.type, input.type)) { this.setDirtyCanvas(false, true); if(changed) this.graph.connectionChange(this, link_info); return null; + }else{ + //console.debug("valid connection",output.type, input.type); } - if(!changed) - this.graph.beforeChange(); + //allows nodes to block connection, callback + if (target_node.onConnectInput) { + if ( target_node.onConnectInput(target_slot, output.type, output, this, slot) === false ) { + return null; + } + } + if (this.onConnectOutput) { // callback + if ( this.onConnectOutput(slot, input.type, input, target_node, target_slot) === false ) { + return null; + } + } + //if there is something already plugged there, disconnect + if (target_node.inputs[target_slot] && target_node.inputs[target_slot].link != null) { + this.graph.beforeChange(); + target_node.disconnectInput(target_slot, {doProcessChange: false}); + changed = true; + } + if (output.links !== null && output.links.length){ + switch(output.type){ + case LiteGraph.EVENT: + if (!LiteGraph.allow_multi_output_for_events){ + this.graph.beforeChange(); + this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); + changed = true; + } + break; + default: + break; + } + } + //create link class link_info = new LLink( ++this.graph.last_link_id, - input.type, + input.type || output.type, this.id, slot, target_node.id, @@ -4671,9 +5180,19 @@ LGraphNode.prototype.executeAction = function(action) this.default_link_color = LiteGraph.LINK_COLOR; this.default_connection_color = { input_off: "#778", - input_on: "#7F7", + input_on: "#7F7", //"#BBD" output_off: "#778", - output_on: "#7F7" + output_on: "#7F7" //"#BBD" + }; + this.default_connection_color_byType = { + /*number: "#7F7", + string: "#77F", + boolean: "#F77",*/ + } + this.default_connection_color_byTypeOff = { + /*number: "#474", + string: "#447", + boolean: "#744",*/ }; this.highquality_render = true; @@ -5032,7 +5551,7 @@ LGraphNode.prototype.executeAction = function(action) this._mousemove_callback = this.processMouseMove.bind(this); this._mouseup_callback = this.processMouseUp.bind(this); - //touch events -- THIS WAY DOES NOT WORK, finish implementing pointerevents, than clean the touchevents + //touch events -- TODO IMPLEMENT //this._touch_callback = this.touchHandler.bind(this); LiteGraph.pointerListenerAdd(canvas,"down", this._mousedown_callback, true); //down do not need to store the binded @@ -5271,8 +5790,9 @@ LGraphNode.prototype.executeAction = function(action) LiteGraph.pointerListenerAdd(ref_window.document,"up", this._mouseup_callback,true); } - if(!is_inside) + if(!is_inside){ return; + } var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, this.visible_nodes, 5 ); var skip_dragging = false; @@ -5317,6 +5837,27 @@ LGraphNode.prototype.executeAction = function(action) skip_action = true; } + // clone node ALT dragging + if (LiteGraph.alt_drag_do_clone_nodes && e.altKey && node && this.allow_interaction && !skip_action && !this.read_only) + { + if (cloned = node.clone()){ + cloned.pos[0] += 5; + cloned.pos[1] += 5; + this.graph.add(cloned,false,{doCalcSize: false}); + node = cloned; + skip_action = true; + if (!block_drag_node) { + if (this.allow_dragnodes) { + this.graph.beforeChange(); + this.node_dragged = node; + } + if (!this.selected_nodes[node.id]) { + this.processNodeSelected(node, e); + } + } + } + } + var clicking_canvas_bg = false; //when clicked on top of a node @@ -5327,7 +5868,11 @@ LGraphNode.prototype.executeAction = function(action) } //if it wasn't selected? //not dragging mouse to connect two slots - if ( !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { + if ( + !this.connecting_node && + !node.flags.collapsed && + !this.live_mode + ) { //Search for corner for resize if ( !skip_action && @@ -5341,7 +5886,7 @@ LGraphNode.prototype.executeAction = function(action) 10 ) ) { - this.graph.beforeChange(); + this.graph.beforeChange(); this.resizing_node = node; this.canvas.style.cursor = "se-resize"; skip_action = true; @@ -5363,11 +5908,14 @@ LGraphNode.prototype.executeAction = function(action) ) { this.connecting_node = node; this.connecting_output = output; + this.connecting_output.slot_index = i; this.connecting_pos = node.getConnectionPos( false, i ); this.connecting_slot = i; - if (e.shiftKey) { - node.disconnectOutput(i); + if (LiteGraph.shift_click_do_break_link_from){ + if (e.shiftKey) { + node.disconnectOutput(i); + } } if (is_double_click) { @@ -5415,12 +5963,22 @@ LGraphNode.prototype.executeAction = function(action) var link_info = this.graph.links[ input.link ]; //before disconnecting - node.disconnectInput(i); + if (LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + this.dirty_bgcanvas = true; + skip_action = true; + }else{ + // do same action as has not node ? + } if ( this.allow_reconnect_links || + //this.move_destination_link_without_shift || e.shiftKey ) { + if (!LiteGraph.click_do_break_link_to){ + node.disconnectInput(i); + } this.connecting_node = this.graph._nodes_by_id[ link_info.origin_id ]; @@ -5430,8 +5988,24 @@ LGraphNode.prototype.executeAction = function(action) this.connecting_slot ]; this.connecting_pos = this.connecting_node.getConnectionPos( false, this.connecting_slot ); + + this.dirty_bgcanvas = true; + skip_action = true; } + + }else{ + // has not node + } + + if (!skip_action){ + // connect from in to out, from to to from + this.connecting_node = node; + this.connecting_input = input; + this.connecting_input.slot_index = i; + this.connecting_pos = node.getConnectionPos( true, i ); + this.connecting_slot = i; + this.dirty_bgcanvas = true; skip_action = true; } @@ -5498,46 +6072,51 @@ LGraphNode.prototype.executeAction = function(action) } } //clicked outside of nodes else { - //search for link connector - if(!this.read_only) - for (var i = 0; i < this.visible_links.length; ++i) { - var link = this.visible_links[i]; - var center = link._pos; - if ( - !center || - e.canvasX < center[0] - 4 || - e.canvasX > center[0] + 4 || - e.canvasY < center[1] - 4 || - e.canvasY > center[1] + 4 - ) { - continue; + if (!skip_action){ + //search for link connector + if(!this.read_only) { + for (var i = 0; i < this.visible_links.length; ++i) { + var link = this.visible_links[i]; + var center = link._pos; + if ( + !center || + e.canvasX < center[0] - 4 || + e.canvasX > center[0] + 4 || + e.canvasY < center[1] - 4 || + e.canvasY > center[1] + 4 + ) { + continue; + } + //link clicked + this.showLinkMenu(link, e); + this.over_link_center = null; //clear tooltip + break; } - //link clicked - this.showLinkMenu(link, e); - this.over_link_center = null; //clear tooltip - break; } - this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); - this.selected_group_resizing = false; - if (this.selected_group && !this.read_only ) { - if (e.ctrlKey) { - this.dragging_rectangle = null; - } + this.selected_group = this.graph.getGroupOnPos( e.canvasX, e.canvasY ); + this.selected_group_resizing = false; + if (this.selected_group && !this.read_only ) { + if (e.ctrlKey) { + this.dragging_rectangle = null; + } - var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); - if (dist * this.ds.scale < 10) { - this.selected_group_resizing = true; - } else { - this.selected_group.recomputeInsideNodes(); - } - } + var dist = distance( [e.canvasX, e.canvasY], [ this.selected_group.pos[0] + this.selected_group.size[0], this.selected_group.pos[1] + this.selected_group.size[1] ] ); + if (dist * this.ds.scale < 10) { + this.selected_group_resizing = true; + } else { + this.selected_group.recomputeInsideNodes(); + } + } - if (is_double_click && !this.read_only && this.allow_searchbox) { - this.showSearchBox(e); - } + if (is_double_click && !this.read_only && this.allow_searchbox) { + this.showSearchBox(e); + e.preventDefault(); + e.stopPropagation(); + } - clicking_canvas_bg = true; + clicking_canvas_bg = true; + } } if (!skip_action && clicking_canvas_bg && this.allow_dragcanvas) { @@ -5547,10 +6126,91 @@ LGraphNode.prototype.executeAction = function(action) } else if (e.which == 2) { //middle button + + if (LiteGraph.middle_click_slot_add_default_node){ + if (node && this.allow_interaction && !skip_action && !this.read_only){ + //not dragging mouse to connect two slots + if ( + !this.connecting_node && + !node.flags.collapsed && + !this.live_mode + ) { + var mClikSlot = false; + var mClikSlot_index = false; + var mClikSlot_isOut = false; + //search for outputs + if (node.outputs) { + for ( var i = 0, l = node.outputs.length; i < l; ++i ) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = output; + mClikSlot_index = i; + mClikSlot_isOut = true; + break; + } + } + } + + //search for inputs + if (node.inputs) { + for ( var i = 0, l = node.inputs.length; i < l; ++i ) { + var input = node.inputs[i]; + var link_pos = node.getConnectionPos(true, i); + if (isInsideRectangle(e.canvasX,e.canvasY,link_pos[0] - 15,link_pos[1] - 10,30,20)) { + mClikSlot = input; + mClikSlot_index = i; + mClikSlot_isOut = false; + break; + } + } + } + //console.log("middleClickSlots? "+mClikSlot+" & "+(mClikSlot_index!==false)); + if (mClikSlot && mClikSlot_index!==false){ + + var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); + var node_bounding = node.getBounding(); + // estimate a position: this is a bad semi-bad-working mess .. REFACTOR with a correct autoplacement that knows about the others slots and nodes + var posRef = [ (!mClikSlot_isOut?node_bounding[0]:node_bounding[0]+node_bounding[2])// + node_bounding[0]/this.canvas.width*150 + ,e.canvasY-80// + node_bounding[0]/this.canvas.width*66 // vertical "derive" + ]; + var nodeCreated = this.createDefaultNodeForSlot({ nodeFrom: !mClikSlot_isOut?null:node + ,slotFrom: !mClikSlot_isOut?null:mClikSlot_index + ,nodeTo: !mClikSlot_isOut?node:null + ,slotTo: !mClikSlot_isOut?mClikSlot_index:null + ,position: posRef //,e: e + ,nodeType: "AUTO" //nodeNewType + ,posAdd:[!mClikSlot_isOut?-30:30, -alphaPosY*130] //-alphaPosY*30] + ,posSizeFix:[!mClikSlot_isOut?-1:0, 0] //-alphaPosY*2*/ + }); + + } + } + } + } + } else if (e.which == 3 || this.pointer_is_double) { + //right button - if(!this.read_only) - this.processContextMenu(node, e); + if (this.allow_interaction && !skip_action && !this.read_only){ + + // is it hover a node ? + if (node){ + if(Object.keys(this.selected_nodes).length + && (this.selected_nodes[node.id] || e.shiftKey || e.ctrlKey || e.metaKey) + ){ + // is multiselected or using shift to include the now node + if (!this.selected_nodes[node.id]) this.selectNodes([node],true); // add this if not present + }else{ + // update selection + this.selectNodes([node]); + } + } + + // show menu on this node + this.processContextMenu(node, e); + } + } //TODO @@ -5713,23 +6373,47 @@ LGraphNode.prototype.executeAction = function(action) //if dragging a link if (this.connecting_node) { - var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput + + if (this.connecting_output){ + + var pos = this._highlight_input || [0, 0]; //to store the output of isOverNodeInput - //on top of input - if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { - //mouse on top of the corner box, don't know what to do - } else { - //check if I have a slot below de mouse - var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); - if (slot != -1 && node.inputs[slot]) { - var slot_type = node.inputs[slot].type; - if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { - this._highlight_input = pos; - this._highlight_input_slot = node.inputs[slot]; - } + //on top of input + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do } else { - this._highlight_input = null; - this._highlight_input_slot = null; + //check if I have a slot below de mouse + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.inputs[slot]) { + var slot_type = node.inputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { + this._highlight_input = pos; + this._highlight_input_slot = node.inputs[slot]; // XXX CHECK THIS + } + } else { + this._highlight_input = null; + this._highlight_input_slot = null; // XXX CHECK THIS + } + } + + }else if(this.connecting_input){ + + var pos = this._highlight_output || [0, 0]; //to store the output of isOverNodeOutput + + //on top of output + if (this.isOverNodeBox(node, e.canvasX, e.canvasY)) { + //mouse on top of the corner box, don't know what to do + } else { + //check if I have a slot below de mouse + var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); + if (slot != -1 && node.outputs[slot]) { + var slot_type = node.outputs[slot].type; + if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { + this._highlight_output = pos; + } + } else { + this._highlight_output = null; + } } } } @@ -5897,11 +6581,17 @@ LGraphNode.prototype.executeAction = function(action) } this.selected_group_resizing = false; + var node = this.graph.getNodeOnPos( + e.canvasX, + e.canvasY, + this.visible_nodes + ); + if (this.dragging_rectangle) { if (this.graph) { var nodes = this.graph._nodes; var node_bounding = new Float32Array(4); - this.deselectAllNodes(); + //compute bounding and flip if left to right var w = Math.abs(this.dragging_rectangle[2]); var h = Math.abs(this.dragging_rectangle[3]); @@ -5918,24 +6608,31 @@ LGraphNode.prototype.executeAction = function(action) this.dragging_rectangle[2] = w; this.dragging_rectangle[3] = h; - //test against all nodes (not visible because the rectangle maybe start outside - var to_select = []; - for (var i = 0; i < nodes.length; ++i) { - var node = nodes[i]; - node.getBounding(node_bounding); - if ( - !overlapBounding( - this.dragging_rectangle, - node_bounding - ) - ) { - continue; - } //out of the visible area - to_select.push(node); - } - if (to_select.length) { - this.selectNodes(to_select); - } + // test dragging rect size, if minimun simulate a click + if (!node || (w > 10 && h > 10 )){ + //test against all nodes (not visible because the rectangle maybe start outside + var to_select = []; + for (var i = 0; i < nodes.length; ++i) { + var nodeX = nodes[i]; + nodeX.getBounding(node_bounding); + if ( + !overlapBounding( + this.dragging_rectangle, + node_bounding + ) + ) { + continue; + } //out of the visible area + to_select.push(nodeX); + } + if (to_select.length) { + this.selectNodes(to_select,e.shiftKey); // add to selection with shift + } + }else{ + // will select of update selection + this.selectNodes([node],e.shiftKey||e.ctrlKey); // add to selection add to selection with ctrlKey or shiftKey + } + } this.dragging_rectangle = null; } else if (this.connecting_node) { @@ -5943,66 +6640,86 @@ LGraphNode.prototype.executeAction = function(action) this.dirty_canvas = true; this.dirty_bgcanvas = true; - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - + var connInOrOut = this.connecting_output || this.connecting_input; + var connType = connInOrOut.type; + //node below mouse if (node) { + + /* no need to condition on event type.. just another type if ( - this.connecting_output.type == LiteGraph.EVENT && + connType == LiteGraph.EVENT && this.isOverNodeBox(node, e.canvasX, e.canvasY) ) { + this.connecting_node.connect( this.connecting_slot, node, LiteGraph.EVENT ); - } else { + + } else {*/ + //slot below mouse? connect - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY - ); - if (slot != -1) { - this.connecting_node.connect( - this.connecting_slot, + + if (this.connecting_output){ + + var slot = this.isOverNodeInput( node, - slot + e.canvasX, + e.canvasY ); - } else { - //not on top of an input - var input = node.getInputInfo(0); - //auto connect - if ( - this.connecting_output.type == LiteGraph.EVENT - ) { - this.connecting_node.connect( - this.connecting_slot, - node, - LiteGraph.EVENT - ); - } else if ( - input && - !input.link && - LiteGraph.isValidConnection( - input.type && this.connecting_output.type - ) - ) { - this.connecting_node.connect( - this.connecting_slot, - node, - 0 - ); + if (slot != -1) { + this.connecting_node.connect(this.connecting_slot, node, slot); + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByType(this.connecting_slot,node,connType); } + + }else if (this.connecting_input){ + + var slot = this.isOverNodeOutput( + node, + e.canvasX, + e.canvasY + ); + + if (slot != -1) { + node.connect(slot, this.connecting_node, this.connecting_slot); // this is inverted has output-input nature like + } else { + //not on top of an input + // look for a good slot + this.connecting_node.connectByTypeOutput(this.connecting_slot,node,connType); + } + } - } + + + //} + + }else{ + + // add menu when releasing link in empty space + if (LiteGraph.release_link_on_empty_shows_menu){ + if (e.shiftKey && this.allow_searchbox){ + if(this.connecting_output){ + this.showSearchBox(e,{node_from: this.connecting_node, slot_from: this.connecting_output, type_filter_in: this.connecting_output.type}); + }else if(this.connecting_input){ + this.showSearchBox(e,{node_to: this.connecting_node, slot_from: this.connecting_input, type_filter_out: this.connecting_input.type}); + } + }else{ + if(this.connecting_output){ + this.showConnectionMenu({nodeFrom: this.connecting_node, slotFrom: this.connecting_output, e: e}); + }else if(this.connecting_input){ + this.showConnectionMenu({nodeTo: this.connecting_node, slotTo: this.connecting_input, e: e}); + } + } + } } this.connecting_output = null; + this.connecting_input = null; this.connecting_pos = null; this.connecting_node = null; this.connecting_slot = -1; @@ -6151,7 +6868,7 @@ LGraphNode.prototype.executeAction = function(action) }; /** - * returns true if a position (in graph space) is on top of a node input slot + * returns the INDEX if a position (in graph space) is on top of a node input slot * @method isOverNodeInput **/ LGraphCanvas.prototype.isOverNodeInput = function( @@ -6195,6 +6912,52 @@ LGraphNode.prototype.executeAction = function(action) } return -1; }; + + /** + * returns the INDEX if a position (in graph space) is on top of a node output slot + * @method isOverNodeOuput + **/ + LGraphCanvas.prototype.isOverNodeOutput = function( + node, + canvasx, + canvasy, + slot_pos + ) { + if (node.outputs) { + for (var i = 0, l = node.outputs.length; i < l; ++i) { + var output = node.outputs[i]; + var link_pos = node.getConnectionPos(false, i); + var is_inside = false; + if (node.horizontal) { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 5, + link_pos[1] - 10, + 10, + 20 + ); + } else { + is_inside = isInsideRectangle( + canvasx, + canvasy, + link_pos[0] - 10, + link_pos[1] - 5, + 40, + 10 + ); + } + if (is_inside) { + if (slot_pos) { + slot_pos[0] = link_pos[0]; + slot_pos[1] = link_pos[1]; + } + return i; + } + } + } + return -1; + }; /** * process a key event @@ -6214,10 +6977,17 @@ LGraphNode.prototype.executeAction = function(action) if (e.type == "keydown") { if (e.keyCode == 32) { - //esc + //space this.dragging_canvas = true; block_default = true; } + + if (e.keyCode == 27) { + //esc + if(this.node_panel) this.node_panel.close(); + if(this.options_panel) this.options_panel.close(); + block_default = true; + } //select all Control A if (e.keyCode == 65 && e.ctrlKey) { @@ -6262,6 +7032,7 @@ LGraphNode.prototype.executeAction = function(action) } } else if (e.type == "keyup") { if (e.keyCode == 32) { + // space this.dragging_canvas = false; } @@ -6348,15 +7119,38 @@ LGraphNode.prototype.executeAction = function(action) //create nodes var clipboard_info = JSON.parse(data); + // calculate top-left node, could work without this processing but using diff with last node pos :: clipboard_info.nodes[clipboard_info.nodes.length-1].pos + var posMin = false; + var posMinIndexes = false; + for (var i = 0; i < clipboard_info.nodes.length; ++i) { + if (posMin){ + if(posMin[0]>clipboard_info.nodes[i].pos[0]){ + posMin[0] = clipboard_info.nodes[i].pos[0]; + posMinIndexes[0] = i; + } + if(posMin[1]>clipboard_info.nodes[i].pos[1]){ + posMin[1] = clipboard_info.nodes[i].pos[1]; + posMinIndexes[1] = i; + } + } + else{ + posMin = [clipboard_info.nodes[i].pos[0], clipboard_info.nodes[i].pos[1]]; + posMinIndexes = [i, i]; + } + } var nodes = []; for (var i = 0; i < clipboard_info.nodes.length; ++i) { var node_data = clipboard_info.nodes[i]; var node = LiteGraph.createNode(node_data.type); if (node) { node.configure(node_data); - node.pos[0] += 5; - node.pos[1] += 5; - this.graph.add(node); + + //paste in last known mouse position + node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; + node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; + + this.graph.add(node,{doProcessChange:false}); + nodes.push(node); } } @@ -6387,8 +7181,10 @@ LGraphNode.prototype.executeAction = function(action) var x = e.clientX; var y = e.clientY; var is_inside = !this.viewport || ( this.viewport && x >= this.viewport[0] && x < (this.viewport[0] + this.viewport[2]) && y >= this.viewport[1] && y < (this.viewport[1] + this.viewport[3]) ); - if(!is_inside) + if(!is_inside){ return; + // --- BREAK --- + } var pos = [e.canvasX, e.canvasY]; @@ -6491,7 +7287,7 @@ LGraphNode.prototype.executeAction = function(action) }; LGraphCanvas.prototype.processNodeSelected = function(node, e) { - this.selectNode(node, e && e.shiftKey); + this.selectNode(node, e && (e.shiftKey||e.ctrlKey)); if (this.onNodeSelected) { this.onNodeSelected(node); } @@ -6518,12 +7314,13 @@ LGraphNode.prototype.executeAction = function(action) **/ LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) { - if (!add_to_current_selection) { + if (!add_to_current_selection) { this.deselectAllNodes(); } nodes = nodes || this.graph._nodes; - for (var i = 0; i < nodes.length; ++i) { + if (typeof nodes == "string") nodes = [nodes]; + for (var i in nodes) { var node = nodes[i]; if (node.is_selected) { continue; @@ -6658,7 +7455,7 @@ LGraphNode.prototype.executeAction = function(action) this.setDirty(true); this.graph.afterChange(); }; - + /** * centers the camera on a given node * @method centerOnNode @@ -6953,8 +7750,14 @@ LGraphNode.prototype.executeAction = function(action) if (this.connecting_pos != null) { ctx.lineWidth = this.connections_width; var link_color = null; + + var connInOrOut = this.connecting_output || this.connecting_input; - switch (this.connecting_output.type) { + var connType = connInOrOut.type; + var connDir = connInOrOut.dir; + var connShape = connInOrOut.shape; + + switch (connType) { case LiteGraph.EVENT: link_color = LiteGraph.EVENT_LINK_COLOR; break; @@ -6971,7 +7774,7 @@ LGraphNode.prototype.executeAction = function(action) false, null, link_color, - this.connecting_output.dir || + connDir || (this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT), @@ -6980,8 +7783,8 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); if ( - this.connecting_output.type === LiteGraph.EVENT || - this.connecting_output.shape === LiteGraph.BOX_SHAPE + connType === LiteGraph.EVENT || + connShape === LiteGraph.BOX_SHAPE ) { ctx.rect( this.connecting_pos[0] - 6 + 0.5, @@ -6997,7 +7800,7 @@ LGraphNode.prototype.executeAction = function(action) 14, 10 ); - } else if (this.connecting_output.shape === LiteGraph.ARROW_SHAPE) { + } else if (connShape === LiteGraph.ARROW_SHAPE) { ctx.moveTo(this.connecting_pos[0] + 8, this.connecting_pos[1] + 0.5); ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] + 6 + 0.5); ctx.lineTo(this.connecting_pos[0] - 4, this.connecting_pos[1] - 6 + 0.5); @@ -7043,6 +7846,24 @@ LGraphNode.prototype.executeAction = function(action) } ctx.fill(); } + if (this._highlight_output) { + ctx.beginPath(); + if (shape === LiteGraph.ARROW_SHAPE) { + ctx.moveTo(this._highlight_output[0] + 8, this._highlight_output[1] + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] + 6 + 0.5); + ctx.lineTo(this._highlight_output[0] - 4, this._highlight_output[1] - 6 + 0.5); + ctx.closePath(); + } else { + ctx.arc( + this._highlight_output[0], + this._highlight_output[1], + 6, + 0, + Math.PI * 2 + ); + } + ctx.fill(); + } } //the selection rectangle @@ -7081,7 +7902,7 @@ LGraphNode.prototype.executeAction = function(action) this.onDrawOverlay(ctx); } - if (area) { + if (area){ ctx.restore(); } @@ -7251,11 +8072,11 @@ LGraphNode.prototype.executeAction = function(action) bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; hovercolor = hovercolor || "#555"; textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; - + var yFix = y + LiteGraph.NODE_TITLE_HEIGHT + 2; // fix the height with the title var pos = this.mouse; - var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,yFix,w,h ); pos = this.last_click_position; - var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,yFix,w,h ); ctx.fillStyle = hover ? hovercolor : bgcolor; if(clicked) @@ -7404,7 +8225,7 @@ LGraphNode.prototype.executeAction = function(action) } else { ctx.globalAlpha = this.editor_alpha; } - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = false; + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = false; // ctx.mozImageSmoothingEnabled = if ( !this._bg_img || this._bg_img.name != this.background_image @@ -7438,7 +8259,7 @@ LGraphNode.prototype.executeAction = function(action) } ctx.globalAlpha = 1.0; - ctx.imageSmoothingEnabled = ctx.mozImageSmoothingEnabled = ctx.imageSmoothingEnabled = true; + ctx.imageSmoothingEnabled = ctx.imageSmoothingEnabled = true; //= ctx.mozImageSmoothingEnabled } //groups @@ -7614,6 +8435,7 @@ LGraphNode.prototype.executeAction = function(action) var render_text = !low_quality; var out_slot = this.connecting_output; + var in_slot = this.connecting_input; ctx.lineWidth = 1; var max_y = 0; @@ -7625,18 +8447,24 @@ LGraphNode.prototype.executeAction = function(action) if (node.inputs) { for (var i = 0; i < node.inputs.length; i++) { var slot = node.inputs[i]; - + + var slot_type = slot.type; + var slot_shape = slot.shape; + ctx.globalAlpha = editor_alpha; //change opacity of incompatible slots when dragging a connection - if ( this.connecting_node && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { + if ( this.connecting_output && !LiteGraph.isValidConnection( slot.type , out_slot.type) ) { ctx.globalAlpha = 0.4 * editor_alpha; } ctx.fillStyle = slot.link != null ? slot.color_on || + this.default_connection_color_byType[slot_type] || this.default_connection_color.input_on : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || this.default_connection_color.input_off; var pos = node.getConnectionPos(true, i, slot_pos); @@ -7648,6 +8476,12 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? + } + + var doStroke = true; + if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE @@ -7667,11 +8501,22 @@ LGraphNode.prototype.executeAction = function(action) 10 ); } - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { ctx.moveTo(pos[0] + 8, pos[1] + 0.5); ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; } else { if(low_quality) ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); //faster @@ -7696,16 +8541,21 @@ LGraphNode.prototype.executeAction = function(action) } //output connection slots - if (this.connecting_node) { - ctx.globalAlpha = 0.4 * editor_alpha; - } ctx.textAlign = horizontal ? "center" : "right"; ctx.strokeStyle = "black"; if (node.outputs) { for (var i = 0; i < node.outputs.length; i++) { var slot = node.outputs[i]; - + + var slot_type = slot.type; + var slot_shape = slot.shape; + + //change opacity of incompatible slots when dragging a connection + if (this.connecting_input && !LiteGraph.isValidConnection( slot_type , in_slot.type) ) { + ctx.globalAlpha = 0.4 * editor_alpha; + } + var pos = node.getConnectionPos(false, i, slot_pos); pos[0] -= node.pos[0]; pos[1] -= node.pos[1]; @@ -7716,15 +8566,24 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = slot.links && slot.links.length ? slot.color_on || + this.default_connection_color_byType[slot_type] || this.default_connection_color.output_on : slot.color_off || + this.default_connection_color_byTypeOff[slot_type] || + this.default_connection_color_byType[slot_type] || this.default_connection_color.output_off; ctx.beginPath(); //ctx.rect( node.size[0] - 14,i*14,10,10); + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; + } + + var doStroke = true; + if ( - slot.type === LiteGraph.EVENT || - slot.shape === LiteGraph.BOX_SHAPE + slot_type === LiteGraph.EVENT || + slot_shape === LiteGraph.BOX_SHAPE ) { if (horizontal) { ctx.rect( @@ -7741,11 +8600,22 @@ LGraphNode.prototype.executeAction = function(action) 10 ); } - } else if (slot.shape === LiteGraph.ARROW_SHAPE) { + } else if (slot_shape === LiteGraph.ARROW_SHAPE) { ctx.moveTo(pos[0] + 8, pos[1] + 0.5); ctx.lineTo(pos[0] - 4, pos[1] + 6 + 0.5); ctx.lineTo(pos[0] - 4, pos[1] - 6 + 0.5); ctx.closePath(); + } else if (slot_shape === LiteGraph.GRID_SHAPE) { + ctx.rect(pos[0] - 4, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 4, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 4, 2, 2); + ctx.rect(pos[0] - 4, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 1, pos[1] - 1, 2, 2); + ctx.rect(pos[0] + 2, pos[1] - 1, 2, 2); + ctx.rect(pos[0] - 4, pos[1] + 2, 2, 2); + ctx.rect(pos[0] - 1, pos[1] + 2, 2, 2); + ctx.rect(pos[0] + 2, pos[1] + 2, 2, 2); + doStroke = false; } else { if(low_quality) ctx.rect(pos[0] - 4, pos[1] - 4, 8, 8 ); @@ -7759,7 +8629,7 @@ LGraphNode.prototype.executeAction = function(action) //if(slot.links != null && slot.links.length) ctx.fill(); - if(!low_quality) + if(!low_quality && doStroke) ctx.stroke(); //render output name @@ -8042,7 +8912,7 @@ LGraphNode.prototype.executeAction = function(action) var grad = LGraphCanvas.gradients[title_color]; if (!grad) { grad = LGraphCanvas.gradients[ title_color ] = ctx.createLinearGradient(0, 0, 400, 0); - grad.addColorStop(0, title_color); + grad.addColorStop(0, title_color); // TODO refactor: validate color !! prevent DOMException grad.addColorStop(1, "#000"); } ctx.fillStyle = grad; @@ -8067,6 +8937,16 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; } + var colState = false; + if (LiteGraph.node_box_coloured_by_mode){ + if(LiteGraph.NODE_MODES_COLORS[node.mode]){ + colState = LiteGraph.NODE_MODES_COLORS[node.mode]; + } + } + if (LiteGraph.node_box_coloured_when_on){ + colState = node.action_triggered ? "#FFF" : (node.execute_triggered ? "#AAA" : colState); + } + //title box var box_size = 10; if (node.onDrawTitleBox) { @@ -8088,8 +8968,8 @@ LGraphNode.prototype.executeAction = function(action) ); ctx.fill(); } - - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; if(low_quality) ctx.fillRect( title_height * 0.5 - box_size *0.5, title_height * -0.5 - box_size *0.5, box_size , box_size ); else @@ -8114,7 +8994,7 @@ LGraphNode.prototype.executeAction = function(action) box_size + 2 ); } - ctx.fillStyle = node.boxcolor || LiteGraph.NODE_DEFAULT_BOXCOLOR; + ctx.fillStyle = node.boxcolor || colState || LiteGraph.NODE_DEFAULT_BOXCOLOR; ctx.fillRect( (title_height - box_size) * 0.5, (title_height + box_size) * -0.5, @@ -8247,6 +9127,10 @@ LGraphNode.prototype.executeAction = function(action) ctx.strokeStyle = fgcolor; ctx.globalAlpha = 1; } + + // these counter helps in conditioning drawing based on if the node has been executed or an action occurred + if (node.execute_triggered>0) node.execute_triggered--; + if (node.action_triggered>0) node.action_triggered--; }; var margin_area = new Float32Array(4); @@ -9290,8 +10174,6 @@ LGraphNode.prototype.executeAction = function(action) }; /* this is an implementation for touch not in production and not ready - * the idea is maybe good: simulate a similar event - * so let's try with pointerevents (working with both mouse and touch), and simulate the old mouseevents IF NECESSARY for retrocompatibility: existing old good nodes */ /*LGraphCanvas.prototype.touchHandler = function(event) { //alert("foo"); @@ -9317,7 +10199,13 @@ LGraphNode.prototype.executeAction = function(action) // screenX, screenY, clientX, clientY, ctrlKey, // altKey, shiftKey, metaKey, button, relatedTarget); - var window = this.getCanvasWindow(); + // this is eventually a Dom object, get the LGraphCanvas back + if(typeof this.getCanvasWindow == "undefined"){ + var window = this.lgraphcanvas.getCanvasWindow(); + }else{ + var window = this.getCanvasWindow(); + } + var document = window.document; var simulatedEvent = document.createEvent("MouseEvent"); @@ -9472,8 +10360,9 @@ LGraphNode.prototype.executeAction = function(action) } } - if (this.onMenuNodeInputs) { - entries = this.onMenuNodeInputs(entries); + if (node.onMenuNodeInputs) { + var retEntries = node.onMenuNodeInputs(entries); + if(retEntries) entries = retEntries; } if (!entries.length) { @@ -9504,6 +10393,10 @@ LGraphNode.prototype.executeAction = function(action) if (v.value) { node.graph.beforeChange(); node.addInput(v.value[0], v.value[1], v.value[2]); + + if (node.onNodeInputAdd) { // callback to the node when adding a slot + node.onNodeInputAdd(v.value); + } node.setDirtyCanvas(true, true); node.graph.afterChange(); } @@ -9567,6 +10460,16 @@ LGraphNode.prototype.executeAction = function(action) if (this.onMenuNodeOutputs) { entries = this.onMenuNodeOutputs(entries); } + 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: {} + } + } + // add callback for modifing the menu elements onMenuNodeOutputs + if (node.onMenuNodeOutputs) { + var retEntries = node.onMenuNodeOutputs(entries); + if(retEntries) entries = retEntries; + } if (!entries.length) { return; @@ -9617,6 +10520,10 @@ LGraphNode.prototype.executeAction = function(action) } 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 + node.onNodeOutputAdd(v.value); + } node.setDirtyCanvas(true, true); node.graph.afterChange(); } @@ -9697,20 +10604,42 @@ LGraphNode.prototype.executeAction = function(action) return e.innerHTML; }; - LGraphCanvas.onResizeNode = function(value, options, e, menu, node) { + LGraphCanvas.onMenuResizeNode = function(value, options, e, menu, node) { if (!node) { return; } - node.size = node.computeSize(); - if (node.onResize) - node.onResize(node.size); + + var fApplyMultiNode = function(node){ + node.size = node.computeSize(); + if (node.onResize) + node.onResize(node.size); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + node.setDirtyCanvas(true, true); }; LGraphCanvas.prototype.showLinkMenu = function(link, e) { var that = this; - console.log(link); - var options = ["Add Node",null,"Delete"]; + // console.log(link); + var node_left = that.graph.getNodeById( link.origin_id ); + var node_right = that.graph.getNodeById( link.target_id ); + var fromType = false; + if (node_left && node_left.outputs && node_left.outputs[link.origin_slot]) fromType = node_left.outputs[link.origin_slot].type; + var destType = false; + if (node_right && node_right.outputs && node_right.outputs[link.target_slot]) destType = node_right.inputs[link.target_slot].type; + + var options = ["Add Node",null,"Delete",null]; + + var menu = new LiteGraph.ContextMenu(options, { event: e, title: link.data != null ? link.data.constructor.name : null, @@ -9721,38 +10650,314 @@ LGraphNode.prototype.executeAction = function(action) switch (v) { case "Add Node": LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - console.log("node autoconnect"); - var node_left = that.graph.getNodeById( link.origin_id ); - var node_right = that.graph.getNodeById( link.target_id ); - if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length) + // console.debug("node autoconnect"); + if(!node.inputs || !node.inputs.length || !node.outputs || !node.outputs.length){ return; - if( node_left.outputs[ link.origin_slot ].type == node.inputs[0].type && node.outputs[0].type == node_right.inputs[0].type ) - { - node_left.connect( link.origin_slot, node, 0 ); - node.connect( 0, node_right, link.target_slot ); - node.pos[0] -= node.size[0] * 0.5; } + // leave the connection type checking inside connectByType + if (node_left.connectByType( link.origin_slot, node, fromType )){ + node.connectByType( link.target_slot, node_right, destType ); + node.pos[0] -= node.size[0] * 0.5; + } }); break; + case "Delete": that.graph.removeLink(link.id); break; default: + /*var nodeCreated = createDefaultNodeForSlot({ nodeFrom: node_left + ,slotFrom: link.origin_slot + ,nodeTo: node + ,slotTo: link.target_slot + ,e: e + ,nodeType: "AUTO" + }); + if(nodeCreated) console.log("new node in beetween "+v+" created");*/ } } return false; }; + + LGraphCanvas.prototype.createDefaultNodeForSlot = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,position: [] // pass the event coords + ,nodeType: null // choose a nodetype to add, AUTO to set at first good + ,posAdd:[0,0] // adjust x,y + ,posSizeFix:[0,0] // alpha, adjust the position x,y based on the new node size w,h + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom!==null; + var isTo = !isFrom && opts.nodeTo && opts.slotTo!==null; + + if (!isFrom && !isTo){ + console.warn("No data passed to createDefaultNodeForSlot "+opts.nodeFrom+" "+opts.slotFrom+" "+opts.nodeTo+" "+opts.slotTo); + return false; + } + if (!opts.nodeType){ + console.warn("No type to createDefaultNodeForSlot"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "undefined": + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + if (slotX===false || iSlotConn===false){ + console.warn("createDefaultNodeForSlot bad slotX "+slotX+" "+iSlotConn); + } + + // check for defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + nodeNewType = false; + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + if (opts.nodeType == slotTypesDefault[fromSlotType][typeX] || opts.nodeType == "AUTO"){ + nodeNewType = slotTypesDefault[fromSlotType][typeX]; + // console.log("opts.nodeType == slotTypesDefault[fromSlotType][typeX] :: "+opts.nodeType); + break; // -------- + } + } + }else{ + if (opts.nodeType == slotTypesDefault[fromSlotType] || opts.nodeType == "AUTO") nodeNewType = slotTypesDefault[fromSlotType]; + } + if (nodeNewType) { + var nodeNewOpts = false; + if (typeof nodeNewType == "object" && nodeNewType.node){ + nodeNewOpts = nodeNewType; + nodeNewType = nodeNewType.node; + } + + //that.graph.beforeChange(); + + var newNode = LiteGraph.createNode(nodeNewType); + if(newNode){ + // if is object pass options + if (nodeNewOpts){ + if (nodeNewOpts.properties) { + for (var i in nodeNewOpts.properties) { + newNode.addProperty( i, nodeNewOpts.properties[i] ); + } + } + if (nodeNewOpts.inputs) { + newNode.inputs = []; + for (var i in nodeNewOpts.inputs) { + newNode.addOutput( + nodeNewOpts.inputs[i][0], + nodeNewOpts.inputs[i][1] + ); + } + } + if (nodeNewOpts.outputs) { + newNode.outputs = []; + for (var i in nodeNewOpts.outputs) { + newNode.addOutput( + nodeNewOpts.outputs[i][0], + nodeNewOpts.outputs[i][1] + ); + } + } + if (nodeNewOpts.title) { + newNode.title = nodeNewOpts.title; + } + if (nodeNewOpts.json) { + newNode.configure(nodeNewOpts.json); + } + } + + // add the node + that.graph.add(newNode); + newNode.pos = [ opts.position[0]+opts.posAdd[0]+(opts.posSizeFix[0]?opts.posSizeFix[0]*newNode.size[0]:0) + ,opts.position[1]+opts.posAdd[1]+(opts.posSizeFix[1]?opts.posSizeFix[1]*newNode.size[1]:0)]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5];*/ + + //that.graph.afterChange(); + + // connect the two! + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, newNode, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, fromSlotType ); + } + + // if connecting in between + if (isFrom && isTo){ + // TODO + } + + return true; + + }else{ + console.log("failed creating "+nodeNewType); + } + } + } + return false; + } + + LGraphCanvas.prototype.showConnectionMenu = function(optPass) { // addNodeMenu for connection + var optPass = optPass || {}; + var opts = Object.assign({ nodeFrom: null // input + ,slotFrom: null // input + ,nodeTo: null // output + ,slotTo: null // output + ,e: null + } + ,optPass + ); + var that = this; + + var isFrom = opts.nodeFrom && opts.slotFrom; + var isTo = !isFrom && opts.nodeTo && opts.slotTo; + + if (!isFrom && !isTo){ + console.warn("No data passed to showConnectionMenu"); + return false; + } + + var nodeX = isFrom ? opts.nodeFrom : opts.nodeTo; + var slotX = isFrom ? opts.slotFrom : opts.slotTo; + + var iSlotConn = false; + switch (typeof slotX){ + case "string": + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX,false) : nodeX.findInputSlot(slotX,false); + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + case "object": + // ok slotX + iSlotConn = isFrom ? nodeX.findOutputSlot(slotX.name) : nodeX.findInputSlot(slotX.name); + break; + case "number": + iSlotConn = slotX; + slotX = isFrom ? nodeX.outputs[slotX] : nodeX.inputs[slotX]; + break; + default: + // bad ? + //iSlotConn = 0; + console.warn("Cant get slot information "+slotX); + return false; + } + + var options = ["Add Node",null]; + + if (that.allow_searchbox){ + options.push("Search"); + options.push(null); + } + + // get defaults nodes for this slottype + var fromSlotType = slotX.type==LiteGraph.EVENT?"_event_":slotX.type; + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[fromSlotType]){ + if(typeof slotTypesDefault[fromSlotType] == "object" || typeof slotTypesDefault[fromSlotType] == "array"){ + for(var typeX in slotTypesDefault[fromSlotType]){ + options.push(slotTypesDefault[fromSlotType][typeX]); + } + }else{ + options.push(slotTypesDefault[fromSlotType]); + } + } + + // build menu + var menu = new LiteGraph.ContextMenu(options, { + event: opts.e, + title: (slotX && slotX.name!="" ? (slotX.name + (fromSlotType?" | ":"")) : "")+(slotX && fromSlotType ? fromSlotType : ""), + callback: inner_clicked + }); + + // callback + function inner_clicked(v,options,e) { + //console.log("Process showConnectionMenu selection"); + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, node, fromSlotType ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, node, fromSlotType ); + } + }); + break; + case "Search": + if(isFrom){ + that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: fromSlotType}); + }else{ + that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: fromSlotType}); + } + break; + default: + // check for defaults nodes for this slottype + var nodeCreated = that.createDefaultNodeForSlot(Object.assign(opts,{ position: [opts.e.canvasX, opts.e.canvasY] + ,nodeType: v + })); + if (nodeCreated){ + // new node created + //console.log("node "+v+" created") + }else{ + // failed or v is not in defaults + } + break; + } + } + + return false; + }; + + // TODO refactor :: this is used fot title but not for properties! LGraphCanvas.onShowPropertyEditor = function(item, options, e, menu, node) { var input_html = ""; var property = item.property || "title"; var value = node[property]; + // TODO refactor :: use createDialog ? + var dialog = document.createElement("div"); + dialog.is_modified = false; dialog.className = "graphdialog"; - dialog.innerHTML = ""; - //dialog.innerHTML = ""; + dialog.innerHTML = + ""; + dialog.close = function() { + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); + } + }; var title = dialog.querySelector(".name"); title.innerText = property; var input = dialog.querySelector(".value"); @@ -9762,10 +10967,15 @@ LGraphNode.prototype.executeAction = function(action) this.focus(); }); input.addEventListener("keydown", function(e) { - if (e.keyCode != 13 && e.target.localName != "textarea") { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC + dialog.close(); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { return; } - inner(); e.preventDefault(); e.stopPropagation(); }); @@ -9794,8 +11004,21 @@ LGraphNode.prototype.executeAction = function(action) button.addEventListener("click", inner); canvas.parentNode.appendChild(dialog); + if(input) input.focus(); + + var dialogCloseTimer = null; + dialog.addEventListener("mouseleave", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); + }); + dialog.addEventListener("mouseenter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + function inner() { - setValue(input.value); + if(input) setValue(input.value); } function setValue(value) { @@ -9812,19 +11035,19 @@ LGraphNode.prototype.executeAction = function(action) } }; + // refactor: there are different dialogs, some uses createDialog some dont LGraphCanvas.prototype.prompt = function(title, value, callback, event, multiline) { var that = this; var input_html = ""; title = title || ""; - var modified = false; - var dialog = document.createElement("div"); + dialog.is_modified = false; dialog.className = "graphdialog rounded"; - if(multiline) + if(multiline) dialog.innerHTML = " "; else - dialog.innerHTML = " "; + dialog.innerHTML = " "; dialog.close = function() { that.prompt_box = null; if (dialog.parentNode) { @@ -9832,15 +11055,42 @@ LGraphNode.prototype.executeAction = function(action) } }; + var graphcanvas = LGraphCanvas.active_canvas; + var canvas = graphcanvas.canvas; + canvas.parentNode.appendChild(dialog); + if (this.ds.scale > 1) { dialog.style.transform = "scale(" + this.ds.scale + ")"; } - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - if (!modified) { - dialog.close(); - } + var dialogCloseTimer = null; + var prevent_timeout = false; + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout) + return; + if(LiteGraph.dialog_close_on_mouse_leave) + if (!dialog.is_modified && LiteGraph.dialog_close_on_mouse_leave) + dialogCloseTimer = setTimeout(dialog.close, LiteGraph.dialog_close_on_mouse_leave_delay); //dialog.close(); }); + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if(LiteGraph.dialog_close_on_mouse_leave) + if(dialogCloseTimer) clearTimeout(dialogCloseTimer); + }); + var selInDia = dialog.querySelectorAll("select"); + if (selInDia){ + // if filtering, check focus changed to comboboxes and prevent closing + selInDia.forEach(function(selIn) { + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + }); + } if (that.prompt_box) { that.prompt_box.close(); @@ -9858,7 +11108,7 @@ LGraphNode.prototype.executeAction = function(action) var input = value_element; input.addEventListener("keydown", function(e) { - modified = true; + dialog.is_modified = true; if (e.keyCode == 27) { //ESC dialog.close(); @@ -9883,9 +11133,6 @@ LGraphNode.prototype.executeAction = function(action) dialog.close(); }); - var graphcanvas = LGraphCanvas.active_canvas; - var canvas = graphcanvas.canvas; - var rect = canvas.getBoundingClientRect(); var offsetx = -20; var offsety = -20; @@ -9902,7 +11149,6 @@ LGraphNode.prototype.executeAction = function(action) dialog.style.top = canvas.height * 0.5 + offsety + "px"; } - canvas.parentNode.appendChild(dialog); setTimeout(function() { input.focus(); }, 10); @@ -9911,7 +11157,24 @@ LGraphNode.prototype.executeAction = function(action) }; LGraphCanvas.search_limit = -1; - LGraphCanvas.prototype.showSearchBox = function(event) { + LGraphCanvas.prototype.showSearchBox = function(event, options) { + // proposed defaults + def_options = { slot_from: null + ,node_from: null + ,node_to: null + ,do_type_filter: LiteGraph.search_filter_enabled // TODO check for registered_slot_[in/out]_types not empty // this will be checked for functionality enabled : filter on slot type, in and out + ,type_filter_in: false // these are default: pass to set initially set values + ,type_filter_out: false + ,show_general_if_none_on_typefilter: true + ,show_general_after_typefiltered: true + ,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 + }; + options = Object.assign(def_options, options || {}); + + //console.log(options); + var that = this; var input_html = ""; var graphcanvas = LGraphCanvas.active_canvas; @@ -9920,8 +11183,27 @@ LGraphNode.prototype.executeAction = function(action) var dialog = document.createElement("div"); dialog.className = "litegraph litesearchbox graphdialog rounded"; - dialog.innerHTML = - "Search
"; + dialog.innerHTML = "Search "; + if (options.do_type_filter){ + dialog.innerHTML += ""; + dialog.innerHTML += ""; + } + dialog.innerHTML += "
"; + + if( root_document.fullscreenElement ) + root_document.fullscreenElement.appendChild(dialog); + else + { + root_document.body.appendChild(dialog); + root_document.body.style.overflow = "hidden"; + } + // dialog element has been appended + + if (options.do_type_filter){ + var selIn = dialog.querySelector(".slot_in_type_filter"); + var selOut = dialog.querySelector(".slot_out_type_filter"); + } + dialog.close = function() { that.search_box = null; root_document.body.focus(); @@ -9935,25 +11217,50 @@ LGraphNode.prototype.executeAction = function(action) } }; - var timeout_close = null; - if (this.ds.scale > 1) { dialog.style.transform = "scale(" + this.ds.scale + ")"; } - LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { - if (timeout_close) { - clearTimeout(timeout_close); - timeout_close = null; + // hide on mouse leave + if(options.hide_on_mouse_leave){ + var prevent_timeout = false; + var timeout_close = null; + LiteGraph.pointerListenerAdd(dialog,"enter", function(e) { + if (timeout_close) { + clearTimeout(timeout_close); + timeout_close = null; + } + }); + LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { + if (prevent_timeout){ + return; + } + timeout_close = setTimeout(function() { + dialog.close(); + }, 500); + }); + // if filtering, check focus changed to comboboxes and prevent closing + if (options.do_type_filter){ + selIn.addEventListener("click", function(e) { + prevent_timeout++; + }); + selIn.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selIn.addEventListener("change", function(e) { + prevent_timeout = -1; + }); + selOut.addEventListener("click", function(e) { + prevent_timeout++; + }); + selOut.addEventListener("blur", function(e) { + prevent_timeout = 0; + }); + selOut.addEventListener("change", function(e) { + prevent_timeout = -1; + }); } - }); - - LiteGraph.pointerListenerAdd(dialog,"leave", function(e) { - //dialog.close(); - timeout_close = setTimeout(function() { - dialog.close(); - }, 500); - }); + } if (that.search_box) { that.search_box.close(); @@ -9993,7 +11300,7 @@ LGraphNode.prototype.executeAction = function(action) if (timeout) { clearInterval(timeout); } - timeout = setTimeout(refreshHelper, 10); + timeout = setTimeout(refreshHelper, 250); return; } e.preventDefault(); @@ -10002,15 +11309,62 @@ LGraphNode.prototype.executeAction = function(action) return true; }); } - - if( root_document.fullscreenElement ) - root_document.fullscreenElement.appendChild(dialog); - else - { - root_document.body.appendChild(dialog); - root_document.body.style.overflow = "hidden"; - } - + + // if should filter on type, load and fill selected and choose elements if passed + if (options.do_type_filter){ + if (selIn){ + var aSlots = LiteGraph.slot_types_in; + var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; + + if (options.type_filter_in == LiteGraph.EVENT || options.type_filter_in == LiteGraph.ACTION) + options.type_filter_in = "_event_"; + /* this will filter on * .. but better do it manually in case + else if(options.type_filter_in === "" || options.type_filter_in === 0) + options.type_filter_in = "*";*/ + + for (var iK=0; iK-1){ + options.node_from.connectByType( iS, node, options.node_from.outputs[iS].type ); + } + }else{ + // console.warn("cant find slot " + options.slot_from); + } + } + if (options.node_to){ + var iS = false; + switch (typeof options.slot_from){ + case "string": + iS = options.node_to.findInputSlot(options.slot_from); + break; + case "object": + if (options.slot_from.name){ + iS = options.node_to.findInputSlot(options.slot_from.name); + }else{ + iS = -1; + } + if (iS==-1 && typeof options.slot_from.slot_index !== "undefined") iS = options.slot_from.slot_index; + break; + case "number": + iS = options.slot_from; + break; + default: + iS = 0; // try with first if no name set + } + if (typeof options.node_to.inputs[iS] !== undefined){ + if (iS!==false && iS>-1){ + // try connection + options.node_to.connectByTypeOutput(iS,node,options.node_to.inputs[iS].type); + } + }else{ + // console.warn("cant find slot_nodeTO " + options.slot_from); + } + } + + graphcanvas.graph.afterChange(); } } @@ -10130,7 +11545,7 @@ LGraphNode.prototype.executeAction = function(action) var str = input.value; first = null; helper.innerHTML = ""; - if (!str) { + if (!str && !options.show_all_if_empty) { return; } @@ -10146,15 +11561,26 @@ LGraphNode.prototype.executeAction = function(action) str = str.toLowerCase(); var filter = graphcanvas.filter || graphcanvas.graph.filter; + // filter by type preprocess + if(options.do_type_filter && that.search_box){ + var sIn = that.search_box.querySelector(".slot_in_type_filter"); + var sOut = that.search_box.querySelector(".slot_out_type_filter"); + }else{ + var sIn = false; + var sOut = false; + } + //extras for (var i in LiteGraph.searchbox_extras) { var extra = LiteGraph.searchbox_extras[i]; - if (extra.desc.toLowerCase().indexOf(str) === -1) { + if ((!options.show_all_if_empty || str) && extra.desc.toLowerCase().indexOf(str) === -1) { continue; } var ctor = LiteGraph.registered_node_types[ extra.type ]; if( ctor && ctor.filter != filter ) continue; + if( ! inner_test_filter(extra.type) ) + continue; addResult( extra.desc, "searchbox_extra" ); if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { break; @@ -10179,13 +11605,98 @@ LGraphNode.prototype.executeAction = function(action) break; } } - - function inner_test_filter( type ) + + // add general type if filtering + if (options.show_general_after_typefiltered + && (sIn.value || sOut.value) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {inTypeOverride: sIn&&sIn.value?"*":false, outTypeOverride: sOut&&sOut.value?"*":false}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "generic_type"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + // check il filtering gave no results + if ((sIn.value || sOut.value) && + ( (helper.childNodes.length == 0 && options.show_general_if_none_on_typefilter) ) + ){ + filtered_extra = []; + for (var i in LiteGraph.registered_node_types) { + if( inner_test_filter(i, {skipFilter: true}) ) + filtered_extra.push(i); + } + for (var i = 0; i < filtered_extra.length; i++) { + addResult(filtered_extra[i], "not_in_filter"); + if ( LGraphCanvas.search_limit !== -1 && c++ > LGraphCanvas.search_limit ) { + break; + } + } + } + + function inner_test_filter( type, optsIn ) { + var optsIn = optsIn || {}; + var optsDef = { skipFilter: false + ,inTypeOverride: false + ,outTypeOverride: false + }; + var opts = Object.assign(optsDef,optsIn); var ctor = LiteGraph.registered_node_types[ type ]; if(filter && ctor.filter != filter ) return false; - return type.toLowerCase().indexOf(str) !== -1; + if ((!options.show_all_if_empty || str) && type.toLowerCase().indexOf(str) === -1) + return false; + + // filter by slot IN, OUT types + if(options.do_type_filter && !opts.skipFilter){ + var sType = type; + + var sV = sIn.value; + if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sIn && sV){ + //console.log("will check filter against "+sV); + if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_in_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_in_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + + var sV = sOut.value; + if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 + + if(sOut && sV){ + //console.log("search will check filter against "+sV); + if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored + //console.debug("check "+sType+" in "+LiteGraph.registered_slot_out_types[sV].nodes); + var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + /*console.debug(LiteGraph.registered_slot_out_types[sV]); + console.log(+" DONT includes "+type);*/ + return false; + } + } + } + } + return true; } } @@ -10242,7 +11753,7 @@ LGraphNode.prototype.executeAction = function(action) ""; } input_html += ""; - } else if (type == "boolean") { + } else if (type == "boolean" || type == "toggle") { input_html = "=0 && options.values[kV]){ + console.debug("update graph options: "+options.key+": "+kV); + graphcanvas[options.key] = kV; + //console.debug(graphcanvas); + break; + } + } + console.warn("unexpected options"); + console.debug(options); + break;*/ + default: + //console.debug("want to update graph options: "+name+": "+value); + if (options && options.key){ + name = options.key; + } + if (options.values){ + value = Object.values(options.values).indexOf(value); + } + //console.debug("update graph option: "+name+": "+value); + graphcanvas[name] = value; + break; + } + }; + + // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement + + var aProps = LiteGraph.availableCanvasOptions; + aProps.sort(); + for(pI in aProps){ + var pX = aProps[pI]; + panel.addWidget( "boolean", pX, graphcanvas[pX], {key: pX, on: "True", off: "False"}, fUpdate); + } + + var aLinks = [ graphcanvas.links_render_mode ]; + panel.addWidget( "combo", "Render mode", LiteGraph.LINK_RENDER_MODES[graphcanvas.links_render_mode], {key: "links_render_mode", values: LiteGraph.LINK_RENDER_MODES}, fUpdate); + + panel.addSeparator(); + + panel.footer.innerHTML = ""; // clear + + } + inner_refresh(); + + graphcanvas.canvas.parentNode.appendChild( panel ); + } + + LGraphCanvas.prototype.showShowNodePanel = function( node ) + { + this.SELECTED_NODE = node; + this.closePanels(); var ref_window = this.getCanvasWindow(); - panel = this.createPanel(node.title || "",{closable: true, window: ref_window }); + var that = this; + var graphcanvas = this; + panel = this.createPanel(node.title || "",{ + closable: true + ,window: ref_window + ,onOpen: function(){ + graphcanvas.NODEPANEL_IS_OPEN = true; + } + ,onClose: function(){ + graphcanvas.NODEPANEL_IS_OPEN = false; + graphcanvas.node_panel = null; + } + }); + graphcanvas.node_panel = panel; panel.id = "node-panel"; panel.node = node; panel.classList.add("settings"); - var that = this; - var graphcanvas = this; function inner_refresh() { @@ -10586,22 +12301,58 @@ LGraphNode.prototype.executeAction = function(action) panel.addHTML("

Properties

"); - for(var i in node.properties) + var fUpdate = function(name,value){ + graphcanvas.graph.beforeChange(node); + switch(name){ + case "Title": + node.title = value; + break; + case "Mode": + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(value); + if (kV>=0 && LiteGraph.NODE_MODES[kV]){ + node.changeMode(kV); + }else{ + console.warn("unexpected mode: "+value); + } + break; + case "Color": + if (LGraphCanvas.node_colors[value]){ + node.color = LGraphCanvas.node_colors[value].color; + node.bgcolor = LGraphCanvas.node_colors[value].bgcolor; + }else{ + console.warn("unexpected color: "+value); + } + break; + default: + node.setProperty(name,value); + break; + } + graphcanvas.graph.afterChange(); + graphcanvas.dirty_canvas = true; + }; + + panel.addWidget( "string", "Title", node.title, {}, fUpdate); + + panel.addWidget( "combo", "Mode", LiteGraph.NODE_MODES[node.mode], {values: LiteGraph.NODE_MODES}, fUpdate); + + var nodeCol = ""; + if (node.color !== undefined){ + nodeCol = Object.keys(LGraphCanvas.node_colors).filter(function(nK){ return LGraphCanvas.node_colors[nK].color == node.color; }); + } + + panel.addWidget( "combo", "Color", nodeCol, {values: Object.keys(LGraphCanvas.node_colors)}, fUpdate); + + for(var pName in node.properties) { - var value = node.properties[i]; - var info = node.getPropertyInfo(i); + var value = node.properties[pName]; + var info = node.getPropertyInfo(pName); var type = info.type || "string"; //in case the user wants control over the side panel widget - if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(i,panel) ) + if( node.onAddPropertyToPanel && node.onAddPropertyToPanel(pName,panel) ) continue; - panel.addWidget( info.widget || info.type, i, value, info, function(name,value){ - graphcanvas.graph.beforeChange(node); - node.setProperty(name,value); - graphcanvas.graph.afterChange(); - graphcanvas.dirty_canvas = true; - }); + panel.addWidget( info.widget || info.type, pName, value, info, fUpdate); } panel.addSeparator(); @@ -10609,13 +12360,7 @@ LGraphNode.prototype.executeAction = function(action) if(node.onShowCustomPanelInfo) node.onShowCustomPanelInfo(panel); - /* - panel.addHTML("

Connections

"); - var connection_containers = panel.addHTML("
","connections"); - var inputs = connection_containers.querySelector(".inputs"); - var outputs = connection_containers.querySelector(".outputs"); - */ - + panel.footer.innerHTML = ""; // clear panel.addButton("Delete",function(){ if(node.block_delete) return; @@ -10624,14 +12369,13 @@ LGraphNode.prototype.executeAction = function(action) }).classList.add("delete"); } - function inner_showCodePad( node, propname ) + panel.inner_showCodePad = function( propname ) { - panel.style.top = "calc( 50% - 250px)"; - panel.style.left = "calc( 50% - 400px)"; - panel.style.width = "800px"; - panel.style.height = "500px"; + panel.classList.remove("settings"); + panel.classList.add("centered"); - if(window.CodeFlask) //disabled for now + + /*if(window.CodeFlask) //disabled for now { panel.content.innerHTML = "
"; var flask = new CodeFlask( "div.code", { language: 'js' }); @@ -10641,30 +12385,37 @@ LGraphNode.prototype.executeAction = function(action) }); } else - { - panel.content.innerHTML = ""; - var textarea = panel.content.querySelector("textarea"); + {*/ + panel.alt_content.innerHTML = ""; + var textarea = panel.alt_content.querySelector("textarea"); + var fDoneWith = function(){ + panel.toggleAltContent(false); //if(node_prop_div) node_prop_div.style.display = "block"; // panel.close(); + panel.toggleFooterVisibility(true); + textarea.parentNode.removeChild(textarea); + panel.classList.add("settings"); + panel.classList.remove("centered"); + inner_refresh(); + } textarea.value = node.properties[propname]; textarea.addEventListener("keydown", function(e){ - //console.log(e); if(e.code == "Enter" && e.ctrlKey ) { - console.log("Assigned"); node.setProperty(propname, textarea.value); + fDoneWith(); } }); + panel.toggleAltContent(true); + panel.toggleFooterVisibility(false); textarea.style.height = "calc(100% - 40px)"; - } - var assign = that.createButton( "Assign", null, function(){ + /*}*/ + var assign = panel.addButton( "Assign", function(){ node.setProperty(propname, textarea.value); + fDoneWith(); }); - panel.content.appendChild(assign); - var button = that.createButton( "Close", null, function(){ - panel.style.height = ""; - inner_refresh(); - }); + panel.alt_content.appendChild(assign); //panel.content.appendChild(assign); + var button = panel.addButton( "Close", fDoneWith); button.style.float = "right"; - panel.content.appendChild(button); + panel.alt_content.appendChild(button); // panel.content.appendChild(button); } inner_refresh(); @@ -10804,9 +12555,22 @@ LGraphNode.prototype.executeAction = function(action) } LGraphCanvas.onMenuNodeCollapse = function(value, options, e, menu, node) { - node.graph.beforeChange(node); - node.collapse(); - node.graph.afterChange(node); + node.graph.beforeChange(/*?*/); + + var fApplyMultiNode = function(node){ + node.collapse(); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); }; LGraphCanvas.onMenuNodePin = function(value, options, e, menu, node) { @@ -10815,7 +12579,7 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.onMenuNodeMode = function(value, options, e, menu, node) { new LiteGraph.ContextMenu( - ["Always", "On Event", "On Trigger", "Never"], + LiteGraph.NODE_MODES, { event: e, callback: inner_clicked, parentMenu: menu, node: node } ); @@ -10823,21 +12587,24 @@ LGraphNode.prototype.executeAction = function(action) if (!node) { return; } - switch (v) { - case "On Event": - node.mode = LiteGraph.ON_EVENT; - break; - case "On Trigger": - node.mode = LiteGraph.ON_TRIGGER; - break; - case "Never": - node.mode = LiteGraph.NEVER; - break; - case "Always": - default: - node.mode = LiteGraph.ALWAYS; - break; - } + var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); + var fApplyMultiNode = function(node){ + if (kV>=0 && LiteGraph.NODE_MODES[kV]) + node.changeMode(kV); + else{ + console.warn("unexpected mode: "+v); + node.changeMode(LiteGraph.ALWAYS); + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } } return false; @@ -10883,17 +12650,29 @@ LGraphNode.prototype.executeAction = function(action) } var color = v.value ? LGraphCanvas.node_colors[v.value] : null; - if (color) { - if (node.constructor === LiteGraph.LGraphGroup) { - node.color = color.groupcolor; - } else { - node.color = color.color; - node.bgcolor = color.bgcolor; - } - } else { - delete node.color; - delete node.bgcolor; - } + + var fApplyColor = function(node){ + if (color) { + if (node.constructor === LiteGraph.LGraphGroup) { + node.color = color.groupcolor; + } else { + node.color = color.color; + node.bgcolor = color.bgcolor; + } + } else { + delete node.color; + delete node.bgcolor; + } + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyColor(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyColor(graphcanvas.selected_nodes[i]); + } + } node.setDirtyCanvas(true, true); } @@ -10916,9 +12695,22 @@ LGraphNode.prototype.executeAction = function(action) if (!node) { return; } - node.graph.beforeChange(node); - node.shape = v; - node.graph.afterChange(node); + node.graph.beforeChange(/*?*/); //node + + var fApplyMultiNode = function(node){ + node.shape = v; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + node.graph.afterChange(/*?*/); //node node.setDirtyCanvas(true); } @@ -10930,13 +12722,26 @@ LGraphNode.prototype.executeAction = function(action) throw "no node passed"; } - if (node.removable === false) { - return; - } - var graph = node.graph; graph.beforeChange(); - graph.remove(node); + + + var fApplyMultiNode = function(node){ + if (node.removable === false) { + return; + } + graph.remove(node); + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + graph.afterChange(); node.setDirtyCanvas(true, true); }; @@ -10962,17 +12767,37 @@ LGraphNode.prototype.executeAction = function(action) }; LGraphCanvas.onMenuNodeClone = function(value, options, e, menu, node) { - if (node.clonable == false) { - return; - } - var newnode = node.clone(); - if (!newnode) { - return; - } - newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; - + node.graph.beforeChange(); - node.graph.add(newnode); + + var newSelected = {}; + + var fApplyMultiNode = function(node){ + if (node.clonable == false) { + return; + } + var newnode = node.clone(); + if (!newnode) { + return; + } + newnode.pos = [node.pos[0] + 5, node.pos[1] + 5]; + node.graph.add(newnode); + newSelected[newnode.id] = newnode; + } + + var graphcanvas = LGraphCanvas.active_canvas; + if (!graphcanvas.selected_nodes || Object.keys(graphcanvas.selected_nodes).length <= 1){ + fApplyMultiNode(node); + }else{ + for (var i in graphcanvas.selected_nodes) { + fApplyMultiNode(graphcanvas.selected_nodes[i]); + } + } + + if(Object.keys(newSelected).length){ + graphcanvas.selectNodes(newSelected); + } + node.graph.afterChange(); node.setDirtyCanvas(true, true); @@ -10996,6 +12821,7 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.getCanvasMenuOptions = function() { var options = null; + var that = this; if (this.getMenuOptions) { options = this.getMenuOptions(); } else { @@ -11005,9 +12831,13 @@ LGraphNode.prototype.executeAction = function(action) has_submenu: true, callback: LGraphCanvas.onMenuAdd }, - { content: "Add Group", callback: LGraphCanvas.onGroupAdd } + { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, + //{ content: "Arrange", callback: that.graph.arrange }, //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; + /*if (LiteGraph.showCanvasOptions){ + options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); + }*/ if (this._graph_stack && this._graph_stack.length > 0) { options.push(null, { @@ -11062,13 +12892,13 @@ LGraphNode.prototype.executeAction = function(action) content: "Mode", has_submenu: true, callback: LGraphCanvas.onMenuNodeMode - }, - { - content: "Resize", callback: function() { - if(node.resizable) - return LGraphCanvas.onResizeNode; - } - }, + }]; + if(node.resizable !== false){ + options.push({ + content: "Resize", callback: LGraphCanvas.onMenuResizeNode + }); + } + options.push( { content: "Collapse", callback: LGraphCanvas.onMenuNodeCollapse @@ -11085,7 +12915,7 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeShapes }, null - ]; + ); } if (node.onGetInputs) { @@ -11194,16 +13024,16 @@ LGraphNode.prototype.executeAction = function(action) menu_info.push({ content: "Disconnect Links", slot: slot }); } var _slot = slot.input || slot.output; - menu_info.push( - _slot.locked || !_slot.removable - ? "Cannot remove" - : { content: "Remove Slot", slot: slot } - ); - menu_info.push( - _slot.nameLocked - ? "Cannot rename" - : { content: "Rename Slot", slot: slot } - ); + if (_slot.removable){ + menu_info.push( + _slot.locked + ? "Cannot remove" + : { content: "Remove Slot", slot: slot } + ); + } + if (!_slot.nameLocked){ + menu_info.push({ content: "Rename Slot", slot: slot }); + } } options.title = @@ -11253,19 +13083,23 @@ LGraphNode.prototype.executeAction = function(action) if (v.content == "Remove Slot") { var info = v.slot; + node.graph.beforeChange(); if (info.input) { node.removeInput(info.slot); } else if (info.output) { node.removeOutput(info.slot); } + node.graph.afterChange(); return; } else if (v.content == "Disconnect Links") { var info = v.slot; + node.graph.beforeChange(); if (info.output) { node.disconnectOutput(info.slot); } else if (info.input) { node.disconnectInput(info.slot); } + node.graph.afterChange(); return; } else if (v.content == "Rename Slot") { var info = v.slot; @@ -11280,17 +13114,32 @@ LGraphNode.prototype.executeAction = function(action) if (input && slot_info) { input.value = slot_info.label || ""; } - dialog - .querySelector("button") - .addEventListener("click", function(e) { - if (input.value) { - if (slot_info) { - slot_info.label = input.value; - } - that.setDirty(true); + var inner = function(){ + node.graph.beforeChange(); + if (input.value) { + if (slot_info) { + slot_info.label = input.value; } + that.setDirty(true); + } + dialog.close(); + node.graph.afterChange(); + } + dialog.querySelector("button").addEventListener("click", inner); + input.addEventListener("keydown", function(e) { + dialog.is_modified = true; + if (e.keyCode == 27) { + //ESC dialog.close(); - }); + } else if (e.keyCode == 13) { + inner(); // save + } else if (e.keyCode != 13 && e.target.localName != "textarea") { + return; + } + e.preventDefault(); + e.stopPropagation(); + }); + input.focus(); } //if(v.callback) @@ -11853,6 +13702,9 @@ LGraphNode.prototype.executeAction = function(action) if (this.root.closing_timer) { clearTimeout(this.root.closing_timer); } + + // TODO implement : LiteGraph.contextMenuClosed(); :: keep track of opened / closed / current ContextMenu + // on key press, allow filtering/selecting the context menu elements }; //this code is used to trigger events easily (used in the context menu mouseleave @@ -12160,23 +14012,65 @@ LGraphNode.prototype.executeAction = function(action) .filter(Boolean); // split & filter [""] }; - // helper pointerListener vs mouseListener, used by LGraphCanvas DragAndScale ContextMenu - LiteGraph.pointerListenerAdd = function(oDOM, sEvent, fCall, capture=false) { - if (!oDOM || !oDOM.addEventListener || !sEvent || typeof fCall!=="function"){ - console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); + /* helper for interaction: pointer, touch, mouse Listeners + used by LGraphCanvas DragAndScale ContextMenu*/ + LiteGraph.pointerListenerAdd = function(oDOM, sEvIn, fCall, capture=false) { + if (!oDOM || !oDOM.addEventListener || !sEvIn || typeof fCall!=="function"){ + //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); return; // -- break -- } + + var sMethod = LiteGraph.pointerevents_method; + var sEvent = sEvIn; + + // UNDER CONSTRUCTION + // convert pointerevents to touch event when not available + if (sMethod=="pointer" && !window.PointerEvent){ + console.warn("sMethod=='pointer' && !window.PointerEvent"); + console.log("Converting pointer["+sEvent+"] : down move up cancel enter TO touchstart touchmove touchend, etc .."); + switch(sEvent){ + case "down":{ + sMethod = "touch"; + sEvent = "start"; + break; + } + case "move":{ + sMethod = "touch"; + //sEvent = "move"; + break; + } + case "up":{ + sMethod = "touch"; + sEvent = "end"; + break; + } + case "cancel":{ + sMethod = "touch"; + //sEvent = "cancel"; + break; + } + case "enter":{ + console.log("debug: Should I send a move event?"); // ??? + break; + } + // case "over": case "out": not used at now + default:{ + console.warn("PointerEvent not available in this browser ? The event "+sEvent+" would not be called"); + } + } + } + switch(sEvent){ //both pointer and move events case "down": case "up": case "move": case "over": case "out": case "enter": { - oDOM.addEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + oDOM.addEventListener(sMethod+sEvent, fCall, capture); } // only pointerevents case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": { - if (LiteGraph.pointerevents_method!="mouse"){ - return oDOM.addEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + if (sMethod!="mouse"){ + return oDOM.addEventListener(sMethod+sEvent, fCall, capture); } } // not "pointer" || "mouse" @@ -12186,19 +14080,21 @@ LGraphNode.prototype.executeAction = function(action) } LiteGraph.pointerListenerRemove = function(oDOM, sEvent, fCall, capture=false) { if (!oDOM || !oDOM.removeEventListener || !sEvent || typeof fCall!=="function"){ - console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); + //console.log("cant pointerListenerRemove "+oDOM+", "+sEvent+", "+fCall); return; // -- break -- } switch(sEvent){ //both pointer and move events case "down": case "up": case "move": case "over": case "out": case "enter": { - oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + if (LiteGraph.pointerevents_method=="pointer" || LiteGraph.pointerevents_method=="mouse"){ + oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); + } } // only pointerevents case "leave": case "cancel": case "gotpointercapture": case "lostpointercapture": { - if (LiteGraph.pointerevents_method!="mouse"){ + if (LiteGraph.pointerevents_method=="pointer"){ return oDOM.removeEventListener(LiteGraph.pointerevents_method+sEvent, fCall, capture); } } diff --git a/src/nodes/audio.js b/src/nodes/audio.js index ca35c6990..2c02bdf33 100644 --- a/src/nodes/audio.js +++ b/src/nodes/audio.js @@ -997,7 +997,7 @@ LiteGraph.registerNodeType("audio/waveShaper", LGAudioWaveShaper); this.audionode = LGAudio.getAudioContext().createGain(); this.audionode.gain.value = 0; this.addInput("in", "audio"); - this.addInput("gate", "bool"); + this.addInput("gate", "boolean"); this.addOutput("out", "audio"); this.gate = false; } diff --git a/src/nodes/base.js b/src/nodes/base.js index 7546d67af..867e4f984 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -724,9 +724,10 @@ LiteGraph.registerNodeType("basic/const", ConstantNumber); function ConstantBoolean() { - this.addOutput("", "boolean"); + this.addOutput("bool", "boolean"); this.addProperty("value", true); this.widget = this.addWidget("toggle","value",true,"value"); + this.serialize_widgets = true; this.widgets_up = true; this.size = [140, 30]; } @@ -753,7 +754,7 @@ LiteGraph.registerNodeType("basic/boolean", ConstantBoolean); function ConstantString() { - this.addOutput("", "string"); + this.addOutput("string", "string"); this.addProperty("value", ""); this.widget = this.addWidget("text","value","","value"); //link to property value this.widgets_up = true; @@ -800,8 +801,8 @@ LiteGraph.registerNodeType( "basic/object", ConstantObject ); function ConstantFile() { - this.addInput("url", ""); - this.addOutput("", ""); + this.addInput("url", "string"); + this.addOutput("file", "string"); this.addProperty("url", ""); this.addProperty("type", "text"); this.widget = this.addWidget("text","url","","url"); @@ -899,7 +900,7 @@ //to store json objects function ConstantData() { - this.addOutput("", ""); + this.addOutput("data", "object"); this.addProperty("value", ""); this.widget = this.addWidget("text","json","","value"); this.widgets_up = true; @@ -934,10 +935,10 @@ //to store json objects function ConstantArray() { - this._value = []; - this.addInput("", ""); - this.addOutput("", "array"); - this.addOutput("length", "number"); + this._value = []; + this.addInput("json", ""); + this.addOutput("arrayOut", "array"); + this.addOutput("length", "number"); this.addProperty("value", "[]"); this.widget = this.addWidget("text","array",this.properties.value,"value"); this.widgets_up = true; @@ -974,7 +975,7 @@ for(var i = 0; i < v.length; ++i) this._value[i] = v[i]; } - this.setOutputData(0, this._value ); + this.setOutputData(0, this._value); this.setOutputData(1, this._value ? ( this._value.length || 0) : 0 ); }; @@ -988,7 +989,7 @@ this.addInput("value", ""); this.addOutput("arr", "array"); this.properties = { index: 0 }; - this.widget = this.addWidget("number","i",this.properties.index,"index"); + this.widget = this.addWidget("number","i",this.properties.index,"index",{precision: 0, step: 10, min: 0}); } SetArray.title = "Set Array"; @@ -1062,9 +1063,9 @@ LiteGraph.registerNodeType("basic/table[][]", TableElement); function ObjectProperty() { - this.addInput("obj", ""); - this.addOutput("", ""); - this.addProperty("value", ""); + this.addInput("obj", "object"); + this.addOutput("property", 0); + this.addProperty("value", 0); this.widget = this.addWidget("text","prop.","",this.setValue.bind(this) ); this.widgets_up = true; this.size = [140, 30]; @@ -1146,9 +1147,9 @@ function MergeObjects() { - this.addInput("A", ""); - this.addInput("B", ""); - this.addOutput("", ""); + this.addInput("A", "object"); + this.addInput("B", "object"); + this.addOutput("out", "object"); this._result = {}; var that = this; this.addWidget("button","clear","",function(){ @@ -1396,21 +1397,27 @@ Console.desc = "Show value inside the console"; Console.prototype.onAction = function(action, param) { + // param is the action + var msg = this.getInputData(1); //getInputDataByName("msg"); + //if (msg == null || typeof msg == "undefined") return; + if (!msg) msg = this.properties.msg; + if (!msg) msg = "Event: "+param; // msg is undefined if the slot is lost? if (action == "log") { - console.log(param); + console.log(msg); } else if (action == "warn") { - console.warn(param); + console.warn(msg); } else if (action == "error") { - console.error(param); + console.error(msg); } }; Console.prototype.onExecute = function() { - var msg = this.getInputData(1); - if (msg !== null) { + var msg = this.getInputData(1); //getInputDataByName("msg"); + if (!msg) msg = this.properties.msg; + if (msg != null && typeof msg != "undefined") { this.properties.msg = msg; + console.log(msg); } - console.log(msg); }; Console.prototype.onGetInputs = function() { @@ -1455,9 +1462,9 @@ function NodeScript() { this.size = [60, 30]; this.addProperty("onExecute", "return A;"); - this.addInput("A", ""); - this.addInput("B", ""); - this.addOutput("out", ""); + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("out", 0); this._func = null; this.data = {}; @@ -1534,4 +1541,102 @@ }; LiteGraph.registerNodeType("basic/script", NodeScript); + + + function GenericCompare() { + this.addInput("A", 0); + this.addInput("B", 0); + this.addOutput("true", "boolean"); + this.addOutput("false", "boolean"); + this.addProperty("A", 1); + this.addProperty("B", 1); + this.addProperty("OP", "==", "enum", { values: GenericCompare.values }); + this.addWidget("combo","Op.",this.properties.OP,{ property: "OP", values: GenericCompare.values } ); + + this.size = [80, 60]; + } + + GenericCompare.values = ["==", "!="]; //[">", "<", "==", "!=", "<=", ">=", "||", "&&" ]; + GenericCompare["@OP"] = { + type: "enum", + title: "operation", + values: GenericCompare.values + }; + + GenericCompare.title = "Compare *"; + GenericCompare.desc = "evaluates condition between A and B"; + + GenericCompare.prototype.getTitle = function() { + return "*A " + this.properties.OP + " *B"; + }; + + GenericCompare.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 = false; + if (typeof A == typeof B){ + switch (this.properties.OP) { + case "==": + case "!=": + // traverse both objects.. consider that this is not a true deep check! consider underscore or other library for thath :: _isEqual() + result = true; + switch(typeof A){ + case "object": + var aProps = Object.getOwnPropertyNames(A); + var bProps = Object.getOwnPropertyNames(B); + if (aProps.length != bProps.length){ + result = false; + break; + } + for (var i = 0; i < aProps.length; i++) { + var propName = aProps[i]; + if (A[propName] !== B[propName]) { + result = false; + break; + } + } + break; + default: + result = A == B; + } + if (this.properties.OP == "!=") result = !result; + 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("basic/CompareValues", GenericCompare); + })(this); diff --git a/src/nodes/events.js b/src/nodes/events.js index af73fb591..00f147ae6 100644 --- a/src/nodes/events.js +++ b/src/nodes/events.js @@ -11,7 +11,7 @@ LogEvent.title = "Log Event"; LogEvent.desc = "Log event in console"; - LogEvent.prototype.onAction = function(action, param) { + LogEvent.prototype.onAction = function(action, param, options) { console.log(action, param); }; @@ -31,18 +31,18 @@ TriggerEvent.title = "TriggerEvent"; TriggerEvent.desc = "Triggers event if input evaluates to true"; - TriggerEvent.prototype.onExecute = function(action, param) { + 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); + this.triggerSlot(0, param, null, options); if(!v && must_resend) - this.triggerSlot(2, param); + this.triggerSlot(2, param, null, options); if(changed) - this.triggerSlot(1, param); + this.triggerSlot(1, param, null, options); this.prev = v; }; @@ -50,8 +50,8 @@ //Sequencer for events function Sequencer() { - var that = this; - this.addInput("", LiteGraph.ACTION); + var that = this; + this.addInput("", LiteGraph.ACTION); this.addInput("", LiteGraph.ACTION); this.addInput("", LiteGraph.ACTION); this.addOutput("", LiteGraph.EVENT); @@ -72,10 +72,13 @@ return ""; }; - Sequencer.prototype.onAction = function(action, param) { + Sequencer.prototype.onAction = function(action, param, options) { if (this.outputs) { + options = options || {}; for (var i = 0; i < this.outputs.length; ++i) { - this.triggerSlot(i, param); + // CREATE A NEW ID FOR THE ACTION + 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); } } }; @@ -97,7 +100,7 @@ FilterEvent.title = "Filter Event"; FilterEvent.desc = "Blocks events that do not match the filter"; - FilterEvent.prototype.onAction = function(action, param) { + FilterEvent.prototype.onAction = function(action, param, options) { if (param == null) { return; } @@ -120,7 +123,7 @@ } } - this.triggerSlot(0, param); + this.triggerSlot(0, param, null, options); }; LiteGraph.registerNodeType("events/filter", FilterEvent); @@ -142,8 +145,9 @@ this._value = this.getInputData(1); } - EventBranch.prototype.onAction = function(action, param) { - this.triggerSlot(this._value ? 0 : 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); @@ -155,6 +159,8 @@ 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; } @@ -168,7 +174,7 @@ return this.title; }; - EventCounter.prototype.onAction = function(action, param) { + EventCounter.prototype.onAction = function(action, param, options) { var v = this.num; if (action == "inc") { this.num += 1; @@ -193,6 +199,9 @@ }; EventCounter.prototype.onExecute = function() { + if(this.properties.doCountExecution){ + this.num += 1; + } this.setOutputData(1, this.num); }; @@ -211,16 +220,16 @@ DelayEvent.title = "Delay"; DelayEvent.desc = "Delays one event"; - DelayEvent.prototype.onAction = function(action, param) { + DelayEvent.prototype.onAction = function(action, param, options) { var time = this.properties.time_in_ms; if (time <= 0) { - this.trigger(null, param); + this.trigger(null, param, options); } else { this._pending.push([time, param]); } }; - DelayEvent.prototype.onExecute = function() { + DelayEvent.prototype.onExecute = function(param, options) { var dt = this.graph.elapsed_time * 1000; //in ms if (this.isInputConnected(1)) { @@ -228,9 +237,9 @@ } for (var i = 0; i < this._pending.length; ++i) { - var action = this._pending[i]; - action[0] -= dt; - if (action[0] > 0) { + var actionPass = this._pending[i]; + actionPass[0] -= dt; + if (actionPass[0] > 0) { continue; } @@ -239,7 +248,7 @@ --i; //trigger - this.trigger(null, action[1]); + this.trigger(null, actionPass[1], options); } }; @@ -359,9 +368,9 @@ function DataStore() { - this.addInput("data", ""); + this.addInput("data", 0); this.addInput("assign", LiteGraph.ACTION); - this.addOutput("data", ""); + this.addOutput("data", 0); this._last_value = null; this.properties = { data: null, serialize: true }; var that = this; @@ -379,7 +388,7 @@ this.setOutputData(0, this.properties.data ); } - DataStore.prototype.onAction = function(action, param) { + DataStore.prototype.onAction = function(action, param, options) { this.properties.data = this._last_value; }; diff --git a/src/nodes/interface.js b/src/nodes/interface.js index 41556429a..62cb51e82 100755 --- a/src/nodes/interface.js +++ b/src/nodes/interface.js @@ -71,6 +71,7 @@ local_pos[1] < this.size[1] - 2 ) { this.clicked = true; + this.setOutputData(1, this.clicked); this.triggerSlot(0, this.properties.message); return true; } @@ -672,7 +673,7 @@ typeof v == "number" ? v.toFixed(this.properties["decimals"]) : v; if (typeof this.str == "string") { - var lines = this.str.split("\\n"); + var lines = this.str.replace(/[\r\n]/g, "\\n").split("\\n"); for (var i=0; i < lines.length; i++) { ctx.fillText( lines[i], diff --git a/src/nodes/logic.js b/src/nodes/logic.js index e13c419ff..fe273dc3f 100755 --- a/src/nodes/logic.js +++ b/src/nodes/logic.js @@ -82,4 +82,120 @@ }; LiteGraph.registerNodeType("logic/sequence", Sequence); + + + function logicAnd(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicAnd.title = "AND"; + logicAnd.desc = "Return true if all inputs are true"; + logicAnd.prototype.onExecute = function() { + ret = true; + for (inX in this.inputs){ + if (!this.getInputData(inX)){ + ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicAnd.prototype.onGetInputs = function() { + return [ + ["and", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/AND", logicAnd); + + + function logicOr(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicOr.title = "OR"; + logicOr.desc = "Return true if at least one input is true"; + logicOr.prototype.onExecute = function() { + ret = false; + for (inX in this.inputs){ + if (this.getInputData(inX)){ + ret = true; + break; + } + } + this.setOutputData(0, ret); + }; + logicOr.prototype.onGetInputs = function() { + return [ + ["or", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/OR", logicOr); + + + function logicNot(){ + this.properties = { }; + this.addInput("in", "boolean"); + this.addOutput("out", "boolean"); + } + logicNot.title = "NOT"; + logicNot.desc = "Return the logical negation"; + logicNot.prototype.onExecute = function() { + var ret = !this.getInputData(0); + this.setOutputData(0, ret); + }; + LiteGraph.registerNodeType("logic/NOT", logicNot); + + + function logicCompare(){ + this.properties = { }; + this.addInput("a", "boolean"); + this.addInput("b", "boolean"); + this.addOutput("out", "boolean"); + } + logicCompare.title = "bool == bool"; + logicCompare.desc = "Compare for logical equality"; + logicCompare.prototype.onExecute = function() { + last = null; + ret = true; + for (inX in this.inputs){ + if (last === null) last = this.getInputData(inX); + else + if (last != this.getInputData(inX)){ + ret = false; + break; + } + } + this.setOutputData(0, ret); + }; + logicCompare.prototype.onGetInputs = function() { + return [ + ["bool", "boolean"] + ]; + }; + LiteGraph.registerNodeType("logic/CompareBool", logicCompare); + + + function logicBranch(){ + 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; + } + logicBranch.title = "Branch"; + logicBranch.desc = "Branch execution on condition"; + logicBranch.prototype.onExecute = function(param, options) { + var condtition = this.getInputData(1); + if (condtition){ + this.triggerSlot(0); + }else{ + this.triggerSlot(1); + } + }; + LiteGraph.registerNodeType("logic/IF", logicBranch); })(this); diff --git a/src/nodes/math.js b/src/nodes/math.js index 66fff8355..609eb3f6f 100755 --- a/src/nodes/math.js +++ b/src/nodes/math.js @@ -1,1329 +1,1329 @@ -(function(global) { - var LiteGraph = global.LiteGraph; - - //Converter - function Converter() { - this.addInput("in", ""); - this.addOutput("out"); - 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", ""); - this.addInput("cond", "boolean"); - this.addOutput("true", ""); - this.addOutput("false", ""); - 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; + + //Converter + function Converter() { + this.addInput("in", 0); + this.addOutput("out", 0); + 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); diff --git a/src/nodes/others.js b/src/nodes/others.js old mode 100755 new mode 100644 index e69de29bb..cca0e6f2b --- a/src/nodes/others.js +++ b/src/nodes/others.js @@ -0,0 +1,37 @@ +(function(global) { + var LiteGraph = global.LiteGraph; + + /* in types :: run in console :: var s=""; LiteGraph.slot_types_in.forEach(function(el){s+=el+"\n";}); console.log(s); */ + + if(typeof LiteGraph.slot_types_default_in == "undefined") LiteGraph.slot_types_default_in = {}; //[]; + LiteGraph.slot_types_default_in["_event_"] = "widget/button"; + LiteGraph.slot_types_default_in["array"] = "basic/array"; + LiteGraph.slot_types_default_in["boolean"] = "basic/boolean"; + LiteGraph.slot_types_default_in["number"] = "widget/number"; + LiteGraph.slot_types_default_in["object"] = "basic/data"; + LiteGraph.slot_types_default_in["string"] = ["basic/string","string/concatenate"]; + LiteGraph.slot_types_default_in["vec2"] = "math3d/xy-to-vec2"; + LiteGraph.slot_types_default_in["vec3"] = "math3d/xyz-to-vec3"; + LiteGraph.slot_types_default_in["vec4"] = "math3d/xyzw-to-vec4"; + + /* 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["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" + ,{node:"math/operation",properties:{OP:"*"},title:"A*B"} + ,{node:"math/operation",properties:{OP:"/"},title:"A/B"} + ,{node:"math/operation",properties:{OP:"+"},title:"A+B"} + ,{node:"math/operation",properties:{OP:"-"},title:"A-B"} + ,{node:"math/compare",outputs:[["A==B", "boolean"]],title:"A==B"} + ,{node:"math/compare",outputs:[["A>B", "boolean"]],title:"A>B"} + ,{node:"math/compare",outputs:[["A