diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css
index 8e9daaf4d..7d4c6cf60 100755
--- a/css/litegraph-editor.css
+++ b/css/litegraph-editor.css
@@ -1,224 +1,228 @@
.litegraph-editor {
- width: 100%;
- height: 100%;
- margin: 0;
- padding: 0;
+ width: 100%;
+ height: 100%;
+ margin: 0;
+ padding: 0;
- background-color: #333;
- color: #EEE;
- font: 14px Tahoma;
+ background-color: #333;
+ color: #eee;
+ font: 14px Tahoma;
- position: relative;
+ 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;
+ 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;
+ font-family: "Arial";
+ font-size: 14px;
+ font-weight: normal;
+ color: #aaa;
}
.litegraph-editor h2 {
- font-family: "Metro Light";
- padding: 5px;
- margin-left: 10px;
+ font-family: "Metro Light";
+ padding: 5px;
+ margin-left: 10px;
}
-
.litegraph-editor * {
- box-sizing: border-box;
- -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
}
.litegraph-editor .content {
- position: relative;
- width: 100%;
- height: calc( 100% - 80px );
- background-color: #1A1A1A;
+ 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 .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;
+.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;
+ margin: 2px 5px 0 0px;
}
.litegraph-editor .tools-left {
- right: auto;
- left: 4px;
+ right: auto;
+ left: 4px;
}
.litegraph-editor .footer {
- height: 40px;
- position: relative;
- /*border-radius: 0 0 10px 10px;*/
+ height: 40px;
+ position: relative;
+ /*border-radius: 0 0 10px 10px;*/
}
.litegraph-editor .miniwindow {
- background-color: #333;
- border: 1px solid #111;
+ 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;
+ position: absolute;
+ top: 2px;
+ right: 2px;
+ font-family: "Tahoma";
+ font-size: 14px;
+ color: #aaa;
+ cursor: pointer;
}
-
/* BUTTONS **********************/
.litegraph-editor button {
- /*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;
+ /*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;
+ background-color: #999;
+ color: #fff;
+ transition: all 1s;
+ -moz-transition: all 1s;
+ -webkit-transition: all 0.4s;
}
.litegraph-editor button:active {
- background-color: white;
+ background-color: white;
}
.litegraph-editor button.fixed {
- position: absolute;
- top: 5px;
- right: 5px;
- font-size: 1.2em;
+ 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;
+ margin: -4px;
+ vertical-align: top;
+ opacity: 0.8;
+ transition: all 1s;
}
.litegraph-editor button:hover img {
- opacity: 1;
+ opacity: 1;
}
.litegraph-editor .header button {
- height: 32px;
- vertical-align: top;
+ height: 32px;
+ vertical-align: top;
}
.litegraph-editor .footer button {
- /*font-size: 16px;*/
+ /*font-size: 16px;*/
}
.litegraph-editor .toolbar-widget {
- display: inline-block;
+ display: inline-block;
}
.litegraph-editor .editor-area {
- width: 100%;
- height: 100%;
+ width: 100%;
+ height: 100%;
}
/* METER *********************/
.litegraph-editor .loadmeter {
- font-family: "Tahoma";
- color: #AAA;
- font-size: 12px;
- border-radius: 2px;
- width: 130px;
- vertical-align: top;
+ 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 .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('../demo/imgs/load-progress-empty.png');
+.litegraph-editor .cpuload .bgload,
+.litegraph-editor .gpuload .bgload {
+ display: inline-block;
+ width: 90px;
+ height: 15px;
+ background-image: url("../demo/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('../demo/imgs/load-progress-full.png');
+.litegraph-editor .cpuload .fgload,
+.litegraph-editor .gpuload .fgload {
+ display: inline-block;
+ width: 4px;
+ height: 15px;
+ max-width: 90px;
+ background-image: url("../demo/imgs/load-progress-full.png");
}
.litegraph-editor .dialog {
- position: absolute;
+ position: absolute;
top: 50%;
left: 50%;
margin-top: -150px;
margin-left: -200px;
- background-color: #151515;
+ background-color: #151515;
- min-width: 400px;
- min-height: 300px;
- box-shadow: 0 0 2px black;
+ min-width: 400px;
+ min-height: 300px;
+ box-shadow: 0 0 2px black;
}
-.litegraph-editor .dialog .dialog-header, .litegraph-editor .dialog .dialog-footer{
- height: 40px;
+.litegraph-editor .dialog .dialog-header,
+.litegraph-editor .dialog .dialog-footer {
+ height: 40px;
}
.litegraph-editor .dialog .dialog-header .dialog-title {
- font: 20px 'Arial';
- margin: 4px;
- padding: 4px 10px;
- display:inline-block;
+ font: 20px "Arial";
+ margin: 4px;
+ padding: 4px 10px;
+ display: inline-block;
}
.litegraph-editor .dialog .dialog-content {
- height: calc( 100% - 40px );
- width: calc( 100% - 10px );
- background-color: black;
- margin: 4px;
- display:inline-block;
-}
\ No newline at end of file
+ height: calc(100% - 40px);
+ width: calc(100% - 10px);
+ background-color: black;
+ margin: 4px;
+ display: inline-block;
+}
diff --git a/css/litegraph.css b/css/litegraph.css
index 86069800f..50083ea1a 100755
--- a/css/litegraph.css
+++ b/css/litegraph.css
@@ -1,334 +1,337 @@
/* this CSS contains only the basic CSS needed to run the app and use it */
.lgraphcanvas {
- /*cursor: crosshair;*/
- user-select: none;
- -moz-user-select: none;
- -webkit-user-select: none;
+ /*cursor: crosshair;*/
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
}
-
.litegraph.litecontextmenu {
- font-family:Tahoma, sans-serif;
- position: fixed;
- top: 100px;
- left: 100px;
- min-width: 100px;
- color: #AAF;
- padding: 0;
- box-shadow: 0 0 10px black !important;
- background-color: #2E2E2E !important;
+ font-family: Tahoma, sans-serif;
+ position: fixed;
+ top: 100px;
+ left: 100px;
+ min-width: 100px;
+ color: #aaf;
+ padding: 0;
+ box-shadow: 0 0 10px black !important;
+ background-color: #2e2e2e !important;
}
.litegraph.litecontextmenu.dark {
- background-color: #000 !important;
+ background-color: #000 !important;
}
.litegraph.litecontextmenu .litemenu-title img {
- margin-top: 2px;
- margin-left: 2px;
- margin-right: 4px;
+ margin-top: 2px;
+ margin-left: 2px;
+ margin-right: 4px;
}
.litegraph.litecontextmenu .litemenu-entry {
- margin: 2px;
- padding: 2px;
+ margin: 2px;
+ padding: 2px;
}
.litegraph.litecontextmenu .litemenu-entry.submenu {
- background-color: #2E2E2E !important;
+ background-color: #2e2e2e !important;
}
.litegraph.litecontextmenu.dark .litemenu-entry.submenu {
- background-color: #000 !important;
+ background-color: #000 !important;
}
-
.litegraph .litemenubar ul {
- font-family:Tahoma, sans-serif;
- margin: 0;
- padding: 0;
+ font-family: Tahoma, sans-serif;
+ margin: 0;
+ padding: 0;
}
.litegraph .litemenubar li {
- font-size: 14px;
- color: #999;
- display: inline-block;
- min-width: 50px;
- padding-left: 10px;
- padding-right: 10px;
- user-select: none;
- -moz-user-select: none;
- -webkit-user-select: none;
- cursor: pointer;
+ font-size: 14px;
+ color: #999;
+ display: inline-block;
+ min-width: 50px;
+ padding-left: 10px;
+ padding-right: 10px;
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ cursor: pointer;
}
.litegraph .litemenubar li:hover {
- background-color: #777;
- color: #EEE;
+ background-color: #777;
+ color: #eee;
}
.litegraph .litegraph .litemenubar-panel {
- position: absolute;
- top: 5px;
- left: 5px;
- min-width: 100px;
- background-color: #444;
- box-shadow: 0 0 3px black;
- padding: 4px;
- border-bottom: 2px solid #AAF;
- z-index: 10;
+ position: absolute;
+ top: 5px;
+ left: 5px;
+ min-width: 100px;
+ background-color: #444;
+ box-shadow: 0 0 3px black;
+ padding: 4px;
+ border-bottom: 2px solid #aaf;
+ z-index: 10;
}
-.litegraph .litemenu-entry, .litemenu-title {
- font-size: 12px;
- color: #AAA;
- padding: 0 0 0 4px;
- margin: 2px;
- padding-left: 2px;
- -moz-user-select: none;
- -webkit-user-select: none;
- user-select: none;
- cursor: pointer;
+.litegraph .litemenu-entry,
+.litemenu-title {
+ font-size: 12px;
+ color: #aaa;
+ padding: 0 0 0 4px;
+ margin: 2px;
+ padding-left: 2px;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ user-select: none;
+ cursor: pointer;
}
.litegraph .litemenu-entry .icon {
- display: inline-block;
- width: 12px;
- height: 12px;
- margin: 2px;
- vertical-align: top;
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ margin: 2px;
+ vertical-align: top;
}
.litegraph .litemenu-entry.checked .icon {
- background-color: #AAF;
+ background-color: #aaf;
}
.litegraph .litemenu-entry .more {
- float: right;
- padding-right:5px;
+ float: right;
+ padding-right: 5px;
}
.litegraph .litemenu-entry.disabled {
- opacity: 0.5;
- cursor: default;
+ opacity: 0.5;
+ cursor: default;
}
.litegraph .litemenu-entry.separator {
- display: block;
- border-top: 1px solid #333;
- border-bottom: 1px solid #666;
- width: 100%;
- height: 0px;
- margin: 3px 0 2px 0;
- background-color: transparent;
- padding: 0 !important;
- cursor: default !important;
+ display: block;
+ border-top: 1px solid #333;
+ border-bottom: 1px solid #666;
+ width: 100%;
+ height: 0px;
+ margin: 3px 0 2px 0;
+ background-color: transparent;
+ padding: 0 !important;
+ cursor: default !important;
}
.litegraph .litemenu-entry.has_submenu {
- border-right: 2px solid cyan;
+ border-right: 2px solid cyan;
}
.litegraph .litemenu-title {
- color: #DDE;
- background-color: #111;
- margin: 0;
- padding: 2px;
- cursor: default;
+ color: #dde;
+ background-color: #111;
+ margin: 0;
+ padding: 2px;
+ cursor: default;
}
.litegraph .litemenu-entry:hover:not(.disabled):not(.separator) {
- background-color: #444 !important;
- color: #EEE;
- transition: all 0.2s;
+ background-color: #444 !important;
+ color: #eee;
+ transition: all 0.2s;
}
.litegraph .litemenu-entry .property_name {
- display: inline-block;
- text-align: left;
- min-width: 80px;
- min-height: 1.2em;
+ display: inline-block;
+ text-align: left;
+ min-width: 80px;
+ min-height: 1.2em;
}
.litegraph .litemenu-entry .property_value {
- display: inline-block;
- background-color: rgba(0,0,0,0.5);
- text-align: right;
- min-width: 80px;
- min-height: 1.2em;
- vertical-align: middle;
- padding-right: 10px;
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0.5);
+ text-align: right;
+ min-width: 80px;
+ min-height: 1.2em;
+ vertical-align: middle;
+ padding-right: 10px;
}
.litegraph.litesearchbox {
- font-family:Tahoma, sans-serif;
- position: absolute;
- background-color: rgba(0,0,0,0.5);
- padding-top: 4px;
+ font-family: Tahoma, sans-serif;
+ position: absolute;
+ background-color: rgba(0, 0, 0, 0.5);
+ padding-top: 4px;
}
-.litegraph.litesearchbox input, .litegraph.litesearchbox select {
- margin-top: 3px;
- min-width: 60px;
- min-height: 1.5em;
- background-color: black;
- border: 0;
- color: white;
- padding-left: 10px;
- margin-right: 5px;
+.litegraph.litesearchbox input,
+.litegraph.litesearchbox select {
+ margin-top: 3px;
+ min-width: 60px;
+ min-height: 1.5em;
+ background-color: black;
+ border: 0;
+ color: white;
+ padding-left: 10px;
+ margin-right: 5px;
}
.litegraph.litesearchbox .name {
- display: inline-block;
- min-width: 60px;
- min-height: 1.5em;
- padding-left: 10px;
+ display: inline-block;
+ min-width: 60px;
+ min-height: 1.5em;
+ padding-left: 10px;
}
.litegraph.litesearchbox .helper {
- overflow: auto;
- max-height: 200px;
- margin-top: 2px;
+ overflow: auto;
+ max-height: 200px;
+ margin-top: 2px;
}
.litegraph.lite-search-item {
- font-family:Tahoma, sans-serif;
- background-color: rgba(0,0,0,0.5);
- color: white;
- padding-top: 2px;
+ font-family: Tahoma, sans-serif;
+ background-color: rgba(0, 0, 0, 0.5);
+ color: white;
+ padding-top: 2px;
}
-.litegraph.lite-search-item:hover, .litegraph.lite-search-item.selected {
- cursor: pointer;
- background-color: white;
- color: black;
+.litegraph.lite-search-item:hover,
+.litegraph.lite-search-item.selected {
+ cursor: pointer;
+ background-color: white;
+ color: black;
}
/* OLD */
.graphcontextmenu {
- padding: 4px;
- min-width: 100px;
+ padding: 4px;
+ min-width: 100px;
}
-
.graphcontextmenu-title {
- color: #DDE;
- background-color: #222;
- margin: 0;
- padding: 2px;
- cursor: default;
+ color: #dde;
+ background-color: #222;
+ margin: 0;
+ padding: 2px;
+ cursor: default;
}
.graphmenu-entry {
- box-sizing: border-box;
- margin: 2px;
- padding-left: 20px;
- user-select: none;
- -moz-user-select: none;
- -webkit-user-select: none;
- transition: all linear 0.3s;
+ box-sizing: border-box;
+ margin: 2px;
+ padding-left: 20px;
+ user-select: none;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+ transition: all linear 0.3s;
}
-.graphmenu-entry.event, .litemenu-entry.event {
- border-left: 8px solid orange;
- padding-left: 12px;
+.graphmenu-entry.event,
+.litemenu-entry.event {
+ border-left: 8px solid orange;
+ padding-left: 12px;
}
.graphmenu-entry.disabled {
- opacity: 0.3;
+ opacity: 0.3;
}
-.graphmenu-entry.submenu {
- border-right: 2px solid #EEE;
+.graphmenu-entry.submenu {
+ border-right: 2px solid #eee;
}
-
.graphmenu-entry:hover {
- background-color: #555;
+ background-color: #555;
}
.graphmenu-entry.separator {
- background-color: #111;
- border-bottom: 1px solid #666;
- height: 1px;
- width: calc( 100% - 20px );
- -moz-width: calc( 100% - 20px );
- -webkit-width: calc( 100% - 20px );
+ background-color: #111;
+ border-bottom: 1px solid #666;
+ height: 1px;
+ width: calc(100% - 20px);
+ -moz-width: calc(100% - 20px);
+ -webkit-width: calc(100% - 20px);
}
.graphmenu-entry .property_name {
- display: inline-block;
- text-align: left;
- min-width: 80px;
- min-height: 1.2em;
+ display: inline-block;
+ text-align: left;
+ min-width: 80px;
+ min-height: 1.2em;
}
-.graphmenu-entry .property_value, .litemenu-entry .property_value {
- display: inline-block;
- background-color: rgba(0,0,0,0.5);
- text-align: right;
- min-width: 80px;
- min-height: 1.2em;
- vertical-align: middle;
- padding-right: 10px;
+.graphmenu-entry .property_value,
+.litemenu-entry .property_value {
+ display: inline-block;
+ background-color: rgba(0, 0, 0, 0.5);
+ text-align: right;
+ min-width: 80px;
+ min-height: 1.2em;
+ vertical-align: middle;
+ padding-right: 10px;
}
.graphdialog {
- position: absolute;
- top: 10px;
- left: 10px;
- min-height: 2em;
- background-color: #333;
- font-size: 1.2em;
- box-shadow: 0 0 10px black !important;
+ position: absolute;
+ top: 10px;
+ left: 10px;
+ min-height: 2em;
+ background-color: #333;
+ font-size: 1.2em;
+ box-shadow: 0 0 10px black !important;
}
.graphdialog.rounded {
- border-radius: 12px;
- padding-right: 2px;
+ border-radius: 12px;
+ padding-right: 2px;
}
.graphdialog .name {
- display: inline-block;
- min-width: 60px;
- min-height: 1.5em;
- padding-left: 10px;
+ display: inline-block;
+ min-width: 60px;
+ min-height: 1.5em;
+ padding-left: 10px;
}
-.graphdialog input, .graphdialog select {
- margin: 3px;
- min-width: 60px;
- min-height: 1.5em;
- background-color: black;
- border: 0;
- color: white;
- padding-left: 10px;
+.graphdialog input,
+.graphdialog select {
+ margin: 3px;
+ min-width: 60px;
+ min-height: 1.5em;
+ background-color: black;
+ border: 0;
+ color: white;
+ padding-left: 10px;
outline: none;
}
.graphdialog button {
- margin-top: 3px;
- vertical-align: top;
+ margin-top: 3px;
+ vertical-align: top;
}
-.graphdialog button.rounded, .graphdialog input.rounded {
- border-radius: 0 12px 12px 0;
+.graphdialog button.rounded,
+.graphdialog input.rounded {
+ border-radius: 0 12px 12px 0;
}
.graphdialog .helper {
- overflow: auto;
- max-height: 200px;
+ overflow: auto;
+ max-height: 200px;
}
.graphdialog .help-item {
- padding-left: 10px;
+ padding-left: 10px;
}
-.graphdialog .help-item:hover, .graphdialog .help-item.selected {
- cursor: pointer;
- background-color: white;
- color: black;
+.graphdialog .help-item:hover,
+.graphdialog .help-item.selected {
+ cursor: pointer;
+ background-color: white;
+ color: black;
}
-
diff --git a/src/litegraph-editor.js b/src/litegraph-editor.js
index dac4f279a..fe824d35a 100755
--- a/src/litegraph-editor.js
+++ b/src/litegraph-editor.js
@@ -1,233 +1,266 @@
//Creates an interface to access extra features from a graph (like play, stop, live, etc)
-function Editor( container_id, options )
-{
- options = options || {};
+function Editor(container_id, options) {
+ options = options || {};
- //fill container
- var html = "
";
- html += "";
- html += "";
-
- var root = document.createElement("div");
- this.root = root;
- root.className = "litegraph-editor";
- root.innerHTML = html;
+ //fill container
+ var html =
+ "";
+ html +=
+ "";
+ html +=
+ "";
- this.tools = root.querySelector(".tools");
- this.footer = root.querySelector(".footer");
+ var root = document.createElement("div");
+ this.root = root;
+ root.className = "litegraph-editor";
+ root.innerHTML = html;
- var canvas = root.querySelector(".graphcanvas");
+ this.tools = root.querySelector(".tools");
+ this.footer = root.querySelector(".footer");
- //create graph
- var graph = this.graph = new LGraph();
- var graphcanvas = this.graphcanvas = new LGraphCanvas(canvas,graph);
- graphcanvas.background_image = "imgs/grid.png";
- graph.onAfterExecute = function() { graphcanvas.draw(true) };
+ var canvas = root.querySelector(".graphcanvas");
- //add stuff
- //this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" );
- //this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" );
- this.addLoadCounter();
- this.addToolsButton("playnode_button","Play","imgs/icon-play.png", this.onPlayButton.bind(this), ".tools-right" );
- this.addToolsButton("playstepnode_button","Step","imgs/icon-playstep.png", this.onPlayStepButton.bind(this), ".tools-right" );
-
- if(!options.skip_livemode)
- this.addToolsButton("livemode_button","Live","imgs/icon-record.png", this.onLiveButton.bind(this), ".tools-right" );
- if(!options.skip_maximize)
- this.addToolsButton("maximize_button","","imgs/icon-maximize.png", this.onFullscreenButton.bind(this), ".tools-right" );
- if(options.miniwindow)
- this.addMiniWindow(300,200);
+ //create graph
+ var graph = (this.graph = new LGraph());
+ var graphcanvas = (this.graphcanvas = new LGraphCanvas(canvas, graph));
+ graphcanvas.background_image = "imgs/grid.png";
+ graph.onAfterExecute = function() {
+ graphcanvas.draw(true);
+ };
- //append to DOM
- var parent = document.getElementById(container_id);
- if(parent)
- parent.appendChild(root);
+ //add stuff
+ //this.addToolsButton("loadsession_button","Load","imgs/icon-load.png", this.onLoadButton.bind(this), ".tools-left" );
+ //this.addToolsButton("savesession_button","Save","imgs/icon-save.png", this.onSaveButton.bind(this), ".tools-left" );
+ this.addLoadCounter();
+ this.addToolsButton(
+ "playnode_button",
+ "Play",
+ "imgs/icon-play.png",
+ this.onPlayButton.bind(this),
+ ".tools-right"
+ );
+ this.addToolsButton(
+ "playstepnode_button",
+ "Step",
+ "imgs/icon-playstep.png",
+ this.onPlayStepButton.bind(this),
+ ".tools-right"
+ );
- graphcanvas.resize();
- //graphcanvas.draw(true,true);
+ if (!options.skip_livemode)
+ this.addToolsButton(
+ "livemode_button",
+ "Live",
+ "imgs/icon-record.png",
+ this.onLiveButton.bind(this),
+ ".tools-right"
+ );
+ if (!options.skip_maximize)
+ this.addToolsButton(
+ "maximize_button",
+ "",
+ "imgs/icon-maximize.png",
+ this.onFullscreenButton.bind(this),
+ ".tools-right"
+ );
+ if (options.miniwindow) this.addMiniWindow(300, 200);
+
+ //append to DOM
+ var parent = document.getElementById(container_id);
+ if (parent) parent.appendChild(root);
+
+ graphcanvas.resize();
+ //graphcanvas.draw(true,true);
}
-Editor.prototype.addLoadCounter = function()
-{
- var meter = document.createElement("div");
- meter.className = 'headerpanel loadmeter toolbar-widget';
+Editor.prototype.addLoadCounter = function() {
+ var meter = document.createElement("div");
+ meter.className = "headerpanel loadmeter toolbar-widget";
- var html = "";
- html += "";
+ var html =
+ "";
+ html +=
+ "";
- meter.innerHTML = html;
- this.root.querySelector(".header .tools-left").appendChild(meter);
- var self = this;
+ meter.innerHTML = html;
+ this.root.querySelector(".header .tools-left").appendChild(meter);
+ var self = this;
- setInterval(function() {
- meter.querySelector(".cpuload .fgload").style.width = ((2*self.graph.execution_time) * 90) + "px";
- if(self.graph.status == LGraph.STATUS_RUNNING)
- meter.querySelector(".gpuload .fgload").style.width = ((self.graphcanvas.render_time*10) * 90) + "px";
- else
- meter.querySelector(".gpuload .fgload").style.width = 4 + "px";
- },200);
-}
+ setInterval(function() {
+ meter.querySelector(".cpuload .fgload").style.width =
+ 2 * self.graph.execution_time * 90 + "px";
+ if (self.graph.status == LGraph.STATUS_RUNNING)
+ meter.querySelector(".gpuload .fgload").style.width =
+ self.graphcanvas.render_time * 10 * 90 + "px";
+ else meter.querySelector(".gpuload .fgload").style.width = 4 + "px";
+ }, 200);
+};
-Editor.prototype.addToolsButton = function(id,name,icon_url, callback, container)
-{
- if(!container)
- container = ".tools";
+Editor.prototype.addToolsButton = function(
+ id,
+ name,
+ icon_url,
+ callback,
+ container
+) {
+ if (!container) container = ".tools";
- var button = this.createButton(name, icon_url);
- button.id = id;
- button.addEventListener("click", callback);
+ var button = this.createButton(name, icon_url);
+ button.id = id;
+ button.addEventListener("click", callback);
- this.root.querySelector(container).appendChild(button);
-}
+ this.root.querySelector(container).appendChild(button);
+};
+Editor.prototype.createPanel = function(title, options) {
+ var root = document.createElement("div");
+ root.className = "dialog";
+ root.innerHTML =
+ "";
+ root.header = root.querySelector(".dialog-header");
+ root.content = root.querySelector(".dialog-content");
+ root.footer = root.querySelector(".dialog-footer");
-Editor.prototype.createPanel = function(title, options)
-{
+ return root;
+};
- var root = document.createElement("div");
- root.className = "dialog";
- root.innerHTML = "";
- root.header = root.querySelector(".dialog-header");
- root.content = root.querySelector(".dialog-content");
- root.footer = root.querySelector(".dialog-footer");
+Editor.prototype.createButton = function(name, icon_url) {
+ var button = document.createElement("button");
+ if (icon_url) button.innerHTML = "
";
+ button.innerHTML += name;
+ return button;
+};
+Editor.prototype.onLoadButton = function() {
+ var panel = this.createPanel("Load session");
+ var close = this.createButton("Close");
+ close.style.float = "right";
+ close.addEventListener("click", function() {
+ panel.parentNode.removeChild(panel);
+ });
+ panel.header.appendChild(close);
+ panel.content.innerHTML = "test";
- return root;
-}
+ this.root.appendChild(panel);
+};
-Editor.prototype.createButton = function(name, icon_url)
-{
- var button = document.createElement("button");
- if(icon_url)
- button.innerHTML = "
";
- button.innerHTML += name;
- return button;
-}
+Editor.prototype.onSaveButton = function() {};
-Editor.prototype.onLoadButton = function()
-{
- var panel = this.createPanel("Load session");
- var close = this.createButton("Close");
- close.style.float = "right";
- close.addEventListener("click", function() { panel.parentNode.removeChild( panel ); });
- panel.header.appendChild(close);
- panel.content.innerHTML = "test";
+Editor.prototype.onPlayButton = function() {
+ var graph = this.graph;
+ var button = this.root.querySelector("#playnode_button");
- this.root.appendChild(panel);
-}
+ if (graph.status == LGraph.STATUS_STOPPED) {
+ button.innerHTML = "
Stop";
+ graph.start();
+ } else {
+ button.innerHTML = "
Play";
+ graph.stop();
+ }
+};
-Editor.prototype.onSaveButton = function()
-{
-}
+Editor.prototype.onPlayStepButton = function() {
+ var graph = this.graph;
+ graph.runStep(1);
+ this.graphcanvas.draw(true, true);
+};
-Editor.prototype.onPlayButton = function()
-{
- var graph = this.graph;
- var button = this.root.querySelector("#playnode_button");
+Editor.prototype.onLiveButton = function() {
+ var is_live_mode = !this.graphcanvas.live_mode;
+ this.graphcanvas.switchLiveMode(true);
+ this.graphcanvas.draw();
+ var url = this.graphcanvas.live_mode
+ ? "imgs/gauss_bg_medium.jpg"
+ : "imgs/gauss_bg.jpg";
+ var button = this.root.querySelector("#livemode_button");
+ button.innerHTML = !is_live_mode
+ ? "
Live"
+ : "
Edit";
+};
- if(graph.status == LGraph.STATUS_STOPPED)
- {
- button.innerHTML = "
Stop";
- graph.start();
- }
- else
- {
- button.innerHTML = "
Play";
- graph.stop();
- }
-}
+Editor.prototype.goFullscreen = function() {
+ if (this.root.requestFullscreen)
+ this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ else if (this.root.mozRequestFullscreen)
+ this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ else if (this.root.webkitRequestFullscreen)
+ this.root.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
+ else throw "Fullscreen not supported";
-Editor.prototype.onPlayStepButton = function()
-{
- var graph = this.graph;
- graph.runStep(1);
- this.graphcanvas.draw(true,true);
-}
+ var self = this;
+ setTimeout(function() {
+ self.graphcanvas.resize();
+ }, 100);
+};
-Editor.prototype.onLiveButton = function()
-{
- var is_live_mode = !this.graphcanvas.live_mode;
- this.graphcanvas.switchLiveMode(true);
- this.graphcanvas.draw();
- var url = this.graphcanvas.live_mode ? "imgs/gauss_bg_medium.jpg" : "imgs/gauss_bg.jpg";
- var button = this.root.querySelector("#livemode_button");
- button.innerHTML = !is_live_mode ? "
Live" : "
Edit" ;
-}
+Editor.prototype.onFullscreenButton = function() {
+ this.goFullscreen();
+};
-Editor.prototype.goFullscreen = function()
-{
- if(this.root.requestFullscreen)
- this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- else if(this.root.mozRequestFullscreen)
- this.root.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- else if(this.root.webkitRequestFullscreen)
- this.root.webkitRequestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
- else
- throw("Fullscreen not supported");
+Editor.prototype.onMaximizeButton = function() {
+ this.maximize();
+};
- var self = this;
- setTimeout(function() {
- self.graphcanvas.resize();
- },100);
-}
+Editor.prototype.addMiniWindow = function(w, h) {
+ var miniwindow = document.createElement("div");
+ miniwindow.className = "litegraph miniwindow";
+ miniwindow.innerHTML =
+ "";
+ var canvas = miniwindow.querySelector("canvas");
+ var that = this;
-Editor.prototype.onFullscreenButton = function()
-{
- this.goFullscreen();
-}
+ var graphcanvas = new LGraphCanvas(canvas, this.graph);
+ graphcanvas.show_info = false;
+ graphcanvas.background_image = "imgs/grid.png";
+ graphcanvas.scale = 0.25;
+ graphcanvas.allow_dragnodes = false;
+ graphcanvas.allow_interaction = false;
+ graphcanvas.render_shadows = false;
+ graphcanvas.max_zoom = 0.25;
+ this.miniwindow_graphcanvas = graphcanvas;
+ graphcanvas.onClear = function() {
+ graphcanvas.scale = 0.25;
+ graphcanvas.allow_dragnodes = false;
+ graphcanvas.allow_interaction = false;
+ };
+ graphcanvas.onRenderBackground = function(canvas, ctx) {
+ ctx.strokeStyle = "#567";
+ var tl = that.graphcanvas.convertOffsetToCanvas([0, 0]);
+ var br = that.graphcanvas.convertOffsetToCanvas([
+ that.graphcanvas.canvas.width,
+ that.graphcanvas.canvas.height
+ ]);
+ tl = this.convertCanvasToOffset(tl);
+ br = this.convertCanvasToOffset(br);
+ ctx.lineWidth = 1;
+ ctx.strokeRect(
+ Math.floor(tl[0]) + 0.5,
+ Math.floor(tl[1]) + 0.5,
+ Math.floor(br[0] - tl[0]),
+ Math.floor(br[1] - tl[1])
+ );
+ };
-Editor.prototype.onMaximizeButton = function()
-{
- this.maximize();
-}
+ miniwindow.style.position = "absolute";
+ miniwindow.style.top = "4px";
+ miniwindow.style.right = "4px";
-Editor.prototype.addMiniWindow = function(w,h)
-{
- var miniwindow = document.createElement("div");
- miniwindow.className = "litegraph miniwindow";
- miniwindow.innerHTML = "";
- var canvas = miniwindow.querySelector("canvas");
- var that = this;
+ var close_button = document.createElement("div");
+ close_button.className = "corner-button";
+ close_button.innerHTML = "X";
+ close_button.addEventListener("click", function(e) {
+ graphcanvas.setGraph(null);
+ miniwindow.parentNode.removeChild(miniwindow);
+ });
+ miniwindow.appendChild(close_button);
- var graphcanvas = new LGraphCanvas(canvas, this.graph);
- graphcanvas.show_info = false;
- graphcanvas.background_image = "imgs/grid.png";
- graphcanvas.scale = 0.25;
- graphcanvas.allow_dragnodes = false;
- graphcanvas.allow_interaction = false;
- graphcanvas.render_shadows = false;
- graphcanvas.max_zoom = 0.25;
- this.miniwindow_graphcanvas = graphcanvas;
- graphcanvas.onClear = function() {
- graphcanvas.scale = 0.25;
- graphcanvas.allow_dragnodes = false;
- graphcanvas.allow_interaction = false;
- };
- graphcanvas.onRenderBackground = function(canvas, ctx)
- {
- ctx.strokeStyle = "#567";
- var tl = that.graphcanvas.convertOffsetToCanvas([0,0]);
- var br = that.graphcanvas.convertOffsetToCanvas([that.graphcanvas.canvas.width,that.graphcanvas.canvas.height]);
- tl = this.convertCanvasToOffset( tl );
- br = this.convertCanvasToOffset( br );
- ctx.lineWidth = 1;
- ctx.strokeRect( Math.floor(tl[0]) + 0.5, Math.floor(tl[1]) + 0.5, Math.floor(br[0] - tl[0]), Math.floor(br[1] - tl[1]) );
- }
+ this.root.querySelector(".content").appendChild(miniwindow);
+};
- miniwindow.style.position = "absolute";
- miniwindow.style.top = "4px";
- miniwindow.style.right = "4px";
-
- var close_button = document.createElement("div");
- close_button.className = "corner-button";
- close_button.innerHTML = "X";
- close_button.addEventListener("click",function(e) {
- graphcanvas.setGraph(null);
- miniwindow.parentNode.removeChild(miniwindow);
- });
- miniwindow.appendChild(close_button);
-
- this.root.querySelector(".content").appendChild(miniwindow);
-}
-
-LiteGraph.Editor = Editor;
\ No newline at end of file
+LiteGraph.Editor = Editor;
diff --git a/src/litegraph.js b/src/litegraph.js
index c579216c7..aac6a7ebe 100755
--- a/src/litegraph.js
+++ b/src/litegraph.js
@@ -1,1822 +1,1655 @@
-(function(global){
-// *************************************************************
-// LiteGraph CLASS *******
-// *************************************************************
+(function(global) {
+ // *************************************************************
+ // LiteGraph CLASS *******
+ // *************************************************************
-/**
-* The Global Scope. It contains all the registered node classes.
-*
-* @class LiteGraph
-* @constructor
-*/
+ /**
+ * The Global Scope. It contains all the registered node classes.
+ *
+ * @class LiteGraph
+ * @constructor
+ */
-var LiteGraph = global.LiteGraph = {
+ var LiteGraph = (global.LiteGraph = {
+ VERSION: 0.4,
- VERSION: 0.4,
+ CANVAS_GRID_SIZE: 10,
- CANVAS_GRID_SIZE: 10,
-
- NODE_TITLE_HEIGHT: 30,
- NODE_TITLE_TEXT_Y: 20,
- NODE_SLOT_HEIGHT: 20,
- NODE_WIDGET_HEIGHT: 20,
- NODE_WIDTH: 140,
- NODE_MIN_WIDTH: 50,
- NODE_COLLAPSED_RADIUS: 10,
- NODE_COLLAPSED_WIDTH: 80,
- NODE_TITLE_COLOR: "#999",
- NODE_TEXT_SIZE: 14,
- NODE_TEXT_COLOR: "#AAA",
- NODE_SUBTEXT_SIZE: 12,
- NODE_DEFAULT_COLOR: "#333",
- NODE_DEFAULT_BGCOLOR: "#353535",
- NODE_DEFAULT_BOXCOLOR: "#666",
- NODE_DEFAULT_SHAPE: "box",
- DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)",
- DEFAULT_GROUP_FONT: 24,
+ NODE_TITLE_HEIGHT: 30,
+ NODE_TITLE_TEXT_Y: 20,
+ NODE_SLOT_HEIGHT: 20,
+ NODE_WIDGET_HEIGHT: 20,
+ NODE_WIDTH: 140,
+ NODE_MIN_WIDTH: 50,
+ NODE_COLLAPSED_RADIUS: 10,
+ NODE_COLLAPSED_WIDTH: 80,
+ NODE_TITLE_COLOR: "#999",
+ NODE_TEXT_SIZE: 14,
+ NODE_TEXT_COLOR: "#AAA",
+ NODE_SUBTEXT_SIZE: 12,
+ NODE_DEFAULT_COLOR: "#333",
+ NODE_DEFAULT_BGCOLOR: "#353535",
+ NODE_DEFAULT_BOXCOLOR: "#666",
+ NODE_DEFAULT_SHAPE: "box",
+ DEFAULT_SHADOW_COLOR: "rgba(0,0,0,0.5)",
+ DEFAULT_GROUP_FONT: 24,
- LINK_COLOR: "#9A9",
- EVENT_LINK_COLOR: "#A86",
- CONNECTING_LINK_COLOR: "#AFA",
+ LINK_COLOR: "#9A9",
+ EVENT_LINK_COLOR: "#A86",
+ CONNECTING_LINK_COLOR: "#AFA",
- MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops
- DEFAULT_POSITION: [100,100],//default node position
- VALID_SHAPES: ["default","box","round","card"], //,"circle"
+ MAX_NUMBER_OF_NODES: 1000, //avoid infinite loops
+ DEFAULT_POSITION: [100, 100], //default node position
+ VALID_SHAPES: ["default", "box", "round", "card"], //,"circle"
- //shapes are used for nodes but also for slots
- BOX_SHAPE: 1,
- ROUND_SHAPE: 2,
- CIRCLE_SHAPE: 3,
- CARD_SHAPE: 4,
- ARROW_SHAPE: 5,
+ //shapes are used for nodes but also for slots
+ BOX_SHAPE: 1,
+ ROUND_SHAPE: 2,
+ CIRCLE_SHAPE: 3,
+ CARD_SHAPE: 4,
+ ARROW_SHAPE: 5,
- //enums
- INPUT: 1,
- OUTPUT: 2,
+ //enums
+ INPUT: 1,
+ OUTPUT: 2,
- EVENT: -1, //for outputs
- ACTION: -1, //for inputs
+ EVENT: -1, //for outputs
+ ACTION: -1, //for inputs
- ALWAYS: 0,
- ON_EVENT: 1,
- NEVER: 2,
- ON_TRIGGER: 3,
+ ALWAYS: 0,
+ ON_EVENT: 1,
+ NEVER: 2,
+ ON_TRIGGER: 3,
- UP: 1,
- DOWN:2,
- LEFT:3,
- RIGHT:4,
- CENTER:5,
+ UP: 1,
+ DOWN: 2,
+ LEFT: 3,
+ RIGHT: 4,
+ CENTER: 5,
- STRAIGHT_LINK: 0,
- LINEAR_LINK: 1,
- SPLINE_LINK: 2,
+ STRAIGHT_LINK: 0,
+ LINEAR_LINK: 1,
+ SPLINE_LINK: 2,
- NORMAL_TITLE: 0,
- NO_TITLE: 1,
- TRANSPARENT_TITLE: 2,
- AUTOHIDE_TITLE: 3,
+ NORMAL_TITLE: 0,
+ NO_TITLE: 1,
+ TRANSPARENT_TITLE: 2,
+ AUTOHIDE_TITLE: 3,
- proxy: null, //used to redirect calls
- node_images_path: "",
+ proxy: null, //used to redirect calls
+ node_images_path: "",
- debug: false,
- catch_exceptions: true,
- throw_errors: true,
- 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
- registered_node_types: {}, //nodetypes by string
- node_types_by_file_extension: {}, //used for dropping files in the canvas
- Nodes: {}, //node types by classname
+ debug: false,
+ catch_exceptions: true,
+ throw_errors: true,
+ 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
+ registered_node_types: {}, //nodetypes by string
+ node_types_by_file_extension: {}, //used for dropping files in the canvas
+ Nodes: {}, //node types by classname
- searchbox_extras: {}, //used to add extra features to the search box
+ searchbox_extras: {}, //used to add extra features to the search box
- /**
- * Register a node class so it can be listed when the user wants to create a new one
- * @method registerNodeType
- * @param {String} type name of the node and path
- * @param {Class} base_class class containing the structure of a node
- */
+ /**
+ * Register a node class so it can be listed when the user wants to create a new one
+ * @method registerNodeType
+ * @param {String} type name of the node and path
+ * @param {Class} base_class class containing the structure of a node
+ */
- registerNodeType: function(type, base_class)
- {
- if(!base_class.prototype)
- throw("Cannot register a simple object, it must be a class with a prototype");
- base_class.type = type;
+ registerNodeType: function(type, base_class) {
+ if (!base_class.prototype)
+ throw "Cannot register a simple object, it must be a class with a prototype";
+ base_class.type = type;
- if(LiteGraph.debug)
- console.log("Node registered: " + type);
+ if (LiteGraph.debug) console.log("Node registered: " + type);
- var categories = type.split("/");
- var classname = base_class.name;
+ var categories = type.split("/");
+ var classname = base_class.name;
- var pos = type.lastIndexOf("/");
- base_class.category = type.substr(0,pos);
+ var pos = type.lastIndexOf("/");
+ base_class.category = type.substr(0, pos);
- if(!base_class.title)
- base_class.title = classname;
- //info.name = name.substr(pos+1,name.length - pos);
+ if (!base_class.title) base_class.title = classname;
+ //info.name = name.substr(pos+1,name.length - pos);
- //extend class
- if(base_class.prototype) //is a class
- for(var i in LGraphNode.prototype)
- if(!base_class.prototype[i])
- base_class.prototype[i] = LGraphNode.prototype[i];
+ //extend class
+ if (base_class.prototype)
+ //is a class
+ for (var i in LGraphNode.prototype)
+ if (!base_class.prototype[i])
+ base_class.prototype[i] = LGraphNode.prototype[i];
- Object.defineProperty( base_class.prototype, "shape",{
- set: function(v) {
- switch(v)
- {
- case "default": delete this._shape; break;
- case "box": this._shape = LiteGraph.BOX_SHAPE; break;
- case "round": this._shape = LiteGraph.ROUND_SHAPE; break;
- case "circle": this._shape = LiteGraph.CIRCLE_SHAPE; break;
- case "card": this._shape = LiteGraph.CARD_SHAPE; break;
- default:
- this._shape = v;
- }
- },
- get: function(v)
- {
- return this._shape;
- },
- enumerable: true
- });
+ Object.defineProperty(base_class.prototype, "shape", {
+ set: function(v) {
+ switch (v) {
+ case "default":
+ delete this._shape;
+ break;
+ case "box":
+ this._shape = LiteGraph.BOX_SHAPE;
+ break;
+ case "round":
+ this._shape = LiteGraph.ROUND_SHAPE;
+ break;
+ case "circle":
+ this._shape = LiteGraph.CIRCLE_SHAPE;
+ break;
+ case "card":
+ this._shape = LiteGraph.CARD_SHAPE;
+ break;
+ default:
+ this._shape = v;
+ }
+ },
+ get: function(v) {
+ return this._shape;
+ },
+ enumerable: true
+ });
- this.registered_node_types[ type ] = base_class;
- if(base_class.constructor.name)
- this.Nodes[ classname ] = base_class;
+ this.registered_node_types[type] = base_class;
+ if (base_class.constructor.name) this.Nodes[classname] = base_class;
- //warnings
- if(base_class.prototype.onPropertyChange)
- console.warn("LiteGraph node class " + type + " has onPropertyChange method, it must be called onPropertyChanged with d at the end");
+ //warnings
+ if (base_class.prototype.onPropertyChange)
+ console.warn(
+ "LiteGraph node class " +
+ type +
+ " has onPropertyChange method, it must be called onPropertyChanged with d at the end"
+ );
- if( base_class.supported_extensions )
- {
- for(var i in base_class.supported_extensions )
- this.node_types_by_file_extension[ base_class.supported_extensions[i].toLowerCase() ] = base_class;
- }
- },
+ if (base_class.supported_extensions) {
+ for (var i in base_class.supported_extensions)
+ this.node_types_by_file_extension[
+ base_class.supported_extensions[i].toLowerCase()
+ ] = base_class;
+ }
+ },
- /**
- * 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.
- * @method wrapFunctionAsNode
- * @param {String} name node name with namespace (p.e.: 'math/sum')
- * @param {Function} func
- * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type
- * @param {String} return_type [optional] string with the return type, otherwise it will be generic
- * @param {Object} properties [optional] properties to be configurable
- */
- wrapFunctionAsNode: function( name, func, param_types, return_type, properties )
- {
- var params = Array(func.length);
- var code = "";
- var names = LiteGraph.getParameterNames( func );
- for(var i = 0; i < names.length; ++i)
- code += "this.addInput('"+names[i]+"',"+(param_types && param_types[i] ? "'" + param_types[i] + "'" : "0") + ");\n";
- code += "this.addOutput('out',"+( return_type ? "'" + return_type + "'" : 0 )+");\n";
- if(properties)
- code += "this.properties = " + JSON.stringify(properties) + ";\n";
- var classobj = Function(code);
- classobj.title = name.split("/").pop();
- classobj.desc = "Generated from " + func.name;
- classobj.prototype.onExecute = function onExecute()
- {
- for(var i = 0; i < params.length; ++i)
- params[i] = this.getInputData(i);
- var r = func.apply( this, params );
- this.setOutputData(0,r);
- }
- this.registerNodeType( name, classobj );
- },
+ /**
+ * 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.
+ * @method wrapFunctionAsNode
+ * @param {String} name node name with namespace (p.e.: 'math/sum')
+ * @param {Function} func
+ * @param {Array} param_types [optional] an array containing the type of every parameter, otherwise parameters will accept any type
+ * @param {String} return_type [optional] string with the return type, otherwise it will be generic
+ * @param {Object} properties [optional] properties to be configurable
+ */
+ wrapFunctionAsNode: function(
+ name,
+ func,
+ param_types,
+ return_type,
+ properties
+ ) {
+ var params = Array(func.length);
+ var code = "";
+ var names = LiteGraph.getParameterNames(func);
+ for (var i = 0; i < names.length; ++i)
+ code +=
+ "this.addInput('" +
+ names[i] +
+ "'," +
+ (param_types && param_types[i]
+ ? "'" + param_types[i] + "'"
+ : "0") +
+ ");\n";
+ code +=
+ "this.addOutput('out'," +
+ (return_type ? "'" + return_type + "'" : 0) +
+ ");\n";
+ if (properties)
+ code +=
+ "this.properties = " + JSON.stringify(properties) + ";\n";
+ var classobj = Function(code);
+ classobj.title = name.split("/").pop();
+ classobj.desc = "Generated from " + func.name;
+ classobj.prototype.onExecute = function onExecute() {
+ for (var i = 0; i < params.length; ++i)
+ params[i] = this.getInputData(i);
+ var r = func.apply(this, params);
+ this.setOutputData(0, r);
+ };
+ this.registerNodeType(name, classobj);
+ },
- /**
- * Adds this method to all nodetypes, existing and to be created
- * (You can add it to LGraphNode.prototype but then existing node types wont have it)
- * @method addNodeMethod
- * @param {Function} func
- */
- addNodeMethod: function( name, func )
- {
- LGraphNode.prototype[name] = func;
- for(var i in this.registered_node_types)
- {
- var type = this.registered_node_types[i];
- if(type.prototype[name])
- type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing
- type.prototype[name] = func;
- }
- },
+ /**
+ * Adds this method to all nodetypes, existing and to be created
+ * (You can add it to LGraphNode.prototype but then existing node types wont have it)
+ * @method addNodeMethod
+ * @param {Function} func
+ */
+ addNodeMethod: function(name, func) {
+ LGraphNode.prototype[name] = func;
+ for (var i in this.registered_node_types) {
+ var type = this.registered_node_types[i];
+ if (type.prototype[name])
+ type.prototype["_" + name] = type.prototype[name]; //keep old in case of replacing
+ type.prototype[name] = func;
+ }
+ },
- /**
- * Create a node of a given type with a name. The node is not attached to any graph yet.
- * @method createNode
- * @param {String} type full name of the node class. p.e. "math/sin"
- * @param {String} name a name to distinguish from other nodes
- * @param {Object} options to set options
- */
+ /**
+ * Create a node of a given type with a name. The node is not attached to any graph yet.
+ * @method createNode
+ * @param {String} type full name of the node class. p.e. "math/sin"
+ * @param {String} name a name to distinguish from other nodes
+ * @param {Object} options to set options
+ */
- createNode: function( type, title, options )
- {
- var base_class = this.registered_node_types[type];
- if (!base_class)
- {
- if(LiteGraph.debug)
- console.log("GraphNode type \"" + type + "\" not registered.");
- return null;
- }
+ createNode: function(type, title, options) {
+ var base_class = this.registered_node_types[type];
+ if (!base_class) {
+ if (LiteGraph.debug)
+ console.log(
+ 'GraphNode type "' + type + '" not registered.'
+ );
+ return null;
+ }
- var prototype = base_class.prototype || base_class;
+ var prototype = base_class.prototype || base_class;
- title = title || base_class.title || type;
+ title = title || base_class.title || type;
- var node = null;
+ var node = null;
- if( LiteGraph.catch_exceptions )
- {
- try
- {
- node = new base_class( title );
- }
- catch (err)
- {
- console.error(err);
- return null;
- }
- }
- else
- node = new base_class( title );
+ if (LiteGraph.catch_exceptions) {
+ try {
+ node = new base_class(title);
+ } catch (err) {
+ console.error(err);
+ return null;
+ }
+ } else node = new base_class(title);
- node.type = type;
+ node.type = type;
- if(!node.title && title) node.title = title;
- if(!node.properties) node.properties = {};
- if(!node.properties_info) node.properties_info = [];
- if(!node.flags) node.flags = {};
- if(!node.size) node.size = node.computeSize();
- if(!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
- if(!node.mode) node.mode = LiteGraph.ALWAYS;
+ if (!node.title && title) node.title = title;
+ if (!node.properties) node.properties = {};
+ if (!node.properties_info) node.properties_info = [];
+ if (!node.flags) node.flags = {};
+ if (!node.size) node.size = node.computeSize();
+ if (!node.pos) node.pos = LiteGraph.DEFAULT_POSITION.concat();
+ if (!node.mode) node.mode = LiteGraph.ALWAYS;
- //extra options
- if(options)
- {
- for(var i in options)
- node[i] = options[i];
- }
+ //extra options
+ if (options) {
+ for (var i in options) node[i] = options[i];
+ }
- return node;
- },
+ return node;
+ },
- /**
- * Returns a registered node type with a given name
- * @method getNodeType
- * @param {String} type full name of the node class. p.e. "math/sin"
- * @return {Class} the node class
- */
- getNodeType: function(type)
- {
- return this.registered_node_types[type];
- },
+ /**
+ * Returns a registered node type with a given name
+ * @method getNodeType
+ * @param {String} type full name of the node class. p.e. "math/sin"
+ * @return {Class} the node class
+ */
+ getNodeType: function(type) {
+ return this.registered_node_types[type];
+ },
- /**
- * Returns a list of node types matching one category
- * @method getNodeType
- * @param {String} category category name
- * @return {Array} array with all the node classes
- */
+ /**
+ * Returns a list of node types matching one category
+ * @method getNodeType
+ * @param {String} category category name
+ * @return {Array} array with all the node classes
+ */
- getNodeTypesInCategory: function( category, filter )
- {
- var r = [];
- for(var i in this.registered_node_types)
- {
- var type = this.registered_node_types[i];
- if(filter && type.filter && type.filter != filter)
- continue;
+ getNodeTypesInCategory: function(category, filter) {
+ var r = [];
+ for (var i in this.registered_node_types) {
+ var type = this.registered_node_types[i];
+ if (filter && type.filter && type.filter != filter) continue;
- if(category == "" )
- {
- if (type.category == null)
- r.push(type);
- }
- else if (type.category == category)
- r.push(type);
- }
+ if (category == "") {
+ if (type.category == null) r.push(type);
+ } else if (type.category == category) r.push(type);
+ }
- return r;
- },
+ return r;
+ },
- /**
- * Returns a list with all the node type categories
- * @method getNodeTypesCategories
- * @return {Array} array with all the names of the categories
- */
+ /**
+ * Returns a list with all the node type categories
+ * @method getNodeTypesCategories
+ * @return {Array} array with all the names of the categories
+ */
- getNodeTypesCategories: function()
- {
- var categories = {"":1};
- for(var i in this.registered_node_types)
- if(this.registered_node_types[i].category && !this.registered_node_types[i].skip_list)
- categories[ this.registered_node_types[i].category ] = 1;
- var result = [];
- for(var i in categories)
- result.push(i);
- return result;
- },
+ getNodeTypesCategories: function() {
+ var categories = { "": 1 };
+ for (var i in this.registered_node_types)
+ if (
+ this.registered_node_types[i].category &&
+ !this.registered_node_types[i].skip_list
+ )
+ categories[this.registered_node_types[i].category] = 1;
+ var result = [];
+ for (var i in categories) result.push(i);
+ return result;
+ },
- //debug purposes: reloads all the js scripts that matches a wildcard
- reloadNodes: function (folder_wildcard)
- {
- var tmp = document.getElementsByTagName("script");
- //weird, this array changes by its own, so we use a copy
- var script_files = [];
- for(var i in tmp)
- script_files.push(tmp[i]);
+ //debug purposes: reloads all the js scripts that matches a wildcard
+ reloadNodes: function(folder_wildcard) {
+ var tmp = document.getElementsByTagName("script");
+ //weird, this array changes by its own, so we use a copy
+ var script_files = [];
+ for (var i in tmp) script_files.push(tmp[i]);
+ var docHeadObj = document.getElementsByTagName("head")[0];
+ folder_wildcard = document.location.href + folder_wildcard;
- var docHeadObj = document.getElementsByTagName("head")[0];
- folder_wildcard = document.location.href + folder_wildcard;
+ for (var i in script_files) {
+ var src = script_files[i].src;
+ if (
+ !src ||
+ src.substr(0, folder_wildcard.length) != folder_wildcard
+ )
+ continue;
- for(var i in script_files)
- {
- var src = script_files[i].src;
- if( !src || src.substr(0,folder_wildcard.length ) != folder_wildcard)
- continue;
+ try {
+ if (LiteGraph.debug) console.log("Reloading: " + src);
+ var dynamicScript = document.createElement("script");
+ dynamicScript.type = "text/javascript";
+ dynamicScript.src = src;
+ docHeadObj.appendChild(dynamicScript);
+ docHeadObj.removeChild(script_files[i]);
+ } catch (err) {
+ if (LiteGraph.throw_errors) throw err;
+ if (LiteGraph.debug)
+ console.log("Error while reloading " + src);
+ }
+ }
- try
- {
- if(LiteGraph.debug)
- console.log("Reloading: " + src);
- var dynamicScript = document.createElement("script");
- dynamicScript.type = "text/javascript";
- dynamicScript.src = src;
- docHeadObj.appendChild(dynamicScript);
- docHeadObj.removeChild(script_files[i]);
- }
- catch (err)
- {
- if(LiteGraph.throw_errors)
- throw err;
- if(LiteGraph.debug)
- console.log("Error while reloading " + src);
- }
- }
+ if (LiteGraph.debug) console.log("Nodes reloaded");
+ },
- if(LiteGraph.debug)
- console.log("Nodes reloaded");
- },
+ //separated just to improve if it doesn't work
+ cloneObject: function(obj, target) {
+ if (obj == null) return null;
+ var r = JSON.parse(JSON.stringify(obj));
+ if (!target) return r;
- //separated just to improve if it doesn't work
- cloneObject: function(obj, target)
- {
- if(obj == null) return null;
- var r = JSON.parse( JSON.stringify( obj ) );
- if(!target) return r;
+ for (var i in r) target[i] = r[i];
+ return target;
+ },
- for(var i in r)
- target[i] = r[i];
- return target;
- },
+ isValidConnection: function(type_a, type_b) {
+ 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)
+ )
+ return true;
- isValidConnection: function( type_a, type_b )
- {
- 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 )
+ // Enforce string type to handle toLowerCase call (-1 number not ok)
+ type_a = String(type_a);
+ type_b = String(type_b);
+ type_a = type_a.toLowerCase();
+ type_b = type_b.toLowerCase();
+
+ // For nodes supporting multiple connection types
+ if (type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1)
+ return type_a == type_b;
+
+ // Check all permutations to see if one is valid
+ var supported_types_a = type_a.split(",");
+ 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])
+ return true;
+
+ return false;
+ },
+
+ registerSearchboxExtra: function(node_type, description, data) {
+ this.searchbox_extras[description] = {
+ type: node_type,
+ desc: description,
+ data: data
+ };
+ }
+ });
+
+ //timer that works everywhere
+ if (typeof performance != "undefined")
+ LiteGraph.getTime = performance.now.bind(performance);
+ else if (typeof Date != "undefined" && Date.now)
+ LiteGraph.getTime = Date.now.bind(Date);
+ else if (typeof process != "undefined")
+ LiteGraph.getTime = function() {
+ var t = process.hrtime();
+ return t[0] * 0.001 + t[1] * 1e-6;
+ };
+ else
+ LiteGraph.getTime = function getTime() {
+ return new Date().getTime();
+ };
+
+ //*********************************************************************************
+ // LGraph CLASS
+ //*********************************************************************************
+
+ /**
+ * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
+ *
+ * @class LGraph
+ * @constructor
+ * @param {Object} o data from previous serialization [optional]
+ */
+
+ function LGraph(o) {
+ if (LiteGraph.debug) console.log("Graph created");
+ this.list_of_graphcanvas = null;
+ this.clear();
+
+ if (o) this.configure(o);
+ }
+
+ global.LGraph = LiteGraph.LGraph = LGraph;
+
+ //default supported types
+ LGraph.supported_types = ["number", "string", "boolean"];
+
+ //used to know which types of connections support this graph (some graphs do not allow certain types)
+ LGraph.prototype.getSupportedTypes = function() {
+ return this.supported_types || LGraph.supported_types;
+ };
+
+ LGraph.STATUS_STOPPED = 1;
+ LGraph.STATUS_RUNNING = 2;
+
+ /**
+ * Removes all nodes from this graph
+ * @method clear
+ */
+
+ LGraph.prototype.clear = function() {
+ this.stop();
+ this.status = LGraph.STATUS_STOPPED;
+
+ this.last_node_id = 1;
+ this.last_link_id = 1;
+
+ this._version = -1; //used to detect changes
+
+ //safe clear
+ if (this._nodes)
+ for (var i = 0; i < this._nodes.length; ++i) {
+ var node = this._nodes[i];
+ if (node.onRemoved) node.onRemoved();
+ }
+
+ //nodes
+ this._nodes = [];
+ this._nodes_by_id = {};
+ this._nodes_in_order = []; //nodes that are executable sorted in execution order
+ this._nodes_executable = null; //nodes that contain onExecute
+
+ //other scene stuff
+ this._groups = [];
+
+ //links
+ this.links = {}; //container with all the links
+
+ //iterations
+ this.iteration = 0;
+
+ //custom data
+ this.config = {};
+
+ //timing
+ this.globaltime = 0;
+ this.runningtime = 0;
+ this.fixedtime = 0;
+ this.fixedtime_lapse = 0.01;
+ this.elapsed_time = 0.01;
+ this.last_update_time = 0;
+ this.starttime = 0;
+
+ this.catch_errors = true;
+
+ //subgraph_data
+ this.inputs = {};
+ this.outputs = {};
+
+ //notify canvas to redraw
+ this.change();
+
+ this.sendActionToCanvas("clear");
+ };
+
+ /**
+ * Attach Canvas to this graph
+ * @method attachCanvas
+ * @param {GraphCanvas} graph_canvas
+ */
+
+ LGraph.prototype.attachCanvas = function(graphcanvas) {
+ if (graphcanvas.constructor != LGraphCanvas)
+ throw "attachCanvas expects a LGraphCanvas instance";
+ if (graphcanvas.graph && graphcanvas.graph != this)
+ graphcanvas.graph.detachCanvas(graphcanvas);
+
+ graphcanvas.graph = this;
+ if (!this.list_of_graphcanvas) this.list_of_graphcanvas = [];
+ this.list_of_graphcanvas.push(graphcanvas);
+ };
+
+ /**
+ * Detach Canvas from this graph
+ * @method detachCanvas
+ * @param {GraphCanvas} graph_canvas
+ */
+ LGraph.prototype.detachCanvas = function(graphcanvas) {
+ if (!this.list_of_graphcanvas) return;
+
+ var pos = this.list_of_graphcanvas.indexOf(graphcanvas);
+ if (pos == -1) return;
+ graphcanvas.graph = null;
+ this.list_of_graphcanvas.splice(pos, 1);
+ };
+
+ /**
+ * Starts running this graph every interval milliseconds.
+ * @method start
+ * @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate
+ */
+
+ LGraph.prototype.start = function(interval) {
+ if (this.status == LGraph.STATUS_RUNNING) return;
+ this.status = LGraph.STATUS_RUNNING;
+
+ if (this.onPlayEvent) this.onPlayEvent();
+
+ this.sendEventToAllNodes("onStart");
+
+ //launch
+ this.starttime = LiteGraph.getTime();
+ this.last_update_time = this.starttime;
+ interval = interval || 0;
+ var that = this;
+
+ if (
+ interval == 0 &&
+ typeof window != "undefined" &&
+ window.requestAnimationFrame
+ ) {
+ function on_frame() {
+ if (that.execution_timer_id != -1) return;
+ window.requestAnimationFrame(on_frame);
+ that.runStep(1, !this.catch_errors);
+ }
+ this.execution_timer_id = -1;
+ on_frame();
+ } else
+ this.execution_timer_id = setInterval(function() {
+ //execute
+ that.runStep(1, !this.catch_errors);
+ }, interval);
+ };
+
+ /**
+ * Stops the execution loop of the graph
+ * @method stop execution
+ */
+
+ LGraph.prototype.stop = function() {
+ if (this.status == LGraph.STATUS_STOPPED) return;
+
+ this.status = LGraph.STATUS_STOPPED;
+
+ if (this.onStopEvent) this.onStopEvent();
+
+ if (this.execution_timer_id != null) {
+ if (this.execution_timer_id != -1)
+ clearInterval(this.execution_timer_id);
+ this.execution_timer_id = null;
+ }
+
+ this.sendEventToAllNodes("onStop");
+ };
+
+ /**
+ * Run N steps (cycles) of the graph
+ * @method runStep
+ * @param {number} num number of steps to run, default is 1
+ */
+
+ LGraph.prototype.runStep = function(num, do_not_catch_errors) {
+ num = num || 1;
+
+ var start = LiteGraph.getTime();
+ this.globaltime = 0.001 * (start - this.starttime);
+
+ var nodes = this._nodes_executable
+ ? this._nodes_executable
+ : this._nodes;
+ if (!nodes) return;
+
+ if (do_not_catch_errors) {
+ //iterations
+ for (var i = 0; i < num; i++) {
+ for (var j = 0, l = nodes.length; j < l; ++j) {
+ var node = nodes[j];
+ if (node.mode == LiteGraph.ALWAYS && node.onExecute)
+ node.onExecute();
+ }
+
+ this.fixedtime += this.fixedtime_lapse;
+ if (this.onExecuteStep) this.onExecuteStep();
+ }
+
+ if (this.onAfterExecute) this.onAfterExecute();
+ } else {
+ try {
+ //iterations
+ for (var i = 0; i < num; i++) {
+ for (var j = 0, l = nodes.length; j < l; ++j) {
+ var node = nodes[j];
+ if (node.mode == LiteGraph.ALWAYS && node.onExecute)
+ node.onExecute();
+ }
+
+ this.fixedtime += this.fixedtime_lapse;
+ if (this.onExecuteStep) this.onExecuteStep();
+ }
+
+ if (this.onAfterExecute) this.onAfterExecute();
+ this.errors_in_execution = false;
+ } catch (err) {
+ this.errors_in_execution = true;
+ if (LiteGraph.throw_errors) throw err;
+ if (LiteGraph.debug)
+ console.log("Error during execution: " + err);
+ this.stop();
+ }
+ }
+
+ var now = LiteGraph.getTime();
+ var elapsed = now - start;
+ if (elapsed == 0) elapsed = 1;
+ this.execution_time = 0.001 * elapsed;
+ this.globaltime += 0.001 * elapsed;
+ this.iteration += 1;
+ this.elapsed_time = (now - this.last_update_time) * 0.001;
+ this.last_update_time = now;
+ };
+
+ /**
+ * Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
+ * nodes with only inputs.
+ * @method updateExecutionOrder
+ */
+ LGraph.prototype.updateExecutionOrder = function() {
+ this._nodes_in_order = this.computeExecutionOrder(false);
+ this._nodes_executable = [];
+ for (var i = 0; i < this._nodes_in_order.length; ++i)
+ if (this._nodes_in_order[i].onExecute)
+ this._nodes_executable.push(this._nodes_in_order[i]);
+ };
+
+ //This is more internal, it computes the executable nodes in order and returns it
+ LGraph.prototype.computeExecutionOrder = function(
+ only_onExecute,
+ set_level
+ ) {
+ var L = [];
+ var S = [];
+ var M = {};
+ var visited_links = {}; //to avoid repeating links
+ var remaining_links = {}; //to a
+
+ //search for the nodes without inputs (starting nodes)
+ for (var i = 0, l = this._nodes.length; i < l; ++i) {
+ var node = this._nodes[i];
+ if (only_onExecute && !node.onExecute) continue;
+
+ M[node.id] = node; //add to pending nodes
+
+ var num = 0; //num of input connections
+ if (node.inputs)
+ for (var j = 0, l2 = node.inputs.length; j < l2; j++)
+ if (node.inputs[j] && node.inputs[j].link != null) num += 1;
+
+ if (num == 0) {
+ //is a starting node
+ S.push(node);
+ if (set_level) node._level = 1;
+ } //num of input links
+ else {
+ if (set_level) node._level = 0;
+ remaining_links[node.id] = num;
+ }
+ }
+
+ while (true) {
+ if (S.length == 0) break;
+
+ //get an starting node
+ var node = S.shift();
+ L.push(node); //add to ordered list
+ delete M[node.id]; //remove from the pending nodes
+
+ if (!node.outputs) continue;
+
+ //for every output
+ for (var i = 0; i < node.outputs.length; i++) {
+ var output = node.outputs[i];
+ //not connected
+ if (
+ output == null ||
+ output.links == null ||
+ output.links.length == 0
+ )
+ continue;
+
+ //for every connection
+ for (var j = 0; j < output.links.length; j++) {
+ var link_id = output.links[j];
+ var link = this.links[link_id];
+ if (!link) continue;
+
+ //already visited link (ignore it)
+ if (visited_links[link.id]) continue;
+
+ var target_node = this.getNodeById(link.target_id);
+ if (target_node == null) {
+ visited_links[link.id] = true;
+ continue;
+ }
+
+ if (
+ set_level &&
+ (!target_node._level ||
+ target_node._level <= node._level)
+ )
+ target_node._level = node._level + 1;
+
+ visited_links[link.id] = true; //mark as visited
+ remaining_links[target_node.id] -= 1; //reduce the number of links remaining
+ if (remaining_links[target_node.id] == 0)
+ S.push(target_node); //if no more links, then add to starters array
+ }
+ }
+ }
+
+ //the remaining ones (loops)
+ for (var i in M) L.push(M[i]);
+
+ if (L.length != this._nodes.length && LiteGraph.debug)
+ console.warn("something went wrong, nodes missing");
+
+ var l = L.length;
+
+ //save order number in the node
+ for (var i = 0; i < l; ++i) L[i].order = i;
+
+ //sort now by priority
+ L = L.sort(function(A, B) {
+ var Ap = A.constructor.priority || A.priority || 0;
+ var Bp = B.constructor.priority || B.priority || 0;
+ if (Ap == Bp)
+ //if same priority, sort by order
+ return A.order - B.order;
+ return Ap - Bp; //sort by priority
+ });
+
+ //save order number in the node, again...
+ for (var i = 0; i < l; ++i) L[i].order = i;
+
+ return L;
+ };
+
+ /**
+ * Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.
+ * It doesn't include the node itself
+ * @method getAncestors
+ * @return {Array} an array with all the LGraphNodes that affect this node, in order of execution
+ */
+ LGraph.prototype.getAncestors = function(node) {
+ var ancestors = [];
+ var pending = [node];
+ var visited = {};
+
+ while (pending.length) {
+ var current = pending.shift();
+ if (!current.inputs) continue;
+ if (!visited[current.id] && current != node) {
+ visited[current.id] = true;
+ ancestors.push(current);
+ }
+
+ for (var i = 0; i < current.inputs.length; ++i) {
+ var input = current.getInputNode(i);
+ if (input && ancestors.indexOf(input) == -1) {
+ pending.push(input);
+ }
+ }
+ }
+
+ ancestors.sort(function(a, b) {
+ return a.order - b.order;
+ });
+ return ancestors;
+ };
+
+ /**
+ * Positions every node in a more readable manner
+ * @method arrange
+ */
+ LGraph.prototype.arrange = function(margin) {
+ margin = margin || 40;
+
+ var nodes = this.computeExecutionOrder(false, true);
+ var columns = [];
+ for (var i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+ var col = node._level || 1;
+ if (!columns[col]) columns[col] = [];
+ columns[col].push(node);
+ }
+
+ var x = margin;
+
+ for (var i = 0; i < columns.length; ++i) {
+ var column = columns[i];
+ if (!column) continue;
+ var max_size = 100;
+ var y = margin;
+ for (var j = 0; j < column.length; ++j) {
+ var node = column[j];
+ node.pos[0] = x;
+ node.pos[1] = y;
+ if (node.size[0] > max_size) max_size = node.size[0];
+ y += node.size[1] + margin;
+ }
+ x += max_size + margin;
+ }
+
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * Returns the amount of time the graph has been running in milliseconds
+ * @method getTime
+ * @return {number} number of milliseconds the graph has been running
+ */
+ LGraph.prototype.getTime = function() {
+ return this.globaltime;
+ };
+
+ /**
+ * Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant
+ * @method getFixedTime
+ * @return {number} number of milliseconds the graph has been running
+ */
+
+ LGraph.prototype.getFixedTime = function() {
+ return this.fixedtime;
+ };
+
+ /**
+ * Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct
+ * if the nodes are using graphical actions
+ * @method getElapsedTime
+ * @return {number} number of milliseconds it took the last cycle
+ */
+
+ LGraph.prototype.getElapsedTime = function() {
+ return this.elapsed_time;
+ };
+
+ /**
+ * Sends an event to all the nodes, useful to trigger stuff
+ * @method sendEventToAllNodes
+ * @param {String} eventname the name of the event (function to be called)
+ * @param {Array} params parameters in array format
+ */
+ LGraph.prototype.sendEventToAllNodes = function(eventname, params, mode) {
+ mode = mode || LiteGraph.ALWAYS;
+
+ var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;
+ if (!nodes) return;
+
+ for (var j = 0, l = nodes.length; j < l; ++j) {
+ var node = nodes[j];
+
+ if (
+ node.constructor === LiteGraph.Subgraph &&
+ eventname != "onExecute"
+ ) {
+ if (node.mode == mode)
+ node.sendEventToAllNodes(eventname, params, mode);
+ continue;
+ }
+
+ if (!node[eventname] || node.mode != mode) continue;
+ if (params === undefined) node[eventname]();
+ else if (params && params.constructor === Array)
+ node[eventname].apply(node, params);
+ else node[eventname](params);
+ }
+ };
+
+ LGraph.prototype.sendActionToCanvas = function(action, params) {
+ if (!this.list_of_graphcanvas) return;
+
+ for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {
+ var c = this.list_of_graphcanvas[i];
+ if (c[action]) c[action].apply(c, params);
+ }
+ };
+
+ /**
+ * Adds a new node instance to this graph
+ * @method add
+ * @param {LGraphNode} node the instance of the node
+ */
+
+ LGraph.prototype.add = function(node, skip_compute_order) {
+ if (!node) return;
+
+ //groups
+ if (node.constructor === LGraphGroup) {
+ this._groups.push(node);
+ this.setDirtyCanvas(true);
+ this.change();
+ node.graph = this;
+ this._version++;
+ return;
+ }
+
+ //nodes
+ if (node.id != -1 && this._nodes_by_id[node.id] != null) {
+ console.warn(
+ "LiteGraph: there is already a node with this ID, changing it"
+ );
+ node.id = ++this.last_node_id;
+ }
+
+ if (this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES)
+ throw "LiteGraph: max number of nodes in a graph reached";
+
+ //give him an id
+ if (node.id == null || node.id == -1) node.id = ++this.last_node_id;
+ else if (this.last_node_id < node.id) this.last_node_id = node.id;
+
+ node.graph = this;
+ this._version++;
+
+ this._nodes.push(node);
+ this._nodes_by_id[node.id] = node;
+
+ if (node.onAdded) node.onAdded(this);
+
+ if (this.config.align_to_grid) node.alignToGrid();
+
+ if (!skip_compute_order) this.updateExecutionOrder();
+
+ if (this.onNodeAdded) this.onNodeAdded(node);
+
+ this.setDirtyCanvas(true);
+ this.change();
+
+ return node; //to chain actions
+ };
+
+ /**
+ * Removes a node from the graph
+ * @method remove
+ * @param {LGraphNode} node the instance of the node
+ */
+
+ LGraph.prototype.remove = function(node) {
+ if (node.constructor === LiteGraph.LGraphGroup) {
+ var index = this._groups.indexOf(node);
+ if (index != -1) this._groups.splice(index, 1);
+ node.graph = null;
+ this._version++;
+ this.setDirtyCanvas(true, true);
+ this.change();
+ return;
+ }
+
+ if (this._nodes_by_id[node.id] == null) return; //not found
+
+ if (node.ignore_remove) return; //cannot be removed
+
+ //disconnect inputs
+ if (node.inputs)
+ for (var i = 0; i < node.inputs.length; i++) {
+ var slot = node.inputs[i];
+ if (slot.link != null) node.disconnectInput(i);
+ }
+
+ //disconnect outputs
+ if (node.outputs)
+ for (var i = 0; i < node.outputs.length; i++) {
+ var slot = node.outputs[i];
+ if (slot.links != null && slot.links.length)
+ node.disconnectOutput(i);
+ }
+
+ //node.id = -1; //why?
+
+ //callback
+ if (node.onRemoved) node.onRemoved();
+
+ node.graph = null;
+ this._version++;
+
+ //remove from canvas render
+ if (this.list_of_graphcanvas) {
+ for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {
+ var canvas = this.list_of_graphcanvas[i];
+ if (canvas.selected_nodes[node.id])
+ delete canvas.selected_nodes[node.id];
+ if (canvas.node_dragged == node) canvas.node_dragged = null;
+ }
+ }
+
+ //remove from containers
+ var pos = this._nodes.indexOf(node);
+ if (pos != -1) this._nodes.splice(pos, 1);
+ delete this._nodes_by_id[node.id];
+
+ if (this.onNodeRemoved) this.onNodeRemoved(node);
+
+ this.setDirtyCanvas(true, true);
+ this.change();
+
+ this.updateExecutionOrder();
+ };
+
+ /**
+ * Returns a node by its id.
+ * @method getNodeById
+ * @param {Number} id
+ */
+
+ LGraph.prototype.getNodeById = function(id) {
+ if (id == null) return null;
+ return this._nodes_by_id[id];
+ };
+
+ /**
+ * Returns a list of nodes that matches a class
+ * @method findNodesByClass
+ * @param {Class} classObject the class itself (not an string)
+ * @return {Array} a list with all the nodes of this type
+ */
+ LGraph.prototype.findNodesByClass = function(classObject, result) {
+ result = result || [];
+ result.length = 0;
+ for (var i = 0, l = this._nodes.length; i < l; ++i)
+ if (this._nodes[i].constructor === classObject)
+ result.push(this._nodes[i]);
+ return result;
+ };
+
+ /**
+ * Returns a list of nodes that matches a type
+ * @method findNodesByType
+ * @param {String} type the name of the node type
+ * @return {Array} a list with all the nodes of this type
+ */
+ LGraph.prototype.findNodesByType = function(type, result) {
+ var type = type.toLowerCase();
+ result = result || [];
+ result.length = 0;
+ for (var i = 0, l = this._nodes.length; i < l; ++i)
+ if (this._nodes[i].type.toLowerCase() == type)
+ result.push(this._nodes[i]);
+ return result;
+ };
+
+ /**
+ * Returns the first node that matches a name in its title
+ * @method findNodeByTitle
+ * @param {String} name the name of the node to search
+ * @return {Node} the node or null
+ */
+ LGraph.prototype.findNodeByTitle = function(title) {
+ for (var i = 0, l = this._nodes.length; i < l; ++i)
+ if (this._nodes[i].title == title) return this._nodes[i];
+ return null;
+ };
+
+ /**
+ * Returns a list of nodes that matches a name
+ * @method findNodesByTitle
+ * @param {String} name the name of the node to search
+ * @return {Array} a list with all the nodes with this name
+ */
+ LGraph.prototype.findNodesByTitle = function(title) {
+ var result = [];
+ for (var i = 0, l = this._nodes.length; i < l; ++i)
+ if (this._nodes[i].title == title) result.push(this._nodes[i]);
+ return result;
+ };
+
+ /**
+ * Returns the top-most node in this position of the canvas
+ * @method getNodeOnPos
+ * @param {number} x the x coordinate in canvas space
+ * @param {number} y the y coordinate in canvas space
+ * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
+ * @return {LGraphNode} the node at this position or null
+ */
+ LGraph.prototype.getNodeOnPos = function(x, y, nodes_list, margin) {
+ nodes_list = nodes_list || this._nodes;
+ for (var i = nodes_list.length - 1; i >= 0; i--) {
+ var n = nodes_list[i];
+ if (n.isPointInside(x, y, margin)) return n;
+ }
+ return null;
+ };
+
+ /**
+ * Returns the top-most group in that position
+ * @method getGroupOnPos
+ * @param {number} x the x coordinate in canvas space
+ * @param {number} y the y coordinate in canvas space
+ * @return {LGraphGroup} the group or null
+ */
+ LGraph.prototype.getGroupOnPos = function(x, y) {
+ for (var i = this._groups.length - 1; i >= 0; i--) {
+ var g = this._groups[i];
+ if (g.isPointInside(x, y, 2, true)) return g;
+ }
+ return null;
+ };
+
+ // ********** GLOBALS *****************
+
+ LGraph.prototype.onAction = function(action, param) {
+ this._input_nodes = this.findNodesByClass(
+ LiteGraph.GraphInput,
+ this._input_nodes
+ );
+ for (var i = 0; i < this._input_nodes.length; ++i) {
+ var node = this._input_nodes[i];
+ if (node.properties.name != action) continue;
+ node.onAction(action, param);
+ break;
+ }
+ };
+
+ LGraph.prototype.trigger = function(action, param) {
+ if (this.onTrigger) this.onTrigger(action, param);
+ };
+
+ /**
+ * Tell this graph it has a global graph input of this type
+ * @method addGlobalInput
+ * @param {String} name
+ * @param {String} type
+ * @param {*} value [optional]
+ */
+ LGraph.prototype.addInput = function(name, type, value) {
+ var input = this.inputs[name];
+ if (input)
+ //already exist
+ return;
+
+ this.inputs[name] = { name: name, type: type, value: value };
+ this._version++;
+
+ if (this.onInputAdded) this.onInputAdded(name, type);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
+ };
+
+ /**
+ * Assign a data to the global graph input
+ * @method setGlobalInputData
+ * @param {String} name
+ * @param {*} data
+ */
+ LGraph.prototype.setInputData = function(name, data) {
+ var input = this.inputs[name];
+ if (!input) return;
+ input.value = data;
+ };
+
+ /**
+ * Returns the current value of a global graph input
+ * @method getInputData
+ * @param {String} name
+ * @return {*} the data
+ */
+ LGraph.prototype.getInputData = function(name) {
+ var input = this.inputs[name];
+ if (!input) return null;
+ return input.value;
+ };
+
+ /**
+ * Changes the name of a global graph input
+ * @method renameInput
+ * @param {String} old_name
+ * @param {String} new_name
+ */
+ LGraph.prototype.renameInput = function(old_name, name) {
+ if (name == old_name) return;
+
+ if (!this.inputs[old_name]) return false;
+
+ if (this.inputs[name]) {
+ console.error("there is already one input with that name");
+ return false;
+ }
+
+ this.inputs[name] = this.inputs[old_name];
+ delete this.inputs[old_name];
+ this._version++;
+
+ if (this.onInputRenamed) this.onInputRenamed(old_name, name);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
+ };
+
+ /**
+ * Changes the type of a global graph input
+ * @method changeInputType
+ * @param {String} name
+ * @param {String} type
+ */
+ LGraph.prototype.changeInputType = function(name, type) {
+ if (!this.inputs[name]) return false;
+
+ if (
+ this.inputs[name].type &&
+ String(this.inputs[name].type).toLowerCase() ==
+ String(type).toLowerCase()
+ )
+ return;
+
+ this.inputs[name].type = type;
+ this._version++;
+ if (this.onInputTypeChanged) this.onInputTypeChanged(name, type);
+ };
+
+ /**
+ * Removes a global graph input
+ * @method removeInput
+ * @param {String} name
+ * @param {String} type
+ */
+ LGraph.prototype.removeInput = function(name) {
+ if (!this.inputs[name]) return false;
+
+ delete this.inputs[name];
+ this._version++;
+
+ if (this.onInputRemoved) this.onInputRemoved(name);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
return true;
-
- // Enforce string type to handle toLowerCase call (-1 number not ok)
- type_a = String(type_a);
- type_b = String(type_b);
- type_a = type_a.toLowerCase();
- type_b = type_b.toLowerCase();
-
- // For nodes supporting multiple connection types
- if( type_a.indexOf(",") == -1 && type_b.indexOf(",") == -1 )
- return type_a == type_b;
-
- // Check all permutations to see if one is valid
- var supported_types_a = type_a.split(",");
- 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] )
- return true;
-
- return false;
- },
-
- registerSearchboxExtra: function( node_type, description, data )
- {
- this.searchbox_extras[ description ] = { type: node_type, desc: description, data: data };
- }
-};
-
-//timer that works everywhere
-if(typeof(performance) != "undefined")
- LiteGraph.getTime = performance.now.bind(performance);
-else if(typeof(Date) != "undefined" && Date.now)
- LiteGraph.getTime = Date.now.bind(Date);
-else if(typeof(process) != "undefined")
- LiteGraph.getTime = function(){
- var t = process.hrtime();
- return t[0]*0.001 + t[1]*(1e-6);
- }
-else
- LiteGraph.getTime = function getTime() { return (new Date).getTime(); }
-
-
-
-
-
-
-//*********************************************************************************
-// LGraph CLASS
-//*********************************************************************************
-
-/**
-* LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop.
-*
-* @class LGraph
-* @constructor
-* @param {Object} o data from previous serialization [optional]
-*/
-
-function LGraph( o )
-{
- if (LiteGraph.debug)
- console.log("Graph created");
- this.list_of_graphcanvas = null;
- this.clear();
-
- if(o)
- this.configure(o);
-}
-
-global.LGraph = LiteGraph.LGraph = LGraph;
-
-//default supported types
-LGraph.supported_types = ["number","string","boolean"];
-
-//used to know which types of connections support this graph (some graphs do not allow certain types)
-LGraph.prototype.getSupportedTypes = function() { return this.supported_types || LGraph.supported_types; }
-
-LGraph.STATUS_STOPPED = 1;
-LGraph.STATUS_RUNNING = 2;
-
-/**
-* Removes all nodes from this graph
-* @method clear
-*/
-
-LGraph.prototype.clear = function()
-{
- this.stop();
- this.status = LGraph.STATUS_STOPPED;
-
- this.last_node_id = 1;
- this.last_link_id = 1;
-
- this._version = -1; //used to detect changes
-
- //safe clear
- if(this._nodes)
- for(var i = 0; i < this._nodes.length; ++i)
- {
- var node = this._nodes[i];
- if(node.onRemoved)
- node.onRemoved();
- }
-
- //nodes
- this._nodes = [];
- this._nodes_by_id = {};
- this._nodes_in_order = []; //nodes that are executable sorted in execution order
- this._nodes_executable = null; //nodes that contain onExecute
-
- //other scene stuff
- this._groups = [];
-
- //links
- this.links = {}; //container with all the links
-
- //iterations
- this.iteration = 0;
-
- //custom data
- this.config = {};
-
- //timing
- this.globaltime = 0;
- this.runningtime = 0;
- this.fixedtime = 0;
- this.fixedtime_lapse = 0.01;
- this.elapsed_time = 0.01;
- this.last_update_time = 0;
- this.starttime = 0;
-
- this.catch_errors = true;
-
- //subgraph_data
- this.inputs = {};
- this.outputs = {};
-
- //notify canvas to redraw
- this.change();
-
- this.sendActionToCanvas("clear");
-}
-
-/**
-* Attach Canvas to this graph
-* @method attachCanvas
-* @param {GraphCanvas} graph_canvas
-*/
-
-LGraph.prototype.attachCanvas = function(graphcanvas)
-{
- if(graphcanvas.constructor != LGraphCanvas)
- throw("attachCanvas expects a LGraphCanvas instance");
- if(graphcanvas.graph && graphcanvas.graph != this)
- graphcanvas.graph.detachCanvas( graphcanvas );
-
- graphcanvas.graph = this;
- if(!this.list_of_graphcanvas)
- this.list_of_graphcanvas = [];
- this.list_of_graphcanvas.push(graphcanvas);
-}
-
-/**
-* Detach Canvas from this graph
-* @method detachCanvas
-* @param {GraphCanvas} graph_canvas
-*/
-LGraph.prototype.detachCanvas = function(graphcanvas)
-{
- if(!this.list_of_graphcanvas)
- return;
-
- var pos = this.list_of_graphcanvas.indexOf( graphcanvas );
- if(pos == -1)
- return;
- graphcanvas.graph = null;
- this.list_of_graphcanvas.splice(pos,1);
-}
-
-/**
-* Starts running this graph every interval milliseconds.
-* @method start
-* @param {number} interval amount of milliseconds between executions, if 0 then it renders to the monitor refresh rate
-*/
-
-LGraph.prototype.start = function( interval )
-{
- if( this.status == LGraph.STATUS_RUNNING )
- return;
- this.status = LGraph.STATUS_RUNNING;
-
- if(this.onPlayEvent)
- this.onPlayEvent();
-
- this.sendEventToAllNodes("onStart");
-
- //launch
- this.starttime = LiteGraph.getTime();
- this.last_update_time = this.starttime;
- interval = interval || 0;
- var that = this;
-
- if(interval == 0 && typeof(window) != "undefined" && window.requestAnimationFrame )
- {
- function on_frame()
- {
- if(that.execution_timer_id != -1)
- return;
- window.requestAnimationFrame(on_frame);
- that.runStep(1, !this.catch_errors );
- }
- this.execution_timer_id = -1;
- on_frame();
- }
- else
- this.execution_timer_id = setInterval( function() {
- //execute
- that.runStep(1, !this.catch_errors );
- },interval);
-}
-
-/**
-* Stops the execution loop of the graph
-* @method stop execution
-*/
-
-LGraph.prototype.stop = function()
-{
- if( this.status == LGraph.STATUS_STOPPED )
- return;
-
- this.status = LGraph.STATUS_STOPPED;
-
- if(this.onStopEvent)
- this.onStopEvent();
-
- if(this.execution_timer_id != null)
- {
- if( this.execution_timer_id != -1 )
- clearInterval(this.execution_timer_id);
- this.execution_timer_id = null;
- }
-
- this.sendEventToAllNodes("onStop");
-}
-
-/**
-* Run N steps (cycles) of the graph
-* @method runStep
-* @param {number} num number of steps to run, default is 1
-*/
-
-LGraph.prototype.runStep = function( num, do_not_catch_errors )
-{
- num = num || 1;
-
- var start = LiteGraph.getTime();
- this.globaltime = 0.001 * (start - this.starttime);
-
- var nodes = this._nodes_executable ? this._nodes_executable : this._nodes;
- if(!nodes)
- return;
-
- if( do_not_catch_errors )
- {
- //iterations
- for(var i = 0; i < num; i++)
- {
- for( var j = 0, l = nodes.length; j < l; ++j )
- {
- var node = nodes[j];
- if( node.mode == LiteGraph.ALWAYS && node.onExecute )
- node.onExecute();
- }
-
- this.fixedtime += this.fixedtime_lapse;
- if( this.onExecuteStep )
- this.onExecuteStep();
- }
-
- if( this.onAfterExecute )
- this.onAfterExecute();
- }
- else
- {
- try
- {
- //iterations
- for(var i = 0; i < num; i++)
- {
- for( var j = 0, l = nodes.length; j < l; ++j )
- {
- var node = nodes[j];
- if( node.mode == LiteGraph.ALWAYS && node.onExecute )
- node.onExecute();
- }
-
- this.fixedtime += this.fixedtime_lapse;
- if( this.onExecuteStep )
- this.onExecuteStep();
- }
-
- if( this.onAfterExecute )
- this.onAfterExecute();
- this.errors_in_execution = false;
- }
- catch (err)
- {
- this.errors_in_execution = true;
- if(LiteGraph.throw_errors)
- throw err;
- if(LiteGraph.debug)
- console.log("Error during execution: " + err);
- this.stop();
- }
- }
-
- var now = LiteGraph.getTime();
- var elapsed = now - start;
- if (elapsed == 0)
- elapsed = 1;
- this.execution_time = 0.001 * elapsed;
- this.globaltime += 0.001 * elapsed;
- this.iteration += 1;
- this.elapsed_time = (now - this.last_update_time) * 0.001;
- this.last_update_time = now;
-}
-
-/**
-* Updates the graph execution order according to relevance of the nodes (nodes with only outputs have more relevance than
-* nodes with only inputs.
-* @method updateExecutionOrder
-*/
-LGraph.prototype.updateExecutionOrder = function()
-{
- this._nodes_in_order = this.computeExecutionOrder( false );
- this._nodes_executable = [];
- for(var i = 0; i < this._nodes_in_order.length; ++i)
- if( this._nodes_in_order[i].onExecute )
- this._nodes_executable.push( this._nodes_in_order[i] );
-}
-
-//This is more internal, it computes the executable nodes in order and returns it
-LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level )
-{
- var L = [];
- var S = [];
- var M = {};
- var visited_links = {}; //to avoid repeating links
- var remaining_links = {}; //to a
-
- //search for the nodes without inputs (starting nodes)
- for (var i = 0, l = this._nodes.length; i < l; ++i)
- {
- var node = this._nodes[i];
- if( only_onExecute && !node.onExecute )
- continue;
-
- M[node.id] = node; //add to pending nodes
-
- var num = 0; //num of input connections
- if(node.inputs)
- for(var j = 0, l2 = node.inputs.length; j < l2; j++)
- if(node.inputs[j] && node.inputs[j].link != null)
- num += 1;
-
- if(num == 0) //is a starting node
- {
- S.push(node);
- if(set_level)
- node._level = 1;
- }
- else //num of input links
- {
- if(set_level)
- node._level = 0;
- remaining_links[ node.id ] = num;
- }
- }
-
- while(true)
- {
- if(S.length == 0)
- break;
-
- //get an starting node
- var node = S.shift();
- L.push(node); //add to ordered list
- delete M[node.id]; //remove from the pending nodes
-
- if(!node.outputs)
- continue;
-
- //for every output
- for(var i = 0; i < node.outputs.length; i++)
- {
- var output = node.outputs[i];
- //not connected
- if(output == null || output.links == null || output.links.length == 0)
- continue;
-
- //for every connection
- for(var j = 0; j < output.links.length; j++)
- {
- var link_id = output.links[j];
- var link = this.links[link_id];
- if(!link)
- continue;
-
- //already visited link (ignore it)
- if(visited_links[ link.id ])
- continue;
-
- var target_node = this.getNodeById( link.target_id );
- if(target_node == null)
- {
- visited_links[ link.id ] = true;
- continue;
- }
-
- if(set_level && (!target_node._level || target_node._level <= node._level))
- target_node._level = node._level + 1;
-
- visited_links[link.id] = true; //mark as visited
- remaining_links[target_node.id] -= 1; //reduce the number of links remaining
- if (remaining_links[ target_node.id ] == 0)
- S.push(target_node); //if no more links, then add to starters array
- }
- }
- }
-
- //the remaining ones (loops)
- for(var i in M)
- L.push( M[i] );
-
- if( L.length != this._nodes.length && LiteGraph.debug )
- console.warn("something went wrong, nodes missing");
-
- var l = L.length;
-
- //save order number in the node
- for(var i = 0; i < l; ++i)
- L[i].order = i;
-
- //sort now by priority
- L = L.sort(function(A,B){
- var Ap = A.constructor.priority || A.priority || 0;
- var Bp = B.constructor.priority || B.priority || 0;
- if(Ap == Bp) //if same priority, sort by order
- return A.order - B.order;
- return Ap - Bp; //sort by priority
- });
-
- //save order number in the node, again...
- for(var i = 0; i < l; ++i)
- L[i].order = i;
-
- return L;
-}
-
-/**
-* Returns all the nodes that could affect this one (ancestors) by crawling all the inputs recursively.
-* It doesn't include the node itself
-* @method getAncestors
-* @return {Array} an array with all the LGraphNodes that affect this node, in order of execution
-*/
-LGraph.prototype.getAncestors = function( node )
-{
- var ancestors = [];
- var pending = [node];
- var visited = {};
-
- while (pending.length)
- {
- var current = pending.shift();
- if(!current.inputs)
- continue;
- if( !visited[ current.id ] && current != node )
- {
- visited[ current.id ] = true;
- ancestors.push( current );
- }
-
- for(var i = 0; i < current.inputs.length;++i)
- {
- var input = current.getInputNode(i);
- if( input && ancestors.indexOf( input ) == -1)
- {
- pending.push( input );
- }
- }
- }
-
- ancestors.sort(function(a,b){ return a.order - b.order;});
- return ancestors;
-}
-
-/**
-* Positions every node in a more readable manner
-* @method arrange
-*/
-LGraph.prototype.arrange = function( margin )
-{
- margin = margin || 40;
-
- var nodes = this.computeExecutionOrder( false, true );
- var columns = [];
- for(var i = 0; i < nodes.length; ++i)
- {
- var node = nodes[i];
- var col = node._level || 1;
- if(!columns[col])
- columns[col] = [];
- columns[col].push( node );
- }
-
- var x = margin;
-
- for(var i = 0; i < columns.length; ++i)
- {
- var column = columns[i];
- if(!column)
- continue;
- var max_size = 100;
- var y = margin;
- for(var j = 0; j < column.length; ++j)
- {
- var node = column[j];
- node.pos[0] = x;
- node.pos[1] = y;
- if(node.size[0] > max_size)
- max_size = node.size[0];
- y += node.size[1] + margin;
- }
- x += max_size + margin;
- }
-
- this.setDirtyCanvas(true,true);
-}
-
-
-/**
-* Returns the amount of time the graph has been running in milliseconds
-* @method getTime
-* @return {number} number of milliseconds the graph has been running
-*/
-LGraph.prototype.getTime = function()
-{
- return this.globaltime;
-}
-
-/**
-* Returns the amount of time accumulated using the fixedtime_lapse var. This is used in context where the time increments should be constant
-* @method getFixedTime
-* @return {number} number of milliseconds the graph has been running
-*/
-
-LGraph.prototype.getFixedTime = function()
-{
- return this.fixedtime;
-}
-
-/**
-* Returns the amount of time it took to compute the latest iteration. Take into account that this number could be not correct
-* if the nodes are using graphical actions
-* @method getElapsedTime
-* @return {number} number of milliseconds it took the last cycle
-*/
-
-LGraph.prototype.getElapsedTime = function()
-{
- return this.elapsed_time;
-}
-
-/**
-* Sends an event to all the nodes, useful to trigger stuff
-* @method sendEventToAllNodes
-* @param {String} eventname the name of the event (function to be called)
-* @param {Array} params parameters in array format
-*/
-LGraph.prototype.sendEventToAllNodes = function( eventname, params, mode )
-{
- mode = mode || LiteGraph.ALWAYS;
-
- var nodes = this._nodes_in_order ? this._nodes_in_order : this._nodes;
- if(!nodes)
- return;
-
- for( var j = 0, l = nodes.length; j < l; ++j )
- {
- var node = nodes[j];
-
- if(node.constructor === LiteGraph.Subgraph && eventname != "onExecute" )
- {
- if(node.mode == mode)
- node.sendEventToAllNodes( eventname, params, mode );
- continue;
- }
-
- if( !node[eventname] || node.mode != mode )
- continue;
- if(params === undefined)
- node[eventname]();
- else if(params && params.constructor === Array)
- node[eventname].apply( node, params );
- else
- node[eventname](params);
- }
-}
-
-LGraph.prototype.sendActionToCanvas = function(action, params)
-{
- if(!this.list_of_graphcanvas)
- return;
-
- for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
- {
- var c = this.list_of_graphcanvas[i];
- if( c[action] )
- c[action].apply(c, params);
- }
-}
-
-/**
-* Adds a new node instance to this graph
-* @method add
-* @param {LGraphNode} node the instance of the node
-*/
-
-LGraph.prototype.add = function( node, skip_compute_order)
-{
- if(!node)
- return;
-
- //groups
- if( node.constructor === LGraphGroup )
- {
- this._groups.push( node );
- this.setDirtyCanvas(true);
- this.change();
- node.graph = this;
- this._version++;
- return;
- }
-
- //nodes
- if(node.id != -1 && this._nodes_by_id[node.id] != null)
- {
- console.warn("LiteGraph: there is already a node with this ID, changing it");
- node.id = ++this.last_node_id;
- }
-
- if(this._nodes.length >= LiteGraph.MAX_NUMBER_OF_NODES)
- throw("LiteGraph: max number of nodes in a graph reached");
-
- //give him an id
- if(node.id == null || node.id == -1)
- node.id = ++this.last_node_id;
- else if (this.last_node_id < node.id)
- this.last_node_id = node.id;
-
-
- node.graph = this;
- this._version++;
-
- this._nodes.push(node);
- this._nodes_by_id[node.id] = node;
-
- if(node.onAdded)
- node.onAdded( this );
-
- if(this.config.align_to_grid)
- node.alignToGrid();
-
- if(!skip_compute_order)
- this.updateExecutionOrder();
-
- if(this.onNodeAdded)
- this.onNodeAdded(node);
-
-
- this.setDirtyCanvas(true);
- this.change();
-
- return node; //to chain actions
-}
-
-/**
-* Removes a node from the graph
-* @method remove
-* @param {LGraphNode} node the instance of the node
-*/
-
-LGraph.prototype.remove = function(node)
-{
- if(node.constructor === LiteGraph.LGraphGroup)
- {
- var index = this._groups.indexOf(node);
- if(index != -1)
- this._groups.splice(index,1);
- node.graph = null;
- this._version++;
- this.setDirtyCanvas(true,true);
- this.change();
- return;
- }
-
- if(this._nodes_by_id[node.id] == null)
- return; //not found
-
- if(node.ignore_remove)
- return; //cannot be removed
-
- //disconnect inputs
- if(node.inputs)
- for(var i = 0; i < node.inputs.length; i++)
- {
- var slot = node.inputs[i];
- if(slot.link != null)
- node.disconnectInput(i);
- }
-
- //disconnect outputs
- if(node.outputs)
- for(var i = 0; i < node.outputs.length; i++)
- {
- var slot = node.outputs[i];
- if(slot.links != null && slot.links.length)
- node.disconnectOutput(i);
- }
-
- //node.id = -1; //why?
-
- //callback
- if(node.onRemoved)
- node.onRemoved();
-
- node.graph = null;
- this._version++;
-
- //remove from canvas render
- if(this.list_of_graphcanvas)
- {
- for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
- {
- var canvas = this.list_of_graphcanvas[i];
- if(canvas.selected_nodes[node.id])
- delete canvas.selected_nodes[node.id];
- if(canvas.node_dragged == node)
- canvas.node_dragged = null;
- }
- }
-
- //remove from containers
- var pos = this._nodes.indexOf(node);
- if(pos != -1)
- this._nodes.splice(pos,1);
- delete this._nodes_by_id[node.id];
-
- if(this.onNodeRemoved)
- this.onNodeRemoved(node);
-
- this.setDirtyCanvas(true,true);
- this.change();
-
- this.updateExecutionOrder();
-}
-
-/**
-* Returns a node by its id.
-* @method getNodeById
-* @param {Number} id
-*/
-
-LGraph.prototype.getNodeById = function( id )
-{
- if( id == null )
- return null;
- return this._nodes_by_id[ id ];
-}
-
-/**
-* Returns a list of nodes that matches a class
-* @method findNodesByClass
-* @param {Class} classObject the class itself (not an string)
-* @return {Array} a list with all the nodes of this type
-*/
-LGraph.prototype.findNodesByClass = function( classObject, result )
-{
- result = result || [];
- result.length = 0;
- for(var i = 0, l = this._nodes.length; i < l; ++i)
- if(this._nodes[i].constructor === classObject)
- result.push( this._nodes[i] );
- return result;
-}
-
-/**
-* Returns a list of nodes that matches a type
-* @method findNodesByType
-* @param {String} type the name of the node type
-* @return {Array} a list with all the nodes of this type
-*/
-LGraph.prototype.findNodesByType = function( type, result )
-{
- var type = type.toLowerCase();
- result = result || [];
- result.length = 0;
- for(var i = 0, l = this._nodes.length; i < l; ++i)
- if(this._nodes[i].type.toLowerCase() == type )
- result.push(this._nodes[i]);
- return result;
-}
-
-/**
-* Returns the first node that matches a name in its title
-* @method findNodeByTitle
-* @param {String} name the name of the node to search
-* @return {Node} the node or null
-*/
-LGraph.prototype.findNodeByTitle = function(title)
-{
- for(var i = 0, l = this._nodes.length; i < l; ++i)
- if(this._nodes[i].title == title)
- return this._nodes[i];
- return null;
-}
-
-/**
-* Returns a list of nodes that matches a name
-* @method findNodesByTitle
-* @param {String} name the name of the node to search
-* @return {Array} a list with all the nodes with this name
-*/
-LGraph.prototype.findNodesByTitle = function(title)
-{
- var result = [];
- for(var i = 0, l = this._nodes.length; i < l; ++i)
- if(this._nodes[i].title == title)
- result.push(this._nodes[i]);
- return result;
-}
-
-/**
-* Returns the top-most node in this position of the canvas
-* @method getNodeOnPos
-* @param {number} x the x coordinate in canvas space
-* @param {number} y the y coordinate in canvas space
-* @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph
-* @return {LGraphNode} the node at this position or null
-*/
-LGraph.prototype.getNodeOnPos = function( x, y, nodes_list, margin )
-{
- nodes_list = nodes_list || this._nodes;
- for (var i = nodes_list.length - 1; i >= 0; i--)
- {
- var n = nodes_list[i];
- if(n.isPointInside( x, y, margin ))
- return n;
- }
- return null;
-}
-
-/**
-* Returns the top-most group in that position
-* @method getGroupOnPos
-* @param {number} x the x coordinate in canvas space
-* @param {number} y the y coordinate in canvas space
-* @return {LGraphGroup} the group or null
-*/
-LGraph.prototype.getGroupOnPos = function(x,y)
-{
- for (var i = this._groups.length - 1; i >= 0; i--)
- {
- var g = this._groups[i];
- if(g.isPointInside( x, y, 2, true ))
- return g;
- }
- return null;
-}
-
-// ********** GLOBALS *****************
-
-
-LGraph.prototype.onAction = function(action, param)
-{
- this._input_nodes = this.findNodesByClass( LiteGraph.GraphInput, this._input_nodes );
- for(var i = 0; i < this._input_nodes.length; ++i)
- {
- var node = this._input_nodes[i];
- if( node.properties.name != action )
- continue;
- node.onAction(action,param);
- break;
- }
-}
-
-LGraph.prototype.trigger = function(action, param)
-{
- if(this.onTrigger)
- this.onTrigger(action,param);
-}
-
-/**
-* Tell this graph it has a global graph input of this type
-* @method addGlobalInput
-* @param {String} name
-* @param {String} type
-* @param {*} value [optional]
-*/
-LGraph.prototype.addInput = function( name, type, value )
-{
- var input = this.inputs[ name ];
- if( input ) //already exist
- return;
-
- this.inputs[ name ] = { name: name, type: type, value: value };
- this._version++;
-
- if(this.onInputAdded)
- this.onInputAdded(name, type);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
-}
-
-/**
-* Assign a data to the global graph input
-* @method setGlobalInputData
-* @param {String} name
-* @param {*} data
-*/
-LGraph.prototype.setInputData = function(name, data)
-{
- var input = this.inputs[name];
- if (!input)
- return;
- input.value = data;
-}
-
-/**
-* Returns the current value of a global graph input
-* @method getInputData
-* @param {String} name
-* @return {*} the data
-*/
-LGraph.prototype.getInputData = function(name)
-{
- var input = this.inputs[name];
- if (!input)
- return null;
- return input.value;
-}
-
-/**
-* Changes the name of a global graph input
-* @method renameInput
-* @param {String} old_name
-* @param {String} new_name
-*/
-LGraph.prototype.renameInput = function(old_name, name)
-{
- if(name == old_name)
- return;
-
- if(!this.inputs[old_name])
- return false;
-
- if(this.inputs[name])
- {
- console.error("there is already one input with that name");
- return false;
- }
-
- this.inputs[name] = this.inputs[old_name];
- delete this.inputs[old_name];
- this._version++;
-
- if(this.onInputRenamed)
- this.onInputRenamed(old_name, name);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
-}
-
-/**
-* Changes the type of a global graph input
-* @method changeInputType
-* @param {String} name
-* @param {String} type
-*/
-LGraph.prototype.changeInputType = function(name, type)
-{
- if(!this.inputs[name])
- return false;
-
- if(this.inputs[name].type && String(this.inputs[name].type).toLowerCase() == String(type).toLowerCase() )
- return;
-
- this.inputs[name].type = type;
- this._version++;
- if(this.onInputTypeChanged)
- this.onInputTypeChanged(name, type);
-}
-
-/**
-* Removes a global graph input
-* @method removeInput
-* @param {String} name
-* @param {String} type
-*/
-LGraph.prototype.removeInput = function(name)
-{
- if(!this.inputs[name])
- return false;
-
- delete this.inputs[name];
- this._version++;
-
- if(this.onInputRemoved)
- this.onInputRemoved(name);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
- return true;
-}
-
-/**
-* Creates a global graph output
-* @method addOutput
-* @param {String} name
-* @param {String} type
-* @param {*} value
-*/
-LGraph.prototype.addOutput = function(name, type, value)
-{
- this.outputs[name] = { name: name, type: type, value: value };
- this._version++;
-
- if(this.onOutputAdded)
- this.onOutputAdded(name, type);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
-}
-
-/**
-* Assign a data to the global output
-* @method setOutputData
-* @param {String} name
-* @param {String} value
-*/
-LGraph.prototype.setOutputData = function(name, value)
-{
- var output = this.outputs[ name ];
- if (!output)
- return;
- output.value = value;
-}
-
-/**
-* Returns the current value of a global graph output
-* @method getOutputData
-* @param {String} name
-* @return {*} the data
-*/
-LGraph.prototype.getOutputData = function(name)
-{
- var output = this.outputs[name];
- if (!output)
- return null;
- return output.value;
-}
-
-/**
-* Renames a global graph output
-* @method renameOutput
-* @param {String} old_name
-* @param {String} new_name
-*/
-LGraph.prototype.renameOutput = function(old_name, name)
-{
- if(!this.outputs[old_name])
- return false;
-
- if(this.outputs[name])
- {
- console.error("there is already one output with that name");
- return false;
- }
-
- this.outputs[name] = this.outputs[old_name];
- delete this.outputs[old_name];
- this._version++;
-
- if(this.onOutputRenamed)
- this.onOutputRenamed(old_name, name);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
-}
-
-/**
-* Changes the type of a global graph output
-* @method changeOutputType
-* @param {String} name
-* @param {String} type
-*/
-LGraph.prototype.changeOutputType = function(name, type)
-{
- if(!this.outputs[name])
- return false;
-
- if(this.outputs[name].type && String(this.outputs[name].type).toLowerCase() == String(type).toLowerCase() )
- return;
-
- this.outputs[name].type = type;
- this._version++;
- if(this.onOutputTypeChanged)
- this.onOutputTypeChanged(name, type);
-}
-
-/**
-* Removes a global graph output
-* @method removeOutput
-* @param {String} name
-*/
-LGraph.prototype.removeOutput = function(name)
-{
- if(!this.outputs[name])
- return false;
- delete this.outputs[name];
- this._version++;
-
- if(this.onOutputRemoved)
- this.onOutputRemoved(name);
-
- if(this.onInputsOutputsChange)
- this.onInputsOutputsChange();
- return true;
-}
-
-LGraph.prototype.triggerInput = function(name,value)
-{
- var nodes = this.findNodesByTitle(name);
- for(var i = 0; i < nodes.length; ++i)
- nodes[i].onTrigger(value);
-}
-
-LGraph.prototype.setCallback = function(name,func)
-{
- var nodes = this.findNodesByTitle(name);
- for(var i = 0; i < nodes.length; ++i)
- nodes[i].setTrigger(func);
-}
-
-
-LGraph.prototype.connectionChange = function( node, link_info )
-{
- this.updateExecutionOrder();
- if( this.onConnectionChange )
- this.onConnectionChange( node );
- this._version++;
- this.sendActionToCanvas("onConnectionChange");
-}
-
-/**
-* returns if the graph is in live mode
-* @method isLive
-*/
-
-LGraph.prototype.isLive = function()
-{
- if(!this.list_of_graphcanvas)
- return false;
-
- for(var i = 0; i < this.list_of_graphcanvas.length; ++i)
- {
- var c = this.list_of_graphcanvas[i];
- if(c.live_mode)
- return true;
- }
- return false;
-}
-
-/**
-* clears the triggered slot animation in all links (stop visual animation)
-* @method clearTriggeredSlots
-*/
-LGraph.prototype.clearTriggeredSlots = function()
-{
- for(var i in this.links)
- {
- var link_info = this.links[i];
- if( !link_info )
- continue;
- if( link_info._last_time )
- link_info._last_time = 0;
- }
-}
-
-
-/* Called when something visually changed (not the graph!) */
-LGraph.prototype.change = function()
-{
- if(LiteGraph.debug)
- console.log("Graph changed");
- this.sendActionToCanvas("setDirty",[true,true]);
- if(this.on_change)
- this.on_change(this);
-}
-
-LGraph.prototype.setDirtyCanvas = function(fg,bg)
-{
- this.sendActionToCanvas("setDirty",[fg,bg]);
-}
-
-/**
-* Destroys a link
-* @method removeLink
-* @param {Number} link_id
-*/
-LGraph.prototype.removeLink = function(link_id)
-{
- var link = this.links[ link_id ];
- if(!link)
- return;
- var node = this.getNodeById( link.target_id );
- if(node)
- node.disconnectInput( link.target_slot );
-}
-
-
-//save and recover app state ***************************************
-/**
-* Creates a Object containing all the info about this graph, it can be serialized
-* @method serialize
-* @return {Object} value of the node
-*/
-LGraph.prototype.serialize = function()
-{
- var nodes_info = [];
- for(var i = 0, l = this._nodes.length; i < l; ++i)
- nodes_info.push( this._nodes[i].serialize() );
-
- //pack link info into a non-verbose format
- var links = [];
- for(var i in this.links) //links is an OBJECT
- {
- var link = this.links[i];
- links.push([ link.id, link.origin_id, link.origin_slot, link.target_id, link.target_slot, link.type ]);
- }
-
- var groups_info = [];
- for(var i = 0; i < this._groups.length; ++i)
- groups_info.push( this._groups[i].serialize() );
-
- var data = {
- last_node_id: this.last_node_id,
- last_link_id: this.last_link_id,
- nodes: nodes_info,
- links: links,
- groups: groups_info,
- config: this.config,
- version: LiteGraph.VERSION
- };
-
- return data;
-}
-
-
-/**
-* Configure a graph from a JSON string
-* @method configure
-* @param {String} str configure a graph from a JSON string
-* @param {Boolean} returns if there was any error parsing
-*/
-LGraph.prototype.configure = function( data, keep_old )
-{
- if(!data)
- return;
-
- if(!keep_old)
- this.clear();
-
- var nodes = data.nodes;
-
- //decode links info (they are very verbose)
- if(data.links && data.links.constructor === Array)
- {
- var links = [];
- for(var i = 0; i < data.links.length; ++i)
- {
- var link_data = data.links[i];
- var link = new LLink();
- link.configure( link_data );
- links[ link.id ] = link;
- }
- data.links = links;
- }
-
- //copy all stored fields
- for (var i in data)
- this[i] = data[i];
-
- var error = false;
-
- //create nodes
- this._nodes = [];
- if(nodes)
- {
- for(var i = 0, l = nodes.length; i < l; ++i)
- {
- var n_info = nodes[i]; //stored info
- var node = LiteGraph.createNode( n_info.type, n_info.title );
- if(!node)
- {
- if(LiteGraph.debug)
- console.log("Node not found or has errors: " + n_info.type);
-
- //in case of error we create a replacement node to avoid losing info
- node = new LGraphNode();
- node.last_serialization = n_info;
- node.has_errors = true;
- error = true;
- //continue;
- }
-
- node.id = n_info.id; //id it or it will create a new id
- this.add(node, true); //add before configure, otherwise configure cannot create links
- }
-
- //configure nodes afterwards so they can reach each other
- for(var i = 0, l = nodes.length; i < l; ++i)
- {
- var n_info = nodes[i];
- var node = this.getNodeById( n_info.id );
- if(node)
- node.configure( n_info );
- }
- }
-
- //groups
- this._groups.length = 0;
- if( data.groups )
- for(var i = 0; i < data.groups.length; ++i )
- {
- var group = new LiteGraph.LGraphGroup();
- group.configure( data.groups[i] );
- this.add( group );
- }
-
- this.updateExecutionOrder();
- this._version++;
- this.setDirtyCanvas(true,true);
- return error;
-}
-
-LGraph.prototype.load = function(url)
-{
- var that = this;
- var req = new XMLHttpRequest();
- req.open('GET', url, true);
- req.send(null);
- req.onload = function (oEvent) {
- if(req.status !== 200)
- {
- console.error("Error loading graph:",req.status,req.response);
- return;
- }
- var data = JSON.parse( req.response );
- that.configure(data);
- }
- req.onerror = function(err)
- {
- console.error("Error loading graph:",err);
- }
-}
-
-LGraph.prototype.onNodeTrace = function(node, msg, color)
-{
- //TODO
-}
-
-//this is the class in charge of storing link information
-function LLink( id, type, origin_id, origin_slot, target_id, target_slot )
-{
- this.id = id;
- this.type = type;
- this.origin_id = origin_id;
- this.origin_slot = origin_slot;
- this.target_id = target_id;
- this.target_slot = target_slot;
-
- this._data = null;
- this._pos = new Float32Array(2); //center
-}
-
-LLink.prototype.configure = function(o)
-{
- if(o.constructor === Array)
- {
- this.id = o[0];
- this.origin_id = o[1];
- this.origin_slot = o[2];
- this.target_id = o[3];
- this.target_slot = o[4];
- this.type = o[5];
- }
- else
- {
- this.id = o.id;
- this.type = o.type;
- this.origin_id = o.origin_id;
- this.origin_slot = o.origin_slot;
- this.target_id = o.target_id;
- this.target_slot = o.target_slot;
- }
-}
-
-LLink.prototype.serialize = function()
-{
- return [ this.id, this.type, this.origin_id, this.origin_slot, this.target_id, this.target_slot ];
-}
-
-LiteGraph.LLink = LLink;
-
-// *************************************************************
-// Node CLASS *******
-// *************************************************************
-
-/*
+ };
+
+ /**
+ * Creates a global graph output
+ * @method addOutput
+ * @param {String} name
+ * @param {String} type
+ * @param {*} value
+ */
+ LGraph.prototype.addOutput = function(name, type, value) {
+ this.outputs[name] = { name: name, type: type, value: value };
+ this._version++;
+
+ if (this.onOutputAdded) this.onOutputAdded(name, type);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
+ };
+
+ /**
+ * Assign a data to the global output
+ * @method setOutputData
+ * @param {String} name
+ * @param {String} value
+ */
+ LGraph.prototype.setOutputData = function(name, value) {
+ var output = this.outputs[name];
+ if (!output) return;
+ output.value = value;
+ };
+
+ /**
+ * Returns the current value of a global graph output
+ * @method getOutputData
+ * @param {String} name
+ * @return {*} the data
+ */
+ LGraph.prototype.getOutputData = function(name) {
+ var output = this.outputs[name];
+ if (!output) return null;
+ return output.value;
+ };
+
+ /**
+ * Renames a global graph output
+ * @method renameOutput
+ * @param {String} old_name
+ * @param {String} new_name
+ */
+ LGraph.prototype.renameOutput = function(old_name, name) {
+ if (!this.outputs[old_name]) return false;
+
+ if (this.outputs[name]) {
+ console.error("there is already one output with that name");
+ return false;
+ }
+
+ this.outputs[name] = this.outputs[old_name];
+ delete this.outputs[old_name];
+ this._version++;
+
+ if (this.onOutputRenamed) this.onOutputRenamed(old_name, name);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
+ };
+
+ /**
+ * Changes the type of a global graph output
+ * @method changeOutputType
+ * @param {String} name
+ * @param {String} type
+ */
+ LGraph.prototype.changeOutputType = function(name, type) {
+ if (!this.outputs[name]) return false;
+
+ if (
+ this.outputs[name].type &&
+ String(this.outputs[name].type).toLowerCase() ==
+ String(type).toLowerCase()
+ )
+ return;
+
+ this.outputs[name].type = type;
+ this._version++;
+ if (this.onOutputTypeChanged) this.onOutputTypeChanged(name, type);
+ };
+
+ /**
+ * Removes a global graph output
+ * @method removeOutput
+ * @param {String} name
+ */
+ LGraph.prototype.removeOutput = function(name) {
+ if (!this.outputs[name]) return false;
+ delete this.outputs[name];
+ this._version++;
+
+ if (this.onOutputRemoved) this.onOutputRemoved(name);
+
+ if (this.onInputsOutputsChange) this.onInputsOutputsChange();
+ return true;
+ };
+
+ LGraph.prototype.triggerInput = function(name, value) {
+ var nodes = this.findNodesByTitle(name);
+ for (var i = 0; i < nodes.length; ++i) nodes[i].onTrigger(value);
+ };
+
+ LGraph.prototype.setCallback = function(name, func) {
+ var nodes = this.findNodesByTitle(name);
+ for (var i = 0; i < nodes.length; ++i) nodes[i].setTrigger(func);
+ };
+
+ LGraph.prototype.connectionChange = function(node, link_info) {
+ this.updateExecutionOrder();
+ if (this.onConnectionChange) this.onConnectionChange(node);
+ this._version++;
+ this.sendActionToCanvas("onConnectionChange");
+ };
+
+ /**
+ * returns if the graph is in live mode
+ * @method isLive
+ */
+
+ LGraph.prototype.isLive = function() {
+ if (!this.list_of_graphcanvas) return false;
+
+ for (var i = 0; i < this.list_of_graphcanvas.length; ++i) {
+ var c = this.list_of_graphcanvas[i];
+ if (c.live_mode) return true;
+ }
+ return false;
+ };
+
+ /**
+ * clears the triggered slot animation in all links (stop visual animation)
+ * @method clearTriggeredSlots
+ */
+ LGraph.prototype.clearTriggeredSlots = function() {
+ for (var i in this.links) {
+ var link_info = this.links[i];
+ if (!link_info) continue;
+ if (link_info._last_time) link_info._last_time = 0;
+ }
+ };
+
+ /* Called when something visually changed (not the graph!) */
+ LGraph.prototype.change = function() {
+ if (LiteGraph.debug) console.log("Graph changed");
+ this.sendActionToCanvas("setDirty", [true, true]);
+ if (this.on_change) this.on_change(this);
+ };
+
+ LGraph.prototype.setDirtyCanvas = function(fg, bg) {
+ this.sendActionToCanvas("setDirty", [fg, bg]);
+ };
+
+ /**
+ * Destroys a link
+ * @method removeLink
+ * @param {Number} link_id
+ */
+ LGraph.prototype.removeLink = function(link_id) {
+ var link = this.links[link_id];
+ if (!link) return;
+ var node = this.getNodeById(link.target_id);
+ if (node) node.disconnectInput(link.target_slot);
+ };
+
+ //save and recover app state ***************************************
+ /**
+ * Creates a Object containing all the info about this graph, it can be serialized
+ * @method serialize
+ * @return {Object} value of the node
+ */
+ LGraph.prototype.serialize = function() {
+ var nodes_info = [];
+ for (var i = 0, l = this._nodes.length; i < l; ++i)
+ nodes_info.push(this._nodes[i].serialize());
+
+ //pack link info into a non-verbose format
+ var links = [];
+ for (var i in this.links) { //links is an OBJECT
+ var link = this.links[i];
+ links.push([
+ link.id,
+ link.origin_id,
+ link.origin_slot,
+ link.target_id,
+ link.target_slot,
+ link.type
+ ]);
+ }
+
+ var groups_info = [];
+ for (var i = 0; i < this._groups.length; ++i)
+ groups_info.push(this._groups[i].serialize());
+
+ var data = {
+ last_node_id: this.last_node_id,
+ last_link_id: this.last_link_id,
+ nodes: nodes_info,
+ links: links,
+ groups: groups_info,
+ config: this.config,
+ version: LiteGraph.VERSION
+ };
+
+ return data;
+ };
+
+ /**
+ * Configure a graph from a JSON string
+ * @method configure
+ * @param {String} str configure a graph from a JSON string
+ * @param {Boolean} returns if there was any error parsing
+ */
+ LGraph.prototype.configure = function(data, keep_old) {
+ if (!data) return;
+
+ if (!keep_old) this.clear();
+
+ var nodes = data.nodes;
+
+ //decode links info (they are very verbose)
+ if (data.links && data.links.constructor === Array) {
+ var links = [];
+ for (var i = 0; i < data.links.length; ++i) {
+ var link_data = data.links[i];
+ var link = new LLink();
+ link.configure(link_data);
+ links[link.id] = link;
+ }
+ data.links = links;
+ }
+
+ //copy all stored fields
+ for (var i in data) this[i] = data[i];
+
+ var error = false;
+
+ //create nodes
+ this._nodes = [];
+ if (nodes) {
+ for (var i = 0, l = nodes.length; i < l; ++i) {
+ var n_info = nodes[i]; //stored info
+ var node = LiteGraph.createNode(n_info.type, n_info.title);
+ if (!node) {
+ if (LiteGraph.debug)
+ console.log(
+ "Node not found or has errors: " + n_info.type
+ );
+
+ //in case of error we create a replacement node to avoid losing info
+ node = new LGraphNode();
+ node.last_serialization = n_info;
+ node.has_errors = true;
+ error = true;
+ //continue;
+ }
+
+ node.id = n_info.id; //id it or it will create a new id
+ this.add(node, true); //add before configure, otherwise configure cannot create links
+ }
+
+ //configure nodes afterwards so they can reach each other
+ for (var i = 0, l = nodes.length; i < l; ++i) {
+ var n_info = nodes[i];
+ var node = this.getNodeById(n_info.id);
+ if (node) node.configure(n_info);
+ }
+ }
+
+ //groups
+ this._groups.length = 0;
+ if (data.groups)
+ for (var i = 0; i < data.groups.length; ++i) {
+ var group = new LiteGraph.LGraphGroup();
+ group.configure(data.groups[i]);
+ this.add(group);
+ }
+
+ this.updateExecutionOrder();
+ this._version++;
+ this.setDirtyCanvas(true, true);
+ return error;
+ };
+
+ LGraph.prototype.load = function(url) {
+ var that = this;
+ var req = new XMLHttpRequest();
+ req.open("GET", url, true);
+ req.send(null);
+ req.onload = function(oEvent) {
+ if (req.status !== 200) {
+ console.error("Error loading graph:", req.status, req.response);
+ return;
+ }
+ var data = JSON.parse(req.response);
+ that.configure(data);
+ };
+ req.onerror = function(err) {
+ console.error("Error loading graph:", err);
+ };
+ };
+
+ LGraph.prototype.onNodeTrace = function(node, msg, color) {
+ //TODO
+ };
+
+ //this is the class in charge of storing link information
+ function LLink(id, type, origin_id, origin_slot, target_id, target_slot) {
+ this.id = id;
+ this.type = type;
+ this.origin_id = origin_id;
+ this.origin_slot = origin_slot;
+ this.target_id = target_id;
+ this.target_slot = target_slot;
+
+ this._data = null;
+ this._pos = new Float32Array(2); //center
+ }
+
+ LLink.prototype.configure = function(o) {
+ if (o.constructor === Array) {
+ this.id = o[0];
+ this.origin_id = o[1];
+ this.origin_slot = o[2];
+ this.target_id = o[3];
+ this.target_slot = o[4];
+ this.type = o[5];
+ } else {
+ this.id = o.id;
+ this.type = o.type;
+ this.origin_id = o.origin_id;
+ this.origin_slot = o.origin_slot;
+ this.target_id = o.target_id;
+ this.target_slot = o.target_slot;
+ }
+ };
+
+ LLink.prototype.serialize = function() {
+ return [
+ this.id,
+ this.type,
+ this.origin_id,
+ this.origin_slot,
+ this.target_id,
+ this.target_slot
+ ];
+ };
+
+ LiteGraph.LLink = LLink;
+
+ // *************************************************************
+ // Node CLASS *******
+ // *************************************************************
+
+ /*
title: string
pos: [x,y]
size: [x,y]
@@ -1867,1528 +1700,1477 @@ LiteGraph.LLink = LLink;
+ getExtraMenuOptions: to add option to context menu
*/
-/**
-* Base Class for all the node type classes
-* @class LGraphNode
-* @param {String} name a name for the node
-*/
-
-function LGraphNode(title)
-{
- this._ctor(title);
-}
-
-global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;
-
-LGraphNode.prototype._ctor = function( title )
-{
- this.title = title || "Unnamed";
- this.size = [LiteGraph.NODE_WIDTH,60];
- this.graph = null;
-
- this._pos = new Float32Array(10,10);
-
- Object.defineProperty( this, "pos", {
- set: function(v)
- {
- if(!v || v.length < 2)
- return;
- this._pos[0] = v[0];
- this._pos[1] = v[1];
- },
- get: function()
- {
- return this._pos;
- },
- enumerable: true
- });
-
- this.id = -1; //not know till not added
- this.type = null;
-
- //inputs available: array of inputs
- this.inputs = [];
- this.outputs = [];
- this.connections = [];
-
- //local data
- this.properties = {}; //for the values
- this.properties_info = []; //for the info
-
- this.flags = {};
-}
-
-/**
-* configure a node from an object containing the serialized info
-* @method configure
-*/
-LGraphNode.prototype.configure = function(info)
-{
- if(this.graph)
- this.graph._version++;
-
- for (var j in info)
- {
- if(j == "properties")
- {
- //i don't want to clone properties, I want to reuse the old container
- for(var k in info.properties)
- {
- this.properties[k] = info.properties[k];
- if(this.onPropertyChanged)
- this.onPropertyChanged(k,info.properties[k]);
- }
- continue;
- }
-
- if(info[j] == null)
- continue;
-
- else if (typeof(info[j]) == 'object') //object
- {
- if(this[j] && this[j].configure)
- this[j].configure( info[j] );
- else
- this[j] = LiteGraph.cloneObject(info[j], this[j]);
- }
- else //value
- this[j] = info[j];
- }
-
- if(!info.title)
- this.title = this.constructor.title;
-
- if(this.onConnectionsChange)
- {
- if(this.inputs)
- for(var i = 0; i < this.inputs.length; ++i)
- {
- var input = this.inputs[i];
- var link_info = this.graph ? this.graph.links[ input.link ] : null;
- this.onConnectionsChange( LiteGraph.INPUT, i, true, link_info, input ); //link_info has been created now, so its updated
- }
-
- if(this.outputs)
- for(var i = 0; i < this.outputs.length; ++i)
- {
- var output = this.outputs[i];
- if(!output.links)
- continue;
- for(var j = 0; j < output.links.length; ++j)
- {
- var link_info = this.graph ? this.graph.links[ output.links[j] ] : null;
- this.onConnectionsChange( LiteGraph.OUTPUT, i, true, link_info, output ); //link_info has been created now, so its updated
- }
- }
- }
-
- if(info.widgets_values && this.widgets)
- {
- for(var i = 0; i < info.widgets_values.length; ++i)
- if( this.widgets[i] )
- this.widgets[i].value = info.widgets_values[i];
- }
-
- if( this.onConfigure )
- this.onConfigure( info );
-}
-
-/**
-* serialize the content
-* @method serialize
-*/
-
-LGraphNode.prototype.serialize = function()
-{
- //create serialization object
- var o = {
- id: this.id,
- type: this.type,
- pos: this.pos,
- size: this.size,
- flags: LiteGraph.cloneObject(this.flags),
- mode: this.mode
- };
-
- //special case for when there were errors
- if( this.constructor === LGraphNode && this.last_serialization )
- return this.last_serialization;
-
- if( this.inputs )
- o.inputs = this.inputs;
-
- if( this.outputs )
- {
- //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)
- for(var i = 0; i < this.outputs.length; i++)
- delete this.outputs[i]._data;
- o.outputs = this.outputs;
- }
-
- if( this.title && this.title != this.constructor.title )
- o.title = this.title;
-
- if( this.properties )
- o.properties = LiteGraph.cloneObject( this.properties );
-
- if( this.widgets && this.serialize_widgets )
- {
- o.widgets_values = [];
- for(var i = 0; i < this.widgets.length; ++i)
- o.widgets_values[i] = this.widgets[i].value;
- }
-
- if( !o.type )
- o.type = this.constructor.type;
-
- if( this.color )
- o.color = this.color;
- if( this.bgcolor )
- o.bgcolor = this.bgcolor;
- if( this.boxcolor )
- o.boxcolor = this.boxcolor;
- if( this.shape )
- o.shape = this.shape;
-
- if(this.onSerialize)
- {
- if( this.onSerialize(o) )
- console.warn("node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter");
- }
-
- return o;
-}
-
-
-/* Creates a clone of this node */
-LGraphNode.prototype.clone = function()
-{
- var node = LiteGraph.createNode(this.type);
- if(!node)
- return null;
-
- //we clone it because serialize returns shared containers
- var data = LiteGraph.cloneObject( this.serialize() );
-
- //remove links
- if(data.inputs)
- for(var i = 0; i < data.inputs.length; ++i)
- data.inputs[i].link = null;
-
- if(data.outputs)
- for(var i = 0; i < data.outputs.length; ++i)
- {
- if(data.outputs[i].links)
- data.outputs[i].links.length = 0;
- }
-
- delete data["id"];
- //remove links
- node.configure(data);
-
- return node;
-}
-
-
-/**
-* serialize and stringify
-* @method toString
-*/
-
-LGraphNode.prototype.toString = function()
-{
- return JSON.stringify( this.serialize() );
-}
-//LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
-
-
-/**
-* get the title string
-* @method getTitle
-*/
-
-LGraphNode.prototype.getTitle = function()
-{
- return this.title || this.constructor.title;
-}
-
-
-
-// Execution *************************
-/**
-* sets the output data
-* @method setOutputData
-* @param {number} slot
-* @param {*} data
-*/
-LGraphNode.prototype.setOutputData = function(slot, data)
-{
- if(!this.outputs)
- return;
-
- //this maybe slow and a niche case
- //if(slot && slot.constructor === String)
- // slot = this.findOutputSlot(slot);
-
- if(slot == -1 || slot >= this.outputs.length)
- return;
-
- var output_info = this.outputs[slot];
- if(!output_info)
- return;
-
- //store data in the output itself in case we want to debug
- output_info._data = data;
-
- //if there are connections, pass the data to the connections
- if( this.outputs[slot].links )
- {
- for(var i = 0; i < this.outputs[slot].links.length; i++)
- {
- var link_id = this.outputs[slot].links[i];
- this.graph.links[ link_id ].data = data;
- }
- }
-}
-
-/**
-* sets the output data type, useful when you want to be able to overwrite the data type
-* @method setOutputDataType
-* @param {number} slot
-* @param {String} datatype
-*/
-LGraphNode.prototype.setOutputDataType = function(slot, type)
-{
- if(!this.outputs)
- return;
- if(slot == -1 || slot >= this.outputs.length)
- return;
- var output_info = this.outputs[slot];
- if(!output_info)
- return;
- //store data in the output itself in case we want to debug
- output_info.type = type;
-
- //if there are connections, pass the data to the connections
- if( this.outputs[slot].links )
- {
- for(var i = 0; i < this.outputs[slot].links.length; i++)
- {
- var link_id = this.outputs[slot].links[i];
- this.graph.links[ link_id ].type = type;
- }
- }
-}
-
-/**
-* Retrieves the input data (data traveling through the connection) from one slot
-* @method getInputData
-* @param {number} slot
-* @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link
-* @return {*} data or if it is not connected returns undefined
-*/
-LGraphNode.prototype.getInputData = function( slot, force_update )
-{
- if(!this.inputs)
- return; //undefined;
-
- if(slot >= this.inputs.length || this.inputs[slot].link == null)
- return;
-
- var link_id = this.inputs[slot].link;
- var link = this.graph.links[ link_id ];
- if(!link) //bug: weird case but it happens sometimes
- return null;
-
- if(!force_update)
- return link.data;
-
- //special case: used to extract data from the incoming connection before the graph has been executed
- var node = this.graph.getNodeById( link.origin_id );
- if(!node)
- return link.data;
-
- if(node.updateOutputData)
- node.updateOutputData( link.origin_slot );
- else if(node.onExecute)
- node.onExecute();
-
- return link.data;
-}
-
-/**
-* Retrieves the input data type (in case this supports multiple input types)
-* @method getInputDataType
-* @param {number} slot
-* @return {String} datatype in string format
-*/
-LGraphNode.prototype.getInputDataType = function( slot )
-{
- if(!this.inputs)
- return null; //undefined;
-
- if(slot >= this.inputs.length || this.inputs[slot].link == null)
- return null;
- var link_id = this.inputs[slot].link;
- var link = this.graph.links[ link_id ];
- if(!link) //bug: weird case but it happens sometimes
- return null;
- var node = this.graph.getNodeById( link.origin_id );
- if(!node)
- return link.type;
- var output_info = node.outputs[ link.origin_slot ];
- if(output_info)
- return output_info.type;
- return null;
-}
-
-/**
-* Retrieves the input data from one slot using its name instead of slot number
-* @method getInputDataByName
-* @param {String} slot_name
-* @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link
-* @return {*} data or if it is not connected returns null
-*/
-LGraphNode.prototype.getInputDataByName = function( slot_name, force_update )
-{
- var slot = this.findInputSlot( slot_name );
- if( slot == -1 )
- return null;
- return this.getInputData( slot, force_update );
-}
-
-
-/**
-* tells you if there is a connection in one input slot
-* @method isInputConnected
-* @param {number} slot
-* @return {boolean}
-*/
-LGraphNode.prototype.isInputConnected = function(slot)
-{
- if(!this.inputs)
- return false;
- return (slot < this.inputs.length && this.inputs[slot].link != null);
-}
-
-/**
-* tells you info about an input connection (which node, type, etc)
-* @method getInputInfo
-* @param {number} slot
-* @return {Object} object or null { link: id, name: string, type: string or 0 }
-*/
-LGraphNode.prototype.getInputInfo = function(slot)
-{
- if(!this.inputs)
- return null;
- if(slot < this.inputs.length)
- return this.inputs[slot];
- return null;
-}
-
-/**
-* returns the node connected in the input slot
-* @method getInputNode
-* @param {number} slot
-* @return {LGraphNode} node or null
-*/
-LGraphNode.prototype.getInputNode = function( slot )
-{
- if(!this.inputs)
- return null;
- if(slot >= this.inputs.length)
- return null;
- var input = this.inputs[slot];
- if(!input || input.link === null)
- return null;
- var link_info = this.graph.links[ input.link ];
- if(!link_info)
- return null;
- return this.graph.getNodeById( link_info.origin_id );
-}
-
-
-/**
-* returns the value of an input with this name, otherwise checks if there is a property with that name
-* @method getInputOrProperty
-* @param {string} name
-* @return {*} value
-*/
-LGraphNode.prototype.getInputOrProperty = function( name )
-{
- if(!this.inputs || !this.inputs.length)
- return this.properties ? this.properties[name] : null;
-
- for(var i = 0, l = this.inputs.length; i < l; ++i)
- {
- var input_info = this.inputs[i];
- if(name == input_info.name && input_info.link != null)
- {
- var link = this.graph.links[ input_info.link ];
- if(link)
- return link.data;
- }
- }
- return this.properties[ name ];
-}
-
-
-
-
-/**
-* tells you the last output data that went in that slot
-* @method getOutputData
-* @param {number} slot
-* @return {Object} object or null
-*/
-LGraphNode.prototype.getOutputData = function(slot)
-{
- if(!this.outputs)
- return null;
- if(slot >= this.outputs.length)
- return null;
-
- var info = this.outputs[slot];
- return info._data;
-}
-
-
-/**
-* tells you info about an output connection (which node, type, etc)
-* @method getOutputInfo
-* @param {number} slot
-* @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] }
-*/
-LGraphNode.prototype.getOutputInfo = function(slot)
-{
- if(!this.outputs)
- return null;
- if(slot < this.outputs.length)
- return this.outputs[slot];
- return null;
-}
-
-
-/**
-* tells you if there is a connection in one output slot
-* @method isOutputConnected
-* @param {number} slot
-* @return {boolean}
-*/
-LGraphNode.prototype.isOutputConnected = function(slot)
-{
- if(!this.outputs)
- return false;
- return (slot < this.outputs.length && this.outputs[slot].links && this.outputs[slot].links.length);
-}
-
-/**
-* tells you if there is any connection in the output slots
-* @method isAnyOutputConnected
-* @return {boolean}
-*/
-LGraphNode.prototype.isAnyOutputConnected = function()
-{
- if(!this.outputs)
- return false;
- for(var i = 0; i < this.outputs.length; ++i)
- if( this.outputs[i].links && this.outputs[i].links.length )
- return true;
- return false;
-}
-
-
-/**
-* retrieves all the nodes connected to this output slot
-* @method getOutputNodes
-* @param {number} slot
-* @return {array}
-*/
-LGraphNode.prototype.getOutputNodes = function(slot)
-{
- if(!this.outputs || this.outputs.length == 0)
- return null;
-
- if(slot >= this.outputs.length)
- return null;
-
- var output = this.outputs[slot];
- if(!output.links || output.links.length == 0)
- return null;
-
- var r = [];
- for(var i = 0; i < output.links.length; i++)
- {
- var link_id = output.links[i];
- var link = this.graph.links[ link_id ];
- if(link)
- {
- var target_node = this.graph.getNodeById( link.target_id );
- if( target_node )
- r.push( target_node );
- }
- }
- return r;
-}
-
-/**
-* 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 )
-{
- if( !this.outputs || !this.outputs.length )
- return;
-
- if(this.graph)
- this.graph._last_trigger_time = LiteGraph.getTime();
-
- for(var i = 0; i < this.outputs.length; ++i)
- {
- var output = this.outputs[ i ];
- if(!output || output.type !== LiteGraph.EVENT || (action && output.name != action) )
- continue;
- this.triggerSlot( i, param );
- }
-}
-
-/**
-* Triggers an slot event in this node
-* @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 )
-{
- if( !this.outputs )
- return;
-
- var output = this.outputs[ slot ];
- if( !output )
- return;
-
- var links = output.links;
- if(!links || !links.length)
- return;
-
- if(this.graph)
- this.graph._last_trigger_time = LiteGraph.getTime();
-
- //for every link attached here
- for(var k = 0; k < links.length; ++k)
- {
- var id = links[k];
- if( link_id != null && link_id != id ) //to skip links
- continue;
- var link_info = this.graph.links[ links[k] ];
- if(!link_info) //not connected
- continue;
- link_info._last_time = LiteGraph.getTime();
- var node = this.graph.getNodeById( link_info.target_id );
- if(!node) //node not found?
- continue;
-
- //used to mark events in graph
- var target_connection = node.inputs[ link_info.target_slot ];
-
- if(node.onAction)
- node.onAction( target_connection.name, param );
- else if(node.mode === LiteGraph.ON_TRIGGER)
- {
- if(node.onExecute)
- node.onExecute(param);
- }
- }
-}
-
-/**
-* clears the trigger slot animation
-* @method clearTriggeredSlot
-* @param {Number} slot the index of the output slot
-* @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot
-*/
-LGraphNode.prototype.clearTriggeredSlot = function( slot, link_id )
-{
- if( !this.outputs )
- return;
-
- var output = this.outputs[ slot ];
- if( !output )
- return;
-
- var links = output.links;
- if(!links || !links.length)
- return;
-
- //for every link attached here
- for(var k = 0; k < links.length; ++k)
- {
- var id = links[k];
- if( link_id != null && link_id != id ) //to skip links
- continue;
- var link_info = this.graph.links[ links[k] ];
- if(!link_info) //not connected
- continue;
- link_info._last_time = 0;
- }
-}
-
-/**
-* add a new property to this node
-* @method addProperty
-* @param {string} name
-* @param {*} default_value
-* @param {string} type string defining the output type ("vec3","number",...)
-* @param {Object} extra_info this can be used to have special properties of the property (like values, etc)
-*/
-LGraphNode.prototype.addProperty = function( name, default_value, type, extra_info )
-{
- var o = { name: name, type: type, default_value: default_value };
- if(extra_info)
- for(var i in extra_info)
- o[i] = extra_info[i];
- if(!this.properties_info)
- this.properties_info = [];
- this.properties_info.push(o);
- if(!this.properties)
- this.properties = {};
- this.properties[ name ] = default_value;
- return o;
-}
-
-
-//connections
-
-/**
-* add a new output slot to use in this node
-* @method addOutput
-* @param {string} name
-* @param {string} type string defining the output type ("vec3","number",...)
-* @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)
-*/
-LGraphNode.prototype.addOutput = function(name,type,extra_info)
-{
- var o = { name: name, type: type, links: null };
- if(extra_info)
- for(var i in extra_info)
- o[i] = extra_info[i];
-
- if(!this.outputs)
- this.outputs = [];
- this.outputs.push(o);
- if(this.onOutputAdded)
- this.onOutputAdded(o);
- this.size = this.computeSize();
- this.setDirtyCanvas(true,true);
- return o;
-}
-
-/**
-* add a new output slot to use in this node
-* @method addOutputs
-* @param {Array} array of triplets like [[name,type,extra_info],[...]]
-*/
-LGraphNode.prototype.addOutputs = function(array)
-{
- for(var i = 0; i < array.length; ++i)
- {
- var info = array[i];
- var o = {name:info[0],type:info[1],link:null};
- if(array[2])
- for(var j in info[2])
- o[j] = info[2][j];
-
- if(!this.outputs)
- this.outputs = [];
- this.outputs.push(o);
- if(this.onOutputAdded)
- this.onOutputAdded(o);
- }
-
- this.size = this.computeSize();
- this.setDirtyCanvas(true,true);
-}
-
-/**
-* remove an existing output slot
-* @method removeOutput
-* @param {number} slot
-*/
-LGraphNode.prototype.removeOutput = function(slot)
-{
- this.disconnectOutput(slot);
- this.outputs.splice(slot,1);
- for(var i = slot; i < this.outputs.length; ++i)
- {
- if( !this.outputs[i] || !this.outputs[i].links )
- continue;
- var links = this.outputs[i].links;
- for(var j = 0; j < links.length; ++j)
- {
- var link = this.graph.links[ links[j] ];
- if(!link)
- continue;
- link.origin_slot -= 1;
- }
- }
-
- this.size = this.computeSize();
- if(this.onOutputRemoved)
- this.onOutputRemoved(slot);
- this.setDirtyCanvas(true,true);
-}
-
-/**
-* add a new input slot to use in this node
-* @method addInput
-* @param {string} name
-* @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0
-* @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)
-*/
-LGraphNode.prototype.addInput = function(name,type,extra_info)
-{
- type = type || 0;
- var o = {name:name,type:type,link:null};
- if(extra_info)
- for(var i in extra_info)
- o[i] = extra_info[i];
-
- if(!this.inputs)
- this.inputs = [];
- this.inputs.push(o);
- this.size = this.computeSize();
- if(this.onInputAdded)
- this.onInputAdded(o);
- this.setDirtyCanvas(true,true);
- return o;
-}
-
-/**
-* add several new input slots in this node
-* @method addInputs
-* @param {Array} array of triplets like [[name,type,extra_info],[...]]
-*/
-LGraphNode.prototype.addInputs = function(array)
-{
- for(var i = 0; i < array.length; ++i)
- {
- var info = array[i];
- var o = {name:info[0], type:info[1], link:null};
- if(array[2])
- for(var j in info[2])
- o[j] = info[2][j];
-
- if(!this.inputs)
- this.inputs = [];
- this.inputs.push(o);
- if(this.onInputAdded)
- this.onInputAdded(o);
- }
-
- this.size = this.computeSize();
- this.setDirtyCanvas(true,true);
-}
-
-/**
-* remove an existing input slot
-* @method removeInput
-* @param {number} slot
-*/
-LGraphNode.prototype.removeInput = function(slot)
-{
- this.disconnectInput(slot);
- this.inputs.splice(slot,1);
- for(var i = slot; i < this.inputs.length; ++i)
- {
- if(!this.inputs[i])
- continue;
- var link = this.graph.links[ this.inputs[i].link ];
- if(!link)
- continue;
- link.target_slot -= 1;
- }
- this.size = this.computeSize();
- if(this.onInputRemoved)
- this.onInputRemoved(slot);
- this.setDirtyCanvas(true,true);
-}
-
-/**
-* add an special connection to this node (used for special kinds of graphs)
-* @method addConnection
-* @param {string} name
-* @param {string} type string defining the input type ("vec3","number",...)
-* @param {[x,y]} pos position of the connection inside the node
-* @param {string} direction if is input or output
-*/
-LGraphNode.prototype.addConnection = function(name,type,pos,direction)
-{
- var o = {
- name: name,
- type: type,
- pos: pos,
- direction: direction,
- links: null
- };
- this.connections.push( o );
- return o;
-}
-
-/**
-* computes the size of a node according to its inputs and output slots
-* @method computeSize
-* @param {number} minHeight
-* @return {number} the total size
-*/
-LGraphNode.prototype.computeSize = function( minHeight, out )
-{
- if( this.constructor.size )
- return this.constructor.size.concat();
-
- var rows = Math.max( this.inputs ? this.inputs.length : 1, this.outputs ? this.outputs.length : 1);
- var size = out || new Float32Array([0,0]);
- rows = Math.max(rows, 1);
- var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size
- size[1] = (this.constructor.slot_start_y || 0) + rows * LiteGraph.NODE_SLOT_HEIGHT;
- var widgets_height = 0;
- if( this.widgets && this.widgets.length )
- widgets_height = this.widgets.length * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 8;
- if( this.widgets_up )
- size[1] = Math.max(size[1], widgets_height);
- else
- size[1] += widgets_height;
-
- var font_size = font_size;
- var title_width = compute_text_size( this.title );
- var input_width = 0;
- var output_width = 0;
-
- if(this.inputs)
- for(var i = 0, l = this.inputs.length; i < l; ++i)
- {
- var input = this.inputs[i];
- var text = input.label || input.name || "";
- var text_width = compute_text_size( text );
- if(input_width < text_width)
- input_width = text_width;
- }
-
- if(this.outputs)
- for(var i = 0, l = this.outputs.length; i < l; ++i)
- {
- var output = this.outputs[i];
- var text = output.label || output.name || "";
- var text_width = compute_text_size( text );
- if(output_width < text_width)
- output_width = text_width;
- }
-
- size[0] = Math.max( input_width + output_width + 10, title_width );
- size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH );
- if(this.widgets && this.widgets.length)
- size[0] = Math.max( size[0], LiteGraph.NODE_WIDTH * 1.5 );
-
- if(this.onResize)
- this.onResize(size);
-
- function compute_text_size( text )
- {
- if(!text)
- return 0;
- return font_size * text.length * 0.6;
- }
-
- if(this.constructor.min_height && size[1] < this.constructor.min_height)
- size[1] = this.constructor.min_height;
-
- size[1] += 6; //margin
-
- return size;
-}
-
-/**
-* Allows to pass
-*
-* @method addWidget
-* @return {Object} the created widget
-*/
-LGraphNode.prototype.addWidget = function( type, name, value, callback, options )
-{
- if(!this.widgets)
- this.widgets = [];
- var w = {
- type: type.toLowerCase(),
- name: name,
- value: value,
- callback: callback,
- options: options || {}
- };
-
- if(w.options.y !== undefined )
- w.y = w.options.y;
-
- if( !callback )
- console.warn("LiteGraph addWidget(...) without a callback");
- if( type == "combo" && !w.options.values )
- throw("LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }");
- this.widgets.push(w);
- return w;
-}
-
-LGraphNode.prototype.addCustomWidget = function( custom_widget )
-{
- if(!this.widgets)
- this.widgets = [];
- this.widgets.push(custom_widget);
- return custom_widget;
-}
-
-
-/**
-* returns the bounding of the object, used for rendering purposes
-* bounding is: [topleft_cornerx, topleft_cornery, width, height]
-* @method getBounding
-* @return {Float32Array[4]} the total size
-*/
-LGraphNode.prototype.getBounding = function( out )
-{
- out = out || new Float32Array(4);
- out[0] = this.pos[0] - 4;
- out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
- out[2] = this.size[0] + 4;
- out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT;
-
- if( this.onBounding )
- this.onBounding( out );
- return out;
-}
-
-/**
-* checks if a point is inside the shape of a node
-* @method isPointInside
-* @param {number} x
-* @param {number} y
-* @return {boolean}
-*/
-LGraphNode.prototype.isPointInside = function( x, y, margin, skip_title )
-{
- margin = margin || 0;
-
- var margin_top = this.graph && this.graph.isLive() ? 0 : 20;
- if(skip_title)
- margin_top = 0;
- if(this.flags && this.flags.collapsed)
- {
- //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
- if( isInsideRectangle( x, y, this.pos[0] - margin, this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin, (this._collapsed_width||LiteGraph.NODE_COLLAPSED_WIDTH) + 2 * margin, LiteGraph.NODE_TITLE_HEIGHT + 2 * margin ) )
- return true;
- }
- else if ( (this.pos[0] - 4 - margin) < x && (this.pos[0] + this.size[0] + 4 + margin) > x
- && (this.pos[1] - margin_top - margin) < y && (this.pos[1] + this.size[1] + margin) > y)
- return true;
- return false;
-}
-
-/**
-* checks if a point is inside a node slot, and returns info about which slot
-* @method getSlotInPosition
-* @param {number} x
-* @param {number} y
-* @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }
-*/
-LGraphNode.prototype.getSlotInPosition = function( x, y )
-{
- //search for inputs
- var link_pos = new Float32Array(2);
- if(this.inputs)
- for(var i = 0, l = this.inputs.length; i < l; ++i)
- {
- var input = this.inputs[i];
- this.getConnectionPos( true,i, link_pos );
- if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
- return { input: input, slot: i, link_pos: link_pos };
- }
-
- if(this.outputs)
- for(var i = 0, l = this.outputs.length; i < l; ++i)
- {
- var output = this.outputs[i];
- this.getConnectionPos(false,i,link_pos);
- if( isInsideRectangle(x, y, link_pos[0] - 10, link_pos[1] - 5, 20,10) )
- return { output: output, slot: i, link_pos: link_pos };
- }
-
- return null;
-}
-
-/**
-* 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)
-*/
-LGraphNode.prototype.findInputSlot = function(name)
-{
- 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 -1;
-}
-
-/**
-* 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)
-*/
-LGraphNode.prototype.findOutputSlot = function(name)
-{
- 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 -1;
-}
-
-/**
-* connect this node output to the input of another node
-* @method connect
-* @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 {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)
-* @return {Object} the link_info is created, otherwise null
-*/
-LGraphNode.prototype.connect = function( slot, target_node, target_slot )
-{
- target_slot = target_slot || 0;
-
- if(!this.graph) //could be connected before adding it to a graph
- {
- console.log("Connect: Error, node doesn\'t belong to any graph. Nodes must be added first to a graph before connecting them."); //due to link ids being associated with graphs
- return null;
- }
-
-
- //seek for the output slot
- if( slot.constructor === String )
- {
- slot = this.findOutputSlot(slot);
- if(slot == -1)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, no slot of name " + slot);
- return null;
- }
- }
- else if(!this.outputs || slot >= this.outputs.length)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, slot number not found");
- return null;
- }
-
- if(target_node && target_node.constructor === Number)
- target_node = this.graph.getNodeById( target_node );
- if(!target_node)
- throw("target node is null");
-
- //avoid loopback
- if(target_node == this)
- return null;
-
- //you can specify the slot by name
- if(target_slot.constructor === String)
- {
- target_slot = target_node.findInputSlot( target_slot );
- if(target_slot == -1)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, no slot of name " + target_slot);
- return null;
- }
- }
- else if( target_slot === LiteGraph.EVENT )
- {
- //search for first slot with event?
- /*
+ /**
+ * Base Class for all the node type classes
+ * @class LGraphNode
+ * @param {String} name a name for the node
+ */
+
+ function LGraphNode(title) {
+ this._ctor(title);
+ }
+
+ global.LGraphNode = LiteGraph.LGraphNode = LGraphNode;
+
+ LGraphNode.prototype._ctor = function(title) {
+ this.title = title || "Unnamed";
+ this.size = [LiteGraph.NODE_WIDTH, 60];
+ this.graph = null;
+
+ this._pos = new Float32Array(10, 10);
+
+ Object.defineProperty(this, "pos", {
+ set: function(v) {
+ if (!v || v.length < 2) return;
+ this._pos[0] = v[0];
+ this._pos[1] = v[1];
+ },
+ get: function() {
+ return this._pos;
+ },
+ enumerable: true
+ });
+
+ this.id = -1; //not know till not added
+ this.type = null;
+
+ //inputs available: array of inputs
+ this.inputs = [];
+ this.outputs = [];
+ this.connections = [];
+
+ //local data
+ this.properties = {}; //for the values
+ this.properties_info = []; //for the info
+
+ this.flags = {};
+ };
+
+ /**
+ * configure a node from an object containing the serialized info
+ * @method configure
+ */
+ LGraphNode.prototype.configure = function(info) {
+ if (this.graph) this.graph._version++;
+
+ for (var j in info) {
+ if (j == "properties") {
+ //i don't want to clone properties, I want to reuse the old container
+ for (var k in info.properties) {
+ this.properties[k] = info.properties[k];
+ if (this.onPropertyChanged)
+ this.onPropertyChanged(k, info.properties[k]);
+ }
+ continue;
+ }
+
+ if (info[j] == null) continue;
+ else if (typeof info[j] == "object") {
+ //object
+ if (this[j] && this[j].configure) this[j].configure(info[j]);
+ else this[j] = LiteGraph.cloneObject(info[j], this[j]);
+ } //value
+ else this[j] = info[j];
+ }
+
+ if (!info.title) this.title = this.constructor.title;
+
+ if (this.onConnectionsChange) {
+ if (this.inputs)
+ for (var i = 0; i < this.inputs.length; ++i) {
+ var input = this.inputs[i];
+ var link_info = this.graph
+ ? this.graph.links[input.link]
+ : null;
+ this.onConnectionsChange(
+ LiteGraph.INPUT,
+ i,
+ true,
+ link_info,
+ input
+ ); //link_info has been created now, so its updated
+ }
+
+ if (this.outputs)
+ for (var i = 0; i < this.outputs.length; ++i) {
+ var output = this.outputs[i];
+ if (!output.links) continue;
+ for (var j = 0; j < output.links.length; ++j) {
+ var link_info = this.graph
+ ? this.graph.links[output.links[j]]
+ : null;
+ this.onConnectionsChange(
+ LiteGraph.OUTPUT,
+ i,
+ true,
+ link_info,
+ output
+ ); //link_info has been created now, so its updated
+ }
+ }
+ }
+
+ if (info.widgets_values && this.widgets) {
+ for (var i = 0; i < info.widgets_values.length; ++i)
+ if (this.widgets[i])
+ this.widgets[i].value = info.widgets_values[i];
+ }
+
+ if (this.onConfigure) this.onConfigure(info);
+ };
+
+ /**
+ * serialize the content
+ * @method serialize
+ */
+
+ LGraphNode.prototype.serialize = function() {
+ //create serialization object
+ var o = {
+ id: this.id,
+ type: this.type,
+ pos: this.pos,
+ size: this.size,
+ flags: LiteGraph.cloneObject(this.flags),
+ mode: this.mode
+ };
+
+ //special case for when there were errors
+ if (this.constructor === LGraphNode && this.last_serialization)
+ return this.last_serialization;
+
+ if (this.inputs) o.inputs = this.inputs;
+
+ if (this.outputs) {
+ //clear outputs last data (because data in connections is never serialized but stored inside the outputs info)
+ for (var i = 0; i < this.outputs.length; i++)
+ delete this.outputs[i]._data;
+ o.outputs = this.outputs;
+ }
+
+ if (this.title && this.title != this.constructor.title)
+ o.title = this.title;
+
+ if (this.properties)
+ o.properties = LiteGraph.cloneObject(this.properties);
+
+ if (this.widgets && this.serialize_widgets) {
+ o.widgets_values = [];
+ for (var i = 0; i < this.widgets.length; ++i)
+ o.widgets_values[i] = this.widgets[i].value;
+ }
+
+ if (!o.type) o.type = this.constructor.type;
+
+ if (this.color) o.color = this.color;
+ if (this.bgcolor) o.bgcolor = this.bgcolor;
+ if (this.boxcolor) o.boxcolor = this.boxcolor;
+ if (this.shape) o.shape = this.shape;
+
+ if (this.onSerialize) {
+ if (this.onSerialize(o))
+ console.warn(
+ "node onSerialize shouldnt return anything, data should be stored in the object pass in the first parameter"
+ );
+ }
+
+ return o;
+ };
+
+ /* Creates a clone of this node */
+ LGraphNode.prototype.clone = function() {
+ var node = LiteGraph.createNode(this.type);
+ if (!node) return null;
+
+ //we clone it because serialize returns shared containers
+ var data = LiteGraph.cloneObject(this.serialize());
+
+ //remove links
+ if (data.inputs)
+ for (var i = 0; i < data.inputs.length; ++i)
+ data.inputs[i].link = null;
+
+ if (data.outputs)
+ for (var i = 0; i < data.outputs.length; ++i) {
+ if (data.outputs[i].links) data.outputs[i].links.length = 0;
+ }
+
+ delete data["id"];
+ //remove links
+ node.configure(data);
+
+ return node;
+ };
+
+ /**
+ * serialize and stringify
+ * @method toString
+ */
+
+ LGraphNode.prototype.toString = function() {
+ return JSON.stringify(this.serialize());
+ };
+ //LGraphNode.prototype.deserialize = function(info) {} //this cannot be done from within, must be done in LiteGraph
+
+ /**
+ * get the title string
+ * @method getTitle
+ */
+
+ LGraphNode.prototype.getTitle = function() {
+ return this.title || this.constructor.title;
+ };
+
+ // Execution *************************
+ /**
+ * sets the output data
+ * @method setOutputData
+ * @param {number} slot
+ * @param {*} data
+ */
+ LGraphNode.prototype.setOutputData = function(slot, data) {
+ if (!this.outputs) return;
+
+ //this maybe slow and a niche case
+ //if(slot && slot.constructor === String)
+ // slot = this.findOutputSlot(slot);
+
+ if (slot == -1 || slot >= this.outputs.length) return;
+
+ var output_info = this.outputs[slot];
+ if (!output_info) return;
+
+ //store data in the output itself in case we want to debug
+ output_info._data = data;
+
+ //if there are connections, pass the data to the connections
+ if (this.outputs[slot].links) {
+ for (var i = 0; i < this.outputs[slot].links.length; i++) {
+ var link_id = this.outputs[slot].links[i];
+ this.graph.links[link_id].data = data;
+ }
+ }
+ };
+
+ /**
+ * sets the output data type, useful when you want to be able to overwrite the data type
+ * @method setOutputDataType
+ * @param {number} slot
+ * @param {String} datatype
+ */
+ LGraphNode.prototype.setOutputDataType = function(slot, type) {
+ if (!this.outputs) return;
+ if (slot == -1 || slot >= this.outputs.length) return;
+ var output_info = this.outputs[slot];
+ if (!output_info) return;
+ //store data in the output itself in case we want to debug
+ output_info.type = type;
+
+ //if there are connections, pass the data to the connections
+ if (this.outputs[slot].links) {
+ for (var i = 0; i < this.outputs[slot].links.length; i++) {
+ var link_id = this.outputs[slot].links[i];
+ this.graph.links[link_id].type = type;
+ }
+ }
+ };
+
+ /**
+ * Retrieves the input data (data traveling through the connection) from one slot
+ * @method getInputData
+ * @param {number} slot
+ * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link
+ * @return {*} data or if it is not connected returns undefined
+ */
+ LGraphNode.prototype.getInputData = function(slot, force_update) {
+ if (!this.inputs) return; //undefined;
+
+ if (slot >= this.inputs.length || this.inputs[slot].link == null)
+ return;
+
+ var link_id = this.inputs[slot].link;
+ var link = this.graph.links[link_id];
+ if (!link)
+ //bug: weird case but it happens sometimes
+ return null;
+
+ if (!force_update) return link.data;
+
+ //special case: used to extract data from the incoming connection before the graph has been executed
+ var node = this.graph.getNodeById(link.origin_id);
+ if (!node) return link.data;
+
+ if (node.updateOutputData) node.updateOutputData(link.origin_slot);
+ else if (node.onExecute) node.onExecute();
+
+ return link.data;
+ };
+
+ /**
+ * Retrieves the input data type (in case this supports multiple input types)
+ * @method getInputDataType
+ * @param {number} slot
+ * @return {String} datatype in string format
+ */
+ LGraphNode.prototype.getInputDataType = function(slot) {
+ if (!this.inputs) return null; //undefined;
+
+ if (slot >= this.inputs.length || this.inputs[slot].link == null)
+ return null;
+ var link_id = this.inputs[slot].link;
+ var link = this.graph.links[link_id];
+ if (!link)
+ //bug: weird case but it happens sometimes
+ return null;
+ var node = this.graph.getNodeById(link.origin_id);
+ if (!node) return link.type;
+ var output_info = node.outputs[link.origin_slot];
+ if (output_info) return output_info.type;
+ return null;
+ };
+
+ /**
+ * Retrieves the input data from one slot using its name instead of slot number
+ * @method getInputDataByName
+ * @param {String} slot_name
+ * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link
+ * @return {*} data or if it is not connected returns null
+ */
+ LGraphNode.prototype.getInputDataByName = function(
+ slot_name,
+ force_update
+ ) {
+ var slot = this.findInputSlot(slot_name);
+ if (slot == -1) return null;
+ return this.getInputData(slot, force_update);
+ };
+
+ /**
+ * tells you if there is a connection in one input slot
+ * @method isInputConnected
+ * @param {number} slot
+ * @return {boolean}
+ */
+ LGraphNode.prototype.isInputConnected = function(slot) {
+ if (!this.inputs) return false;
+ return slot < this.inputs.length && this.inputs[slot].link != null;
+ };
+
+ /**
+ * tells you info about an input connection (which node, type, etc)
+ * @method getInputInfo
+ * @param {number} slot
+ * @return {Object} object or null { link: id, name: string, type: string or 0 }
+ */
+ LGraphNode.prototype.getInputInfo = function(slot) {
+ if (!this.inputs) return null;
+ if (slot < this.inputs.length) return this.inputs[slot];
+ return null;
+ };
+
+ /**
+ * returns the node connected in the input slot
+ * @method getInputNode
+ * @param {number} slot
+ * @return {LGraphNode} node or null
+ */
+ LGraphNode.prototype.getInputNode = function(slot) {
+ if (!this.inputs) return null;
+ if (slot >= this.inputs.length) return null;
+ var input = this.inputs[slot];
+ if (!input || input.link === null) return null;
+ var link_info = this.graph.links[input.link];
+ if (!link_info) return null;
+ return this.graph.getNodeById(link_info.origin_id);
+ };
+
+ /**
+ * returns the value of an input with this name, otherwise checks if there is a property with that name
+ * @method getInputOrProperty
+ * @param {string} name
+ * @return {*} value
+ */
+ LGraphNode.prototype.getInputOrProperty = function(name) {
+ if (!this.inputs || !this.inputs.length)
+ return this.properties ? this.properties[name] : null;
+
+ for (var i = 0, l = this.inputs.length; i < l; ++i) {
+ var input_info = this.inputs[i];
+ if (name == input_info.name && input_info.link != null) {
+ var link = this.graph.links[input_info.link];
+ if (link) return link.data;
+ }
+ }
+ return this.properties[name];
+ };
+
+ /**
+ * tells you the last output data that went in that slot
+ * @method getOutputData
+ * @param {number} slot
+ * @return {Object} object or null
+ */
+ LGraphNode.prototype.getOutputData = function(slot) {
+ if (!this.outputs) return null;
+ if (slot >= this.outputs.length) return null;
+
+ var info = this.outputs[slot];
+ return info._data;
+ };
+
+ /**
+ * tells you info about an output connection (which node, type, etc)
+ * @method getOutputInfo
+ * @param {number} slot
+ * @return {Object} object or null { name: string, type: string, links: [ ids of links in number ] }
+ */
+ LGraphNode.prototype.getOutputInfo = function(slot) {
+ if (!this.outputs) return null;
+ if (slot < this.outputs.length) return this.outputs[slot];
+ return null;
+ };
+
+ /**
+ * tells you if there is a connection in one output slot
+ * @method isOutputConnected
+ * @param {number} slot
+ * @return {boolean}
+ */
+ LGraphNode.prototype.isOutputConnected = function(slot) {
+ if (!this.outputs) return false;
+ return (
+ slot < this.outputs.length &&
+ this.outputs[slot].links &&
+ this.outputs[slot].links.length
+ );
+ };
+
+ /**
+ * tells you if there is any connection in the output slots
+ * @method isAnyOutputConnected
+ * @return {boolean}
+ */
+ LGraphNode.prototype.isAnyOutputConnected = function() {
+ if (!this.outputs) return false;
+ for (var i = 0; i < this.outputs.length; ++i)
+ if (this.outputs[i].links && this.outputs[i].links.length)
+ return true;
+ return false;
+ };
+
+ /**
+ * retrieves all the nodes connected to this output slot
+ * @method getOutputNodes
+ * @param {number} slot
+ * @return {array}
+ */
+ LGraphNode.prototype.getOutputNodes = function(slot) {
+ if (!this.outputs || this.outputs.length == 0) return null;
+
+ if (slot >= this.outputs.length) return null;
+
+ var output = this.outputs[slot];
+ if (!output.links || output.links.length == 0) return null;
+
+ var r = [];
+ for (var i = 0; i < output.links.length; i++) {
+ var link_id = output.links[i];
+ var link = this.graph.links[link_id];
+ if (link) {
+ var target_node = this.graph.getNodeById(link.target_id);
+ if (target_node) r.push(target_node);
+ }
+ }
+ return r;
+ };
+
+ /**
+ * 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) {
+ if (!this.outputs || !this.outputs.length) return;
+
+ if (this.graph) this.graph._last_trigger_time = LiteGraph.getTime();
+
+ for (var i = 0; i < this.outputs.length; ++i) {
+ var output = this.outputs[i];
+ if (
+ !output ||
+ output.type !== LiteGraph.EVENT ||
+ (action && output.name != action)
+ )
+ continue;
+ this.triggerSlot(i, param);
+ }
+ };
+
+ /**
+ * Triggers an slot event in this node
+ * @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) {
+ if (!this.outputs) return;
+
+ var output = this.outputs[slot];
+ if (!output) return;
+
+ var links = output.links;
+ if (!links || !links.length) return;
+
+ if (this.graph) this.graph._last_trigger_time = LiteGraph.getTime();
+
+ //for every link attached here
+ for (var k = 0; k < links.length; ++k) {
+ var id = links[k];
+ if (link_id != null && link_id != id)
+ //to skip links
+ continue;
+ var link_info = this.graph.links[links[k]];
+ if (!link_info)
+ //not connected
+ continue;
+ link_info._last_time = LiteGraph.getTime();
+ var node = this.graph.getNodeById(link_info.target_id);
+ if (!node)
+ //node not found?
+ continue;
+
+ //used to mark events in graph
+ var target_connection = node.inputs[link_info.target_slot];
+
+ if (node.onAction) node.onAction(target_connection.name, param);
+ else if (node.mode === LiteGraph.ON_TRIGGER) {
+ if (node.onExecute) node.onExecute(param);
+ }
+ }
+ };
+
+ /**
+ * clears the trigger slot animation
+ * @method clearTriggeredSlot
+ * @param {Number} slot the index of the output slot
+ * @param {Number} link_id [optional] in case you want to trigger and specific output link in a slot
+ */
+ LGraphNode.prototype.clearTriggeredSlot = function(slot, link_id) {
+ if (!this.outputs) return;
+
+ var output = this.outputs[slot];
+ if (!output) return;
+
+ var links = output.links;
+ if (!links || !links.length) return;
+
+ //for every link attached here
+ for (var k = 0; k < links.length; ++k) {
+ var id = links[k];
+ if (link_id != null && link_id != id)
+ //to skip links
+ continue;
+ var link_info = this.graph.links[links[k]];
+ if (!link_info)
+ //not connected
+ continue;
+ link_info._last_time = 0;
+ }
+ };
+
+ /**
+ * add a new property to this node
+ * @method addProperty
+ * @param {string} name
+ * @param {*} default_value
+ * @param {string} type string defining the output type ("vec3","number",...)
+ * @param {Object} extra_info this can be used to have special properties of the property (like values, etc)
+ */
+ LGraphNode.prototype.addProperty = function(
+ name,
+ default_value,
+ type,
+ extra_info
+ ) {
+ var o = { name: name, type: type, default_value: default_value };
+ if (extra_info) for (var i in extra_info) o[i] = extra_info[i];
+ if (!this.properties_info) this.properties_info = [];
+ this.properties_info.push(o);
+ if (!this.properties) this.properties = {};
+ this.properties[name] = default_value;
+ return o;
+ };
+
+ //connections
+
+ /**
+ * add a new output slot to use in this node
+ * @method addOutput
+ * @param {string} name
+ * @param {string} type string defining the output type ("vec3","number",...)
+ * @param {Object} extra_info this can be used to have special properties of an output (label, special color, position, etc)
+ */
+ LGraphNode.prototype.addOutput = function(name, type, extra_info) {
+ var o = { name: name, type: type, links: null };
+ if (extra_info) for (var i in extra_info) o[i] = extra_info[i];
+
+ if (!this.outputs) this.outputs = [];
+ this.outputs.push(o);
+ if (this.onOutputAdded) this.onOutputAdded(o);
+ this.size = this.computeSize();
+ this.setDirtyCanvas(true, true);
+ return o;
+ };
+
+ /**
+ * add a new output slot to use in this node
+ * @method addOutputs
+ * @param {Array} array of triplets like [[name,type,extra_info],[...]]
+ */
+ LGraphNode.prototype.addOutputs = function(array) {
+ for (var i = 0; i < array.length; ++i) {
+ var info = array[i];
+ var o = { name: info[0], type: info[1], link: null };
+ if (array[2]) for (var j in info[2]) o[j] = info[2][j];
+
+ if (!this.outputs) this.outputs = [];
+ this.outputs.push(o);
+ if (this.onOutputAdded) this.onOutputAdded(o);
+ }
+
+ this.size = this.computeSize();
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * remove an existing output slot
+ * @method removeOutput
+ * @param {number} slot
+ */
+ LGraphNode.prototype.removeOutput = function(slot) {
+ this.disconnectOutput(slot);
+ this.outputs.splice(slot, 1);
+ for (var i = slot; i < this.outputs.length; ++i) {
+ if (!this.outputs[i] || !this.outputs[i].links) continue;
+ var links = this.outputs[i].links;
+ for (var j = 0; j < links.length; ++j) {
+ var link = this.graph.links[links[j]];
+ if (!link) continue;
+ link.origin_slot -= 1;
+ }
+ }
+
+ this.size = this.computeSize();
+ if (this.onOutputRemoved) this.onOutputRemoved(slot);
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * add a new input slot to use in this node
+ * @method addInput
+ * @param {string} name
+ * @param {string} type string defining the input type ("vec3","number",...), it its a generic one use 0
+ * @param {Object} extra_info this can be used to have special properties of an input (label, color, position, etc)
+ */
+ LGraphNode.prototype.addInput = function(name, type, extra_info) {
+ type = type || 0;
+ var o = { name: name, type: type, link: null };
+ if (extra_info) for (var i in extra_info) o[i] = extra_info[i];
+
+ if (!this.inputs) this.inputs = [];
+ this.inputs.push(o);
+ this.size = this.computeSize();
+ if (this.onInputAdded) this.onInputAdded(o);
+ this.setDirtyCanvas(true, true);
+ return o;
+ };
+
+ /**
+ * add several new input slots in this node
+ * @method addInputs
+ * @param {Array} array of triplets like [[name,type,extra_info],[...]]
+ */
+ LGraphNode.prototype.addInputs = function(array) {
+ for (var i = 0; i < array.length; ++i) {
+ var info = array[i];
+ var o = { name: info[0], type: info[1], link: null };
+ if (array[2]) for (var j in info[2]) o[j] = info[2][j];
+
+ if (!this.inputs) this.inputs = [];
+ this.inputs.push(o);
+ if (this.onInputAdded) this.onInputAdded(o);
+ }
+
+ this.size = this.computeSize();
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * remove an existing input slot
+ * @method removeInput
+ * @param {number} slot
+ */
+ LGraphNode.prototype.removeInput = function(slot) {
+ this.disconnectInput(slot);
+ this.inputs.splice(slot, 1);
+ for (var i = slot; i < this.inputs.length; ++i) {
+ if (!this.inputs[i]) continue;
+ var link = this.graph.links[this.inputs[i].link];
+ if (!link) continue;
+ link.target_slot -= 1;
+ }
+ this.size = this.computeSize();
+ if (this.onInputRemoved) this.onInputRemoved(slot);
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * add an special connection to this node (used for special kinds of graphs)
+ * @method addConnection
+ * @param {string} name
+ * @param {string} type string defining the input type ("vec3","number",...)
+ * @param {[x,y]} pos position of the connection inside the node
+ * @param {string} direction if is input or output
+ */
+ LGraphNode.prototype.addConnection = function(name, type, pos, direction) {
+ var o = {
+ name: name,
+ type: type,
+ pos: pos,
+ direction: direction,
+ links: null
+ };
+ this.connections.push(o);
+ return o;
+ };
+
+ /**
+ * computes the size of a node according to its inputs and output slots
+ * @method computeSize
+ * @param {number} minHeight
+ * @return {number} the total size
+ */
+ LGraphNode.prototype.computeSize = function(minHeight, out) {
+ if (this.constructor.size) return this.constructor.size.concat();
+
+ var rows = Math.max(
+ this.inputs ? this.inputs.length : 1,
+ this.outputs ? this.outputs.length : 1
+ );
+ var size = out || new Float32Array([0, 0]);
+ rows = Math.max(rows, 1);
+ var font_size = LiteGraph.NODE_TEXT_SIZE; //although it should be graphcanvas.inner_text_font size
+ size[1] =
+ (this.constructor.slot_start_y || 0) +
+ rows * LiteGraph.NODE_SLOT_HEIGHT;
+ var widgets_height = 0;
+ if (this.widgets && this.widgets.length)
+ widgets_height =
+ this.widgets.length * (LiteGraph.NODE_WIDGET_HEIGHT + 4) + 8;
+ if (this.widgets_up) size[1] = Math.max(size[1], widgets_height);
+ else size[1] += widgets_height;
+
+ var font_size = font_size;
+ var title_width = compute_text_size(this.title);
+ var input_width = 0;
+ var output_width = 0;
+
+ if (this.inputs)
+ for (var i = 0, l = this.inputs.length; i < l; ++i) {
+ var input = this.inputs[i];
+ var text = input.label || input.name || "";
+ var text_width = compute_text_size(text);
+ if (input_width < text_width) input_width = text_width;
+ }
+
+ if (this.outputs)
+ for (var i = 0, l = this.outputs.length; i < l; ++i) {
+ var output = this.outputs[i];
+ var text = output.label || output.name || "";
+ var text_width = compute_text_size(text);
+ if (output_width < text_width) output_width = text_width;
+ }
+
+ size[0] = Math.max(input_width + output_width + 10, title_width);
+ size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH);
+ if (this.widgets && this.widgets.length)
+ size[0] = Math.max(size[0], LiteGraph.NODE_WIDTH * 1.5);
+
+ if (this.onResize) this.onResize(size);
+
+ function compute_text_size(text) {
+ if (!text) return 0;
+ return font_size * text.length * 0.6;
+ }
+
+ if (
+ this.constructor.min_height &&
+ size[1] < this.constructor.min_height
+ )
+ size[1] = this.constructor.min_height;
+
+ size[1] += 6; //margin
+
+ return size;
+ };
+
+ /**
+ * Allows to pass
+ *
+ * @method addWidget
+ * @return {Object} the created widget
+ */
+ LGraphNode.prototype.addWidget = function(
+ type,
+ name,
+ value,
+ callback,
+ options
+ ) {
+ if (!this.widgets) this.widgets = [];
+ var w = {
+ type: type.toLowerCase(),
+ name: name,
+ value: value,
+ callback: callback,
+ options: options || {}
+ };
+
+ if (w.options.y !== undefined) w.y = w.options.y;
+
+ if (!callback)
+ console.warn("LiteGraph addWidget(...) without a callback");
+ if (type == "combo" && !w.options.values)
+ throw "LiteGraph addWidget('combo',...) requires to pass values in options: { values:['red','blue'] }";
+ this.widgets.push(w);
+ return w;
+ };
+
+ LGraphNode.prototype.addCustomWidget = function(custom_widget) {
+ if (!this.widgets) this.widgets = [];
+ this.widgets.push(custom_widget);
+ return custom_widget;
+ };
+
+ /**
+ * returns the bounding of the object, used for rendering purposes
+ * bounding is: [topleft_cornerx, topleft_cornery, width, height]
+ * @method getBounding
+ * @return {Float32Array[4]} the total size
+ */
+ LGraphNode.prototype.getBounding = function(out) {
+ out = out || new Float32Array(4);
+ out[0] = this.pos[0] - 4;
+ out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
+ out[2] = this.size[0] + 4;
+ out[3] = this.size[1] + LiteGraph.NODE_TITLE_HEIGHT;
+
+ if (this.onBounding) this.onBounding(out);
+ return out;
+ };
+
+ /**
+ * checks if a point is inside the shape of a node
+ * @method isPointInside
+ * @param {number} x
+ * @param {number} y
+ * @return {boolean}
+ */
+ LGraphNode.prototype.isPointInside = function(x, y, margin, skip_title) {
+ margin = margin || 0;
+
+ var margin_top = this.graph && this.graph.isLive() ? 0 : 20;
+ if (skip_title) margin_top = 0;
+ if (this.flags && this.flags.collapsed) {
+ //if ( distance([x,y], [this.pos[0] + this.size[0]*0.5, this.pos[1] + this.size[1]*0.5]) < LiteGraph.NODE_COLLAPSED_RADIUS)
+ if (
+ isInsideRectangle(
+ x,
+ y,
+ this.pos[0] - margin,
+ this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT - margin,
+ (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH) +
+ 2 * margin,
+ LiteGraph.NODE_TITLE_HEIGHT + 2 * margin
+ )
+ )
+ return true;
+ } else if (
+ this.pos[0] - 4 - margin < x &&
+ this.pos[0] + this.size[0] + 4 + margin > x &&
+ this.pos[1] - margin_top - margin < y &&
+ this.pos[1] + this.size[1] + margin > y
+ )
+ return true;
+ return false;
+ };
+
+ /**
+ * checks if a point is inside a node slot, and returns info about which slot
+ * @method getSlotInPosition
+ * @param {number} x
+ * @param {number} y
+ * @return {Object} if found the object contains { input|output: slot object, slot: number, link_pos: [x,y] }
+ */
+ LGraphNode.prototype.getSlotInPosition = function(x, y) {
+ //search for inputs
+ var link_pos = new Float32Array(2);
+ if (this.inputs)
+ for (var i = 0, l = this.inputs.length; i < l; ++i) {
+ var input = this.inputs[i];
+ this.getConnectionPos(true, i, link_pos);
+ if (
+ isInsideRectangle(
+ x,
+ y,
+ link_pos[0] - 10,
+ link_pos[1] - 5,
+ 20,
+ 10
+ )
+ )
+ return { input: input, slot: i, link_pos: link_pos };
+ }
+
+ if (this.outputs)
+ for (var i = 0, l = this.outputs.length; i < l; ++i) {
+ var output = this.outputs[i];
+ this.getConnectionPos(false, i, link_pos);
+ if (
+ isInsideRectangle(
+ x,
+ y,
+ link_pos[0] - 10,
+ link_pos[1] - 5,
+ 20,
+ 10
+ )
+ )
+ return { output: output, slot: i, link_pos: link_pos };
+ }
+
+ return null;
+ };
+
+ /**
+ * 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)
+ */
+ LGraphNode.prototype.findInputSlot = function(name) {
+ 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 -1;
+ };
+
+ /**
+ * 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)
+ */
+ LGraphNode.prototype.findOutputSlot = function(name) {
+ 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 -1;
+ };
+
+ /**
+ * connect this node output to the input of another node
+ * @method connect
+ * @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 {number_or_string} target_slot the input slot of the target node (could be the number of the slot or the string with the name of the slot, or -1 to connect a trigger)
+ * @return {Object} the link_info is created, otherwise null
+ */
+ LGraphNode.prototype.connect = function(slot, target_node, target_slot) {
+ target_slot = target_slot || 0;
+
+ if (!this.graph) {
+ //could be connected before adding it to a graph
+ console.log(
+ "Connect: Error, node doesn't belong to any graph. Nodes must be added first to a graph before connecting them."
+ ); //due to link ids being associated with graphs
+ return null;
+ }
+
+ //seek for the output slot
+ if (slot.constructor === String) {
+ slot = this.findOutputSlot(slot);
+ if (slot == -1) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, no slot of name " + slot);
+ return null;
+ }
+ } else if (!this.outputs || slot >= this.outputs.length) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, slot number not found");
+ return null;
+ }
+
+ if (target_node && target_node.constructor === Number)
+ target_node = this.graph.getNodeById(target_node);
+ if (!target_node) throw "target node is null";
+
+ //avoid loopback
+ if (target_node == this) return null;
+
+ //you can specify the slot by name
+ if (target_slot.constructor === String) {
+ target_slot = target_node.findInputSlot(target_slot);
+ if (target_slot == -1) {
+ if (LiteGraph.debug)
+ console.log(
+ "Connect: Error, no slot of name " + target_slot
+ );
+ 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;
- }
- else if( !target_node.inputs || target_slot >= target_node.inputs.length )
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, slot number not found");
- return null;
- }
+ return null;
+ } else if (
+ !target_node.inputs ||
+ target_slot >= target_node.inputs.length
+ ) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, slot number not found");
+ return null;
+ }
- //if there is something already plugged there, disconnect
- if(target_node.inputs[ target_slot ].link != null )
- target_node.disconnectInput( target_slot );
+ //if there is something already plugged there, disconnect
+ if (target_node.inputs[target_slot].link != null)
+ target_node.disconnectInput(target_slot);
- //why here??
- //this.setDirtyCanvas(false,true);
- //this.graph.connectionChange( this );
+ //why here??
+ //this.setDirtyCanvas(false,true);
+ //this.graph.connectionChange( this );
- var output = this.outputs[slot];
+ var output = this.outputs[slot];
- //allows nodes to block connection
- if(target_node.onConnectInput)
- if( target_node.onConnectInput( target_slot, output.type, output ) === false)
- return null;
+ //allows nodes to block connection
+ if (target_node.onConnectInput)
+ if (
+ target_node.onConnectInput(target_slot, output.type, output) ===
+ false
+ )
+ return null;
- var input = target_node.inputs[target_slot];
- var link_info = null;
+ var input = target_node.inputs[target_slot];
+ var link_info = null;
- if( LiteGraph.isValidConnection( output.type, input.type ) )
- {
- link_info = new LLink( this.graph.last_link_id++, input.type, this.id, slot, target_node.id, target_slot );
+ if (LiteGraph.isValidConnection(output.type, input.type)) {
+ link_info = new LLink(
+ this.graph.last_link_id++,
+ input.type,
+ this.id,
+ slot,
+ target_node.id,
+ target_slot
+ );
- //add to graph links list
- this.graph.links[ link_info.id ] = link_info;
+ //add to graph links list
+ this.graph.links[link_info.id] = link_info;
- //connect in output
- if( output.links == null )
- output.links = [];
- output.links.push( link_info.id );
- //connect in input
- target_node.inputs[target_slot].link = link_info.id;
- if(this.graph)
- this.graph._version++;
- if(this.onConnectionsChange)
- this.onConnectionsChange( LiteGraph.OUTPUT, slot, true, link_info, output ); //link_info has been created now, so its updated
- if(target_node.onConnectionsChange)
- target_node.onConnectionsChange( LiteGraph.INPUT, target_slot, true, link_info, input );
- if( this.graph && this.graph.onNodeConnectionChange )
- {
- this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, target_slot, this, slot );
- this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot, target_node, target_slot );
- }
- }
+ //connect in output
+ if (output.links == null) output.links = [];
+ output.links.push(link_info.id);
+ //connect in input
+ target_node.inputs[target_slot].link = link_info.id;
+ if (this.graph) this.graph._version++;
+ if (this.onConnectionsChange)
+ this.onConnectionsChange(
+ LiteGraph.OUTPUT,
+ slot,
+ true,
+ link_info,
+ output
+ ); //link_info has been created now, so its updated
+ if (target_node.onConnectionsChange)
+ target_node.onConnectionsChange(
+ LiteGraph.INPUT,
+ target_slot,
+ true,
+ link_info,
+ input
+ );
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(
+ LiteGraph.INPUT,
+ target_node,
+ target_slot,
+ this,
+ slot
+ );
+ this.graph.onNodeConnectionChange(
+ LiteGraph.OUTPUT,
+ this,
+ slot,
+ target_node,
+ target_slot
+ );
+ }
+ }
- this.setDirtyCanvas(false,true);
- this.graph.connectionChange( this, link_info );
+ this.setDirtyCanvas(false, true);
+ this.graph.connectionChange(this, link_info);
- return link_info;
-}
+ return link_info;
+ };
-/**
-* disconnect one output to an specific node
-* @method disconnectOutput
-* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
-* @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]
-* @return {boolean} if it was disconnected successfully
-*/
-LGraphNode.prototype.disconnectOutput = function( slot, target_node )
-{
- if( slot.constructor === String )
- {
- slot = this.findOutputSlot(slot);
- if(slot == -1)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, no slot of name " + slot);
- return false;
- }
- }
- else if(!this.outputs || slot >= this.outputs.length)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, slot number not found");
- return false;
- }
+ /**
+ * disconnect one output to an specific node
+ * @method disconnectOutput
+ * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
+ * @param {LGraphNode} target_node the target node to which this slot is connected [Optional, if not target_node is specified all nodes will be disconnected]
+ * @return {boolean} if it was disconnected successfully
+ */
+ LGraphNode.prototype.disconnectOutput = function(slot, target_node) {
+ if (slot.constructor === String) {
+ slot = this.findOutputSlot(slot);
+ if (slot == -1) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, no slot of name " + slot);
+ return false;
+ }
+ } else if (!this.outputs || slot >= this.outputs.length) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, slot number not found");
+ return false;
+ }
- //get output slot
- var output = this.outputs[slot];
- if(!output || !output.links || output.links.length == 0)
- return false;
+ //get output slot
+ var output = this.outputs[slot];
+ if (!output || !output.links || output.links.length == 0) return false;
- //one of the output links in this slot
- if(target_node)
- {
- if(target_node.constructor === Number)
- target_node = this.graph.getNodeById( target_node );
- if(!target_node)
- throw("Target Node not found");
+ //one of the output links in this slot
+ if (target_node) {
+ if (target_node.constructor === Number)
+ target_node = this.graph.getNodeById(target_node);
+ if (!target_node) throw "Target Node not found";
- for(var i = 0, l = output.links.length; i < l; i++)
- {
- var link_id = output.links[i];
- var link_info = this.graph.links[ link_id ];
+ for (var i = 0, l = output.links.length; i < l; i++) {
+ var link_id = output.links[i];
+ var link_info = this.graph.links[link_id];
- //is the link we are searching for...
- if( link_info.target_id == target_node.id )
- {
- output.links.splice(i,1); //remove here
- var input = target_node.inputs[ link_info.target_slot ];
- input.link = null; //remove there
- delete this.graph.links[ link_id ]; //remove the link from the links pool
- if(this.graph)
- this.graph._version++;
- if(target_node.onConnectionsChange)
- target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasn't been modified so its ok
- if(this.onConnectionsChange)
- this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
- if( this.graph && this.graph.onNodeConnectionChange )
- this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );
- if( this.graph && this.graph.onNodeConnectionChange )
- {
- this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );
- this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );
- }
- break;
- }
- }
- }
- else //all the links in this output slot
- {
- for(var i = 0, l = output.links.length; i < l; i++)
- {
- var link_id = output.links[i];
- var link_info = this.graph.links[ link_id ];
- if(!link_info) //bug: it happens sometimes
- continue;
+ //is the link we are searching for...
+ if (link_info.target_id == target_node.id) {
+ output.links.splice(i, 1); //remove here
+ var input = target_node.inputs[link_info.target_slot];
+ input.link = null; //remove there
+ delete this.graph.links[link_id]; //remove the link from the links pool
+ if (this.graph) this.graph._version++;
+ if (target_node.onConnectionsChange)
+ target_node.onConnectionsChange(
+ LiteGraph.INPUT,
+ link_info.target_slot,
+ false,
+ link_info,
+ input
+ ); //link_info hasn't been modified so its ok
+ if (this.onConnectionsChange)
+ this.onConnectionsChange(
+ LiteGraph.OUTPUT,
+ slot,
+ false,
+ link_info,
+ output
+ );
+ if (this.graph && this.graph.onNodeConnectionChange)
+ this.graph.onNodeConnectionChange(
+ LiteGraph.OUTPUT,
+ this,
+ slot
+ );
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(
+ LiteGraph.OUTPUT,
+ this,
+ slot
+ );
+ this.graph.onNodeConnectionChange(
+ LiteGraph.INPUT,
+ target_node,
+ link_info.target_slot
+ );
+ }
+ break;
+ }
+ }
+ } //all the links in this output slot
+ else {
+ for (var i = 0, l = output.links.length; i < l; i++) {
+ var link_id = output.links[i];
+ var link_info = this.graph.links[link_id];
+ if (!link_info)
+ //bug: it happens sometimes
+ continue;
- var target_node = this.graph.getNodeById( link_info.target_id );
- var input = null;
- if(this.graph)
- this.graph._version++;
- if(target_node)
- {
- input = target_node.inputs[ link_info.target_slot ];
- input.link = null; //remove other side link
- if(target_node.onConnectionsChange)
- target_node.onConnectionsChange( LiteGraph.INPUT, link_info.target_slot, false, link_info, input ); //link_info hasn't been modified so its ok
- if( this.graph && this.graph.onNodeConnectionChange )
- this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );
- }
- delete this.graph.links[ link_id ]; //remove the link from the links pool
- if(this.onConnectionsChange)
- this.onConnectionsChange( LiteGraph.OUTPUT, slot, false, link_info, output );
- if( this.graph && this.graph.onNodeConnectionChange )
- {
- this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, this, slot );
- this.graph.onNodeConnectionChange( LiteGraph.INPUT, target_node, link_info.target_slot );
- }
- }
- output.links = null;
- }
+ var target_node = this.graph.getNodeById(link_info.target_id);
+ var input = null;
+ if (this.graph) this.graph._version++;
+ if (target_node) {
+ input = target_node.inputs[link_info.target_slot];
+ input.link = null; //remove other side link
+ if (target_node.onConnectionsChange)
+ target_node.onConnectionsChange(
+ LiteGraph.INPUT,
+ link_info.target_slot,
+ false,
+ link_info,
+ input
+ ); //link_info hasn't been modified so its ok
+ if (this.graph && this.graph.onNodeConnectionChange)
+ this.graph.onNodeConnectionChange(
+ LiteGraph.INPUT,
+ target_node,
+ link_info.target_slot
+ );
+ }
+ delete this.graph.links[link_id]; //remove the link from the links pool
+ if (this.onConnectionsChange)
+ this.onConnectionsChange(
+ LiteGraph.OUTPUT,
+ slot,
+ false,
+ link_info,
+ output
+ );
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(
+ LiteGraph.OUTPUT,
+ this,
+ slot
+ );
+ this.graph.onNodeConnectionChange(
+ LiteGraph.INPUT,
+ target_node,
+ link_info.target_slot
+ );
+ }
+ }
+ output.links = null;
+ }
+ this.setDirtyCanvas(false, true);
+ this.graph.connectionChange(this);
+ return true;
+ };
- this.setDirtyCanvas(false,true);
- this.graph.connectionChange( this );
- return true;
-}
+ /**
+ * disconnect one input
+ * @method disconnectInput
+ * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
+ * @return {boolean} if it was disconnected successfully
+ */
+ LGraphNode.prototype.disconnectInput = function(slot) {
+ //seek for the output slot
+ if (slot.constructor === String) {
+ slot = this.findInputSlot(slot);
+ if (slot == -1) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, no slot of name " + slot);
+ return false;
+ }
+ } else if (!this.inputs || slot >= this.inputs.length) {
+ if (LiteGraph.debug)
+ console.log("Connect: Error, slot number not found");
+ return false;
+ }
-/**
-* disconnect one input
-* @method disconnectInput
-* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
-* @return {boolean} if it was disconnected successfully
-*/
-LGraphNode.prototype.disconnectInput = function( slot )
-{
- //seek for the output slot
- if( slot.constructor === String )
- {
- slot = this.findInputSlot(slot);
- if(slot == -1)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, no slot of name " + slot);
- return false;
- }
- }
- else if(!this.inputs || slot >= this.inputs.length)
- {
- if(LiteGraph.debug)
- console.log("Connect: Error, slot number not found");
- return false;
- }
+ var input = this.inputs[slot];
+ if (!input) return false;
- var input = this.inputs[slot];
- if(!input)
- return false;
+ var link_id = this.inputs[slot].link;
+ this.inputs[slot].link = null;
- var link_id = this.inputs[slot].link;
- this.inputs[slot].link = null;
+ //remove other side
+ var link_info = this.graph.links[link_id];
+ if (link_info) {
+ var target_node = this.graph.getNodeById(link_info.origin_id);
+ if (!target_node) return false;
- //remove other side
- var link_info = this.graph.links[ link_id ];
- if( link_info )
- {
- var target_node = this.graph.getNodeById( link_info.origin_id );
- if(!target_node)
- return false;
+ var output = target_node.outputs[link_info.origin_slot];
+ if (!output || !output.links || output.links.length == 0)
+ return false;
- var output = target_node.outputs[ link_info.origin_slot ];
- if(!output || !output.links || output.links.length == 0)
- return false;
+ //search in the inputs list for this link
+ for (var i = 0, l = output.links.length; i < l; i++) {
+ if (output.links[i] == link_id) {
+ output.links.splice(i, 1);
+ break;
+ }
+ }
- //search in the inputs list for this link
- for(var i = 0, l = output.links.length; i < l; i++)
- {
- if( output.links[i] == link_id )
- {
- output.links.splice(i,1);
- break;
- }
- }
+ delete this.graph.links[link_id]; //remove from the pool
+ if (this.graph) this.graph._version++;
+ if (this.onConnectionsChange)
+ this.onConnectionsChange(
+ LiteGraph.INPUT,
+ slot,
+ false,
+ link_info,
+ input
+ );
+ if (target_node.onConnectionsChange)
+ target_node.onConnectionsChange(
+ LiteGraph.OUTPUT,
+ i,
+ false,
+ link_info,
+ output
+ );
+ if (this.graph && this.graph.onNodeConnectionChange) {
+ this.graph.onNodeConnectionChange(
+ LiteGraph.OUTPUT,
+ target_node,
+ i
+ );
+ this.graph.onNodeConnectionChange(LiteGraph.INPUT, this, slot);
+ }
+ }
- delete this.graph.links[ link_id ]; //remove from the pool
- if(this.graph)
- this.graph._version++;
- if( this.onConnectionsChange )
- this.onConnectionsChange( LiteGraph.INPUT, slot, false, link_info, input );
- if( target_node.onConnectionsChange )
- target_node.onConnectionsChange( LiteGraph.OUTPUT, i, false, link_info, output );
- if( this.graph && this.graph.onNodeConnectionChange )
- {
- this.graph.onNodeConnectionChange( LiteGraph.OUTPUT, target_node, i );
- this.graph.onNodeConnectionChange( LiteGraph.INPUT, this, slot );
- }
- }
+ this.setDirtyCanvas(false, true);
+ this.graph.connectionChange(this);
+ return true;
+ };
- this.setDirtyCanvas(false,true);
- this.graph.connectionChange( this );
- return true;
-}
+ /**
+ * returns the center of a connection point in canvas coords
+ * @method getConnectionPos
+ * @param {boolean} is_input true if if a input slot, false if it is an output
+ * @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
+ * @param {vec2} out [optional] a place to store the output, to free garbage
+ * @return {[x,y]} the position
+ **/
+ LGraphNode.prototype.getConnectionPos = function(
+ is_input,
+ slot_number,
+ out
+ ) {
+ out = out || new Float32Array(2);
+ var num_slots = 0;
+ if (is_input && this.inputs) num_slots = this.inputs.length;
+ if (!is_input && this.outputs) num_slots = this.outputs.length;
-/**
-* returns the center of a connection point in canvas coords
-* @method getConnectionPos
-* @param {boolean} is_input true if if a input slot, false if it is an output
-* @param {number_or_string} slot (could be the number of the slot or the string with the name of the slot)
-* @param {vec2} out [optional] a place to store the output, to free garbage
-* @return {[x,y]} the position
-**/
-LGraphNode.prototype.getConnectionPos = function( is_input, slot_number, out )
-{
- out = out || new Float32Array(2);
- var num_slots = 0;
- if( is_input && this.inputs )
- num_slots = this.inputs.length;
- if( !is_input && this.outputs )
- num_slots = this.outputs.length;
+ var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
- var offset = LiteGraph.NODE_SLOT_HEIGHT * 0.5;
+ if (this.flags.collapsed) {
+ var w = this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH;
+ if (this.horizontal) {
+ out[0] = this.pos[0] + w * 0.5;
+ if (is_input)
+ out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
+ else out[1] = this.pos[1];
+ } else {
+ if (is_input) out[0] = this.pos[0];
+ else out[0] = this.pos[0] + w;
+ out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
+ }
+ return out;
+ }
- if(this.flags.collapsed)
- {
- var w = (this._collapsed_width || LiteGraph.NODE_COLLAPSED_WIDTH);
- if( this.horizontal )
- {
- out[0] = this.pos[0] + w * 0.5;
- if(is_input)
- out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
- else
- out[1] = this.pos[1];
- }
- else
- {
- if(is_input)
- out[0] = this.pos[0];
- else
- out[0] = this.pos[0] + w;
- out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT * 0.5;
- }
- return out;
- }
+ //weird feature that never got finished
+ if (is_input && slot_number == -1) {
+ out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
+ out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
+ return out;
+ }
- //weird feature that never got finished
- if(is_input && slot_number == -1)
- {
- out[0] = this.pos[0] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
- out[1] = this.pos[1] + LiteGraph.NODE_TITLE_HEIGHT * 0.5;
- return out;
- }
+ //hard-coded pos
+ if (
+ is_input &&
+ num_slots > slot_number &&
+ this.inputs[slot_number].pos
+ ) {
+ out[0] = this.pos[0] + this.inputs[slot_number].pos[0];
+ out[1] = this.pos[1] + this.inputs[slot_number].pos[1];
+ return out;
+ } else if (
+ !is_input &&
+ num_slots > slot_number &&
+ this.outputs[slot_number].pos
+ ) {
+ out[0] = this.pos[0] + this.outputs[slot_number].pos[0];
+ out[1] = this.pos[1] + this.outputs[slot_number].pos[1];
+ return out;
+ }
- //hard-coded pos
- if(is_input && num_slots > slot_number && this.inputs[ slot_number ].pos)
- {
- out[0] = this.pos[0] + this.inputs[slot_number].pos[0];
- out[1] = this.pos[1] + this.inputs[slot_number].pos[1];
- return out;
- }
- else if(!is_input && num_slots > slot_number && this.outputs[ slot_number ].pos)
- {
- out[0] = this.pos[0] + this.outputs[slot_number].pos[0];
- out[1] = this.pos[1] + this.outputs[slot_number].pos[1];
- return out;
- }
+ //horizontal distributed slots
+ if (this.horizontal) {
+ out[0] =
+ this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);
+ if (is_input) out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
+ else out[1] = this.pos[1] + this.size[1];
+ return out;
+ }
- //horizontal distributed slots
- if(this.horizontal)
- {
- out[0] = this.pos[0] + (slot_number + 0.5) * (this.size[0] / num_slots);
- if(is_input)
- out[1] = this.pos[1] - LiteGraph.NODE_TITLE_HEIGHT;
- else
- out[1] = this.pos[1] + this.size[1];
- return out;
- }
-
- //default vertical slots
- if(is_input)
- out[0] = this.pos[0] + offset;
- else
- out[0] = this.pos[0] + this.size[0] + 1 - offset;
- out[1] = this.pos[1] + (slot_number + 0.7 ) * LiteGraph.NODE_SLOT_HEIGHT + (this.constructor.slot_start_y || 0);
- return out;
-}
+ //default vertical slots
+ if (is_input) out[0] = this.pos[0] + offset;
+ else out[0] = this.pos[0] + this.size[0] + 1 - offset;
+ out[1] =
+ this.pos[1] +
+ (slot_number + 0.7) * LiteGraph.NODE_SLOT_HEIGHT +
+ (this.constructor.slot_start_y || 0);
+ return out;
+ };
-/* Force align to grid */
-LGraphNode.prototype.alignToGrid = function()
-{
- this.pos[0] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
- this.pos[1] = LiteGraph.CANVAS_GRID_SIZE * Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
-}
+ /* Force align to grid */
+ LGraphNode.prototype.alignToGrid = function() {
+ this.pos[0] =
+ LiteGraph.CANVAS_GRID_SIZE *
+ Math.round(this.pos[0] / LiteGraph.CANVAS_GRID_SIZE);
+ this.pos[1] =
+ LiteGraph.CANVAS_GRID_SIZE *
+ Math.round(this.pos[1] / LiteGraph.CANVAS_GRID_SIZE);
+ };
+ /* Console output */
+ LGraphNode.prototype.trace = function(msg) {
+ if (!this.console) this.console = [];
+ this.console.push(msg);
+ if (this.console.length > LGraphNode.MAX_CONSOLE) this.console.shift();
-/* Console output */
-LGraphNode.prototype.trace = function(msg)
-{
- if(!this.console)
- this.console = [];
- this.console.push(msg);
- if(this.console.length > LGraphNode.MAX_CONSOLE)
- this.console.shift();
+ this.graph.onNodeTrace(this, msg);
+ };
- this.graph.onNodeTrace(this,msg);
-}
+ /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
+ LGraphNode.prototype.setDirtyCanvas = function(
+ dirty_foreground,
+ dirty_background
+ ) {
+ if (!this.graph) return;
+ this.graph.sendActionToCanvas("setDirty", [
+ dirty_foreground,
+ dirty_background
+ ]);
+ };
-/* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */
-LGraphNode.prototype.setDirtyCanvas = function(dirty_foreground, dirty_background)
-{
- if(!this.graph)
- return;
- this.graph.sendActionToCanvas("setDirty",[dirty_foreground, dirty_background]);
-}
+ LGraphNode.prototype.loadImage = function(url) {
+ var img = new Image();
+ img.src = LiteGraph.node_images_path + url;
+ img.ready = false;
-LGraphNode.prototype.loadImage = function(url)
-{
- var img = new Image();
- img.src = LiteGraph.node_images_path + url;
- img.ready = false;
+ var that = this;
+ img.onload = function() {
+ this.ready = true;
+ that.setDirtyCanvas(true);
+ };
+ return img;
+ };
- var that = this;
- img.onload = function() {
- this.ready = true;
- that.setDirtyCanvas(true);
- }
- return img;
-}
-
-//safe LGraphNode action execution (not sure if safe)
-/*
+ //safe LGraphNode action execution (not sure if safe)
+ /*
LGraphNode.prototype.executeAction = function(action)
{
if(action == "") return false;
@@ -3426,1008 +3208,1017 @@ LGraphNode.prototype.executeAction = function(action)
}
*/
-/* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
-LGraphNode.prototype.captureInput = function(v)
-{
- if(!this.graph || !this.graph.list_of_graphcanvas)
- return;
-
- var list = this.graph.list_of_graphcanvas;
-
- for(var i = 0; i < list.length; ++i)
- {
- var c = list[i];
- //releasing somebody elses capture?!
- if(!v && c.node_capturing_input != this)
- continue;
-
- //change
- c.node_capturing_input = v ? this : null;
- }
-}
-
-/**
-* Collapse the node to make it smaller on the canvas
-* @method collapse
-**/
-LGraphNode.prototype.collapse = function( force )
-{
- this.graph._version++;
- if(this.constructor.collapsable === false && !force)
- return;
- if(!this.flags.collapsed)
- this.flags.collapsed = true;
- else
- this.flags.collapsed = false;
- this.setDirtyCanvas(true,true);
-}
-
-/**
-* Forces the node to do not move or realign on Z
-* @method pin
-**/
-
-LGraphNode.prototype.pin = function(v)
-{
- this.graph._version++;
- if(v === undefined)
- this.flags.pinned = !this.flags.pinned;
- else
- this.flags.pinned = v;
-}
-
-LGraphNode.prototype.localToScreen = function(x,y, graphcanvas)
-{
- return [(x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],
- (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]];
-}
-
-
-
-
-function LGraphGroup( title )
-{
- this._ctor( title );
-}
-
-global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;
-
-LGraphGroup.prototype._ctor = function( title )
-{
- this.title = title || "Group";
- this.font_size = 24;
- this.color = LGraphCanvas.node_colors.pale_blue ? LGraphCanvas.node_colors.pale_blue.groupcolor : "#AAA";
- this._bounding = new Float32Array([10,10,140,80]);
- this._pos = this._bounding.subarray(0,2);
- this._size = this._bounding.subarray(2,4);
- this._nodes = [];
- this.graph = null;
-
- Object.defineProperty( this, "pos", {
- set: function(v)
- {
- if(!v || v.length < 2)
- return;
- this._pos[0] = v[0];
- this._pos[1] = v[1];
- },
- get: function()
- {
- return this._pos;
- },
- enumerable: true
- });
-
- Object.defineProperty( this, "size", {
- set: function(v)
- {
- if(!v || v.length < 2)
- return;
- this._size[0] = Math.max(140,v[0]);
- this._size[1] = Math.max(80,v[1]);
- },
- get: function()
- {
- return this._size;
- },
- enumerable: true
- });
-}
-
-LGraphGroup.prototype.configure = function(o)
-{
- this.title = o.title;
- this._bounding.set( o.bounding );
- this.color = o.color;
- this.font = o.font;
-}
-
-LGraphGroup.prototype.serialize = function()
-{
- var b = this._bounding;
- return {
- title: this.title,
- bounding: [ Math.round(b[0]), Math.round(b[1]), Math.round(b[2]), Math.round(b[3]) ],
- color: this.color,
- font: this.font
- };
-}
-
-LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes)
-{
- this._pos[0] += deltax;
- this._pos[1] += deltay;
- if(ignore_nodes)
- return;
- for(var i = 0; i < this._nodes.length; ++i)
- {
- var node = this._nodes[i];
- node.pos[0] += deltax;
- node.pos[1] += deltay;
- }
-}
-
-LGraphGroup.prototype.recomputeInsideNodes = function()
-{
- this._nodes.length = 0;
- var nodes = this.graph._nodes;
- var node_bounding = new Float32Array(4);
-
- for(var i = 0; i < nodes.length; ++i)
- {
- var node = nodes[i];
- node.getBounding( node_bounding );
- if(!overlapBounding( this._bounding, node_bounding ))
- continue; //out of the visible area
- this._nodes.push( node );
- }
-}
-
-LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;
-LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;
-
-
-
-//****************************************
-
-//Scale and Offset
-function DragAndScale( element, skip_events )
-{
- this.offset = new Float32Array([0,0]);
- this.scale = 1;
- this.max_scale = 10;
- this.min_scale = 0.1;
- this.onredraw = null;
- this.enabled = true;
- this.last_mouse = [0,0];
- this.element = null;
- this.visible_area = new Float32Array(4);
-
- if(element)
- {
- this.element = element;
- if(!skip_events)
- this.bindEvents( element );
- }
-}
-
-LiteGraph.DragAndScale = DragAndScale;
-
-DragAndScale.prototype.bindEvents = function( element )
-{
- this.last_mouse = new Float32Array(2);
-
- this._binded_mouse_callback = this.onMouse.bind(this);
-
- element.addEventListener("mousedown", this._binded_mouse_callback );
- element.addEventListener("mousemove", this._binded_mouse_callback );
-
- element.addEventListener("mousewheel", this._binded_mouse_callback, false);
- element.addEventListener("wheel", this._binded_mouse_callback, false);
-}
-
-DragAndScale.prototype.computeVisibleArea = function()
-{
- if(!this.element)
- {
- this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;
- return;
- }
- var width = this.element.width;
- var height = this.element.height;
- var startx = -this.offset[0];
- var starty = -this.offset[1];
- var endx = startx + width / this.scale;
- var endy = starty + height / this.scale;
- this.visible_area[0] = startx;
- this.visible_area[1] = starty;
- this.visible_area[2] = endx - startx;
- this.visible_area[3] = endy - starty;
-}
-
-DragAndScale.prototype.onMouse = function(e)
-{
- if(!this.enabled)
- return;
-
- var canvas = this.element;
- var rect = canvas.getBoundingClientRect();
- var x = e.clientX - rect.left;
- var y = e.clientY - rect.top;
- e.canvasx = x;
- e.canvasy = y;
- e.dragging = this.dragging;
-
- var ignore = false;
- if(this.onmouse)
- ignore = this.onmouse(e);
-
- if(e.type == "mousedown")
- {
- this.dragging = true;
- canvas.removeEventListener("mousemove", this._binded_mouse_callback );
- document.body.addEventListener("mousemove", this._binded_mouse_callback );
- document.body.addEventListener("mouseup", this._binded_mouse_callback );
- }
- else if(e.type == "mousemove")
- {
- if(!ignore)
- {
- var deltax = x - this.last_mouse[0];
- var deltay = y - this.last_mouse[1];
- if( this.dragging )
- this.mouseDrag( deltax, deltay );
- }
- }
- else if(e.type == "mouseup")
- {
- this.dragging = false;
- document.body.removeEventListener("mousemove", this._binded_mouse_callback );
- document.body.removeEventListener("mouseup", this._binded_mouse_callback );
- canvas.addEventListener("mousemove", this._binded_mouse_callback );
- }
- else if(e.type == "mousewheel" || e.type == "wheel" || e.type == "DOMMouseScroll")
- {
- e.eventType = "mousewheel";
- if(e.type == "wheel")
- e.wheel = -e.deltaY;
- else
- e.wheel = (e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60);
-
- //from stack overflow
- e.delta = e.wheelDelta ? e.wheelDelta/40 : e.deltaY ? -e.deltaY/3 : 0;
- this.changeDeltaScale(1.0 + e.delta * 0.05);
- }
-
- this.last_mouse[0] = x;
- this.last_mouse[1] = y;
-
- e.preventDefault();
- e.stopPropagation();
- return false;
-}
-
-DragAndScale.prototype.toCanvasContext = function( ctx )
-{
- ctx.scale( this.scale, this.scale );
- ctx.translate( this.offset[0], this.offset[1] );
-}
-
-DragAndScale.prototype.convertOffsetToCanvas = function(pos)
-{
- //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];
- return [ (pos[0] + this.offset[0]) * this.scale, (pos[1] + this.offset[1]) * this.scale ];
-}
-
-DragAndScale.prototype.convertCanvasToOffset = function(pos, out)
-{
- out = out || [0,0];
- out[0] = pos[0] / this.scale - this.offset[0];
- out[1] = pos[1] / this.scale - this.offset[1];
- return out;
-}
-
-DragAndScale.prototype.mouseDrag = function(x,y)
-{
- this.offset[0] += x / this.scale;
- this.offset[1] += y / this.scale;
-
- if( this.onredraw )
- this.onredraw( this );
-}
-
-DragAndScale.prototype.changeScale = function( value, zooming_center )
-{
- if(value < this.min_scale)
- value = this.min_scale;
- else if(value > this.max_scale)
- value = this.max_scale;
-
- if(value == this.scale)
- return;
-
- if(!this.element)
- return;
-
- var rect = this.element.getBoundingClientRect();
- if(!rect)
- return;
-
- zooming_center = zooming_center || [rect.width * 0.5,rect.height * 0.5];
- var center = this.convertCanvasToOffset( zooming_center );
- this.scale = value;
- if( Math.abs( this.scale - 1 ) < 0.01 )
- this.scale = 1;
-
- var new_center = this.convertCanvasToOffset( zooming_center );
- var delta_offset = [new_center[0] - center[0], new_center[1] - center[1]];
-
- this.offset[0] += delta_offset[0];
- this.offset[1] += delta_offset[1];
-
- if( this.onredraw )
- this.onredraw( this );
-}
-
-DragAndScale.prototype.changeDeltaScale = function( value, zooming_center )
-{
- this.changeScale( this.scale * value, zooming_center );
-}
-
-DragAndScale.prototype.reset = function()
-{
- this.scale = 1;
- this.offset[0] = 0;
- this.offset[1] = 0;
-}
-
-
-//*********************************************************************************
-// LGraphCanvas: LGraph renderer CLASS
-//*********************************************************************************
-
-/**
-* This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.
-* Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked
-*
-* @class LGraphCanvas
-* @constructor
-* @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)
-* @param {LGraph} graph [optional]
-* @param {Object} options [optional] { skip_rendering, autoresize }
-*/
-function LGraphCanvas( canvas, graph, options )
-{
- options = options || {};
-
- //if(graph === undefined)
- // throw ("No graph assigned");
- this.background_image = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII='
-
- if(canvas && canvas.constructor === String )
- canvas = document.querySelector( canvas );
-
- this.ds = new DragAndScale();
- this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much
-
- this.title_text_font = ""+LiteGraph.NODE_TEXT_SIZE+"px Arial";
- this.inner_text_font = "normal "+LiteGraph.NODE_SUBTEXT_SIZE+"px Arial";
- this.node_title_color = LiteGraph.NODE_TITLE_COLOR;
- this.default_link_color = LiteGraph.LINK_COLOR;
- this.default_connection_color = {
- input_off: "#778",
- input_on: "#7F7",
- output_off: "#778",
- output_on: "#7F7"
- };
-
- this.highquality_render = true;
- this.use_gradients = false; //set to true to render titlebar with gradients
- this.editor_alpha = 1; //used for transition
- this.pause_rendering = false;
- this.clear_background = true;
-
- this.render_only_selected = true;
- this.live_mode = false;
- this.show_info = true;
- this.allow_dragcanvas = true;
- this.allow_dragnodes = true;
- this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc
- this.allow_searchbox = true;
- this.allow_reconnect_links = false; //allows to change a connection with having to redo it again
-
- this.drag_mode = false;
- this.dragging_rectangle = null;
-
- this.filter = null; //allows to filter to only accept some type of nodes in a graph
-
- this.always_render_background = false;
- this.render_shadows = true;
- this.render_canvas_border = true;
- this.render_connections_shadows = false; //too much cpu
- this.render_connections_border = true;
- this.render_curved_connections = false;
- this.render_connection_arrows = false;
- this.render_collapsed_slots = true;
- this.render_execution_order = false;
- this.render_title_colored = true;
-
- this.links_render_mode = LiteGraph.SPLINE_LINK;
-
- this.canvas_mouse = [0,0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle
-
- //to personalize the search box
- this.onSearchBox = null;
- this.onSearchBoxSelection = null;
-
- //callbacks
- this.onMouse = null;
- this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform
- this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform
- this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)
-
- this.connections_width = 3;
- this.round_radius = 8;
-
- this.current_node = null;
- this.node_widget = null; //used for widgets
- this.last_mouse_position = [0,0];
- this.visible_area = this.ds.visible_area;
- this.visible_links = [];
-
- //link canvas and graph
- if(graph)
- graph.attachCanvas(this);
-
- this.setCanvas( canvas );
- this.clear();
-
- if(!options.skip_render)
- this.startRendering();
-
- this.autoresize = options.autoresize;
-}
-
-global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;
-
-LGraphCanvas.link_type_colors = {"-1": LiteGraph.EVENT_LINK_COLOR,'number':"#AAA","node":"#DCA"};
-LGraphCanvas.gradients = {}; //cache of gradients
-
-/**
-* clears all the data inside
-*
-* @method clear
-*/
-LGraphCanvas.prototype.clear = function()
-{
- this.frame = 0;
- this.last_draw_time = 0;
- this.render_time = 0;
- this.fps = 0;
-
- //this.scale = 1;
- //this.offset = [0,0];
-
- this.dragging_rectangle = null;
-
- this.selected_nodes = {};
- this.selected_group = null;
-
- this.visible_nodes = [];
- this.node_dragged = null;
- this.node_over = null;
- this.node_capturing_input = null;
- this.connecting_node = null;
- this.highlighted_links = {};
-
- this.dirty_canvas = true;
- this.dirty_bgcanvas = true;
- this.dirty_area = null;
-
- this.node_in_panel = null;
- this.node_widget = null;
-
- this.last_mouse = [0,0];
- this.last_mouseclick = 0;
- this.visible_area.set([0,0,0,0]);
-
- if(this.onClear)
- this.onClear();
-}
-
-/**
-* assigns a graph, you can reassign graphs to the same canvas
-*
-* @method setGraph
-* @param {LGraph} graph
-*/
-LGraphCanvas.prototype.setGraph = function( graph, skip_clear )
-{
- if(this.graph == graph)
- return;
-
- if(!skip_clear)
- this.clear();
-
- if(!graph && this.graph)
- {
- this.graph.detachCanvas(this);
- return;
- }
-
- /*
+ /* Allows to get onMouseMove and onMouseUp events even if the mouse is out of focus */
+ LGraphNode.prototype.captureInput = function(v) {
+ if (!this.graph || !this.graph.list_of_graphcanvas) return;
+
+ var list = this.graph.list_of_graphcanvas;
+
+ for (var i = 0; i < list.length; ++i) {
+ var c = list[i];
+ //releasing somebody elses capture?!
+ if (!v && c.node_capturing_input != this) continue;
+
+ //change
+ c.node_capturing_input = v ? this : null;
+ }
+ };
+
+ /**
+ * Collapse the node to make it smaller on the canvas
+ * @method collapse
+ **/
+ LGraphNode.prototype.collapse = function(force) {
+ this.graph._version++;
+ if (this.constructor.collapsable === false && !force) return;
+ if (!this.flags.collapsed) this.flags.collapsed = true;
+ else this.flags.collapsed = false;
+ this.setDirtyCanvas(true, true);
+ };
+
+ /**
+ * Forces the node to do not move or realign on Z
+ * @method pin
+ **/
+
+ LGraphNode.prototype.pin = function(v) {
+ this.graph._version++;
+ if (v === undefined) this.flags.pinned = !this.flags.pinned;
+ else this.flags.pinned = v;
+ };
+
+ LGraphNode.prototype.localToScreen = function(x, y, graphcanvas) {
+ return [
+ (x + this.pos[0]) * graphcanvas.scale + graphcanvas.offset[0],
+ (y + this.pos[1]) * graphcanvas.scale + graphcanvas.offset[1]
+ ];
+ };
+
+ function LGraphGroup(title) {
+ this._ctor(title);
+ }
+
+ global.LGraphGroup = LiteGraph.LGraphGroup = LGraphGroup;
+
+ LGraphGroup.prototype._ctor = function(title) {
+ this.title = title || "Group";
+ this.font_size = 24;
+ this.color = LGraphCanvas.node_colors.pale_blue
+ ? LGraphCanvas.node_colors.pale_blue.groupcolor
+ : "#AAA";
+ this._bounding = new Float32Array([10, 10, 140, 80]);
+ this._pos = this._bounding.subarray(0, 2);
+ this._size = this._bounding.subarray(2, 4);
+ this._nodes = [];
+ this.graph = null;
+
+ Object.defineProperty(this, "pos", {
+ set: function(v) {
+ if (!v || v.length < 2) return;
+ this._pos[0] = v[0];
+ this._pos[1] = v[1];
+ },
+ get: function() {
+ return this._pos;
+ },
+ enumerable: true
+ });
+
+ Object.defineProperty(this, "size", {
+ set: function(v) {
+ if (!v || v.length < 2) return;
+ this._size[0] = Math.max(140, v[0]);
+ this._size[1] = Math.max(80, v[1]);
+ },
+ get: function() {
+ return this._size;
+ },
+ enumerable: true
+ });
+ };
+
+ LGraphGroup.prototype.configure = function(o) {
+ this.title = o.title;
+ this._bounding.set(o.bounding);
+ this.color = o.color;
+ this.font = o.font;
+ };
+
+ LGraphGroup.prototype.serialize = function() {
+ var b = this._bounding;
+ return {
+ title: this.title,
+ bounding: [
+ Math.round(b[0]),
+ Math.round(b[1]),
+ Math.round(b[2]),
+ Math.round(b[3])
+ ],
+ color: this.color,
+ font: this.font
+ };
+ };
+
+ LGraphGroup.prototype.move = function(deltax, deltay, ignore_nodes) {
+ this._pos[0] += deltax;
+ this._pos[1] += deltay;
+ if (ignore_nodes) return;
+ for (var i = 0; i < this._nodes.length; ++i) {
+ var node = this._nodes[i];
+ node.pos[0] += deltax;
+ node.pos[1] += deltay;
+ }
+ };
+
+ LGraphGroup.prototype.recomputeInsideNodes = function() {
+ this._nodes.length = 0;
+ var nodes = this.graph._nodes;
+ var node_bounding = new Float32Array(4);
+
+ for (var i = 0; i < nodes.length; ++i) {
+ var node = nodes[i];
+ node.getBounding(node_bounding);
+ if (!overlapBounding(this._bounding, node_bounding)) continue; //out of the visible area
+ this._nodes.push(node);
+ }
+ };
+
+ LGraphGroup.prototype.isPointInside = LGraphNode.prototype.isPointInside;
+ LGraphGroup.prototype.setDirtyCanvas = LGraphNode.prototype.setDirtyCanvas;
+
+ //****************************************
+
+ //Scale and Offset
+ function DragAndScale(element, skip_events) {
+ this.offset = new Float32Array([0, 0]);
+ this.scale = 1;
+ this.max_scale = 10;
+ this.min_scale = 0.1;
+ this.onredraw = null;
+ this.enabled = true;
+ this.last_mouse = [0, 0];
+ this.element = null;
+ this.visible_area = new Float32Array(4);
+
+ if (element) {
+ this.element = element;
+ if (!skip_events) this.bindEvents(element);
+ }
+ }
+
+ LiteGraph.DragAndScale = DragAndScale;
+
+ DragAndScale.prototype.bindEvents = function(element) {
+ this.last_mouse = new Float32Array(2);
+
+ this._binded_mouse_callback = this.onMouse.bind(this);
+
+ element.addEventListener("mousedown", this._binded_mouse_callback);
+ element.addEventListener("mousemove", this._binded_mouse_callback);
+
+ element.addEventListener(
+ "mousewheel",
+ this._binded_mouse_callback,
+ false
+ );
+ element.addEventListener("wheel", this._binded_mouse_callback, false);
+ };
+
+ DragAndScale.prototype.computeVisibleArea = function() {
+ if (!this.element) {
+ this.visible_area[0] = this.visible_area[1] = this.visible_area[2] = this.visible_area[3] = 0;
+ return;
+ }
+ var width = this.element.width;
+ var height = this.element.height;
+ var startx = -this.offset[0];
+ var starty = -this.offset[1];
+ var endx = startx + width / this.scale;
+ var endy = starty + height / this.scale;
+ this.visible_area[0] = startx;
+ this.visible_area[1] = starty;
+ this.visible_area[2] = endx - startx;
+ this.visible_area[3] = endy - starty;
+ };
+
+ DragAndScale.prototype.onMouse = function(e) {
+ if (!this.enabled) return;
+
+ var canvas = this.element;
+ var rect = canvas.getBoundingClientRect();
+ var x = e.clientX - rect.left;
+ var y = e.clientY - rect.top;
+ e.canvasx = x;
+ e.canvasy = y;
+ e.dragging = this.dragging;
+
+ var ignore = false;
+ if (this.onmouse) ignore = this.onmouse(e);
+
+ if (e.type == "mousedown") {
+ this.dragging = true;
+ canvas.removeEventListener(
+ "mousemove",
+ this._binded_mouse_callback
+ );
+ document.body.addEventListener(
+ "mousemove",
+ this._binded_mouse_callback
+ );
+ document.body.addEventListener(
+ "mouseup",
+ this._binded_mouse_callback
+ );
+ } else if (e.type == "mousemove") {
+ if (!ignore) {
+ var deltax = x - this.last_mouse[0];
+ var deltay = y - this.last_mouse[1];
+ if (this.dragging) this.mouseDrag(deltax, deltay);
+ }
+ } else if (e.type == "mouseup") {
+ this.dragging = false;
+ document.body.removeEventListener(
+ "mousemove",
+ this._binded_mouse_callback
+ );
+ document.body.removeEventListener(
+ "mouseup",
+ this._binded_mouse_callback
+ );
+ canvas.addEventListener("mousemove", this._binded_mouse_callback);
+ } else if (
+ e.type == "mousewheel" ||
+ e.type == "wheel" ||
+ e.type == "DOMMouseScroll"
+ ) {
+ e.eventType = "mousewheel";
+ if (e.type == "wheel") e.wheel = -e.deltaY;
+ else
+ e.wheel =
+ e.wheelDeltaY != null ? e.wheelDeltaY : e.detail * -60;
+
+ //from stack overflow
+ e.delta = e.wheelDelta
+ ? e.wheelDelta / 40
+ : e.deltaY
+ ? -e.deltaY / 3
+ : 0;
+ this.changeDeltaScale(1.0 + e.delta * 0.05);
+ }
+
+ this.last_mouse[0] = x;
+ this.last_mouse[1] = y;
+
+ e.preventDefault();
+ e.stopPropagation();
+ return false;
+ };
+
+ DragAndScale.prototype.toCanvasContext = function(ctx) {
+ ctx.scale(this.scale, this.scale);
+ ctx.translate(this.offset[0], this.offset[1]);
+ };
+
+ DragAndScale.prototype.convertOffsetToCanvas = function(pos) {
+ //return [pos[0] / this.scale - this.offset[0], pos[1] / this.scale - this.offset[1]];
+ return [
+ (pos[0] + this.offset[0]) * this.scale,
+ (pos[1] + this.offset[1]) * this.scale
+ ];
+ };
+
+ DragAndScale.prototype.convertCanvasToOffset = function(pos, out) {
+ out = out || [0, 0];
+ out[0] = pos[0] / this.scale - this.offset[0];
+ out[1] = pos[1] / this.scale - this.offset[1];
+ return out;
+ };
+
+ DragAndScale.prototype.mouseDrag = function(x, y) {
+ this.offset[0] += x / this.scale;
+ this.offset[1] += y / this.scale;
+
+ if (this.onredraw) this.onredraw(this);
+ };
+
+ DragAndScale.prototype.changeScale = function(value, zooming_center) {
+ if (value < this.min_scale) value = this.min_scale;
+ else if (value > this.max_scale) value = this.max_scale;
+
+ if (value == this.scale) return;
+
+ if (!this.element) return;
+
+ var rect = this.element.getBoundingClientRect();
+ if (!rect) return;
+
+ zooming_center = zooming_center || [
+ rect.width * 0.5,
+ rect.height * 0.5
+ ];
+ var center = this.convertCanvasToOffset(zooming_center);
+ this.scale = value;
+ if (Math.abs(this.scale - 1) < 0.01) this.scale = 1;
+
+ var new_center = this.convertCanvasToOffset(zooming_center);
+ var delta_offset = [
+ new_center[0] - center[0],
+ new_center[1] - center[1]
+ ];
+
+ this.offset[0] += delta_offset[0];
+ this.offset[1] += delta_offset[1];
+
+ if (this.onredraw) this.onredraw(this);
+ };
+
+ DragAndScale.prototype.changeDeltaScale = function(value, zooming_center) {
+ this.changeScale(this.scale * value, zooming_center);
+ };
+
+ DragAndScale.prototype.reset = function() {
+ this.scale = 1;
+ this.offset[0] = 0;
+ this.offset[1] = 0;
+ };
+
+ //*********************************************************************************
+ // LGraphCanvas: LGraph renderer CLASS
+ //*********************************************************************************
+
+ /**
+ * This class is in charge of rendering one graph inside a canvas. And provides all the interaction required.
+ * Valid callbacks are: onNodeSelected, onNodeDeselected, onShowNodePanel, onNodeDblClicked
+ *
+ * @class LGraphCanvas
+ * @constructor
+ * @param {HTMLCanvas} canvas the canvas where you want to render (it accepts a selector in string format or the canvas element itself)
+ * @param {LGraph} graph [optional]
+ * @param {Object} options [optional] { skip_rendering, autoresize }
+ */
+ function LGraphCanvas(canvas, graph, options) {
+ options = options || {};
+
+ //if(graph === undefined)
+ // throw ("No graph assigned");
+ this.background_image =
+ "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAIAAAD/gAIDAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAQBJREFUeNrs1rEKwjAUhlETUkj3vP9rdmr1Ysammk2w5wdxuLgcMHyptfawuZX4pJSWZTnfnu/lnIe/jNNxHHGNn//HNbbv+4dr6V+11uF527arU7+u63qfa/bnmh8sWLBgwYJlqRf8MEptXPBXJXa37BSl3ixYsGDBMliwFLyCV/DeLIMFCxYsWLBMwSt4Be/NggXLYMGCBUvBK3iNruC9WbBgwYJlsGApeAWv4L1ZBgsWLFiwYJmCV/AK3psFC5bBggULloJX8BpdwXuzYMGCBctgwVLwCl7Be7MMFixYsGDBsu8FH1FaSmExVfAxBa/gvVmwYMGCZbBg/W4vAQYA5tRF9QYlv/QAAAAASUVORK5CYII=";
+
+ if (canvas && canvas.constructor === String)
+ canvas = document.querySelector(canvas);
+
+ this.ds = new DragAndScale();
+ this.zoom_modify_alpha = true; //otherwise it generates ugly patterns when scaling down too much
+
+ this.title_text_font = "" + LiteGraph.NODE_TEXT_SIZE + "px Arial";
+ this.inner_text_font =
+ "normal " + LiteGraph.NODE_SUBTEXT_SIZE + "px Arial";
+ this.node_title_color = LiteGraph.NODE_TITLE_COLOR;
+ this.default_link_color = LiteGraph.LINK_COLOR;
+ this.default_connection_color = {
+ input_off: "#778",
+ input_on: "#7F7",
+ output_off: "#778",
+ output_on: "#7F7"
+ };
+
+ this.highquality_render = true;
+ this.use_gradients = false; //set to true to render titlebar with gradients
+ this.editor_alpha = 1; //used for transition
+ this.pause_rendering = false;
+ this.clear_background = true;
+
+ this.render_only_selected = true;
+ this.live_mode = false;
+ this.show_info = true;
+ this.allow_dragcanvas = true;
+ this.allow_dragnodes = true;
+ this.allow_interaction = true; //allow to control widgets, buttons, collapse, etc
+ this.allow_searchbox = true;
+ this.allow_reconnect_links = false; //allows to change a connection with having to redo it again
+
+ this.drag_mode = false;
+ this.dragging_rectangle = null;
+
+ this.filter = null; //allows to filter to only accept some type of nodes in a graph
+
+ this.always_render_background = false;
+ this.render_shadows = true;
+ this.render_canvas_border = true;
+ this.render_connections_shadows = false; //too much cpu
+ this.render_connections_border = true;
+ this.render_curved_connections = false;
+ this.render_connection_arrows = false;
+ this.render_collapsed_slots = true;
+ this.render_execution_order = false;
+ this.render_title_colored = true;
+
+ this.links_render_mode = LiteGraph.SPLINE_LINK;
+
+ this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle
+
+ //to personalize the search box
+ this.onSearchBox = null;
+ this.onSearchBoxSelection = null;
+
+ //callbacks
+ this.onMouse = null;
+ this.onDrawBackground = null; //to render background objects (behind nodes and connections) in the canvas affected by transform
+ this.onDrawForeground = null; //to render foreground objects (above nodes and connections) in the canvas affected by transform
+ this.onDrawOverlay = null; //to render foreground objects not affected by transform (for GUIs)
+
+ this.connections_width = 3;
+ this.round_radius = 8;
+
+ this.current_node = null;
+ this.node_widget = null; //used for widgets
+ this.last_mouse_position = [0, 0];
+ this.visible_area = this.ds.visible_area;
+ this.visible_links = [];
+
+ //link canvas and graph
+ if (graph) graph.attachCanvas(this);
+
+ this.setCanvas(canvas);
+ this.clear();
+
+ if (!options.skip_render) this.startRendering();
+
+ this.autoresize = options.autoresize;
+ }
+
+ global.LGraphCanvas = LiteGraph.LGraphCanvas = LGraphCanvas;
+
+ LGraphCanvas.link_type_colors = {
+ "-1": LiteGraph.EVENT_LINK_COLOR,
+ number: "#AAA",
+ node: "#DCA"
+ };
+ LGraphCanvas.gradients = {}; //cache of gradients
+
+ /**
+ * clears all the data inside
+ *
+ * @method clear
+ */
+ LGraphCanvas.prototype.clear = function() {
+ this.frame = 0;
+ this.last_draw_time = 0;
+ this.render_time = 0;
+ this.fps = 0;
+
+ //this.scale = 1;
+ //this.offset = [0,0];
+
+ this.dragging_rectangle = null;
+
+ this.selected_nodes = {};
+ this.selected_group = null;
+
+ this.visible_nodes = [];
+ this.node_dragged = null;
+ this.node_over = null;
+ this.node_capturing_input = null;
+ this.connecting_node = null;
+ this.highlighted_links = {};
+
+ this.dirty_canvas = true;
+ this.dirty_bgcanvas = true;
+ this.dirty_area = null;
+
+ this.node_in_panel = null;
+ this.node_widget = null;
+
+ this.last_mouse = [0, 0];
+ this.last_mouseclick = 0;
+ this.visible_area.set([0, 0, 0, 0]);
+
+ if (this.onClear) this.onClear();
+ };
+
+ /**
+ * assigns a graph, you can reassign graphs to the same canvas
+ *
+ * @method setGraph
+ * @param {LGraph} graph
+ */
+ LGraphCanvas.prototype.setGraph = function(graph, skip_clear) {
+ if (this.graph == graph) return;
+
+ if (!skip_clear) this.clear();
+
+ if (!graph && this.graph) {
+ this.graph.detachCanvas(this);
+ return;
+ }
+
+ /*
if(this.graph)
this.graph.canvas = null; //remove old graph link to the canvas
this.graph = graph;
if(this.graph)
this.graph.canvas = this;
*/
- graph.attachCanvas(this);
- this.setDirty(true,true);
-}
+ graph.attachCanvas(this);
+ this.setDirty(true, true);
+ };
-/**
-* opens a graph contained inside a node in the current graph
-*
-* @method openSubgraph
-* @param {LGraph} graph
-*/
-LGraphCanvas.prototype.openSubgraph = function(graph)
-{
- if(!graph)
- throw("graph cannot be null");
+ /**
+ * opens a graph contained inside a node in the current graph
+ *
+ * @method openSubgraph
+ * @param {LGraph} graph
+ */
+ LGraphCanvas.prototype.openSubgraph = function(graph) {
+ if (!graph) throw "graph cannot be null";
- if(this.graph == graph)
- throw("graph cannot be the same");
+ if (this.graph == graph) throw "graph cannot be the same";
- this.clear();
+ this.clear();
- if(this.graph)
- {
- if(!this._graph_stack)
- this._graph_stack = [];
- this._graph_stack.push(this.graph);
- }
+ if (this.graph) {
+ if (!this._graph_stack) this._graph_stack = [];
+ this._graph_stack.push(this.graph);
+ }
- graph.attachCanvas(this);
- this.setDirty(true,true);
-}
+ graph.attachCanvas(this);
+ this.setDirty(true, true);
+ };
-/**
-* closes a subgraph contained inside a node
-*
-* @method closeSubgraph
-* @param {LGraph} assigns a graph
-*/
-LGraphCanvas.prototype.closeSubgraph = function()
-{
- if(!this._graph_stack || this._graph_stack.length == 0)
- return;
- var subgraph_node = this.graph._subgraph_node;
- var graph = this._graph_stack.pop();
- this.selected_nodes = {};
- this.highlighted_links = {};
- graph.attachCanvas(this);
- this.setDirty(true,true);
- if( subgraph_node )
- {
- this.centerOnNode( subgraph_node );
- this.selectNodes( [subgraph_node] );
- }
-}
+ /**
+ * closes a subgraph contained inside a node
+ *
+ * @method closeSubgraph
+ * @param {LGraph} assigns a graph
+ */
+ LGraphCanvas.prototype.closeSubgraph = function() {
+ if (!this._graph_stack || this._graph_stack.length == 0) return;
+ var subgraph_node = this.graph._subgraph_node;
+ var graph = this._graph_stack.pop();
+ this.selected_nodes = {};
+ this.highlighted_links = {};
+ graph.attachCanvas(this);
+ this.setDirty(true, true);
+ if (subgraph_node) {
+ this.centerOnNode(subgraph_node);
+ this.selectNodes([subgraph_node]);
+ }
+ };
-/**
-* assigns a canvas
-*
-* @method setCanvas
-* @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)
-*/
-LGraphCanvas.prototype.setCanvas = function( canvas, skip_events )
-{
- var that = this;
+ /**
+ * assigns a canvas
+ *
+ * @method setCanvas
+ * @param {Canvas} assigns a canvas (also accepts the ID of the element (not a selector)
+ */
+ LGraphCanvas.prototype.setCanvas = function(canvas, skip_events) {
+ var that = this;
- if(canvas)
- {
- if( canvas.constructor === String )
- {
- canvas = document.getElementById(canvas);
- if(!canvas)
- throw("Error creating LiteGraph canvas: Canvas not found");
- }
- }
+ if (canvas) {
+ if (canvas.constructor === String) {
+ canvas = document.getElementById(canvas);
+ if (!canvas)
+ throw "Error creating LiteGraph canvas: Canvas not found";
+ }
+ }
- if(canvas === this.canvas)
- return;
+ if (canvas === this.canvas) return;
- if(!canvas && this.canvas)
- {
- //maybe detach events from old_canvas
- if(!skip_events)
- this.unbindEvents();
- }
+ if (!canvas && this.canvas) {
+ //maybe detach events from old_canvas
+ if (!skip_events) this.unbindEvents();
+ }
- this.canvas = canvas;
- this.ds.element = canvas;
+ this.canvas = canvas;
+ this.ds.element = canvas;
- if(!canvas)
- return;
+ if (!canvas) return;
- //this.canvas.tabindex = "1000";
- canvas.className += " lgraphcanvas";
- canvas.data = this;
- canvas.tabindex = '1'; //to allow key events
+ //this.canvas.tabindex = "1000";
+ canvas.className += " lgraphcanvas";
+ canvas.data = this;
+ canvas.tabindex = "1"; //to allow key events
- //bg canvas: used for non changing stuff
- this.bgcanvas = null;
- if(!this.bgcanvas)
- {
- this.bgcanvas = document.createElement("canvas");
- this.bgcanvas.width = this.canvas.width;
- this.bgcanvas.height = this.canvas.height;
- }
+ //bg canvas: used for non changing stuff
+ this.bgcanvas = null;
+ if (!this.bgcanvas) {
+ this.bgcanvas = document.createElement("canvas");
+ this.bgcanvas.width = this.canvas.width;
+ this.bgcanvas.height = this.canvas.height;
+ }
- if(canvas.getContext == null)
- {
- if( canvas.localName != "canvas" )
- throw("Element supplied for LGraphCanvas must be a