From a3055c7fcb64ee75424d9c8e16fd17ff2c133f83 Mon Sep 17 00:00:00 2001 From: atlasan Date: Wed, 6 Oct 2021 17:43:48 +0200 Subject: [PATCH 01/14] Couple implementations - matrix shape (enabled for array slots) - dim opacity on incompatibile slots when dragging --- src/litegraph.js | 59 ++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 49 insertions(+), 10 deletions(-) mode change 100755 => 100644 src/litegraph.js diff --git a/src/litegraph.js b/src/litegraph.js old mode 100755 new mode 100644 index b8f68291d..9c21c89ca --- a/src/litegraph.js +++ b/src/litegraph.js @@ -56,6 +56,7 @@ CIRCLE_SHAPE: 3, CARD_SHAPE: 4, ARROW_SHAPE: 5, + GRID_SHAPE: 6, // initially implemented for array slots //enums INPUT: 1, @@ -99,7 +100,7 @@ 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 - pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues + pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) /** @@ -7648,6 +7649,9 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); + if (slot.type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; // ??? should place somewhere else.. in addInput? addOutput instead? + } if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE @@ -7667,11 +7671,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 +7711,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]; @@ -7722,6 +7742,12 @@ LGraphNode.prototype.executeAction = function(action) 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 @@ -7741,11 +7767,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 +7796,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 @@ -12193,12 +12230,14 @@ LGraphNode.prototype.executeAction = function(action) //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); } } From b337cd07ef4591086f1519cb399a930e59969aef Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 11:34:33 +0200 Subject: [PATCH 02/14] Many improvements and implementations, TEST EM - allow connecting from IN to OUT (drag an IN slot to create a link to OUT slots) - dim (opacity) uncompatible slots while creating a link - filter in the searchbox for types (slotsIn, slotsOut), autofilter when chaining - drag-shift a slot to search and connect a new node - code widget re-enabled - properties panel improvements - paste will use mouse coordinates :: properties and methods :: - additional shape GRID_SHAPE intended for slot arrays - NODE_MODES_COLORS array of colors based on the node modes node_box_coloured_by_mode: false, // [true!] nodebox colored on node mode, visual feedback node_box_coloured_when_on: false, // [true!] this make the nodes box (top left circle) coloured when triggered (execute/action), visual feedback dialog_close_on_mouse_leave: true, // better true if not touch device dialog_close_on_mouse_leave_delay: 500, shift_click_do_break_link_from: false, // [false!] prefer false if results too easy to break links - TODO custom keys click_do_break_link_to: false, // [false!] prefer false, way too easy to break links search_hide_on_mouse_leave: true, // better true if not touch device search_filter_enabled: true, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types search_show_all_on_open: true, // [true!] opens the results list when opening the search widget 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] - this will create (without adding it) a node for each class when they are registered. This allows for slots checking. Could raise errors in case some node miss something: somehow nice. 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 allowMultiOutputForEvents: true, // [false!] being events, it is strongly reccomended to use them sequentually, one by one - find(Input/Output)Slot functions can return the object instead - connectByType - allow connecting a node slot to a target node using an auto-slot mode that looks for the right types - onNodeCreated - new callback - addOnTriggerInput, addOnExecutedOutput - creates action slots (triggerIn, executedOut) when needed (changing mode, dragging events onto the node) - doExecute and doAction - wraps the onExecute and onAction node functions with helpers and checks - onAfterExecuteNode - new callback - onBeforeConnectInput - new callback, can change slot while connecting (or create a new one) - onConnectOutput - new callback, similar to onConnectInput - onNodeInputAdd, onNodeOutputAdd - new callbacks - isOverNodeOutput - similar to isOverNodeInput - helpers findInput, findOutput, findInputSlotFree, findOutputSlotFree, findSlotByType - canvas default_connection_color_byType[Off] allows custom colors type based - ESC will close panels - showConnectionMenu will show the "Add menu" while dragging, to connect after creation --- src/litegraph.js | 1595 +++++++++++++++++++++++++++++++++++++++------- 1 file changed, 1351 insertions(+), 244 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index 9c21c89ca..59170d9b6 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -56,7 +56,7 @@ CIRCLE_SHAPE: 3, CARD_SHAPE: 4, ARROW_SHAPE: 5, - GRID_SHAPE: 6, // initially implemented for array slots + GRID_SHAPE: 6, // slot arrays //enums INPUT: 1, @@ -65,6 +65,8 @@ EVENT: -1, //for outputs ACTION: -1, //for inputs + NODE_MODES: ["Always", "On Event", "Never", "On Trigger", "On Request"], + NODE_MODES_COLORS:["#666","#422","#333","#224","#626"], // use with node_box_coloured_by_mode ALWAYS: 0, ON_EVENT: 1, NEVER: 2, @@ -76,6 +78,7 @@ RIGHT: 4, CENTER: 5, + LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], STRAIGHT_LINK: 0, LINEAR_LINK: 1, SPLINE_LINK: 2, @@ -99,7 +102,30 @@ 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 + + node_box_coloured_when_on: false, // this make the nodes box (top left circle) coloured when triggered (execute/action) + node_box_coloured_by_mode: false, // nodebox based on node mode + dialog_close_on_mouse_leave: false, // better true if not touch device + dialog_close_on_mouse_leave_delay: 500, + + shift_click_do_break_link_from: false, // prefer false if results too easy to break links - implement with ALT or TODO custom keys + click_do_break_link_to: false, // prefer false + + search_hide_on_mouse_leave: false, // better true if not touch device + search_filter_enabled: true, // enable filtering slots type in the search widget, !requires auto_load_slot_types + search_show_all_on_open: true, // opens the results list when opening the search widget + + auto_load_slot_types: true, // 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] + + 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 + + alt_drag_do_clone_nodes: false, // very handy, ALT click to clone and drag the new node + pointerevents_method: "pointer", // "mouse"|"pointer" use mouse for retrocompatibility issues? (none found @ now) // TODO implement pointercancel, gotpointercapture, lostpointercapture, (pointerover, pointerout if necessary) @@ -223,6 +249,9 @@ this.node_types_by_file_extension[ ext.toLowerCase() ] = base_class; } } + + // this allow trought registerNodeAndSlotType to get all the slots types + if (this.auto_load_slot_types) nodeTmp = new base_class(base_class.title || "tmpnode"); }, /** @@ -239,6 +268,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/ACTION"]; + }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)){ + this.slot_types_in.push(sT); + this.slot_types_in.sort(); + } + }else{ + if (!this.slot_types_out.includes(sT)){ + this.slot_types_out.push(sT); + 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. @@ -385,6 +458,10 @@ } } + if ( node.onNodeCreated ) { + node.onNodeCreated(); + } + return node; }, @@ -1374,7 +1451,7 @@ return; } //cannot be removed - this.beforeChange(); //sure? + this.beforeChange(); //sure? - almost sure is wrong //disconnect inputs if (node.inputs) { @@ -1434,7 +1511,7 @@ this.sendActionToCanvas("checkPanels"); this.setDirtyCanvas(true, true); - this.afterChange(); //sure? + this.afterChange(); //sure? - almost sure is wrong this.change(); this.updateExecutionOrder(); @@ -3611,15 +3688,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; @@ -3629,20 +3707,232 @@ * 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; + }; + + // atlasan: 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; + } + for (var i = 0, l = aSlots.length; i < l; ++i) { + // console.debug(type+" "+aSlots[i].type+" "+aSlots[i].links); // atlasan debug REMOVE + var tFound = false; + var aSource = (type+"").toLowerCase().split(","); + var aDest = (aSlots[i].type+"").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 + return this.connect(slot, target_node, -1); + } + if (opts.generalTypeInCase && (target_slotType !== 0 && target_slotType !== "" && target_slotType !== "*")){ + // connect TO a general type (*, 0), if not found the specific type + target_slot = target_node.findInputSlotByType(0, false, true); + if (target_slot >= 0 && target_slot !== null){ + return this.connect(slot, target_node, target_slot); + } + }else{ + //console.debug("no way to connect "+target_slotType+" to "+target_node); + } + 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 + 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.log("type OUT! "+source_slotType+" not found or not free?") + return null; + } + } + /** * connect this node output to the input of another node * @method connect @@ -4672,9 +4962,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; @@ -5272,8 +5572,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; @@ -5318,6 +5619,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 @@ -5328,7 +5650,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 && @@ -5342,7 +5668,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; @@ -5364,11 +5690,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) { @@ -5416,12 +5745,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 ]; @@ -5431,8 +5770,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 // atlasan implemented + 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; } @@ -5536,6 +5891,9 @@ LGraphNode.prototype.executeAction = function(action) if (is_double_click && !this.read_only && this.allow_searchbox) { this.showSearchBox(e); + e.preventDefault(); + e.stopPropagation(); + } clicking_canvas_bg = true; @@ -5714,23 +6072,49 @@ 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 ); + //console.debug("check slot "+slot); // atlasan debug REMOVE + if (slot != -1 && node.outputs[slot]) { + var slot_type = node.outputs[slot].type; + //console.debug("check slotType "+slot_type); // atlasan debug REMOVE + if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { + this._highlight_output = pos; + } + } else { + this._highlight_output = null; + } } } } @@ -5944,6 +6328,9 @@ LGraphNode.prototype.executeAction = function(action) this.dirty_canvas = true; this.dirty_bgcanvas = true; + var connInOrOut = this.connecting_output || this.connecting_input; + var connType = connInOrOut.type; + var node = this.graph.getNodeOnPos( e.canvasX, e.canvasY, @@ -5952,58 +6339,80 @@ LGraphNode.prototype.executeAction = function(action) //node below mouse if (node) { + + /* no need to condition on event type.. jsut 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 (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; @@ -6152,7 +6561,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( @@ -6196,6 +6605,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 @@ -6215,10 +6670,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) { @@ -6263,6 +6725,7 @@ LGraphNode.prototype.executeAction = function(action) } } else if (e.type == "keyup") { if (e.keyCode == 32) { + // space this.dragging_canvas = false; } @@ -6349,15 +6812,37 @@ 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); + + node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; // atlasan edit :: paste in last known mouse position + node.pos[1] += this.graph_mouse[1] - posMin[1]; //+= 5; + + this.graph.add(node,{doProcessChange:false}); + nodes.push(node); } } @@ -6388,8 +6873,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]; @@ -6659,6 +7146,15 @@ LGraphNode.prototype.executeAction = function(action) this.setDirty(true); this.graph.afterChange(); }; + + /** + * connect TWO nodes looking for matching types + * @method autoConnectNodes + **/ + // TODO implement: use connectByType (check all matching input-outputs?) + /*LGraphCanvas.prototype.autoConnectNodes = function(input_node, output_node){ + + }*/ /** * centers the camera on a given node @@ -6954,8 +7450,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; @@ -6972,7 +7474,7 @@ LGraphNode.prototype.executeAction = function(action) false, null, link_color, - this.connecting_output.dir || + connDir || (this.connecting_node.horizontal ? LiteGraph.DOWN : LiteGraph.RIGHT), @@ -6981,8 +7483,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, @@ -6998,7 +7500,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); @@ -7044,6 +7546,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 @@ -7082,7 +7602,7 @@ LGraphNode.prototype.executeAction = function(action) this.onDrawOverlay(ctx); } - if (area) { + if (area){ ctx.restore(); } @@ -7115,7 +7635,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = "#111"; ctx.globalAlpha = 0.8; ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); + ctx.roundRect(10, 10, w, (num + 1) * h + 50, 8); ctx.fill(); ctx.globalAlpha = 1; @@ -7184,7 +7704,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = "#111"; ctx.globalAlpha = 0.8; ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); + ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, 8); ctx.fill(); ctx.globalAlpha = 1; @@ -7262,7 +7782,7 @@ LGraphNode.prototype.executeAction = function(action) if(clicked) ctx.fillStyle = "#AAA"; ctx.beginPath(); - ctx.roundRect(x,y,w,h,[4] ); + ctx.roundRect(x,y,w,h,4 ); ctx.fill(); if(text != null) @@ -7405,7 +7925,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 @@ -7439,7 +7959,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 @@ -7615,6 +8135,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; @@ -7626,18 +8147,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); @@ -7649,9 +8176,13 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); - if (slot.type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // ??? should place somewhere else.. in addInput? addOutput instead? + if (slot_type == "array"){ + slot_shape = LiteGraph.GRID_SHAPE; // atlasan edit :: this is dirty, should place somewhere else.. in addInput? addOutput instead? + // console.debug("ARRAY SLOT"); atlasan debug REMOVE } + + var doStroke = true; + if ( slot.type === LiteGraph.EVENT || slot.shape === LiteGraph.BOX_SHAPE @@ -7718,7 +8249,7 @@ LGraphNode.prototype.executeAction = function(action) for (var i = 0; i < node.outputs.length; i++) { var slot = node.outputs[i]; - var slot_type = slot.type; + var slot_type = slot.type; // atlasan edit var slot_shape = slot.shape; //change opacity of incompatible slots when dragging a connection @@ -7736,21 +8267,25 @@ 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"){ + if (slot_type == "array"){ slot_shape = LiteGraph.GRID_SHAPE; + // console.debug("ARRAY SLOT"); atlasan debug REMOVE } 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( @@ -7963,7 +8498,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowBlur = 3; ctx.fillStyle = "#454"; ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); + ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h,3, 3); ctx.moveTo( pos[0] - 10, pos[1] - 15 ); ctx.lineTo( pos[0] + 10, pos[1] - 15 ); ctx.lineTo( pos[0], pos[1] - 5 ); @@ -8032,7 +8567,8 @@ LGraphNode.prototype.executeAction = function(action) area[1], area[2], area[3], - shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] + this.round_radius, + shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius ); } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( @@ -8079,7 +8615,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; @@ -8097,13 +8633,24 @@ LGraphNode.prototype.executeAction = function(action) -title_height, size[0] + 1, title_height, - node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] + this.round_radius, + node.flags.collapsed ? this.round_radius : 0 ); } ctx.fill(); 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) { @@ -8125,8 +8672,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 @@ -8151,7 +8698,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, @@ -8214,7 +8761,7 @@ LGraphNode.prototype.executeAction = function(action) else { ctx.beginPath(); - ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); + ctx.roundRect(x+2, -w+2, w-4, w-4,4); ctx.fill(); } ctx.fillStyle = "#333"; @@ -8260,7 +8807,7 @@ LGraphNode.prototype.executeAction = function(action) -6 + area[1], 12 + area[2], 12 + area[3], - [this.round_radius * 2] + this.round_radius * 2 ); } else if (shape == LiteGraph.CARD_SHAPE) { ctx.roundRect( @@ -8268,7 +8815,8 @@ LGraphNode.prototype.executeAction = function(action) -6 + area[1], 12 + area[2], 12 + area[3], - [this.round_radius * 2,2,this.round_radius * 2,2] + this.round_radius * 2, + 2 ); } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( @@ -8284,6 +8832,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); @@ -8858,7 +9410,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if (show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5]); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); else ctx.rect(margin, posY, widget_width - margin * 2, H ); ctx.fill(); @@ -8915,7 +9467,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if(show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5] ); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); else ctx.rect(margin, posY, widget_width - margin * 2, H ); ctx.fill(); @@ -8975,7 +9527,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if (show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5]); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); else ctx.rect( margin, posY, widget_width - margin * 2, H ); ctx.fill(); @@ -9354,7 +9906,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"); @@ -9509,8 +10067,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) { @@ -9604,6 +10163,16 @@ LGraphNode.prototype.executeAction = function(action) if (this.onMenuNodeOutputs) { entries = this.onMenuNodeOutputs(entries); } + if (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; @@ -9746,7 +10315,7 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.showLinkMenu = function(link, e) { var that = this; - console.log(link); + // console.log(link); var options = ["Add Node",null,"Delete"]; var menu = new LiteGraph.ContextMenu(options, { event: e, @@ -9758,17 +10327,21 @@ LGraphNode.prototype.executeAction = function(action) switch (v) { case "Add Node": LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ - console.log("node autoconnect"); + // console.debug("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) + 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.outputs[ link.origin_slot ].type == node.inputs[0].type && node.outputs[0].type == node_right.inputs[0].type ){ + var orType = node_left.outputs[link.origin_slot].type; + var destType = node_right.inputs[link.target_slot].type; + if (node_left.connectByType( link.origin_slot, node, orType )){ + node.connectByType( link.target_slot, node_right, destType ); + node.pos[0] -= node.size[0] * 0.5; + } + //} }); break; case "Delete": @@ -9780,16 +10353,98 @@ LGraphNode.prototype.executeAction = function(action) 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; // try with first if no name set :: implement, check better, if no name but type, look for type .. BUT PLEASE PASS A NAME, or an INDEX + console.warn("Cant get slot information "+slotX); + return false; + } + + var options = ["Add Node",null]; + var menu = new LiteGraph.ContextMenu(options, { + event: opts.e, + title: slotX && slotX.name ? slotX.name : null, + callback: inner_clicked + }); + + function inner_clicked(v,options,e) { + console.debug("menuadd"); + switch (v) { + case "Add Node": + LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, node, slotX.type ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, node, slotX.type ); + } + }); + break; + /*case "Delete": + that.graph.removeLink(link.id); + break;*/ + default: + } + } + + 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"); @@ -9799,10 +10454,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(); }); @@ -9831,8 +10491,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) { @@ -9849,19 +10522,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) { @@ -9869,15 +10542,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(); @@ -9895,7 +10595,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(); @@ -9920,9 +10620,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; @@ -9939,7 +10636,6 @@ LGraphNode.prototype.executeAction = function(action) dialog.style.top = canvas.height * 0.5 + offsety + "px"; } - canvas.parentNode.appendChild(dialog); setTimeout(function() { input.focus(); }, 10); @@ -9948,7 +10644,22 @@ 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.auto_load_slot_types && LiteGraph.search_filter_enabled // 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 || {}); + var that = this; var input_html = ""; var graphcanvas = LGraphCanvas.active_canvas; @@ -9957,8 +10668,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(); @@ -9972,25 +10702,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(); @@ -10030,7 +10785,7 @@ LGraphNode.prototype.executeAction = function(action) if (timeout) { clearInterval(timeout); } - timeout = setTimeout(refreshHelper, 10); + timeout = setTimeout(refreshHelper, 250); return; } e.preventDefault(); @@ -10039,15 +10794,59 @@ 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/action"; + /* 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(); } } @@ -10167,7 +11027,7 @@ LGraphNode.prototype.executeAction = function(action) var str = input.value; first = null; helper.innerHTML = ""; - if (!str) { + if (!str && !options.show_all_if_empty) { return; } @@ -10183,15 +11043,26 @@ LGraphNode.prototype.executeAction = function(action) str = str.toLowerCase(); var filter = graphcanvas.filter || graphcanvas.graph.filter; + // filter by type preprocess + if(options.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; @@ -10216,13 +11087,92 @@ 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/action") sV = LiteGraph.EVENT; // -1 + + if(sIn && sV){ + if (LiteGraph.registered_slot_in_types[sV] && LiteGraph.registered_slot_in_types[sV].nodes){ // type is stored + var doesInc = LiteGraph.registered_slot_in_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + // console.log(+" DONT includes "+type); //console.debug(LiteGraph.registered_slot_in_types[sV]); + return false; + } + } + } + + var sV = sOut.value; + if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; + //if (sV.toLowerCase() == "event/action") sV = LiteGraph.EVENT; // -1 + + if(sOut && sV){ + if (LiteGraph.registered_slot_out_types[sV] && LiteGraph.registered_slot_out_types[sV].nodes){ // type is stored + var doesInc = LiteGraph.registered_slot_out_types[sV].nodes.includes(sType); + if (doesInc!==false){ + //console.log(sType+" HAS "+sV); + }else{ + // console.log(+" DONT includes "+type); //console.debug(LiteGraph.registered_slot_out_types[sV]); + return false; + } + } + } + } + return true; } } @@ -10279,7 +11229,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); // atlasan: implement: why not? + + 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() { @@ -10623,22 +11690,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(); @@ -10646,13 +11749,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; @@ -10661,14 +11758,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' }); @@ -10678,30 +11774,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(); @@ -10852,7 +11955,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 } ); @@ -10860,20 +11963,12 @@ 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); + if (kV>=0 && LiteGraph.NODE_MODES[kV]) + node.changeMode(kV); + else{ + console.warn("unexpected mode: "+v); + node.changeMode(LiteGraph.ALWAYS); } } @@ -11045,6 +12140,9 @@ LGraphNode.prototype.executeAction = function(action) { content: "Add Group", callback: LGraphCanvas.onGroupAdd } //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; + if (LiteGraph.showCanvasOptions){ + options.push({ content: "Options", callback: thisGCanvas.showShowGraphOptionsPanel }); + } if (this._graph_stack && this._graph_stack.length > 0) { options.push(null, { @@ -11099,13 +12197,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.onResizeNode + }); + } + options.push( { content: "Collapse", callback: LGraphCanvas.onMenuNodeCollapse @@ -11122,7 +12220,7 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeShapes }, null - ]; + ); } if (node.onGetInputs) { @@ -11290,19 +12388,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; @@ -11320,6 +12422,7 @@ LGraphNode.prototype.executeAction = function(action) dialog .querySelector("button") .addEventListener("click", function(e) { + node.graph.beforeChange(); if (input.value) { if (slot_info) { slot_info.label = input.value; @@ -11327,6 +12430,7 @@ LGraphNode.prototype.executeAction = function(action) that.setDirty(true); } dialog.close(); + node.graph.afterChange(); }); } @@ -11890,6 +12994,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 From 719a74a0d86d7f9f80ac3c4059c2ff9b7b7a0fee Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 11:51:07 +0200 Subject: [PATCH 03/14] Integration and Fixes - few new nodes - onAction, onExecute, onTrigger has the third parameters for additional options - onAction has to refresh the getInputData values for future behaviours - prefer 0 or "*" for slot type instead of empty string "" --- src/nodes/audio.js | 2 +- src/nodes/base.js | 157 ++- src/nodes/events.js | 54 +- src/nodes/interface.js | 3 +- src/nodes/logic.js | 116 ++ src/nodes/math.js | 2658 ++++++++++++++++++++-------------------- src/nodes/others.js | 1 + src/nodes/strings.js | 8 +- 8 files changed, 1616 insertions(+), 1383 deletions(-) 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..3664fd62e 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,7 +50,6 @@ //Sequencer for events function Sequencer() { - var that = this; this.addInput("", LiteGraph.ACTION); this.addInput("", LiteGraph.ACTION); this.addInput("", LiteGraph.ACTION); @@ -72,10 +71,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 +99,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 +122,7 @@ } } - this.triggerSlot(0, param); + this.triggerSlot(0, param, null, options); }; LiteGraph.registerNodeType("events/filter", FilterEvent); @@ -142,8 +144,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 +158,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 +173,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 +198,9 @@ }; EventCounter.prototype.onExecute = function() { + if(this.properties.doCountExecution){ + this.num += 1; + } this.setOutputData(1, this.num); }; @@ -211,16 +219,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 +236,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 +247,7 @@ --i; //trigger - this.trigger(null, action[1]); + this.trigger(null, actionPass[1], options); } }; @@ -359,9 +367,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 +387,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 index e69de29bb..7d720af64 100755 --- a/src/nodes/others.js +++ b/src/nodes/others.js @@ -0,0 +1 @@ +// extra generic nodes \ No newline at end of file diff --git a/src/nodes/strings.js b/src/nodes/strings.js index c39173465..88f641d91 100644 --- a/src/nodes/strings.js +++ b/src/nodes/strings.js @@ -17,7 +17,7 @@ return String(a); } - LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "String"); + LiteGraph.wrapFunctionAsNode("string/toString", toString, [""], "string"); function compare(a, b) { return a == b; @@ -85,8 +85,10 @@ else if( str.constructor === Array ) { var r = []; - for(var i = 0; i < str.length; ++i) - r[i] = str[i].split(separator || " "); + for(var i = 0; i < str.length; ++i){ + if (typeof str[i] == "string") + r[i] = str[i].split(separator || " "); + } return r; } return null; From 275e55eff140a574021cd85715e67d8ddcf8d707 Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 11:52:22 +0200 Subject: [PATCH 04/14] Styles: Integrations and fixes --- css/litegraph-editor.css | 425 ++++++++++++++++++++------------------- css/litegraph.css | 57 +++++- 2 files changed, 266 insertions(+), 216 deletions(-) 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; From 662817ecbd85faaf701b6b2b9fa2e0935bae4088 Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 12:34:56 +0200 Subject: [PATCH 05/14] Cleaning and LOST CHANGES --- src/litegraph.js | 470 +++++++++++++++++++++++++++++++++++++---------- 1 file changed, 368 insertions(+), 102 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index 59170d9b6..d61b1ab24 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -56,7 +56,7 @@ CIRCLE_SHAPE: 3, CARD_SHAPE: 4, ARROW_SHAPE: 5, - GRID_SHAPE: 6, // slot arrays + GRID_SHAPE: 6, // intended for slot arrays //enums INPUT: 1, @@ -65,7 +65,7 @@ EVENT: -1, //for outputs ACTION: -1, //for inputs - NODE_MODES: ["Always", "On Event", "Never", "On Trigger", "On Request"], + 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, @@ -78,7 +78,7 @@ RIGHT: 4, CENTER: 5, - LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], + LINK_RENDER_MODES: ["Straight", "Linear", "Spline"], // helper STRAIGHT_LINK: 0, LINEAR_LINK: 1, SPLINE_LINK: 2, @@ -101,22 +101,22 @@ 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, // this make the nodes box (top left circle) coloured when triggered (execute/action) - node_box_coloured_by_mode: false, // nodebox based on node mode + 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 - dialog_close_on_mouse_leave: false, // better true if not touch device + 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, // prefer false if results too easy to break links - implement with ALT or TODO custom keys - click_do_break_link_to: false, // prefer false + 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: false, // better true if not touch device - search_filter_enabled: true, // enable filtering slots type in the search widget, !requires auto_load_slot_types - search_show_all_on_open: true, // opens the results list when opening the search widget + 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: true, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types + search_show_all_on_open: true, // [true!] opens the results list when opening the search widget - auto_load_slot_types: true, // 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] + 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] registered_slot_in_types: {}, // slot types for nodeclass registered_slot_out_types: {}, // slot types for nodeclass @@ -124,11 +124,15 @@ slot_types_in: [], // slot types IN slot_types_out: [], // slot types OUT - alt_drag_do_clone_nodes: false, // very handy, ALT click to clone and drag the new node + 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 + + allowMultiOutputForEvents: true, // [false!] being events, it is strongly reccomended to use them sequentually, one by one 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 @@ -250,7 +254,8 @@ } } - // this allow trought registerNodeAndSlotType to get all the slots types + // 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"); }, @@ -458,6 +463,7 @@ } } + // callback if ( node.onNodeCreated ) { node.onNodeCreated(); } @@ -982,7 +988,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(); } } @@ -1664,7 +1671,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 @@ -1674,7 +1681,8 @@ if (node.properties.name != action) { continue; } - node.onAction(action, param); + //wrap node.onAction(action, param); + node.actionDo(action, param, options); break; } }; @@ -3016,13 +3024,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; } @@ -3034,18 +3159,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; } @@ -3088,12 +3214,22 @@ 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]; + //console.debug("ACTION: "+this.id+":"+this.order+" :: "+target_connection.name); + if (LiteGraph.refreshAncestorsOnActions) node.refreshAncestors({action: target_connection.name, param: param, options:options}); + // wrap node.onAction(target_connection.name, param); + node.actionDo(target_connection.name, param, options); } } }; @@ -3202,6 +3338,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; @@ -3229,6 +3368,9 @@ if (this.onOutputAdded) { this.onOutputAdded(o); } + + if (LiteGraph.auto_load_slot_types) LiteGraph.registerNodeAndSlotType(this,info[1],true); + } this.setSize( this.computeSize() ); @@ -3289,7 +3431,9 @@ if (this.onInputAdded) { this.onInputAdded(o); - } + } + + LiteGraph.registerNodeAndSlotType(this,type); this.setDirtyCanvas(true, true); return o; @@ -3317,6 +3461,8 @@ if (this.onInputAdded) { this.onInputAdded(o); } + + LiteGraph.registerNodeAndSlotType(this,info[1]); } this.setSize( this.computeSize() ); @@ -3387,7 +3533,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; @@ -3723,7 +3868,7 @@ return -1; }; - // atlasan: refactor: USE SINGLE findInput/findOutput functions! :: merge options + // TODO refactor: USE SINGLE findInput/findOutput functions! :: merge options /** * returns the first free input slot @@ -3812,7 +3957,6 @@ return -1; } for (var i = 0, l = aSlots.length; i < l; ++i) { - // console.debug(type+" "+aSlots[i].type+" "+aSlots[i].links); // atlasan debug REMOVE var tFound = false; var aSource = (type+"").toLowerCase().split(","); var aDest = (aSlots[i].type+"").toLowerCase().split(","); @@ -3862,22 +4006,24 @@ } target_slot = target_node.findInputSlotByType(target_slotType, false, true); if (target_slot >= 0 && target_slot !== null){ - //console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) + 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?") + 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); } if (opts.generalTypeInCase && (target_slotType !== 0 && target_slotType !== "" && target_slotType !== "*")){ // connect TO a general type (*, 0), if not found the specific type + console.debug("connect TO a general type (*, 0), if not found the specific type "+target_slotType+" to "+target_node); target_slot = target_node.findInputSlotByType(0, false, true); if (target_slot >= 0 && target_slot !== null){ return this.connect(slot, target_node, target_slot); } }else{ - //console.debug("no way to connect "+target_slotType+" to "+target_node); + console.debug("no way to connect "+target_slotType+" to "+target_node); } return null; } @@ -3917,8 +4063,10 @@ if (opts.createEventInCase && source_slotType == LiteGraph.EVENT){ // WILL CREATE THE onExecuted OUT SLOT - var source_slot = source_node.addOnExecutedOutput(); - return source_node.connect(source_slot, this, 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 == "")){ @@ -3992,14 +4140,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 @@ -4012,31 +4162,24 @@ 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) @@ -4044,13 +4187,42 @@ return null; } - 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.allowMultiOutputForEvents){ + 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, @@ -5781,7 +5953,7 @@ LGraphNode.prototype.executeAction = function(action) } if (!skip_action){ - // connect from in to out, from to to from // atlasan implemented + // connect from in to out, from to to from this.connecting_node = node; this.connecting_input = input; this.connecting_input.slot_index = i; @@ -6105,10 +6277,8 @@ LGraphNode.prototype.executeAction = function(action) } else { //check if I have a slot below de mouse var slot = this.isOverNodeOutput( node, e.canvasX, e.canvasY, pos ); - //console.debug("check slot "+slot); // atlasan debug REMOVE if (slot != -1 && node.outputs[slot]) { var slot_type = node.outputs[slot].type; - //console.debug("check slotType "+slot_type); // atlasan debug REMOVE if ( LiteGraph.isValidConnection( this.connecting_input.type, slot_type ) ) { this._highlight_output = pos; } @@ -6340,7 +6510,7 @@ LGraphNode.prototype.executeAction = function(action) //node below mouse if (node) { - /* no need to condition on event type.. jsut another type + /* no need to condition on event type.. just another type if ( connType == LiteGraph.EVENT && this.isOverNodeBox(node, e.canvasX, e.canvasY) @@ -6838,7 +7008,8 @@ LGraphNode.prototype.executeAction = function(action) if (node) { node.configure(node_data); - node.pos[0] += this.graph_mouse[0] - posMin[0]; //+= 5; // atlasan edit :: paste in last known mouse position + //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}); @@ -7147,15 +7318,6 @@ LGraphNode.prototype.executeAction = function(action) this.graph.afterChange(); }; - /** - * connect TWO nodes looking for matching types - * @method autoConnectNodes - **/ - // TODO implement: use connectByType (check all matching input-outputs?) - /*LGraphCanvas.prototype.autoConnectNodes = function(input_node, output_node){ - - }*/ - /** * centers the camera on a given node * @method centerOnNode @@ -8177,8 +8339,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.beginPath(); if (slot_type == "array"){ - slot_shape = LiteGraph.GRID_SHAPE; // atlasan edit :: this is dirty, should place somewhere else.. in addInput? addOutput instead? - // console.debug("ARRAY SLOT"); atlasan debug REMOVE + slot_shape = LiteGraph.GRID_SHAPE; // place in addInput? addOutput instead? } var doStroke = true; @@ -8249,7 +8410,7 @@ LGraphNode.prototype.executeAction = function(action) for (var i = 0; i < node.outputs.length; i++) { var slot = node.outputs[i]; - var slot_type = slot.type; // atlasan edit + var slot_type = slot.type; var slot_shape = slot.shape; //change opacity of incompatible slots when dragging a connection @@ -8278,7 +8439,6 @@ LGraphNode.prototype.executeAction = function(action) if (slot_type == "array"){ slot_shape = LiteGraph.GRID_SHAPE; - // console.debug("ARRAY SLOT"); atlasan debug REMOVE } var doStroke = true; @@ -10100,6 +10260,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(); } @@ -10163,7 +10327,7 @@ LGraphNode.prototype.executeAction = function(action) if (this.onMenuNodeOutputs) { entries = this.onMenuNodeOutputs(entries); } - if (canvas.allow_addOutSlot_onExecuted){ + 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: {} } @@ -10223,6 +10387,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(); } @@ -10660,6 +10828,8 @@ LGraphNode.prototype.executeAction = function(action) }; options = Object.assign(def_options, options || {}); + console.log(options); + var that = this; var input_html = ""; var graphcanvas = LGraphCanvas.active_canvas; @@ -10815,8 +10985,11 @@ LGraphNode.prototype.executeAction = function(action) if(options.type_filter_in !==false && (options.type_filter_in+"").toLowerCase() == (aSlots[iK]+"").toLowerCase()){ //selIn.selectedIndex .. opt.selected = true; - } - } + //console.log("comparing IN "+options.type_filter_in+" :: "+aSlots[iK]); + }else{ + //console.log("comparing OUT "+options.type_filter_in+" :: "+aSlots[iK]); + } + } selIn.addEventListener("change",function(){ refreshHelper(); }); @@ -10896,7 +11069,7 @@ LGraphNode.prototype.executeAction = function(action) node.pos = graphcanvas.convertEventToCanvasOffset( event ); - graphcanvas.graph.add(node); + graphcanvas.graph.add(node, false); } if (extra && extra.data) { @@ -11044,7 +11217,7 @@ LGraphNode.prototype.executeAction = function(action) var filter = graphcanvas.filter || graphcanvas.graph.filter; // filter by type preprocess - if(options.type_filter && that.search_box){ + 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{ @@ -11145,12 +11318,15 @@ LGraphNode.prototype.executeAction = function(action) //if (sV.toLowerCase() == "event/action") 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.log(+" DONT includes "+type); //console.debug(LiteGraph.registered_slot_in_types[sV]); + /*console.debug(LiteGraph.registered_slot_in_types[sV]); + console.log(+" DONT includes "+type);*/ return false; } } @@ -11161,12 +11337,15 @@ LGraphNode.prototype.executeAction = function(action) //if (sV.toLowerCase() == "event/action") sV = LiteGraph.EVENT; // -1 if(sOut && sV){ + console.log("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.log(+" DONT includes "+type); //console.debug(LiteGraph.registered_slot_out_types[sV]); + /*console.debug(LiteGraph.registered_slot_out_types[sV]); + console.log(+" DONT includes "+type);*/ return false; } } @@ -11330,12 +11509,15 @@ LGraphNode.prototype.executeAction = function(action) return dialog; }; + // TODO refactor, theer are different dialog, some uses createDialog, some dont LGraphCanvas.prototype.createDialog = function(html, options) { - options = options || {}; + def_options = { checkForInput: false, closeOnLeave: true, closeOnLeave_checkModified: true }; + options = Object.assign(def_options, options || {}); var dialog = document.createElement("div"); dialog.className = "graphdialog"; dialog.innerHTML = html; + dialog.is_modified = false; var rect = this.canvas.getBoundingClientRect(); var offsetx = -20; @@ -11361,12 +11543,66 @@ LGraphNode.prototype.executeAction = function(action) dialog.style.top = offsety + "px"; this.canvas.parentNode.appendChild(dialog); - + + // acheck for input and use default behaviour: save on enter, close on esc + if (options.checkForInput){ + var aI = []; + var focused = false; + if (aI = dialog.querySelectorAll("input")){ + aI.forEach(function(iX) { + iX.addEventListener("keydown",function(e){ + dialog.modified(); + if (e.keyCode == 27) { + dialog.close(); + } else if (e.keyCode != 13) { + return; + } + // set value ? + e.preventDefault(); + e.stopPropagation(); + }); + if (!focused) iX.focus(); + }); + } + } + + dialog.modified = function(){ + dialog.is_modified = true; + } dialog.close = function() { - if (this.parentNode) { - this.parentNode.removeChild(this); + if (dialog.parentNode) { + dialog.parentNode.removeChild(dialog); } }; + + var dialogCloseTimer = null; + var prevent_timeout = false; + dialog.addEventListener("mouseleave", function(e) { + if (prevent_timeout) + return; + if(options.closeOnLeave || 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(options.closeOnLeave || 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; + }); + }); + } return dialog; }; @@ -11377,7 +11613,7 @@ LGraphNode.prototype.executeAction = function(action) var ref_window = options.window || window; var root = document.createElement("div"); root.className = "litegraph dialog"; - root.innerHTML = "
"; + root.innerHTML = "
"; root.header = root.querySelector(".dialog-header"); if(options.width) @@ -11397,14 +11633,44 @@ LGraphNode.prototype.executeAction = function(action) root.title_element = root.querySelector(".dialog-title"); root.title_element.innerText = title; root.content = root.querySelector(".dialog-content"); + root.alt_content = root.querySelector(".dialog-alt-content"); root.footer = root.querySelector(".dialog-footer"); root.close = function() { - if(this.parentNode) - this.parentNode.removeChild(this); + if (root.onClose && typeof root.onClose == "function"){ + root.onClose(); + } + root.parentNode.removeChild(root); + /* XXX CHECK THIS */ + if(this.parentNode){ + this.parentNode.removeChild(this); + } + /* XXX this was not working, was fixed with an IF, check this */ } + // function to swap panel content + root.toggleAltContent = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + var vAlt = force ? "none" : "block"; + }else{ + var vTo = root.alt_content.style.display != "block" ? "block" : "none"; + var vAlt = root.alt_content.style.display != "block" ? "none" : "block"; + } + root.alt_content.style.display = vTo; + root.content.style.display = vAlt; + } + + root.toggleFooterVisibility = function(force){ + if (typeof force != "undefined"){ + var vTo = force ? "block" : "none"; + }else{ + var vTo = root.footer.style.display != "block" ? "block" : "none"; + } + root.footer.style.display = vTo; + } + root.clear = function() { this.content.innerHTML = ""; @@ -11527,9 +11793,9 @@ LGraphNode.prototype.executeAction = function(action) console.log("change",name,value); //that.dirty_canvas = true; if(options.callback) - options.callback(name,value); + options.callback(name,value,options); if(callback) - callback(name,value); + callback(name,value,options); } return elem; @@ -11638,7 +11904,7 @@ LGraphNode.prototype.executeAction = function(action) } }; - // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // atlasan: implement: why not? + // panel.addWidget( "string", "Graph name", "", {}, fUpdate); // implement var aProps = LiteGraph.availableCanvasOptions; aProps.sort(); From 95e10bd80e3e042465442bf5a15fe5867076b147 Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 12:42:46 +0200 Subject: [PATCH 06/14] some cleaning --- src/litegraph.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index d61b1ab24..ac0cc8324 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -3226,8 +3226,6 @@ if (!options.action_call) options.action_call = this.id+"_act_"+Math.floor(Math.random()*9999); //pass the action name var target_connection = node.inputs[link_info.target_slot]; - //console.debug("ACTION: "+this.id+":"+this.order+" :: "+target_connection.name); - if (LiteGraph.refreshAncestorsOnActions) node.refreshAncestors({action: target_connection.name, param: param, options:options}); // wrap node.onAction(target_connection.name, param); node.actionDo(target_connection.name, param, options); } From edfcc5ccbf3a7d41516977a0728cfebbaf3b29bc Mon Sep 17 00:00:00 2001 From: atlasan Date: Sat, 9 Oct 2021 18:48:45 +0200 Subject: [PATCH 07/14] Fixes and cleaning - broken missing LGraph.nodes_executing and nodes_actioning - fix detect location for in out buttons in subgraph - clean debugs --- src/litegraph.js | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index ac0cc8324..ae6bf5cc3 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -1045,6 +1045,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 = []; }; /** @@ -4004,24 +4007,24 @@ } target_slot = target_node.findInputSlotByType(target_slotType, false, true); if (target_slot >= 0 && target_slot !== null){ - console.debug("CONNbyTYPE type "+target_slotType+" for "+target_slot) + //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?") + //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); + //console.debug("connect WILL CREATE THE onTrigger "+target_slotType+" to "+target_node); return this.connect(slot, target_node, -1); } if (opts.generalTypeInCase && (target_slotType !== 0 && target_slotType !== "" && target_slotType !== "*")){ // connect TO a general type (*, 0), if not found the specific type - console.debug("connect TO a general type (*, 0), if not found the specific type "+target_slotType+" to "+target_node); + //console.debug("connect TO a general type (*, 0), if not found the specific type "+target_slotType+" to "+target_node); target_slot = target_node.findInputSlotByType(0, false, true); if (target_slot >= 0 && target_slot !== null){ return this.connect(slot, target_node, target_slot); } }else{ - console.debug("no way to connect "+target_slotType+" to "+target_node); + //console.debug("no way to connect "+target_slotType+" to "+target_node); } return null; } @@ -7932,11 +7935,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) @@ -10826,7 +10829,7 @@ LGraphNode.prototype.executeAction = function(action) }; options = Object.assign(def_options, options || {}); - console.log(options); + //console.log(options); var that = this; var input_html = ""; @@ -11335,7 +11338,7 @@ LGraphNode.prototype.executeAction = function(action) //if (sV.toLowerCase() == "event/action") sV = LiteGraph.EVENT; // -1 if(sOut && sV){ - console.log("will check filter against "+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); @@ -11788,7 +11791,7 @@ LGraphNode.prototype.executeAction = function(action) function innerChange(name, value) { - console.log("change",name,value); + //console.log("change",name,value); //that.dirty_canvas = true; if(options.callback) options.callback(name,value,options); @@ -13571,7 +13574,7 @@ LGraphNode.prototype.executeAction = function(action) // 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); + //console.log("cant pointerListenerAdd "+oDOM+", "+sEvent+", "+fCall); return; // -- break -- } switch(sEvent){ @@ -13594,7 +13597,7 @@ 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){ From cbbdd38eab7e4754fba3a36ce89df3315834846c Mon Sep 17 00:00:00 2001 From: atlasan Date: Mon, 11 Oct 2021 11:51:51 +0200 Subject: [PATCH 08/14] ctx.roundRect re-Apply modern way for [radius] --- src/litegraph.js | 27 ++++++++++++--------------- 1 file changed, 12 insertions(+), 15 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index ae6bf5cc3..313d06ba7 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -7798,7 +7798,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = "#111"; ctx.globalAlpha = 0.8; ctx.beginPath(); - ctx.roundRect(10, 10, w, (num + 1) * h + 50, 8); + ctx.roundRect(10, 10, w, (num + 1) * h + 50, [8]); ctx.fill(); ctx.globalAlpha = 1; @@ -7867,7 +7867,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = "#111"; ctx.globalAlpha = 0.8; ctx.beginPath(); - ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, 8); + ctx.roundRect(canvas_w - w - 10, 10, w, (num + 1) * h + 50, [8]); ctx.fill(); ctx.globalAlpha = 1; @@ -7945,7 +7945,7 @@ LGraphNode.prototype.executeAction = function(action) if(clicked) ctx.fillStyle = "#AAA"; ctx.beginPath(); - ctx.roundRect(x,y,w,h,4 ); + ctx.roundRect(x,y,w,h,[4] ); ctx.fill(); if(text != null) @@ -8659,7 +8659,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowBlur = 3; ctx.fillStyle = "#454"; ctx.beginPath(); - ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h,3, 3); + ctx.roundRect( pos[0] - w*0.5, pos[1] - 15 - h, w, h, [3]); ctx.moveTo( pos[0] - 10, pos[1] - 15 ); ctx.lineTo( pos[0] + 10, pos[1] - 15 ); ctx.lineTo( pos[0], pos[1] - 5 ); @@ -8728,8 +8728,7 @@ LGraphNode.prototype.executeAction = function(action) area[1], area[2], area[3], - this.round_radius, - shape == LiteGraph.CARD_SHAPE ? 0 : this.round_radius + shape == LiteGraph.CARD_SHAPE ? [this.round_radius,this.round_radius,0,0] : [this.round_radius] ); } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( @@ -8794,8 +8793,7 @@ LGraphNode.prototype.executeAction = function(action) -title_height, size[0] + 1, title_height, - this.round_radius, - node.flags.collapsed ? this.round_radius : 0 + node.flags.collapsed ? [this.round_radius] : [this.round_radius,this.round_radius,0,0] ); } ctx.fill(); @@ -8922,7 +8920,7 @@ LGraphNode.prototype.executeAction = function(action) else { ctx.beginPath(); - ctx.roundRect(x+2, -w+2, w-4, w-4,4); + ctx.roundRect(x+2, -w+2, w-4, w-4,[4]); ctx.fill(); } ctx.fillStyle = "#333"; @@ -8968,7 +8966,7 @@ LGraphNode.prototype.executeAction = function(action) -6 + area[1], 12 + area[2], 12 + area[3], - this.round_radius * 2 + [this.round_radius * 2] ); } else if (shape == LiteGraph.CARD_SHAPE) { ctx.roundRect( @@ -8976,8 +8974,7 @@ LGraphNode.prototype.executeAction = function(action) -6 + area[1], 12 + area[2], 12 + area[3], - this.round_radius * 2, - 2 + [this.round_radius * 2,2,this.round_radius * 2,2] ); } else if (shape == LiteGraph.CIRCLE_SHAPE) { ctx.arc( @@ -9571,7 +9568,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if (show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5]); else ctx.rect(margin, posY, widget_width - margin * 2, H ); ctx.fill(); @@ -9628,7 +9625,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if(show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5] ); else ctx.rect(margin, posY, widget_width - margin * 2, H ); ctx.fill(); @@ -9688,7 +9685,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.fillStyle = background_color; ctx.beginPath(); if (show_text) - ctx.roundRect(margin, posY, widget_width - margin * 2, H, H * 0.5); + ctx.roundRect(margin, posY, widget_width - margin * 2, H, [H * 0.5]); else ctx.rect( margin, posY, widget_width - margin * 2, H ); ctx.fill(); From dfa7a2a71a4c57d2ec8142f19e20a6c6367092d1 Mon Sep 17 00:00:00 2001 From: atlasan Date: Mon, 11 Oct 2021 14:53:34 +0200 Subject: [PATCH 09/14] Change new options to disabled, fix retrocompatibility - currently testing with webglstudio --- src/litegraph.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index 313d06ba7..9f3b1428e 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -113,14 +113,14 @@ 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: true, // [true!] enable filtering slots type in the search widget, !requires auto_load_slot_types + 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: 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] + 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 @@ -3098,7 +3098,11 @@ // 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); + if (this.graph.nodes_executing) this.graph.nodes_executing[this.id] = true; //.push(this.id); + else{ + this.graph.nodes_executing = {}; + this.graph.nodes_executing[this.id] = true; + } this.onExecute(param, options); @@ -10815,7 +10819,7 @@ LGraphNode.prototype.executeAction = function(action) def_options = { slot_from: null ,node_from: null ,node_to: null - ,do_type_filter: LiteGraph.auto_load_slot_types && LiteGraph.search_filter_enabled // this will be checked for functionality enabled : filter on slot type, in and out + ,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 From 84ff381c464fbaaf30d00e2bd1c4afc0ca1b4bf3 Mon Sep 17 00:00:00 2001 From: atlasan Date: Thu, 14 Oct 2021 18:42:57 +0200 Subject: [PATCH 10/14] Small fix and new features - nodes_executing, nodes_actioning initial set - slot_types_default (in and out) helps in having defaults nodes for specific slot_types - slot_types_default are filled as an example in src/nodes/others.js - middle_click_slot_add_default_node allows auto-placing defaults nodes next to a slot - release_link_on_empty_shows_menu conditions having realease-in-empty-space functionality - keypress for slot name - do include new js/defaults.js in editor and leave the Lib with new functionalities off by default - new html with mobile editor (working on) - separate defaults - optional thirdy-party html console --- editor/editor_mobile.html | 144 +++++++++++++ editor/index.html | 91 ++++---- editor/js/code.js | 7 +- editor/js/defaults.js | 31 +++ editor/js/defaults_mobile.js | 31 +++ src/litegraph.js | 388 +++++++++++++++++++++++++++++------ src/nodes/others.js | 38 +++- 7 files changed, 623 insertions(+), 107 deletions(-) create mode 100644 editor/editor_mobile.html create mode 100644 editor/js/defaults.js create mode 100644 editor/js/defaults_mobile.js 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 index 9f3b1428e..b5d30a68b 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -123,13 +123,19 @@ 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 - allowMultiOutputForEvents: true, // [false!] being events, it is strongly reccomended to use them sequentually, one by one + 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) @@ -288,7 +294,7 @@ if (typeof slot_type == "string"){ var aTypes = slot_type.split(","); }else if (slot_type == this.EVENT || slot_type == this.ACTION){ - var aTypes = ["EVENT/ACTION"]; + var aTypes = ["_event_"]; }else{ var aTypes = ["*"]; } @@ -304,13 +310,13 @@ // check if is a new type if (!out){ - if (!this.slot_types_in.includes(sT)){ - this.slot_types_in.push(sT); + 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)){ - this.slot_types_out.push(sT); + if (!this.slot_types_out.includes(sT.toLowerCase())){ + this.slot_types_out.push(sT.toLowerCase()); this.slot_types_out.sort(); } } @@ -832,6 +838,10 @@ this.catch_errors = true; + this.nodes_executing = []; + this.nodes_actioning = []; + this.nodes_executedAction = []; + //subgraph_data this.inputs = {}; this.outputs = {}; @@ -3098,12 +3108,8 @@ // enable this to give the event an ID if (!options.action_call) options.action_call = this.id+"_exec_"+Math.floor(Math.random()*9999); - if (this.graph.nodes_executing) this.graph.nodes_executing[this.id] = true; //.push(this.id); - else{ - this.graph.nodes_executing = {}; - this.graph.nodes_executing[this.id] = true; - } - + this.graph.nodes_executing[this.id] = true; //.push(this.id); + this.onExecute(param, options); this.graph.nodes_executing[this.id] = false; //.pop(); @@ -4213,7 +4219,7 @@ if (output.links !== null && output.links.length){ switch(output.type){ case LiteGraph.EVENT: - if (!LiteGraph.allowMultiOutputForEvents){ + if (!LiteGraph.allow_multi_output_for_events){ this.graph.beforeChange(); this.disconnectOutput(slot, false, {doProcessChange: false}); // Input(target_slot, {doProcessChange: false}); changed = true; @@ -6083,6 +6089,148 @@ 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 middleClickedSlot = false; + var middleClickedSlot_index = false; + var middleClickedSlot_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 + ) + ) { + middleClickedSlot = output; + middleClickedSlot_index = i; + middleClickedSlot_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 + ) + ) { + middleClickedSlot = input; + middleClickedSlot_index = i; + middleClickedSlot_isOut = false; + break; + } + } + } + //console.log("middleClickSlots? "+middleClickedSlot+" & "+(middleClickedSlot_index!==false)); + if (middleClickedSlot && middleClickedSlot_index!==false){ + // check for defaults nodes for this slottype + var slotTypesDefault = middleClickedSlot_isOut ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + //console.log("middleClickSlots "+middleClickedSlot.type+" in "+slotTypesDefault[middleClickedSlot.type]); + if(slotTypesDefault && slotTypesDefault[middleClickedSlot.type]){ + if (middleClickedSlot.link !== null) { + // is connected + }else{ + // is not not connected + } + nodeNewType = false; + if(typeof slotTypesDefault[middleClickedSlot.type] == "object" || typeof slotTypesDefault[middleClickedSlot.type] == "array"){ + for (typeX in slotTypesDefault[middleClickedSlot.type]){ + nodeNewType = slotTypesDefault[middleClickedSlot.type][typeX]; + break; // -------- + } + }else{ + nodeNewType = slotTypesDefault[middleClickedSlot.type]; + } + 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); + // connect the two! + if (middleClickedSlot_isOut){ + newNode.pos = [e.canvasX+30,e.canvasY]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5]; + node.connectByType( middleClickedSlot_index, newNode, middleClickedSlot.type ); + }else{ + newNode.pos = [e.canvasX-30-(newNode.size?newNode.size[0]:0),e.canvasY]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5]; + node.connectByTypeOutput( middleClickedSlot_index, newNode, middleClickedSlot.type ); + } + //that.graph.afterChange(); + }else{ + console.log("failed creating "+nodeNewType); + } + } + }else{ + //console.log("no slotTypesDefault for MIDDLECLICK "+middleClickedSlot.type); + } + } + } + } + } + } else if (e.which == 3 || this.pointer_is_double) { //right button if(!this.read_only) @@ -6570,20 +6718,21 @@ LGraphNode.prototype.executeAction = function(action) }else{ // add menu when releasing link in empty space - - 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}); - } - } + 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; @@ -10569,14 +10718,40 @@ LGraphNode.prototype.executeAction = function(action) } var options = ["Add Node",null]; + + if (that.allow_searchbox){ + options.push("Search"); + options.push(null); + } + + // get defaults nodes for this slottype + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[slotX.type]){ + //console.log("TypeDefaulMenu (OUT?"+isFrom+") "+slotX.type+" :: "+slotTypesDefault[slotX.type]); + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + //console.log(typeof slotTypesDefault[slotX.type]); + if(typeof slotTypesDefault[slotX.type] == "object" || typeof slotTypesDefault[slotX.type] == "array"){ + for(var typeX in slotTypesDefault[slotX.type]){ + //console.log(slotX.type+" has default "+slotTypesDefault[slotX.type][typeX]); + options.push(slotTypesDefault[slotX.type][typeX]); + } + }else{ + options.push(slotTypesDefault[slotX.type]); + } + } + var menu = new LiteGraph.ContextMenu(options, { event: opts.e, - title: slotX && slotX.name ? slotX.name : null, + title: (slotX && slotX.name!="" ? (slotX.name + (slotX.type?" | ":"")) : "")+(slotX && slotX.type ? slotX.type : ""), callback: inner_clicked }); function inner_clicked(v,options,e) { - console.debug("menuadd"); + //console.log("Process showConnectionMenu selection"); switch (v) { case "Add Node": LGraphCanvas.onMenuAdd(null, null, e, menu, function(node){ @@ -10587,10 +10762,90 @@ LGraphNode.prototype.executeAction = function(action) } }); break; - /*case "Delete": - that.graph.removeLink(link.id); - break;*/ + case "Search": + if(isFrom){ + that.showSearchBox(e,{node_from: opts.nodeFrom, slot_from: slotX, type_filter_in: slotX.type}); + }else{ + that.showSearchBox(e,{node_to: opts.nodeTo, slot_from: slotX, type_filter_out: slotX.type}); + } + break; default: + // check for defaults nodes for this slottype + var slotTypesDefault = isFrom ? LiteGraph.slot_types_default_out : LiteGraph.slot_types_default_in; + if(slotTypesDefault && slotTypesDefault[slotX.type]){ + if (slotX.link !== null) { + // is connected + }else{ + // is not not connected + } + nodeNewType = false; + if(typeof slotTypesDefault[slotX.type] == "object" || typeof slotTypesDefault[slotX.type] == "array"){ + for(var typeX in slotTypesDefault[slotX.type]){ + if (v == slotTypesDefault[slotX.type][typeX]) nodeNewType = slotTypesDefault[slotX.type][typeX]; + } + }else{ + if (v == slotTypesDefault[slotX.type]) nodeNewType = slotTypesDefault[slotX.type]; + } + 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 + newNode.pos = [opts.e.canvasX,opts.e.canvasY]; //that.last_click_position; //[e.canvasX+30, e.canvasX+5]; + that.graph.add(newNode); + //that.graph.afterChange(); + // connect the two! + if (isFrom){ + opts.nodeFrom.connectByType( iSlotConn, newNode, slotX.type ); + }else{ + opts.nodeTo.connectByTypeOutput( iSlotConn, newNode, slotX.type ); + } + }else{ + console.log("failed creating "+nodeNewType); + } + } + }else{ + //console.log("no slotTypesDefault for "+slotX.type); + } + break; } } @@ -10974,7 +11229,7 @@ LGraphNode.prototype.executeAction = function(action) 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/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 = "*";*/ @@ -11001,7 +11256,7 @@ LGraphNode.prototype.executeAction = function(action) var nSlots = aSlots.length; // this for object :: Object.keys(aSlots).length; if (options.type_filter_out == LiteGraph.EVENT || options.type_filter_out == LiteGraph.ACTION) - options.type_filter_out = "event/action"; + options.type_filter_out = "_event_"; /* this will filter on * .. but better do it manually in case else if(options.type_filter_out === "" || options.type_filter_out === 0) options.type_filter_out = "*";*/ @@ -11317,7 +11572,7 @@ LGraphNode.prototype.executeAction = function(action) var sV = sIn.value; if (opts.inTypeOverride!==false) sV = opts.inTypeOverride; - //if (sV.toLowerCase() == "event/action") sV = LiteGraph.EVENT; // -1 + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 if(sIn && sV){ //console.log("will check filter against "+sV); @@ -11336,7 +11591,7 @@ LGraphNode.prototype.executeAction = function(action) var sV = sOut.value; if (opts.outTypeOverride!==false) sV = opts.outTypeOverride; - //if (sV.toLowerCase() == "event/action") sV = LiteGraph.EVENT; // -1 + //if (sV.toLowerCase() == "_event_") sV = LiteGraph.EVENT; // -1 if(sOut && sV){ //console.log("search will check filter against "+sV); @@ -11846,8 +12101,8 @@ LGraphNode.prototype.executeAction = function(action) // assume coming from the menu event click if (!obEv || !obEv.event || !obEv.event.target || !obEv.event.target.lgraphcanvas){ console.warn("Canvas not found"); // need a ref to canvas obj - console.debug(event); - console.debug(event.target); + /*console.debug(event); + console.debug(event.target);*/ return; } var graphcanvas = obEv.event.target.lgraphcanvas; @@ -12597,16 +12852,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 = @@ -12687,19 +12942,32 @@ LGraphNode.prototype.executeAction = function(action) if (input && slot_info) { input.value = slot_info.label || ""; } - dialog - .querySelector("button") - .addEventListener("click", function(e) { - node.graph.beforeChange(); - 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(); - node.graph.afterChange(); - }); + } 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) diff --git a/src/nodes/others.js b/src/nodes/others.js index 7d720af64..2b2f0fe95 100755 --- a/src/nodes/others.js +++ b/src/nodes/others.js @@ -1 +1,37 @@ -// extra generic nodes \ No newline at end of file +(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/sequence","events/log","events/counter"]; + LiteGraph.slot_types_default_out["array"] = ["basic/watch","basic/set_array","basic/array[]"]; + LiteGraph.slot_types_default_out["boolean"] = ["logic/IF","basic/watch","math/branch","math/gate"]; + LiteGraph.slot_types_default_out["number"] = ["basic/watch" + ,{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 Date: Fri, 15 Oct 2021 02:27:46 +0200 Subject: [PATCH 11/14] Fix and refactor --- src/litegraph.js | 441 +++++++++++++++++++++++--------------------- src/nodes/events.js | 3 +- src/nodes/others.js | 4 +- 3 files changed, 233 insertions(+), 215 deletions(-) mode change 100755 => 100644 src/nodes/others.js diff --git a/src/litegraph.js b/src/litegraph.js index b5d30a68b..3bcb1661b 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -3973,7 +3973,10 @@ var aDest = (aSlots[i].type+"").toLowerCase().split(","); for(sI=0;sI Date: Fri, 15 Oct 2021 16:37:25 +0200 Subject: [PATCH 12/14] MultiActions for context menu - right click node menu actions will be applied to all selected nodes (mode, resize, collapse, colors, shapes, clone, remove) - can add to selection with both cntrl and shift, and dragging new rects with cntr+shift - TODO apply to alt-drag clone, properties(?), .. --- src/litegraph.js | 360 ++++++++++++++++++++++++++++++++--------------- 1 file changed, 243 insertions(+), 117 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index 3bcb1661b..e14487eec 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -1626,13 +1626,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; }; /** @@ -6040,49 +6046,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); - e.preventDefault(); - e.stopPropagation(); - - } + 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) { @@ -6136,7 +6144,7 @@ LGraphNode.prototype.executeAction = function(action) var alphaPosY = 0.5-((mClikSlot_index+1)/((mClikSlot_isOut?node.outputs.length:node.inputs.length))); var node_bounding = node.getBounding(); - // etimate a position: this is a bad semi-bad-working mess .. + // 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" ]; @@ -6156,9 +6164,22 @@ LGraphNode.prototype.executeAction = function(action) } } else if (e.which == 3 || this.pointer_is_double) { + //right button - if(!this.read_only) - this.processContextMenu(node, e); + if(!this.read_only){ + 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 @@ -6529,11 +6550,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]); @@ -6550,24 +6577,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) { @@ -6578,12 +6612,6 @@ LGraphNode.prototype.executeAction = function(action) var connInOrOut = this.connecting_output || this.connecting_input; var connType = connInOrOut.type; - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); - //node below mouse if (node) { @@ -7228,7 +7256,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); } @@ -7255,12 +7283,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; @@ -10546,13 +10575,26 @@ 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); }; @@ -12484,9 +12526,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) { @@ -12504,12 +12559,23 @@ LGraphNode.prototype.executeAction = function(action) return; } var kV = Object.values(LiteGraph.NODE_MODES).indexOf(v); - if (kV>=0 && LiteGraph.NODE_MODES[kV]) - node.changeMode(kV); - else{ - console.warn("unexpected mode: "+v); - node.changeMode(LiteGraph.ALWAYS); - } + 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; @@ -12555,17 +12621,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); } @@ -12588,9 +12666,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); } @@ -12602,13 +12693,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); }; @@ -12634,17 +12738,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); @@ -12668,6 +12792,7 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.prototype.getCanvasMenuOptions = function() { var options = null; + var that = this; if (this.getMenuOptions) { options = this.getMenuOptions(); } else { @@ -12677,12 +12802,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: thisGCanvas.showShowGraphOptionsPanel }); - } + /*if (LiteGraph.showCanvasOptions){ + options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); + }*/ if (this._graph_stack && this._graph_stack.length > 0) { options.push(null, { @@ -12740,7 +12866,7 @@ LGraphNode.prototype.executeAction = function(action) }]; if(node.resizable !== false){ options.push({ - content: "Resize", callback: LGraphCanvas.onResizeNode + content: "Resize", callback: LGraphCanvas.onMenuResizeNode }); } options.push( From fc605e01717865937b0666a064de9647f0381caa Mon Sep 17 00:00:00 2001 From: atlasan Date: Fri, 22 Oct 2021 16:09:08 +0200 Subject: [PATCH 13/14] Fixes Autoconnect: allows generic types * Touches, they should be evaluated when pointerEvents are not available? first glimpse --- src/litegraph.js | 139 +++++++++++++++++++++++++++++++++++------------ 1 file changed, 105 insertions(+), 34 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index e14487eec..73744005d 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -611,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) + !generic //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; } @@ -636,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; } } @@ -3973,15 +3976,19 @@ 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+"").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){ + // 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); } - }else{ - //console.debug("no way to connect "+target_slotType+" to "+target_node); } + // 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; } } @@ -4096,6 +4117,9 @@ } } + 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; } @@ -4205,6 +4229,8 @@ if(changed) this.graph.connectionChange(this, link_info); return null; + }else{ + //console.debug("valid connection",output.type, input.type); } //allows nodes to block connection, callback @@ -5525,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 @@ -6166,16 +6192,21 @@ LGraphNode.prototype.executeAction = function(action) } else if (e.which == 3 || this.pointer_is_double) { //right button - if(!this.read_only){ - 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]); + 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); } @@ -10143,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"); @@ -13983,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"){ + /* 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" From cb70928146fd4e8617b3cc1395373d3d92716b59 Mon Sep 17 00:00:00 2001 From: atlasan Date: Mon, 25 Oct 2021 12:02:13 +0200 Subject: [PATCH 14/14] tiny fix --- src/litegraph.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/litegraph.js b/src/litegraph.js index 73744005d..56f8c2806 100644 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -614,7 +614,7 @@ if (type_a=="" || type_a==="*") type_a = 0; if (type_b=="" || type_b==="*") type_b = 0; if ( - !generic //generic output + !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)