From b80209f7da60a4ba21c2a11af2eae13193c2f59e Mon Sep 17 00:00:00 2001 From: Marc Meszaros Date: Wed, 26 Apr 2023 10:29:34 -0700 Subject: [PATCH 1/7] Update documentation to mention 'precision' as widget option --- guides/README.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/guides/README.md b/guides/README.md index 536f4a7b6..f24d55d62 100755 --- a/guides/README.md +++ b/guides/README.md @@ -110,7 +110,7 @@ Slots have the next information: * **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT * **color_on**: color to render when it is connected * **color_off**: color to render when it is not connected - + To retrieve the data traveling through a link you can call ```node.getInputData``` or ```node.getOutputData``` ### Define your Graph Node @@ -151,7 +151,7 @@ node.onDrawForeground = function(ctx, graphcanvas) } ``` -### Custom Node Behaviour +### Custom Node Behaviour You can also grab events from the mouse in case your node has some sort of special interactivity. @@ -187,16 +187,16 @@ function MyNodeType() ``` This is the list of supported widgets: -* **"number"** to change a value of a number, the syntax is ```this.addWidget("number","Number", current_value, callback, { min: 0, max: 100, step: 1} );``` +* **"number"** to change a value of a number, the syntax is ```this.addWidget("number","Number", current_value, callback, { min: 0, max: 100, step: 1, precision: 3 } );``` * **"slider"** to change a number by dragging the mouse, the syntax is the same as number. * **"combo"** to select between multiple choices, the syntax is: ```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );``` - + or if you want to use objects: - + ```this.addWidget("combo","Combo", value1, callback, { values: { "title1":value1, "title2":value2 } } );``` - + * **"text"** to edit a short string * **"toggle"** like a checkbox * **"button"** @@ -205,6 +205,7 @@ The fourth optional parameter could be options for the widget, the parameters ac * **property**: specifies the name of a property to modify when the widget changes * **min**: min value * **max**: max value +* **precision**: set the number of digits after decimal point * **callback**: function to call when the value changes. Widget's value is not serialized by default when storing the node state, but if you want to store the value of widgets just set serialize_widgets to true: @@ -223,7 +224,7 @@ Or if you want to associate a widget with a property of the node, then specify i function MyNode() { this.properties = { surname: "smith" }; - this.addWidget("text","Surname","", { property: "surname"}); //this will modify the node.properties + this.addWidget("text","Surname","", { property: "surname"}); //this will modify the node.properties } ``` ## LGraphCanvas @@ -277,7 +278,7 @@ To define slots for nodes you must use the type LiteGraph.ACTION for inputs, and function MyNode() { this.addInput("play", LiteGraph.ACTION ); - this.addInput("onFinish", LiteGraph.EVENT ); + this.addInput("onFinish", LiteGraph.EVENT ); } ``` From 1dc167044e0eccd7a2731527f8b4b554e0486100 Mon Sep 17 00:00:00 2001 From: Marc Meszaros Date: Thu, 20 Apr 2023 11:29:46 -0700 Subject: [PATCH 2/7] Allow dragging and interacting with nodes with graph being read-only --- guides/README.md | 21 ++++++++++++++------- src/litegraph.js | 12 ++++++------ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/guides/README.md b/guides/README.md index 536f4a7b6..efdc6c308 100755 --- a/guides/README.md +++ b/guides/README.md @@ -110,7 +110,7 @@ Slots have the next information: * **dir**: optional, could be LiteGraph.UP, LiteGraph.RIGHT, LiteGraph.DOWN, LiteGraph.LEFT * **color_on**: color to render when it is connected * **color_off**: color to render when it is not connected - + To retrieve the data traveling through a link you can call ```node.getInputData``` or ```node.getOutputData``` ### Define your Graph Node @@ -151,7 +151,7 @@ node.onDrawForeground = function(ctx, graphcanvas) } ``` -### Custom Node Behaviour +### Custom Node Behaviour You can also grab events from the mouse in case your node has some sort of special interactivity. @@ -192,11 +192,11 @@ This is the list of supported widgets: * **"combo"** to select between multiple choices, the syntax is: ```this.addWidget("combo","Combo", "red", callback, { values:["red","green","blue"]} );``` - + or if you want to use objects: - + ```this.addWidget("combo","Combo", value1, callback, { values: { "title1":value1, "title2":value2 } } );``` - + * **"text"** to edit a short string * **"toggle"** like a checkbox * **"button"** @@ -223,11 +223,18 @@ Or if you want to associate a widget with a property of the node, then specify i function MyNode() { this.properties = { surname: "smith" }; - this.addWidget("text","Surname","", { property: "surname"}); //this will modify the node.properties + this.addWidget("text","Surname","", { property: "surname"}); //this will modify the node.properties } ``` ## LGraphCanvas LGraphCanvas is the class in charge of rendering/interaction with the nodes inside the browser. + +## LGraphCanvas settings +There are graph canvas settings that could be defined or modified to change behaviour: + +* **allow_interaction**: when set to `false` disable interaction with the canvas +* **drag_mode**: when set to `true` and `allow_interaction` is `false`, allow individual nodes with `flags.allow_interaction` set to `true` to be interacted with + ### Canvas Shortcuts * Space - Holding space key while moving the cursor moves the canvas around. It works when holding the mouse button down so it is easier to connect different nodes when the canvas gets too large. * Ctrl/Shift + Click - Add clicked node to selection. @@ -277,7 +284,7 @@ To define slots for nodes you must use the type LiteGraph.ACTION for inputs, and function MyNode() { this.addInput("play", LiteGraph.ACTION ); - this.addInput("onFinish", LiteGraph.EVENT ); + this.addInput("onFinish", LiteGraph.EVENT ); } ``` diff --git a/src/litegraph.js b/src/litegraph.js index a51b14463..195102b68 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -5214,7 +5214,7 @@ LGraphNode.prototype.executeAction = function(action) this.allow_reconnect_links = true; //allows to change a connection with having to redo it again this.align_to_grid = false; //snap to grid - this.drag_mode = false; + this.drag_mode = false; // allow dragging when interactions are disabled this.dragging_rectangle = null; this.filter = null; //allows to filter to only accept some type of nodes in a graph @@ -5865,13 +5865,13 @@ LGraphNode.prototype.executeAction = function(action) //when clicked on top of a node //and it is not interactive - if (node && this.allow_interaction && !skip_action && !this.read_only) { + if (node && (this.allow_interaction || (!this.allow_interaction && node.flags.allow_interaction)) && !skip_action && !this.read_only) { if (!this.live_mode && !node.flags.pinned) { this.bringToFront(node); } //if it wasn't selected? //not dragging mouse to connect two slots - if ( !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { + if ( this.allow_interaction && !this.connecting_node && !node.flags.collapsed && !this.live_mode ) { //Search for corner for resize if ( !skip_action && node.resizable !== false && @@ -6025,7 +6025,7 @@ LGraphNode.prototype.executeAction = function(action) } //double clicking - if (is_double_click && this.selected_nodes[node.id]) { + if (this.allow_interaction && is_double_click && this.selected_nodes[node.id]) { //double click node if (node.onDblClick) { node.onDblClick( e, pos, this ); @@ -6328,7 +6328,7 @@ LGraphNode.prototype.executeAction = function(action) this.ds.offset[1] += delta[1] / this.ds.scale; this.dirty_canvas = true; this.dirty_bgcanvas = true; - } else if (this.allow_interaction && !this.read_only) { + } else if ((this.allow_interaction || (!this.allow_interaction && this.drag_mode)) && !this.read_only) { if (this.connecting_node) { this.dirty_canvas = true; } @@ -9912,7 +9912,7 @@ LGraphNode.prototype.executeAction = function(action) event, active_widget ) { - if (!node.widgets || !node.widgets.length) { + if (!node.widgets || !node.widgets.length || (!this.allow_interaction && !node.flags.allow_interaction)) { return null; } From 044b29c614d7ce99c286630addb40ff99859f202 Mon Sep 17 00:00:00 2001 From: Marc Meszaros Date: Sun, 30 Apr 2023 10:34:04 -0700 Subject: [PATCH 3/7] Simplify the allow_interaction override to use node flags Remove the need to use 'drag_mode' on the graph cavas and instead use a similarly name flag on the node 'allow_interaction' to allow per node interaction when the global 'allow_interaction' flag is set to false. --- guides/README.md | 3 +-- src/litegraph.js | 12 ++++++------ 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/guides/README.md b/guides/README.md index 9a18be56a..e7875d260 100755 --- a/guides/README.md +++ b/guides/README.md @@ -233,8 +233,7 @@ LGraphCanvas is the class in charge of rendering/interaction with the nodes insi ## LGraphCanvas settings There are graph canvas settings that could be defined or modified to change behaviour: -* **allow_interaction**: when set to `false` disable interaction with the canvas -* **drag_mode**: when set to `true` and `allow_interaction` is `false`, allow individual nodes with `flags.allow_interaction` set to `true` to be interacted with +* **allow_interaction**: when set to `false` disable interaction with the canvas (`flags.allow_interaction` on node can be used to override graph canvas setting) ### Canvas Shortcuts * Space - Holding space key while moving the cursor moves the canvas around. It works when holding the mouse button down so it is easier to connect different nodes when the canvas gets too large. diff --git a/src/litegraph.js b/src/litegraph.js index a77e0d12e..94e5ca0ef 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -5214,7 +5214,7 @@ LGraphNode.prototype.executeAction = function(action) this.allow_reconnect_links = true; //allows to change a connection with having to redo it again this.align_to_grid = false; //snap to grid - this.drag_mode = false; // allow dragging when interactions are disabled + this.drag_mode = false; this.dragging_rectangle = null; this.filter = null; //allows to filter to only accept some type of nodes in a graph @@ -5865,7 +5865,7 @@ LGraphNode.prototype.executeAction = function(action) //when clicked on top of a node //and it is not interactive - if (node && (this.allow_interaction || (!this.allow_interaction && node.flags.allow_interaction)) && !skip_action && !this.read_only) { + if (node && (this.allow_interaction || node.flags.allow_interaction) && !skip_action && !this.read_only) { if (!this.live_mode && !node.flags.pinned) { this.bringToFront(node); } //if it wasn't selected? @@ -6299,6 +6299,9 @@ LGraphNode.prototype.executeAction = function(action) this.dirty_canvas = true; } + //get node over + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); + if (this.dragging_rectangle) { this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; @@ -6328,14 +6331,11 @@ LGraphNode.prototype.executeAction = function(action) this.ds.offset[1] += delta[1] / this.ds.scale; this.dirty_canvas = true; this.dirty_bgcanvas = true; - } else if ((this.allow_interaction || (!this.allow_interaction && this.drag_mode)) && !this.read_only) { + } else if ((this.allow_interaction || (node && node.flags.allow_interaction)) && !this.read_only) { if (this.connecting_node) { this.dirty_canvas = true; } - //get node over - var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); - //remove mouseover flag for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { From badf134f123ceb60ed1aa9a86528e1d3fcfb53c6 Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Sun, 30 Apr 2023 23:26:07 -0700 Subject: [PATCH 4/7] feat: Add alignment for selected node --- src/litegraph.js | 91 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) diff --git a/src/litegraph.js b/src/litegraph.js index a77e0d12e..e61b781c7 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -10294,6 +10294,96 @@ LGraphNode.prototype.executeAction = function(action) canvas.graph.add(group); }; + /** + * Determines the furthest nodes in each direction + * @param nodes {LGraphNode[]} the nodes to from which boundary nodes will be extracted + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.getBoundaryNodes = function(nodes) { + let top = null; + let right = null; + let bottom = null; + let left = null; + for (const nID in nodes) { + const node = nodes[nID]; + const [x, y] = node.pos; + const [width, height] = node.size; + + if (top === null || y < top.pos[1]) { + top = node; + } + if (right === null || x + width > right.pos[0] + right.size[0]) { + right = node; + } + if (bottom === null || y + height > bottom.pos[1] + bottom.size[1]) { + bottom = node; + } + if (left === null || x < left.pos[0]) { + left = node; + } + } + + return { + "top": top, + "right": right, + "bottom": bottom, + "left": left + }; + } + /** + * Determines the furthest nodes in each direction for the currently selected nodes + * @return {{left: LGraphNode, top: LGraphNode, right: LGraphNode, bottom: LGraphNode}} + */ + LGraphCanvas.prototype.boundaryNodesForSelection = function() { + return LGraphCanvas.getBoundaryNodes(Object.values(this.selected_nodes)); + } + + /** + * + * @param {LGraphNode[]} nodes a list of nodes + * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes + */ + LGraphCanvas.alignNodes = function (nodes, direction) { + if (!nodes) { + return; + } + + const canvas = LGraphCanvas.active_canvas; + const boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) + + for (const [_, node] of Object.entries(canvas.selected_nodes)) { + switch (direction) { + case "right": + node.pos[0] = boundaryNodes["right"].pos[0] + boundaryNodes["right"].size[0] - node.size[0]; + break; + case "left": + node.pos[0] = boundaryNodes["left"].pos[0]; + break; + case "top": + node.pos[1] = boundaryNodes["top"].pos[1]; + break; + case "bottom": + node.pos[1] = boundaryNodes["bottom"].pos[1] + boundaryNodes["bottom"].size[1] - node.size[1]; + break; + } + } + + canvas.dirty_canvas = true; + canvas.dirty_bgcanvas = true; + }; + + LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase()); + } + } + LGraphCanvas.onMenuAdd = function (node, options, e, prev_menu, callback) { var canvas = LGraphCanvas.active_canvas; @@ -12887,6 +12977,7 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuAdd }, { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, + { content: "Align", has_submenu: true, callback: LGraphCanvas.onGroupAlign }, //{ content: "Arrange", callback: that.graph.arrange }, //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; From 9f3245d9f0043b87a3d4b8aa111264a228286efc Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Sun, 30 Apr 2023 22:40:02 -0700 Subject: [PATCH 5/7] refactor: Only show Align options if more than one node is selected --- src/litegraph.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/litegraph.js b/src/litegraph.js index e61b781c7..043df4d88 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -12977,7 +12977,6 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuAdd }, { content: "Add Group", callback: LGraphCanvas.onGroupAdd }, - { content: "Align", has_submenu: true, callback: LGraphCanvas.onGroupAlign }, //{ content: "Arrange", callback: that.graph.arrange }, //{content:"Collapse All", callback: LGraphCanvas.onMenuCollapseAll } ]; @@ -12985,6 +12984,14 @@ LGraphNode.prototype.executeAction = function(action) options.push({ content: "Options", callback: that.showShowGraphOptionsPanel }); }*/ + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align", + has_submenu: true, + callback: LGraphCanvas.onGroupAlign, + }) + } + if (this._graph_stack && this._graph_stack.length > 0) { options.push(null, { content: "Close subgraph", From cfc64dfe5290bf588430d974d8f855b25984d16b Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Sun, 30 Apr 2023 22:45:53 -0700 Subject: [PATCH 6/7] feat: Target alignment when right clicking a specific node --- src/litegraph.js | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-) diff --git a/src/litegraph.js b/src/litegraph.js index 043df4d88..760d55bf8 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -10342,14 +10342,25 @@ LGraphNode.prototype.executeAction = function(action) * * @param {LGraphNode[]} nodes a list of nodes * @param {"top"|"bottom"|"left"|"right"} direction Direction to align the nodes + * @param {LGraphNode?} align_to Node to align to (if null, align to the furthest node in the given direction) */ - LGraphCanvas.alignNodes = function (nodes, direction) { + LGraphCanvas.alignNodes = function (nodes, direction, align_to) { if (!nodes) { return; } const canvas = LGraphCanvas.active_canvas; - const boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) + let boundaryNodes = [] + if (align_to === undefined) { + boundaryNodes = LGraphCanvas.getBoundaryNodes(nodes) + } else { + boundaryNodes = { + "top": align_to, + "right": align_to, + "bottom": align_to, + "left": align_to + } + } for (const [_, node] of Object.entries(canvas.selected_nodes)) { switch (direction) { @@ -10372,6 +10383,18 @@ LGraphNode.prototype.executeAction = function(action) canvas.dirty_bgcanvas = true; }; + LGraphCanvas.onNodeAlign = function(value, options, event, prev_menu, node) { + new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { + event: event, + callback: inner_clicked, + parentMenu: prev_menu, + }); + + function inner_clicked(value) { + LGraphCanvas.alignNodes(LGraphCanvas.active_canvas.selected_nodes, value.toLowerCase(), node); + } + } + LGraphCanvas.onGroupAlign = function(value, options, event, prev_menu) { new LiteGraph.ContextMenu(["Top", "Bottom", "Left", "Right"], { event: event, @@ -13106,6 +13129,14 @@ LGraphNode.prototype.executeAction = function(action) callback: LGraphCanvas.onMenuNodeToSubgraph }); + if (Object.keys(this.selected_nodes).length > 1) { + options.push({ + content: "Align Selected To", + has_submenu: true, + callback: LGraphCanvas.onNodeAlign, + }) + } + options.push(null, { content: "Remove", disabled: !(node.removable !== false && !node.block_delete ), From e466b5edc8f76bdb2c3e2cdc0bf6e9fc798d4f52 Mon Sep 17 00:00:00 2001 From: Michael Poutre Date: Sun, 30 Apr 2023 22:47:42 -0700 Subject: [PATCH 7/7] refactor: Update startup message for example app to include URL Allows clicking the URL to launch example app, this also allows some IDEs such as WebStorm to launch integrated debugger far more easily --- utils/server.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/utils/server.js b/utils/server.js index 9171c7b9d..490522588 100755 --- a/utils/server.js +++ b/utils/server.js @@ -7,4 +7,4 @@ app.use('/external', express.static('external')) app.use('/editor', express.static('editor')) app.use('/', express.static('editor')) -app.listen(8000, () => console.log('Example app listening on port 8000!')) +app.listen(8000, () => console.log('Example app listening on http://127.0.0.1:8000!'))