lexgui 0.1.29 → 0.1.30
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/codeeditor.js +11 -22
- package/build/components/nodegraph.js +806 -342
- package/build/lexgui.css +70 -36
- package/build/lexgui.js +110 -29
- package/build/lexgui.module.js +108 -27
- package/changelog.md +19 -0
- package/examples/code_editor.html +10 -13
- package/examples/index.html +1 -1
- package/examples/node_graph.html +1 -0
- package/examples/previews/code_editor.png +0 -0
- package/examples/previews/dialogs.png +0 -0
- package/examples/previews/node_graph.png +0 -0
- package/package.json +1 -1
|
@@ -4,43 +4,7 @@ if(!LX) {
|
|
|
4
4
|
throw("lexgui.js missing!");
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
LX.components.push( '
|
|
8
|
-
|
|
9
|
-
function flushCss(element) {
|
|
10
|
-
// By reading the offsetHeight property, we are forcing
|
|
11
|
-
// the browser to flush the pending CSS changes (which it
|
|
12
|
-
// does to ensure the value obtained is accurate).
|
|
13
|
-
element.offsetHeight;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
function swapElements (obj, a, b) {
|
|
17
|
-
[obj[a], obj[b]] = [obj[b], obj[a]];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
function swapArrayElements (array, id0, id1) {
|
|
21
|
-
[array[id0], array[id1]] = [array[id1], array[id0]];
|
|
22
|
-
};
|
|
23
|
-
|
|
24
|
-
function sliceChar(str, idx) {
|
|
25
|
-
return str.substr(0, idx) + str.substr(idx + 1);
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function firstNonspaceIndex(str) {
|
|
29
|
-
return str.search(/\S|$/);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function deleteElement( el ) {
|
|
33
|
-
if( el ) el.remove();
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
let ASYNC_ENABLED = true;
|
|
37
|
-
|
|
38
|
-
function doAsync( fn, ms ) {
|
|
39
|
-
if( ASYNC_ENABLED )
|
|
40
|
-
setTimeout( fn, ms ?? 0 );
|
|
41
|
-
else
|
|
42
|
-
fn();
|
|
43
|
-
}
|
|
7
|
+
LX.components.push( 'GraphEditor' );
|
|
44
8
|
|
|
45
9
|
class BoundingBox {
|
|
46
10
|
|
|
@@ -50,7 +14,7 @@ class BoundingBox {
|
|
|
50
14
|
this.size = s ?? new LX.vec2( 0, 0 );
|
|
51
15
|
}
|
|
52
16
|
|
|
53
|
-
merge
|
|
17
|
+
merge( bb ) {
|
|
54
18
|
|
|
55
19
|
console.assert( bb.constructor == BoundingBox );
|
|
56
20
|
|
|
@@ -67,7 +31,7 @@ class BoundingBox {
|
|
|
67
31
|
this.size = merge_max.sub( merge_min );
|
|
68
32
|
}
|
|
69
33
|
|
|
70
|
-
inside
|
|
34
|
+
inside( bb, full = true ) {
|
|
71
35
|
|
|
72
36
|
const min_0 = this.origin;
|
|
73
37
|
const max_0 = this.origin.add( this.size );
|
|
@@ -75,8 +39,16 @@ class BoundingBox {
|
|
|
75
39
|
const min_1 = bb.origin;
|
|
76
40
|
const max_1 = bb.origin.add( bb.size );
|
|
77
41
|
|
|
78
|
-
|
|
79
|
-
|
|
42
|
+
if( full )
|
|
43
|
+
{
|
|
44
|
+
return min_1.x >= min_0.x && max_1.x <= max_0.x
|
|
45
|
+
&& min_1.y >= min_0.y && max_1.y <= max_0.y;
|
|
46
|
+
}
|
|
47
|
+
else
|
|
48
|
+
{
|
|
49
|
+
return max_1.x >= min_0.x && min_1.x <= max_0.x
|
|
50
|
+
&& max_1.y >= min_0.y && min_1.y <= max_0.y;
|
|
51
|
+
}
|
|
80
52
|
}
|
|
81
53
|
};
|
|
82
54
|
|
|
@@ -97,6 +69,10 @@ class GraphEditor {
|
|
|
97
69
|
static EVENT_MOUSEWHEEL = 1;
|
|
98
70
|
|
|
99
71
|
static LAST_GROUP_ID = 0;
|
|
72
|
+
static LAST_FUNCTION_ID = 0;
|
|
73
|
+
|
|
74
|
+
static STOPPED = 0;
|
|
75
|
+
static RUNNING = 1;
|
|
100
76
|
|
|
101
77
|
// Node Drawing
|
|
102
78
|
|
|
@@ -114,10 +90,10 @@ class GraphEditor {
|
|
|
114
90
|
|
|
115
91
|
GraphEditor.__instances.push( this );
|
|
116
92
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
m.add( "
|
|
93
|
+
const useSidebar = options.sidebar ?? true;
|
|
94
|
+
|
|
95
|
+
this._sidebar = area.addSidebar( m => {
|
|
96
|
+
m.add( "Create", { icon: "fa fa-add", bottom: true, callback: (e) => this._onSidebarCreate( e ) } );
|
|
121
97
|
});
|
|
122
98
|
|
|
123
99
|
this.base_area = area;
|
|
@@ -127,12 +103,20 @@ class GraphEditor {
|
|
|
127
103
|
|
|
128
104
|
this.root = this.area.root;
|
|
129
105
|
this.root.tabIndex = -1;
|
|
106
|
+
|
|
130
107
|
area.attach( this.root );
|
|
131
108
|
|
|
109
|
+
this._graphContainer = area.sections[ 1 ].root;
|
|
110
|
+
this._sidebarDom = area.sections[ 0 ].root;
|
|
111
|
+
this._sidebarActive = useSidebar;
|
|
112
|
+
|
|
113
|
+
// Set sidebar state depending on options..
|
|
114
|
+
this._toggleSideBar( useSidebar );
|
|
115
|
+
|
|
132
116
|
// Bind resize
|
|
133
117
|
|
|
134
118
|
area.onresize = ( bb ) => {
|
|
135
|
-
|
|
119
|
+
|
|
136
120
|
};
|
|
137
121
|
|
|
138
122
|
area.addOverlayButtons( [
|
|
@@ -141,27 +125,20 @@ class GraphEditor {
|
|
|
141
125
|
icon: "fa fa-table-columns",
|
|
142
126
|
callback: () => this._toggleSideBar(),
|
|
143
127
|
},
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
// {
|
|
159
|
-
// name: "Rotate",
|
|
160
|
-
// icon: "fa-solid fa-rotate-right",
|
|
161
|
-
// callback: (value, event) => console.log(value),
|
|
162
|
-
// selectable: true
|
|
163
|
-
// }
|
|
164
|
-
// ],
|
|
128
|
+
[
|
|
129
|
+
{
|
|
130
|
+
name: "Start Graph",
|
|
131
|
+
icon: "fa fa-play",
|
|
132
|
+
callback: (value, event) => this.start(),
|
|
133
|
+
selectable: true
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "Stop Graph",
|
|
137
|
+
icon: "fa-solid fa-stop",
|
|
138
|
+
callback: (value, event) => this.stop(),
|
|
139
|
+
selectable: true
|
|
140
|
+
}
|
|
141
|
+
],
|
|
165
142
|
[
|
|
166
143
|
{
|
|
167
144
|
name: "Enable Snapping",
|
|
@@ -184,9 +161,14 @@ class GraphEditor {
|
|
|
184
161
|
{
|
|
185
162
|
name: "Export",
|
|
186
163
|
icon: "fa fa-diagram-project",
|
|
187
|
-
callback: (value, event) => this.
|
|
164
|
+
callback: (value, event) => this.currentGraph.export()
|
|
188
165
|
}
|
|
189
|
-
]
|
|
166
|
+
],
|
|
167
|
+
{
|
|
168
|
+
name: "",
|
|
169
|
+
class: "graph-title",
|
|
170
|
+
callback: (value, event) => this._showRenameGraphDialog()
|
|
171
|
+
}
|
|
190
172
|
], { float: "htc" } );
|
|
191
173
|
|
|
192
174
|
this.root.addEventListener( 'keydown', this._processKeyDown.bind( this ), true );
|
|
@@ -202,7 +184,7 @@ class GraphEditor {
|
|
|
202
184
|
this.root.addEventListener( 'focusout', this._processFocus.bind( this, false ) );
|
|
203
185
|
|
|
204
186
|
this.propertiesDialog = new LX.PocketDialog( "Properties", null, {
|
|
205
|
-
size: [ "
|
|
187
|
+
size: [ "350px", null ],
|
|
206
188
|
position: [ "8px", "8px" ],
|
|
207
189
|
float: "left",
|
|
208
190
|
class: 'lexgraphpropdialog'
|
|
@@ -236,18 +218,17 @@ class GraphEditor {
|
|
|
236
218
|
|
|
237
219
|
this.keys = { };
|
|
238
220
|
|
|
239
|
-
this._sidebar = area.sections[ 0 ].root;
|
|
240
|
-
this._sidebarActive = false;
|
|
241
|
-
|
|
242
221
|
this._snapToGrid = false;
|
|
243
222
|
this._snapValue = 1.0;
|
|
244
223
|
|
|
245
|
-
|
|
224
|
+
// Graphs, Nodes and connections
|
|
246
225
|
|
|
247
|
-
|
|
226
|
+
this.currentGraph = null;
|
|
248
227
|
|
|
249
|
-
this.
|
|
250
|
-
this.
|
|
228
|
+
this.graphs = { };
|
|
229
|
+
this.nodes = { };
|
|
230
|
+
this.groups = { };
|
|
231
|
+
this.variables = { };
|
|
251
232
|
|
|
252
233
|
this.selectedNodes = [ ];
|
|
253
234
|
|
|
@@ -273,7 +254,6 @@ class GraphEditor {
|
|
|
273
254
|
// Back pattern
|
|
274
255
|
|
|
275
256
|
const f = 15.0;
|
|
276
|
-
this._patternPosition = new LX.vec2( 0, 0 );
|
|
277
257
|
this._patternSize = new LX.vec2( f );
|
|
278
258
|
this._circlePatternSize = f * 0.04;
|
|
279
259
|
this._circlePatternColor = '#71717a9c';
|
|
@@ -393,24 +373,41 @@ class GraphEditor {
|
|
|
393
373
|
|
|
394
374
|
setGraph( graph ) {
|
|
395
375
|
|
|
376
|
+
// Nothing to do, already there...
|
|
377
|
+
if( this.currentGraph && graph.id == this.currentGraph.id )
|
|
378
|
+
return;
|
|
379
|
+
|
|
396
380
|
this.clear();
|
|
397
381
|
|
|
398
|
-
|
|
382
|
+
graph.id = graph.id ?? graph.constructor.name + '-' + LX.UTILS.uidGenerator();
|
|
399
383
|
|
|
400
|
-
|
|
384
|
+
this.graphs[ graph.id ] = graph;
|
|
385
|
+
|
|
386
|
+
if( !graph.nodes )
|
|
401
387
|
{
|
|
402
388
|
console.warn( 'Graph does not contain any node!' );
|
|
403
389
|
return;
|
|
404
390
|
}
|
|
405
391
|
|
|
406
|
-
|
|
392
|
+
this.currentGraph = graph;
|
|
393
|
+
|
|
394
|
+
this._updatePattern();
|
|
395
|
+
|
|
396
|
+
for( let node of graph.nodes )
|
|
407
397
|
{
|
|
408
398
|
this._createNodeDOM( node );
|
|
409
399
|
}
|
|
410
400
|
|
|
411
|
-
for( let
|
|
401
|
+
for( let group of graph.groups )
|
|
402
|
+
{
|
|
403
|
+
const groupDom = this._createGroup( group );
|
|
404
|
+
groupDom.querySelector( '.lexgraphgrouptitle' ).value = group.name;
|
|
405
|
+
this._domNodes.prepend( groupDom );
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
for( let linkId in graph.links )
|
|
412
409
|
{
|
|
413
|
-
const links =
|
|
410
|
+
const links = graph.links[ linkId ];
|
|
414
411
|
|
|
415
412
|
for( let link of links )
|
|
416
413
|
{
|
|
@@ -418,24 +415,21 @@ class GraphEditor {
|
|
|
418
415
|
}
|
|
419
416
|
}
|
|
420
417
|
|
|
421
|
-
|
|
422
|
-
this.
|
|
423
|
-
//
|
|
418
|
+
this._updateGraphName( graph.name );
|
|
419
|
+
this._togglePropertiesDialog( false );
|
|
424
420
|
}
|
|
425
421
|
|
|
426
422
|
/**
|
|
427
423
|
* @method loadGraph
|
|
428
|
-
* @param {
|
|
424
|
+
* @param {String} url
|
|
425
|
+
* @param {Function} callback Function to call once the graph is loaded
|
|
429
426
|
*/
|
|
430
427
|
|
|
431
428
|
loadGraph( url, callback ) {
|
|
432
429
|
|
|
433
430
|
const onComplete = ( json ) => {
|
|
434
431
|
|
|
435
|
-
|
|
436
|
-
graph.configure( json );
|
|
437
|
-
|
|
438
|
-
this.setGraph( graph );
|
|
432
|
+
let graph = ( json.type == 'Graph' ) ? this.addGraph( json ) : this.addGraphFunction( json );
|
|
439
433
|
|
|
440
434
|
if( callback )
|
|
441
435
|
callback( graph );
|
|
@@ -446,6 +440,65 @@ class GraphEditor {
|
|
|
446
440
|
LX.requestJSON( url, onComplete, onError );
|
|
447
441
|
}
|
|
448
442
|
|
|
443
|
+
/**
|
|
444
|
+
* @method addGraph
|
|
445
|
+
* @param {Object} o Options to configure the graph
|
|
446
|
+
*/
|
|
447
|
+
|
|
448
|
+
addGraph( o ) {
|
|
449
|
+
|
|
450
|
+
let graph = new Graph();
|
|
451
|
+
graph.editor = this;
|
|
452
|
+
|
|
453
|
+
if( o ) graph.configure( o );
|
|
454
|
+
|
|
455
|
+
this.setGraph( graph );
|
|
456
|
+
|
|
457
|
+
this._sidebar.add( graph.name, { icon: "fa fa-diagram-project", className: graph.id, callback: (e) => { this.setGraph( graph ) } } );
|
|
458
|
+
|
|
459
|
+
this._sidebar.select( graph.name );
|
|
460
|
+
|
|
461
|
+
return graph;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
/**
|
|
465
|
+
* @method addGraphFunction
|
|
466
|
+
* @param {Object} o Options to configure the graph
|
|
467
|
+
*/
|
|
468
|
+
|
|
469
|
+
addGraphFunction( o ) {
|
|
470
|
+
|
|
471
|
+
let func = new GraphFunction();
|
|
472
|
+
func.editor = this;
|
|
473
|
+
|
|
474
|
+
if( o ) func.configure( o );
|
|
475
|
+
|
|
476
|
+
this.setGraph( func );
|
|
477
|
+
|
|
478
|
+
// Add a new node to use this function..
|
|
479
|
+
|
|
480
|
+
class NodeFunction extends GraphNode
|
|
481
|
+
{
|
|
482
|
+
onCreate() {
|
|
483
|
+
this.addInput( null, "float" );
|
|
484
|
+
this.addOutput( null, "any" );
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
onExecute() {
|
|
488
|
+
const func = NodeFunction.func;
|
|
489
|
+
const value = func.getOutputData( this.getInput( 0 ) );
|
|
490
|
+
this.setOutput( 0, value );
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
NodeFunction.func = func;
|
|
495
|
+
GraphEditor.registerCustomNode( "function/" + func.name, NodeFunction );
|
|
496
|
+
|
|
497
|
+
this._sidebar.add( func.name, { icon: "fa fa-florin-sign", className: func.id, callback: (e) => { this.setGraph( func ) } } );
|
|
498
|
+
|
|
499
|
+
this._sidebar.select( func.name );
|
|
500
|
+
}
|
|
501
|
+
|
|
449
502
|
/**
|
|
450
503
|
* @method clear
|
|
451
504
|
*/
|
|
@@ -470,10 +523,10 @@ class GraphEditor {
|
|
|
470
523
|
|
|
471
524
|
propagateEventToAllNodes( eventName, params ) {
|
|
472
525
|
|
|
473
|
-
if( !this.
|
|
526
|
+
if( !this.currentGraph )
|
|
474
527
|
return;
|
|
475
528
|
|
|
476
|
-
for ( let node of this.
|
|
529
|
+
for ( let node of this.currentGraph.nodes )
|
|
477
530
|
{
|
|
478
531
|
if( !node[ eventName ] )
|
|
479
532
|
continue;
|
|
@@ -511,6 +564,7 @@ class GraphEditor {
|
|
|
511
564
|
_createNodeDOM( node ) {
|
|
512
565
|
|
|
513
566
|
node.editor = this;
|
|
567
|
+
node.graphID = this.currentGraph.id;
|
|
514
568
|
|
|
515
569
|
var nodeContainer = document.createElement( 'div' );
|
|
516
570
|
nodeContainer.classList.add( 'lexgraphnode' );
|
|
@@ -527,6 +581,12 @@ class GraphEditor {
|
|
|
527
581
|
const category = node.constructor.category;
|
|
528
582
|
nodeContainer.classList.add( category );
|
|
529
583
|
}
|
|
584
|
+
else
|
|
585
|
+
{
|
|
586
|
+
const pos = node.type.lastIndexOf( "/" );
|
|
587
|
+
const category = node.type.substring( 0, pos );
|
|
588
|
+
nodeContainer.classList.add( category );
|
|
589
|
+
}
|
|
530
590
|
|
|
531
591
|
// Update with manual color
|
|
532
592
|
|
|
@@ -582,8 +642,7 @@ class GraphEditor {
|
|
|
582
642
|
LX.addContextMenu(null, e, m => {
|
|
583
643
|
|
|
584
644
|
m.add( "Copy", () => {
|
|
585
|
-
|
|
586
|
-
// ...
|
|
645
|
+
this._clipboardData = node.id;
|
|
587
646
|
} );
|
|
588
647
|
|
|
589
648
|
m.add( "Paste", () => {
|
|
@@ -604,6 +663,13 @@ class GraphEditor {
|
|
|
604
663
|
// Only for left click..
|
|
605
664
|
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
606
665
|
return;
|
|
666
|
+
|
|
667
|
+
// Open graph function..
|
|
668
|
+
if( node.constructor.func )
|
|
669
|
+
{
|
|
670
|
+
this._sidebar.select( node.constructor.func.name )
|
|
671
|
+
}
|
|
672
|
+
|
|
607
673
|
} );
|
|
608
674
|
|
|
609
675
|
// Title header
|
|
@@ -613,7 +679,7 @@ class GraphEditor {
|
|
|
613
679
|
nodeContainer.appendChild( nodeHeader );
|
|
614
680
|
|
|
615
681
|
// Properties
|
|
616
|
-
// if( node.properties.length
|
|
682
|
+
// if( node.properties.length )
|
|
617
683
|
// {
|
|
618
684
|
// var nodeProperties = document.createElement( 'div' );
|
|
619
685
|
// nodeProperties.classList.add( 'lexgraphnodeproperties' );
|
|
@@ -656,7 +722,7 @@ class GraphEditor {
|
|
|
656
722
|
{
|
|
657
723
|
var nodeInputs = null;
|
|
658
724
|
|
|
659
|
-
if(
|
|
725
|
+
if( hasInputs )
|
|
660
726
|
{
|
|
661
727
|
nodeInputs = document.createElement( 'div' );
|
|
662
728
|
nodeInputs.classList.add( 'lexgraphnodeinputs' );
|
|
@@ -678,6 +744,7 @@ class GraphEditor {
|
|
|
678
744
|
|
|
679
745
|
var type = document.createElement( 'span' );
|
|
680
746
|
type.className = 'io__type input ' + i.type;
|
|
747
|
+
type.dataset[ 'type' ] = i.type;
|
|
681
748
|
type.innerHTML = '<span>' + i.type[ 0 ].toUpperCase() + '</span>';
|
|
682
749
|
input.appendChild( type );
|
|
683
750
|
|
|
@@ -702,7 +769,7 @@ class GraphEditor {
|
|
|
702
769
|
{
|
|
703
770
|
var nodeOutputs = null;
|
|
704
771
|
|
|
705
|
-
if(
|
|
772
|
+
if( hasOutputs )
|
|
706
773
|
{
|
|
707
774
|
nodeOutputs = document.createElement( 'div' );
|
|
708
775
|
nodeOutputs.classList.add( 'lexgraphnodeoutputs' );
|
|
@@ -731,6 +798,7 @@ class GraphEditor {
|
|
|
731
798
|
|
|
732
799
|
var type = document.createElement( 'span' );
|
|
733
800
|
type.className = 'io__type output ' + o.type;
|
|
801
|
+
type.dataset[ 'type' ] = o.type;
|
|
734
802
|
type.innerHTML = '<span>' + o.type[ 0 ].toUpperCase() + '</span>';
|
|
735
803
|
output.appendChild( type );
|
|
736
804
|
|
|
@@ -750,6 +818,141 @@ class GraphEditor {
|
|
|
750
818
|
onDragStart: this._onDragNode.bind( this )
|
|
751
819
|
} );
|
|
752
820
|
|
|
821
|
+
this._addNodeIOEvents( nodeContainer );
|
|
822
|
+
|
|
823
|
+
const id = node.id ?? node.title.toLowerCase().replaceAll( /\s/g, '-' ) + '-' + LX.UTILS.uidGenerator();
|
|
824
|
+
this.nodes[ id ] = { data: node, dom: nodeContainer };
|
|
825
|
+
|
|
826
|
+
node.id = id;
|
|
827
|
+
nodeContainer.dataset[ 'id' ] = id;
|
|
828
|
+
|
|
829
|
+
this._domNodes.appendChild( nodeContainer );
|
|
830
|
+
|
|
831
|
+
// Only 1 main per graph!
|
|
832
|
+
if( node.title == 'Main' )
|
|
833
|
+
{
|
|
834
|
+
this.main = id;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
node.size = new LX.vec2( nodeContainer.offsetWidth, nodeContainer.offsetHeight );
|
|
838
|
+
|
|
839
|
+
node.resizeObserver = new ResizeObserver( entries => {
|
|
840
|
+
|
|
841
|
+
for( const entry of entries ) {
|
|
842
|
+
const bb = entry.contentRect;
|
|
843
|
+
if( !bb.width || !bb.height )
|
|
844
|
+
continue;
|
|
845
|
+
node.size = new LX.vec2( nodeContainer.offsetWidth, nodeContainer.offsetHeight );
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
node.resizeObserver.observe( nodeContainer );
|
|
850
|
+
|
|
851
|
+
return nodeContainer;
|
|
852
|
+
}
|
|
853
|
+
|
|
854
|
+
_updateNodeDOMIOs( dom, node ) {
|
|
855
|
+
|
|
856
|
+
// Inputs and outputs
|
|
857
|
+
var nodeIO = dom.querySelector( '.lexgraphnodeios' );
|
|
858
|
+
|
|
859
|
+
const hasInputs = node.inputs && node.inputs.length;
|
|
860
|
+
const hasOutputs = node.outputs && node.outputs.length;
|
|
861
|
+
|
|
862
|
+
// Inputs
|
|
863
|
+
{
|
|
864
|
+
var nodeInputs = null;
|
|
865
|
+
|
|
866
|
+
if( hasInputs )
|
|
867
|
+
{
|
|
868
|
+
nodeInputs = nodeIO.querySelector( '.lexgraphnodeinputs' );
|
|
869
|
+
nodeInputs.innerHTML = "";
|
|
870
|
+
}
|
|
871
|
+
|
|
872
|
+
for( let i of node.inputs )
|
|
873
|
+
{
|
|
874
|
+
if( !i.type )
|
|
875
|
+
{
|
|
876
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
var input = document.createElement( 'div' );
|
|
881
|
+
input.className = 'lexgraphnodeio ioinput';
|
|
882
|
+
input.dataset[ 'index' ] = nodeInputs.childElementCount;
|
|
883
|
+
|
|
884
|
+
var type = document.createElement( 'span' );
|
|
885
|
+
type.className = 'io__type input ' + i.type;
|
|
886
|
+
type.innerHTML = '<span>' + i.type[ 0 ].toUpperCase() + '</span>';
|
|
887
|
+
input.appendChild( type );
|
|
888
|
+
|
|
889
|
+
var typeDesc = document.createElement( 'span' );
|
|
890
|
+
typeDesc.className = 'io__typedesc input ' + i.type;
|
|
891
|
+
typeDesc.innerHTML = i.type;
|
|
892
|
+
input.appendChild( typeDesc );
|
|
893
|
+
|
|
894
|
+
if( i.name )
|
|
895
|
+
{
|
|
896
|
+
var name = document.createElement( 'span' );
|
|
897
|
+
name.classList.add( 'io__name' );
|
|
898
|
+
name.innerText = i.name;
|
|
899
|
+
input.appendChild( name );
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
nodeInputs.appendChild( input );
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
|
|
906
|
+
// Outputs
|
|
907
|
+
{
|
|
908
|
+
var nodeOutputs = null;
|
|
909
|
+
|
|
910
|
+
if( hasOutputs )
|
|
911
|
+
{
|
|
912
|
+
nodeOutputs = nodeIO.querySelector( '.lexgraphnodeoutputs' );
|
|
913
|
+
nodeOutputs.innerHTML = "";
|
|
914
|
+
}
|
|
915
|
+
|
|
916
|
+
for( let o of node.outputs )
|
|
917
|
+
{
|
|
918
|
+
if( !o.type )
|
|
919
|
+
{
|
|
920
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
var output = document.createElement( 'div' );
|
|
924
|
+
output.className = 'lexgraphnodeio iooutput';
|
|
925
|
+
output.dataset[ 'index' ] = nodeOutputs.childElementCount;
|
|
926
|
+
|
|
927
|
+
if( o.name )
|
|
928
|
+
{
|
|
929
|
+
var name = document.createElement( 'span' );
|
|
930
|
+
name.classList.add( 'io__name' );
|
|
931
|
+
name.innerText = o.name;
|
|
932
|
+
output.appendChild( name );
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
var type = document.createElement( 'span' );
|
|
936
|
+
type.className = 'io__type output ' + o.type;
|
|
937
|
+
type.innerHTML = '<span>' + o.type[ 0 ].toUpperCase() + '</span>';
|
|
938
|
+
output.appendChild( type );
|
|
939
|
+
|
|
940
|
+
var typeDesc = document.createElement( 'span' );
|
|
941
|
+
typeDesc.className = 'io__typedesc output ' + o.type;
|
|
942
|
+
typeDesc.innerHTML = o.type;
|
|
943
|
+
output.appendChild( typeDesc );
|
|
944
|
+
|
|
945
|
+
nodeOutputs.appendChild( output );
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
this._addNodeIOEvents( dom );
|
|
950
|
+
}
|
|
951
|
+
|
|
952
|
+
_addNodeIOEvents( nodeContainer ) {
|
|
953
|
+
|
|
954
|
+
const nodeIO = nodeContainer.querySelector( '.lexgraphnodeios' );
|
|
955
|
+
|
|
753
956
|
// Manage links
|
|
754
957
|
|
|
755
958
|
nodeIO.querySelectorAll( '.lexgraphnodeio' ).forEach( el => {
|
|
@@ -775,8 +978,11 @@ class GraphEditor {
|
|
|
775
978
|
|
|
776
979
|
el.addEventListener( 'mouseup', e => {
|
|
777
980
|
|
|
981
|
+
e.stopPropagation();
|
|
982
|
+
e.stopImmediatePropagation();
|
|
983
|
+
|
|
778
984
|
// Single click..
|
|
779
|
-
if( ( LX.getTime() - this.lastMouseDown ) <
|
|
985
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 200 ) {
|
|
780
986
|
delete this._generatingLink;
|
|
781
987
|
return;
|
|
782
988
|
}
|
|
@@ -787,14 +993,11 @@ class GraphEditor {
|
|
|
787
993
|
if( !this._onLink( e ) )
|
|
788
994
|
{
|
|
789
995
|
// Delete entire SVG if not a successful connection..
|
|
790
|
-
deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
996
|
+
LX.UTILS.deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
791
997
|
}
|
|
792
998
|
|
|
793
999
|
delete this._generatingLink;
|
|
794
1000
|
}
|
|
795
|
-
|
|
796
|
-
e.stopPropagation();
|
|
797
|
-
e.stopImmediatePropagation();
|
|
798
1001
|
} );
|
|
799
1002
|
|
|
800
1003
|
el.addEventListener( 'click', e => {
|
|
@@ -808,36 +1011,29 @@ class GraphEditor {
|
|
|
808
1011
|
} );
|
|
809
1012
|
|
|
810
1013
|
} );
|
|
1014
|
+
}
|
|
811
1015
|
|
|
812
|
-
|
|
813
|
-
this.nodes[ id ] = { data: node, dom: nodeContainer };
|
|
1016
|
+
_getAllDOMNodes( includeGroups, exclude ) {
|
|
814
1017
|
|
|
815
|
-
|
|
816
|
-
nodeContainer.dataset[ 'id' ] = id;
|
|
1018
|
+
var elements = null;
|
|
817
1019
|
|
|
818
|
-
|
|
1020
|
+
if( includeGroups )
|
|
1021
|
+
elements = Array.from( this._domNodes.childNodes );
|
|
1022
|
+
else
|
|
1023
|
+
elements = Array.from( this._domNodes.childNodes ).filter( v => v.classList.contains( 'lexgraphnode' ) );
|
|
819
1024
|
|
|
820
|
-
|
|
821
|
-
if( node.title == 'Main' )
|
|
1025
|
+
if( exclude )
|
|
822
1026
|
{
|
|
823
|
-
|
|
1027
|
+
elements = elements.filter( v => v != exclude );
|
|
824
1028
|
}
|
|
825
1029
|
|
|
826
|
-
return
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
_getAllDOMNodes( includeGroups ) {
|
|
830
|
-
|
|
831
|
-
if( includeGroups )
|
|
832
|
-
return this._domNodes.childNodes;
|
|
833
|
-
|
|
834
|
-
return Array.from( this._domNodes.childNodes ).filter( v => v.classList.contains( 'lexgraphnode' ) );
|
|
1030
|
+
return elements;
|
|
835
1031
|
}
|
|
836
1032
|
|
|
837
1033
|
_onMoveNodes( target ) {
|
|
838
1034
|
|
|
839
1035
|
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
840
|
-
dT.div( this.
|
|
1036
|
+
dT.div( this.currentGraph.scale, dT);
|
|
841
1037
|
|
|
842
1038
|
for( let nodeId of this.selectedNodes )
|
|
843
1039
|
{
|
|
@@ -867,17 +1063,20 @@ class GraphEditor {
|
|
|
867
1063
|
return;
|
|
868
1064
|
|
|
869
1065
|
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
870
|
-
dT.div( this.
|
|
1066
|
+
dT.div( this.currentGraph.scale, dT);
|
|
871
1067
|
|
|
872
1068
|
this._translateNode( target, dT );
|
|
873
1069
|
|
|
874
1070
|
for( let nodeId of groupNodeIds )
|
|
875
1071
|
{
|
|
876
|
-
const
|
|
1072
|
+
const isGroup = nodeId.constructor !== String;
|
|
877
1073
|
|
|
878
|
-
this.
|
|
1074
|
+
const el = isGroup ? nodeId : this._getNodeDOMElement( nodeId );
|
|
879
1075
|
|
|
880
|
-
this.
|
|
1076
|
+
this._translateNode( el, dT, !isGroup );
|
|
1077
|
+
|
|
1078
|
+
if( !isGroup )
|
|
1079
|
+
this._updateNodeLinks( nodeId );
|
|
881
1080
|
}
|
|
882
1081
|
}
|
|
883
1082
|
|
|
@@ -889,7 +1088,7 @@ class GraphEditor {
|
|
|
889
1088
|
|
|
890
1089
|
const groupNodeIds = [ ];
|
|
891
1090
|
|
|
892
|
-
for( let dom of this._getAllDOMNodes() )
|
|
1091
|
+
for( let dom of this._getAllDOMNodes( true, target ) )
|
|
893
1092
|
{
|
|
894
1093
|
const x = parseFloat( dom.style.left );
|
|
895
1094
|
const y = parseFloat( dom.style.top );
|
|
@@ -898,7 +1097,7 @@ class GraphEditor {
|
|
|
898
1097
|
if( !group_bb.inside( node_bb ) )
|
|
899
1098
|
continue;
|
|
900
1099
|
|
|
901
|
-
groupNodeIds.push( dom.dataset[ 'id' ] );
|
|
1100
|
+
groupNodeIds.push( dom.dataset[ 'id' ] ?? dom );
|
|
902
1101
|
}
|
|
903
1102
|
|
|
904
1103
|
target.nodes = groupNodeIds;
|
|
@@ -966,6 +1165,16 @@ class GraphEditor {
|
|
|
966
1165
|
case 'select':
|
|
967
1166
|
panel.addDropdown( p.name, p.options, p.value, (v) => { p.value = v } );
|
|
968
1167
|
break;
|
|
1168
|
+
case 'array':
|
|
1169
|
+
panel.addArray( p.name, p.value, (v) => {
|
|
1170
|
+
p.value = v;
|
|
1171
|
+
if( node.type == "function/Input" )
|
|
1172
|
+
{
|
|
1173
|
+
node.setOutputs( v );
|
|
1174
|
+
this._updateNodeDOMIOs( dom, node );
|
|
1175
|
+
}
|
|
1176
|
+
}, { innerValues: p.options } );
|
|
1177
|
+
break;
|
|
969
1178
|
}
|
|
970
1179
|
}
|
|
971
1180
|
|
|
@@ -984,7 +1193,7 @@ class GraphEditor {
|
|
|
984
1193
|
this._togglePropertiesDialog( false );
|
|
985
1194
|
}
|
|
986
1195
|
|
|
987
|
-
_translateNode( dom, deltaTranslation ) {
|
|
1196
|
+
_translateNode( dom, deltaTranslation, updateBasePosition = true ) {
|
|
988
1197
|
|
|
989
1198
|
const translation = deltaTranslation.add( new LX.vec2( parseFloat( dom.style.left ), parseFloat( dom.style.top ) ) );
|
|
990
1199
|
|
|
@@ -998,27 +1207,44 @@ class GraphEditor {
|
|
|
998
1207
|
|
|
999
1208
|
dom.style.left = ( translation.x ) + "px";
|
|
1000
1209
|
dom.style.top = ( translation.y ) + "px";
|
|
1210
|
+
|
|
1211
|
+
// Update base node position..
|
|
1212
|
+
if( updateBasePosition && dom.dataset[ 'id' ] )
|
|
1213
|
+
{
|
|
1214
|
+
let baseNode = this.nodes[ dom.dataset[ 'id' ] ];
|
|
1215
|
+
baseNode.data.position = translation;
|
|
1216
|
+
}
|
|
1001
1217
|
}
|
|
1002
1218
|
|
|
1003
1219
|
_deleteNode( nodeId ) {
|
|
1004
1220
|
|
|
1005
|
-
const
|
|
1221
|
+
const nodeInfo = this.nodes[ nodeId ];
|
|
1222
|
+
const node = nodeInfo.data;
|
|
1223
|
+
const el = nodeInfo.dom;
|
|
1006
1224
|
|
|
1007
1225
|
console.assert( el );
|
|
1008
1226
|
|
|
1009
|
-
if(
|
|
1227
|
+
if( node.constructor.blockDelete )
|
|
1010
1228
|
{
|
|
1011
|
-
console.warn( `Can't delete
|
|
1229
|
+
console.warn( `Can't delete node!` );
|
|
1012
1230
|
return;
|
|
1013
1231
|
}
|
|
1014
1232
|
|
|
1015
|
-
deleteElement( el );
|
|
1233
|
+
LX.UTILS.deleteElement( el );
|
|
1234
|
+
|
|
1235
|
+
// Delete from the editor
|
|
1016
1236
|
|
|
1017
1237
|
delete this.nodes[ nodeId ];
|
|
1018
1238
|
|
|
1239
|
+
// Delete from the graph data
|
|
1240
|
+
|
|
1241
|
+
const idx = this.currentGraph.nodes.findIndex( v => v.id === nodeId );
|
|
1242
|
+
console.assert( idx >= 0 );
|
|
1243
|
+
this.currentGraph.nodes.splice( idx, 1 );
|
|
1244
|
+
|
|
1019
1245
|
// Delete connected links..
|
|
1020
1246
|
|
|
1021
|
-
for( let key in this.
|
|
1247
|
+
for( let key in this.currentGraph.links )
|
|
1022
1248
|
{
|
|
1023
1249
|
if( !key.includes( nodeId ) )
|
|
1024
1250
|
continue;
|
|
@@ -1028,13 +1254,13 @@ class GraphEditor {
|
|
|
1028
1254
|
|
|
1029
1255
|
// Remove the connection from the other before deleting..
|
|
1030
1256
|
|
|
1031
|
-
const numLinks = this.
|
|
1257
|
+
const numLinks = this.currentGraph.links[ key ].length;
|
|
1032
1258
|
|
|
1033
1259
|
for( var i = 0; i < numLinks; ++i )
|
|
1034
1260
|
{
|
|
1035
|
-
var link = this.
|
|
1261
|
+
var link = this.currentGraph.links[ key ][ i ];
|
|
1036
1262
|
|
|
1037
|
-
deleteElement( link.path.parentElement );
|
|
1263
|
+
LX.UTILS.deleteElement( link.path.parentElement );
|
|
1038
1264
|
|
|
1039
1265
|
const targetNodeId = targetIsInput ? link.inputNode : link.outputNode;
|
|
1040
1266
|
|
|
@@ -1057,19 +1283,38 @@ class GraphEditor {
|
|
|
1057
1283
|
{
|
|
1058
1284
|
var active = false;
|
|
1059
1285
|
for( var links of io.links )
|
|
1060
|
-
|
|
1061
|
-
|
|
1286
|
+
{
|
|
1287
|
+
if( !links )
|
|
1288
|
+
continue;
|
|
1289
|
+
for( var j of links ) {
|
|
1062
1290
|
active |= ( !!j );
|
|
1063
1291
|
}
|
|
1292
|
+
}
|
|
1064
1293
|
if( !active )
|
|
1065
1294
|
delete io.dataset[ 'active' ];
|
|
1066
1295
|
}
|
|
1067
1296
|
}
|
|
1068
1297
|
|
|
1069
|
-
delete this.
|
|
1298
|
+
delete this.currentGraph.links[ key ];
|
|
1070
1299
|
}
|
|
1071
1300
|
}
|
|
1072
1301
|
|
|
1302
|
+
_deleteGroup( groupId ) {
|
|
1303
|
+
|
|
1304
|
+
const dom = this.groups[ groupId ];
|
|
1305
|
+
LX.UTILS.deleteElement( dom );
|
|
1306
|
+
|
|
1307
|
+
// Delete from the editor
|
|
1308
|
+
|
|
1309
|
+
delete this.groups[ groupId ];
|
|
1310
|
+
|
|
1311
|
+
// Delete from the graph data
|
|
1312
|
+
|
|
1313
|
+
const idx = this.currentGraph.groups.findIndex( v => v.id === groupId );
|
|
1314
|
+
console.assert( idx >= 0 );
|
|
1315
|
+
this.currentGraph.groups.splice( idx, 1 );
|
|
1316
|
+
}
|
|
1317
|
+
|
|
1073
1318
|
_cloneNodes() {
|
|
1074
1319
|
|
|
1075
1320
|
// Clone all selected nodes
|
|
@@ -1092,7 +1337,7 @@ class GraphEditor {
|
|
|
1092
1337
|
|
|
1093
1338
|
this._selectNode( newDom, true );
|
|
1094
1339
|
|
|
1095
|
-
this.
|
|
1340
|
+
this.currentGraph.nodes.push( newNode );
|
|
1096
1341
|
}
|
|
1097
1342
|
}
|
|
1098
1343
|
|
|
@@ -1110,7 +1355,7 @@ class GraphEditor {
|
|
|
1110
1355
|
_getLinks( nodeSrcId, nodeDstId ) {
|
|
1111
1356
|
|
|
1112
1357
|
const str = nodeSrcId + '@' + nodeDstId;
|
|
1113
|
-
return this.
|
|
1358
|
+
return this.currentGraph.links[ str ];
|
|
1114
1359
|
}
|
|
1115
1360
|
|
|
1116
1361
|
_deleteLinks( nodeId, io ) {
|
|
@@ -1132,7 +1377,7 @@ class GraphEditor {
|
|
|
1132
1377
|
var links = this._getLinks( targetId, nodeId );
|
|
1133
1378
|
|
|
1134
1379
|
var linkIdx = links.findIndex( i => ( i.inputIdx == srcIndex && i.outputIdx == targetIndex ) );
|
|
1135
|
-
deleteElement( links[ linkIdx ].path.parentElement );
|
|
1380
|
+
LX.UTILS.deleteElement( links[ linkIdx ].path.parentElement );
|
|
1136
1381
|
links.splice( linkIdx, 1 );
|
|
1137
1382
|
|
|
1138
1383
|
// Input has no longer any connected link
|
|
@@ -1182,7 +1427,7 @@ class GraphEditor {
|
|
|
1182
1427
|
var links = this._getLinks( nodeId, targetId );
|
|
1183
1428
|
|
|
1184
1429
|
var linkIdx = links.findIndex( i => ( i.inputIdx == targetIndex && i.outputIdx == srcIndex ) );
|
|
1185
|
-
deleteElement( links[ linkIdx ].path.parentElement );
|
|
1430
|
+
LX.UTILS.deleteElement( links[ linkIdx ].path.parentElement );
|
|
1186
1431
|
links.splice( linkIdx, 1 );
|
|
1187
1432
|
|
|
1188
1433
|
// Remove a connection from the output connections
|
|
@@ -1274,7 +1519,7 @@ class GraphEditor {
|
|
|
1274
1519
|
|
|
1275
1520
|
if( this._snapToGrid )
|
|
1276
1521
|
{
|
|
1277
|
-
const snapSize = this._patternSize.x * this._snapValue * this.
|
|
1522
|
+
const snapSize = this._patternSize.x * this._snapValue * this.currentGraph.scale;
|
|
1278
1523
|
snapPosition.x = Math.floor( snapPosition.x / snapSize ) * snapSize;
|
|
1279
1524
|
snapPosition.y = Math.floor( snapPosition.y / snapSize ) * snapSize;
|
|
1280
1525
|
this._snappedDeltaMousePosition = snapPosition.sub( this._lastSnappedMousePosition );
|
|
@@ -1291,7 +1536,8 @@ class GraphEditor {
|
|
|
1291
1536
|
|
|
1292
1537
|
else if( e.type == 'mouseup' )
|
|
1293
1538
|
{
|
|
1294
|
-
if( ( LX.getTime() - this.lastMouseDown ) <
|
|
1539
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 200 ) {
|
|
1540
|
+
|
|
1295
1541
|
this._processClick( e );
|
|
1296
1542
|
}
|
|
1297
1543
|
|
|
@@ -1323,7 +1569,7 @@ class GraphEditor {
|
|
|
1323
1569
|
|
|
1324
1570
|
e.preventDefault();
|
|
1325
1571
|
|
|
1326
|
-
if( ( LX.getTime() - this.lastMouseDown ) <
|
|
1572
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 300 ) {
|
|
1327
1573
|
this._processContextMenu( e );
|
|
1328
1574
|
}
|
|
1329
1575
|
}
|
|
@@ -1374,8 +1620,14 @@ class GraphEditor {
|
|
|
1374
1620
|
// It the event reaches this, the link isn't valid..
|
|
1375
1621
|
if( this._generatingLink )
|
|
1376
1622
|
{
|
|
1377
|
-
|
|
1623
|
+
const linkInfo = Object.assign( { }, this._generatingLink );
|
|
1624
|
+
|
|
1625
|
+
// Delete old link
|
|
1626
|
+
LX.UTILS.deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
1378
1627
|
delete this._generatingLink;
|
|
1628
|
+
|
|
1629
|
+
// Open contextmenu to auto-connect something..
|
|
1630
|
+
this._processContextMenu( e, linkInfo );
|
|
1379
1631
|
}
|
|
1380
1632
|
|
|
1381
1633
|
else if( this._boxSelecting )
|
|
@@ -1385,7 +1637,7 @@ class GraphEditor {
|
|
|
1385
1637
|
|
|
1386
1638
|
this._selectNodesInBox( this._boxSelecting, this._mousePosition, e.altKey );
|
|
1387
1639
|
|
|
1388
|
-
deleteElement( this._currentBoxSelectionSVG );
|
|
1640
|
+
LX.UTILS.deleteElement( this._currentBoxSelectionSVG );
|
|
1389
1641
|
|
|
1390
1642
|
delete this._currentBoxSelectionSVG;
|
|
1391
1643
|
delete this._boxSelecting;
|
|
@@ -1399,7 +1651,7 @@ class GraphEditor {
|
|
|
1399
1651
|
|
|
1400
1652
|
if( rightPressed )
|
|
1401
1653
|
{
|
|
1402
|
-
this.
|
|
1654
|
+
this.currentGraph.translation.add( this._deltaMousePosition.div( this.currentGraph.scale ), this.currentGraph.translation );
|
|
1403
1655
|
|
|
1404
1656
|
this._updatePattern();
|
|
1405
1657
|
|
|
@@ -1435,10 +1687,10 @@ class GraphEditor {
|
|
|
1435
1687
|
|
|
1436
1688
|
const delta = e.deltaY;
|
|
1437
1689
|
|
|
1438
|
-
if( delta > 0.0 ) this.
|
|
1439
|
-
else this.
|
|
1690
|
+
if( delta > 0.0 ) this.currentGraph.scale *= 0.9;
|
|
1691
|
+
else this.currentGraph.scale *= ( 1.0 / 0.9 );
|
|
1440
1692
|
|
|
1441
|
-
this.
|
|
1693
|
+
this.currentGraph.scale = LX.UTILS.clamp( this.currentGraph.scale, GraphEditor.MIN_SCALE, GraphEditor.MAX_SCALE );
|
|
1442
1694
|
|
|
1443
1695
|
// Compute zoom center in pattern space using new scale
|
|
1444
1696
|
// and get delta..
|
|
@@ -1447,44 +1699,87 @@ class GraphEditor {
|
|
|
1447
1699
|
|
|
1448
1700
|
const deltaCenter = newCenter.sub( center );
|
|
1449
1701
|
|
|
1450
|
-
this.
|
|
1702
|
+
this.currentGraph.translation.add( deltaCenter, this.currentGraph.translation );
|
|
1451
1703
|
|
|
1452
|
-
this._updatePattern(
|
|
1704
|
+
this._updatePattern();
|
|
1453
1705
|
}
|
|
1454
1706
|
|
|
1455
|
-
_processContextMenu( e ) {
|
|
1707
|
+
_processContextMenu( e, autoConnect ) {
|
|
1456
1708
|
|
|
1457
|
-
|
|
1458
|
-
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
dom.mustSnap = true;
|
|
1470
|
-
}
|
|
1709
|
+
if( this._clipboardData )
|
|
1710
|
+
{
|
|
1711
|
+
LX.addContextMenu(null, e, m => {
|
|
1712
|
+
m.add( "Paste", () => {
|
|
1713
|
+
// TODO: paste node data
|
|
1714
|
+
// ...
|
|
1715
|
+
} );
|
|
1716
|
+
});
|
|
1717
|
+
}
|
|
1718
|
+
else
|
|
1719
|
+
{
|
|
1720
|
+
LX.addContextMenu( "ADD NODE", e, m => {
|
|
1471
1721
|
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
let position = new LX.vec2( e.clientX - rect.x, e.clientY - rect.y );
|
|
1477
|
-
|
|
1478
|
-
position = this._getPatternPosition( position );
|
|
1479
|
-
|
|
1480
|
-
this._translateNode( dom, position );
|
|
1481
|
-
}
|
|
1722
|
+
for( let type in GraphEditor.NODE_TYPES )
|
|
1723
|
+
{
|
|
1724
|
+
const baseClass = GraphEditor.NODE_TYPES[ type ];
|
|
1482
1725
|
|
|
1483
|
-
|
|
1726
|
+
if( baseClass.blockAdd )
|
|
1727
|
+
continue;
|
|
1484
1728
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1729
|
+
m.add( type, () => {
|
|
1730
|
+
|
|
1731
|
+
const newNode = GraphEditor.addNode( type );
|
|
1732
|
+
|
|
1733
|
+
const dom = this._createNodeDOM( newNode );
|
|
1734
|
+
|
|
1735
|
+
if( this._snapToGrid )
|
|
1736
|
+
{
|
|
1737
|
+
dom.mustSnap = true;
|
|
1738
|
+
}
|
|
1739
|
+
|
|
1740
|
+
if( e )
|
|
1741
|
+
{
|
|
1742
|
+
const rect = this.root.getBoundingClientRect();
|
|
1743
|
+
|
|
1744
|
+
let position = new LX.vec2( e.clientX - rect.x, e.clientY - rect.y );
|
|
1745
|
+
|
|
1746
|
+
position = this._getPatternPosition( position );
|
|
1747
|
+
|
|
1748
|
+
this._translateNode( dom, position );
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
this.currentGraph.nodes.push( newNode );
|
|
1752
|
+
|
|
1753
|
+
if( autoConnect && newNode.inputs.length )
|
|
1754
|
+
{
|
|
1755
|
+
const srcId = autoConnect.domEl.dataset[ 'id' ];
|
|
1756
|
+
const srcType = autoConnect.io.childNodes[ autoConnect.index ].dataset[ 'type' ];
|
|
1757
|
+
const srcIsInput = autoConnect.ioType == GraphEditor.NODE_IO_INPUT;
|
|
1758
|
+
|
|
1759
|
+
const newLink = {
|
|
1760
|
+
inputNode: srcIsInput ? srcId : newNode.id,
|
|
1761
|
+
inputIdx: srcIsInput ? autoConnect.index : 0,
|
|
1762
|
+
inputType: srcIsInput ? srcType : newNode.inputs[ 0 ].type,
|
|
1763
|
+
outputNode: srcIsInput ? newNode.id : srcId,
|
|
1764
|
+
outputIdx: srcIsInput ? 0 : autoConnect.index,
|
|
1765
|
+
outputType: srcIsInput ? newNode.inputs[ 0 ].type : srcType,
|
|
1766
|
+
}
|
|
1767
|
+
|
|
1768
|
+
// Store link
|
|
1769
|
+
|
|
1770
|
+
const pathId = newLink.outputNode + '@' + newLink.inputNode;
|
|
1771
|
+
|
|
1772
|
+
if( !this.currentGraph.links[ pathId ] ) this.currentGraph.links[ pathId ] = [];
|
|
1773
|
+
|
|
1774
|
+
this.currentGraph.links[ pathId ].push( newLink );
|
|
1775
|
+
|
|
1776
|
+
this._createLink( newLink );
|
|
1777
|
+
}
|
|
1778
|
+
|
|
1779
|
+
} );
|
|
1780
|
+
}
|
|
1781
|
+
});
|
|
1782
|
+
}
|
|
1488
1783
|
}
|
|
1489
1784
|
|
|
1490
1785
|
/**
|
|
@@ -1494,6 +1789,7 @@ class GraphEditor {
|
|
|
1494
1789
|
start() {
|
|
1495
1790
|
|
|
1496
1791
|
this.mustStop = false;
|
|
1792
|
+
this.state = GraphEditor.RUNNING;
|
|
1497
1793
|
|
|
1498
1794
|
this.propagateEventToAllNodes( 'onStart' );
|
|
1499
1795
|
|
|
@@ -1525,70 +1821,8 @@ class GraphEditor {
|
|
|
1525
1821
|
|
|
1526
1822
|
requestAnimationFrame( this._frame.bind(this) );
|
|
1527
1823
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
/**
|
|
1532
|
-
* @method _runStep
|
|
1533
|
-
*/
|
|
1534
|
-
|
|
1535
|
-
_runStep() {
|
|
1536
|
-
|
|
1537
|
-
const main = this.main;
|
|
1538
|
-
|
|
1539
|
-
if( !main )
|
|
1540
|
-
return;
|
|
1541
|
-
|
|
1542
|
-
const visitedNodes = { };
|
|
1543
|
-
|
|
1544
|
-
this._executionNodes = [ ];
|
|
1545
|
-
|
|
1546
|
-
// Reser variables each step?
|
|
1547
|
-
this.variables = { };
|
|
1548
|
-
|
|
1549
|
-
const mainId = this.main.dataset[ 'id' ];
|
|
1550
|
-
|
|
1551
|
-
const addNode = ( id ) => {
|
|
1552
|
-
|
|
1553
|
-
if( visitedNodes[ id ] )
|
|
1554
|
-
return;
|
|
1555
|
-
|
|
1556
|
-
visitedNodes[ id ] = true;
|
|
1557
|
-
|
|
1558
|
-
for( let linkId in this.graph.links )
|
|
1559
|
-
{
|
|
1560
|
-
const idx = linkId.indexOf( '@' + id );
|
|
1561
|
-
|
|
1562
|
-
if( idx < 0 )
|
|
1563
|
-
continue;
|
|
1564
|
-
|
|
1565
|
-
const preNodeId = linkId.substring( 0, idx );
|
|
1566
|
-
|
|
1567
|
-
this._executionNodes.push( preNodeId );
|
|
1568
|
-
|
|
1569
|
-
addNode( preNodeId );
|
|
1570
|
-
}
|
|
1571
|
-
};
|
|
1572
|
-
|
|
1573
|
-
// TODO: Search "no output" nodes and add to the executable list (same as main)..
|
|
1574
|
-
// ...
|
|
1575
|
-
|
|
1576
|
-
this._executionNodes.push( mainId );
|
|
1577
|
-
|
|
1578
|
-
addNode( mainId );
|
|
1579
|
-
|
|
1580
|
-
for( var i = this._executionNodes.length - 1; i >= 0; --i )
|
|
1581
|
-
{
|
|
1582
|
-
const node = this.nodes[ this._executionNodes[ i ] ];
|
|
1583
|
-
|
|
1584
|
-
if( node.data.onBeforeStep )
|
|
1585
|
-
node.data.onBeforeStep();
|
|
1586
|
-
|
|
1587
|
-
node.data.execute();
|
|
1588
|
-
|
|
1589
|
-
if( node.data.onBeforeStep )
|
|
1590
|
-
node.data.onAfterStep();
|
|
1591
|
-
}
|
|
1824
|
+
// Only run here main graph!
|
|
1825
|
+
this.currentGraph._runStep( this.main );
|
|
1592
1826
|
}
|
|
1593
1827
|
|
|
1594
1828
|
_generatePattern() {
|
|
@@ -1597,8 +1831,8 @@ class GraphEditor {
|
|
|
1597
1831
|
{
|
|
1598
1832
|
var pattern = document.createElementNS( 'http://www.w3.org/2000/svg', 'pattern' );
|
|
1599
1833
|
pattern.setAttribute( 'id', 'pattern-0' );
|
|
1600
|
-
pattern.setAttribute( 'x',
|
|
1601
|
-
pattern.setAttribute( 'y',
|
|
1834
|
+
pattern.setAttribute( 'x', 0.0 );
|
|
1835
|
+
pattern.setAttribute( 'y', 0.0 );
|
|
1602
1836
|
pattern.setAttribute( 'width', this._patternSize.x )
|
|
1603
1837
|
pattern.setAttribute( 'height', this._patternSize.y );
|
|
1604
1838
|
pattern.setAttribute( 'patternUnits', 'userSpaceOnUse' );
|
|
@@ -1638,9 +1872,9 @@ class GraphEditor {
|
|
|
1638
1872
|
if( !this._background )
|
|
1639
1873
|
return;
|
|
1640
1874
|
|
|
1641
|
-
const patternSize = this._patternSize.mul( this.
|
|
1642
|
-
const circlePatternSize = this._circlePatternSize * this.
|
|
1643
|
-
const patternPosition = this.
|
|
1875
|
+
const patternSize = this._patternSize.mul( this.currentGraph.scale );
|
|
1876
|
+
const circlePatternSize = this._circlePatternSize * this.currentGraph.scale;
|
|
1877
|
+
const patternPosition = this.currentGraph.translation.mul( this.currentGraph.scale );
|
|
1644
1878
|
|
|
1645
1879
|
let pattern = this._background.querySelector( 'pattern' );
|
|
1646
1880
|
pattern.setAttribute( 'x', patternPosition.x );
|
|
@@ -1658,24 +1892,34 @@ class GraphEditor {
|
|
|
1658
1892
|
const w = this._domNodes.offsetWidth * 0.5;
|
|
1659
1893
|
const h = this._domNodes.offsetHeight * 0.5;
|
|
1660
1894
|
|
|
1661
|
-
const dw = w - w * this.
|
|
1662
|
-
const dh = h - h * this.
|
|
1895
|
+
const dw = w - w * this.currentGraph.scale;
|
|
1896
|
+
const dh = h - h * this.currentGraph.scale;
|
|
1663
1897
|
|
|
1664
1898
|
this._domNodes.style.transform = `
|
|
1665
1899
|
translate(` + ( patternPosition.x - dw ) + `px, ` + ( patternPosition.y - dh ) + `px)
|
|
1666
|
-
scale(` + this.
|
|
1900
|
+
scale(` + this.currentGraph.scale + `)
|
|
1667
1901
|
`;
|
|
1668
1902
|
this._domLinks.style.transform = this._domNodes.style.transform;
|
|
1903
|
+
|
|
1904
|
+
// Hide nodes outside the viewport
|
|
1905
|
+
|
|
1906
|
+
const nodesOutsideViewport = this._getNonVisibleNodes();
|
|
1907
|
+
|
|
1908
|
+
for( let node of nodesOutsideViewport )
|
|
1909
|
+
{
|
|
1910
|
+
let dom = this._getNodeDOMElement( node.id );
|
|
1911
|
+
dom.classList.toggle( 'hiddenOpacity', true );
|
|
1912
|
+
}
|
|
1669
1913
|
}
|
|
1670
1914
|
|
|
1671
1915
|
_getPatternPosition( renderPosition ) {
|
|
1672
1916
|
|
|
1673
|
-
return renderPosition.div( this.
|
|
1917
|
+
return renderPosition.div( this.currentGraph.scale ).sub( this.currentGraph.translation );
|
|
1674
1918
|
}
|
|
1675
1919
|
|
|
1676
1920
|
_getRenderPosition( patternPosition ) {
|
|
1677
1921
|
|
|
1678
|
-
return patternPosition.add( this.
|
|
1922
|
+
return patternPosition.add( this.currentGraph.translation ).mul( this.currentGraph.scale );
|
|
1679
1923
|
}
|
|
1680
1924
|
|
|
1681
1925
|
_onLink( e ) {
|
|
@@ -1738,11 +1982,6 @@ class GraphEditor {
|
|
|
1738
1982
|
this._deleteLinks( src_nodeId, linkData.io );
|
|
1739
1983
|
}
|
|
1740
1984
|
|
|
1741
|
-
// Mark as active
|
|
1742
|
-
|
|
1743
|
-
linkData.io.dataset[ 'active' ] = true;
|
|
1744
|
-
e.target.parentElement.dataset[ 'active' ] = true;
|
|
1745
|
-
|
|
1746
1985
|
// Store the end io..
|
|
1747
1986
|
|
|
1748
1987
|
var srcDom = linkData.io;
|
|
@@ -1763,9 +2002,9 @@ class GraphEditor {
|
|
|
1763
2002
|
|
|
1764
2003
|
const pathId = ( srcIsInput ? dst_nodeId : src_nodeId ) + '@' + ( srcIsInput ? src_nodeId : dst_nodeId );
|
|
1765
2004
|
|
|
1766
|
-
if( !this.
|
|
2005
|
+
if( !this.currentGraph.links[ pathId ] ) this.currentGraph.links[ pathId ] = [];
|
|
1767
2006
|
|
|
1768
|
-
this.
|
|
2007
|
+
this.currentGraph.links[ pathId ].push( {
|
|
1769
2008
|
path: path,
|
|
1770
2009
|
inputNode: srcIsInput ? src_nodeId : dst_nodeId,
|
|
1771
2010
|
inputIdx: srcIsInput ? src_ioIndex : dst_ioIndex,
|
|
@@ -1777,6 +2016,11 @@ class GraphEditor {
|
|
|
1777
2016
|
|
|
1778
2017
|
path.dataset[ 'id' ] = pathId;
|
|
1779
2018
|
|
|
2019
|
+
// Mark as active links...
|
|
2020
|
+
|
|
2021
|
+
linkData.io.dataset[ 'active' ] = true;
|
|
2022
|
+
e.target.parentElement.dataset[ 'active' ] = true;
|
|
2023
|
+
|
|
1780
2024
|
// Successful link..
|
|
1781
2025
|
return true;
|
|
1782
2026
|
}
|
|
@@ -1814,6 +2058,7 @@ class GraphEditor {
|
|
|
1814
2058
|
|
|
1815
2059
|
let startPos = new LX.vec2( startRect.x - offsetX, startRect.y - offsetY );
|
|
1816
2060
|
let endPos = null;
|
|
2061
|
+
let endioEl = null;
|
|
1817
2062
|
|
|
1818
2063
|
if( e )
|
|
1819
2064
|
{
|
|
@@ -1822,7 +2067,6 @@ class GraphEditor {
|
|
|
1822
2067
|
// Add node position, since I can't get the correct position directly from the event..
|
|
1823
2068
|
if( e.target.hasClass( [ 'lexgraphnode', 'lexgraphgroup' ] ) )
|
|
1824
2069
|
{
|
|
1825
|
-
console.log( this._getNodePosition( e.target ) );
|
|
1826
2070
|
endPos.add( this._getNodePosition( e.target ), endPos );
|
|
1827
2071
|
endPos.add( new LX.vec2( 3, 3 ), endPos );
|
|
1828
2072
|
}
|
|
@@ -1840,7 +2084,8 @@ class GraphEditor {
|
|
|
1840
2084
|
}
|
|
1841
2085
|
else
|
|
1842
2086
|
{
|
|
1843
|
-
|
|
2087
|
+
endioEl = endIO.querySelector( '.io__type' );
|
|
2088
|
+
const ioRect = endioEl.getBoundingClientRect();
|
|
1844
2089
|
endPos = new LX.vec2( ioRect.x - offsetX, ioRect.y - offsetY );
|
|
1845
2090
|
}
|
|
1846
2091
|
|
|
@@ -1851,7 +2096,11 @@ class GraphEditor {
|
|
|
1851
2096
|
startPos = tmp;
|
|
1852
2097
|
}
|
|
1853
2098
|
|
|
1854
|
-
|
|
2099
|
+
let color = getComputedStyle( ioEl ).backgroundColor;
|
|
2100
|
+
|
|
2101
|
+
if( type == GraphEditor.NODE_IO_OUTPUT && endioEl )
|
|
2102
|
+
color = getComputedStyle( endioEl ).backgroundColor;
|
|
2103
|
+
|
|
1855
2104
|
this._createLinkPath( path, startPos, endPos, color, !!e );
|
|
1856
2105
|
|
|
1857
2106
|
return path;
|
|
@@ -1907,11 +2156,14 @@ class GraphEditor {
|
|
|
1907
2156
|
io1.links = [ ];
|
|
1908
2157
|
io1.links[ link.outputIdx ] = io1.links[ link.outputIdx ] ?? [ ];
|
|
1909
2158
|
io1.links[ link.outputIdx ].push( link.outputNode );
|
|
2159
|
+
|
|
2160
|
+
io0.dataset[ 'active' ] = true;
|
|
2161
|
+
io1.dataset[ 'active' ] = true;
|
|
1910
2162
|
}
|
|
1911
2163
|
|
|
1912
2164
|
_createLinkPath( path, startPos, endPos, color, exactEnd ) {
|
|
1913
2165
|
|
|
1914
|
-
const dist = 6 * this.
|
|
2166
|
+
const dist = 6 * this.currentGraph.scale;
|
|
1915
2167
|
startPos.add( new LX.vec2( dist, dist ), startPos );
|
|
1916
2168
|
|
|
1917
2169
|
if( !exactEnd )
|
|
@@ -2062,16 +2314,44 @@ class GraphEditor {
|
|
|
2062
2314
|
"/>`;
|
|
2063
2315
|
}
|
|
2064
2316
|
|
|
2065
|
-
|
|
2066
|
-
_getVisibleNodes() {
|
|
2317
|
+
_getNonVisibleNodes() {
|
|
2067
2318
|
|
|
2068
|
-
|
|
2319
|
+
const nonVisibleNodes = [ ];
|
|
2320
|
+
|
|
2321
|
+
if( !this.currentGraph )
|
|
2069
2322
|
{
|
|
2070
2323
|
console.warn( "No graph set" );
|
|
2071
2324
|
return [];
|
|
2072
2325
|
}
|
|
2073
2326
|
|
|
2074
|
-
|
|
2327
|
+
const graph_bb = new BoundingBox( new LX.vec2( 0, 0 ), new LX.vec2( this.root.offsetWidth, this.root.offsetHeight ) );
|
|
2328
|
+
|
|
2329
|
+
for( let node of this.currentGraph.nodes )
|
|
2330
|
+
{
|
|
2331
|
+
let pos = this._getRenderPosition( node.position );
|
|
2332
|
+
|
|
2333
|
+
let dom = this._getNodeDOMElement( node.id );
|
|
2334
|
+
|
|
2335
|
+
if( !dom )
|
|
2336
|
+
continue;
|
|
2337
|
+
|
|
2338
|
+
const node_bb = new BoundingBox( pos, node.size.mul( this.currentGraph.scale ) );
|
|
2339
|
+
|
|
2340
|
+
if( graph_bb.inside( node_bb, false ) )
|
|
2341
|
+
{
|
|
2342
|
+
// Show if node in viewport..
|
|
2343
|
+
dom.classList.toggle( 'hiddenOpacity', false );
|
|
2344
|
+
|
|
2345
|
+
// And hide content if scale is very small..
|
|
2346
|
+
dom.childNodes[ 1 ].classList.toggle( 'hiddenOpacity', this.currentGraph.scale < 0.5 );
|
|
2347
|
+
|
|
2348
|
+
continue;
|
|
2349
|
+
}
|
|
2350
|
+
|
|
2351
|
+
nonVisibleNodes.push( node );
|
|
2352
|
+
}
|
|
2353
|
+
|
|
2354
|
+
return nonVisibleNodes;
|
|
2075
2355
|
}
|
|
2076
2356
|
|
|
2077
2357
|
_selectNodesInBox( lt, rb, remove ) {
|
|
@@ -2145,11 +2425,8 @@ class GraphEditor {
|
|
|
2145
2425
|
|
|
2146
2426
|
for( let nodeId of nodeIds )
|
|
2147
2427
|
{
|
|
2148
|
-
const
|
|
2149
|
-
|
|
2150
|
-
const x = parseFloat( dom.style.left );
|
|
2151
|
-
const y = parseFloat( dom.style.top );
|
|
2152
|
-
const node_bb = new BoundingBox( new LX.vec2( x, y ), new LX.vec2( dom.offsetWidth - 6, dom.offsetHeight - 6 ) );
|
|
2428
|
+
const node = this.nodes[ nodeId ].data;
|
|
2429
|
+
const node_bb = new BoundingBox( node.position, node.size );
|
|
2153
2430
|
|
|
2154
2431
|
if( group_bb )
|
|
2155
2432
|
{
|
|
@@ -2180,11 +2457,13 @@ class GraphEditor {
|
|
|
2180
2457
|
* @returns JSON data from the serialized graph
|
|
2181
2458
|
*/
|
|
2182
2459
|
|
|
2183
|
-
_createGroup() {
|
|
2460
|
+
_createGroup( bb ) {
|
|
2184
2461
|
|
|
2185
|
-
const group_bb = this._getBoundingFromNodes( this.selectedNodes );
|
|
2462
|
+
const group_bb = bb ?? this._getBoundingFromNodes( this.selectedNodes );
|
|
2463
|
+
const group_id = bb ? bb.id : "group-" + LX.UTILS.uidGenerator();
|
|
2186
2464
|
|
|
2187
2465
|
let groupDOM = document.createElement( 'div' );
|
|
2466
|
+
groupDOM.id = group_id;
|
|
2188
2467
|
groupDOM.classList.add( 'lexgraphgroup' );
|
|
2189
2468
|
groupDOM.style.left = group_bb.origin.x + "px";
|
|
2190
2469
|
groupDOM.style.top = group_bb.origin.y + "px";
|
|
@@ -2196,6 +2475,8 @@ class GraphEditor {
|
|
|
2196
2475
|
|
|
2197
2476
|
groupResizer.addEventListener( 'mousedown', inner_mousedown );
|
|
2198
2477
|
|
|
2478
|
+
this.groups[ group_id ] = groupDOM;
|
|
2479
|
+
|
|
2199
2480
|
var that = this;
|
|
2200
2481
|
var lastPos = [0,0];
|
|
2201
2482
|
|
|
@@ -2215,7 +2496,7 @@ class GraphEditor {
|
|
|
2215
2496
|
function inner_mousemove( e )
|
|
2216
2497
|
{
|
|
2217
2498
|
let dt = new LX.vec2( lastPos[0] - e.x, lastPos[1] - e.y );
|
|
2218
|
-
dt.div( that.
|
|
2499
|
+
dt.div( that.currentGraph.scale, dt);
|
|
2219
2500
|
|
|
2220
2501
|
groupDOM.style.width = ( parseFloat( groupDOM.style.width ) - dt.x ) + "px";
|
|
2221
2502
|
groupDOM.style.height = ( parseFloat( groupDOM.style.height ) - dt.y ) + "px";
|
|
@@ -2273,6 +2554,19 @@ class GraphEditor {
|
|
|
2273
2554
|
groupTitle.focus();
|
|
2274
2555
|
} );
|
|
2275
2556
|
|
|
2557
|
+
groupDOM.addEventListener( 'contextmenu', e => {
|
|
2558
|
+
|
|
2559
|
+
e.preventDefault();
|
|
2560
|
+
e.stopPropagation();
|
|
2561
|
+
e.stopImmediatePropagation();
|
|
2562
|
+
|
|
2563
|
+
LX.addContextMenu(null, e, m => {
|
|
2564
|
+
m.add( "Delete", () => {
|
|
2565
|
+
this._deleteGroup( group_id );
|
|
2566
|
+
} );
|
|
2567
|
+
});
|
|
2568
|
+
} );
|
|
2569
|
+
|
|
2276
2570
|
groupDOM.appendChild( groupResizer );
|
|
2277
2571
|
groupDOM.appendChild( groupTitle );
|
|
2278
2572
|
|
|
@@ -2286,6 +2580,8 @@ class GraphEditor {
|
|
|
2286
2580
|
} );
|
|
2287
2581
|
|
|
2288
2582
|
GraphEditor.LAST_GROUP_ID++;
|
|
2583
|
+
|
|
2584
|
+
return groupDOM;
|
|
2289
2585
|
}
|
|
2290
2586
|
|
|
2291
2587
|
_addUndoStep( deleteRedo = true ) {
|
|
@@ -2370,9 +2666,43 @@ class GraphEditor {
|
|
|
2370
2666
|
}
|
|
2371
2667
|
}
|
|
2372
2668
|
|
|
2373
|
-
_toggleSideBar() {
|
|
2669
|
+
_toggleSideBar( force ) {
|
|
2670
|
+
|
|
2671
|
+
this._sidebarActive = force ?? !this._sidebarActive;
|
|
2672
|
+
this._sidebarDom.classList.toggle( 'hidden', !this._sidebarActive );
|
|
2673
|
+
this._graphContainer.style.width = this._sidebarActive ? "calc( 100% - 64px )" : "100%";
|
|
2674
|
+
}
|
|
2675
|
+
|
|
2676
|
+
_onSidebarCreate( e ) {
|
|
2677
|
+
|
|
2678
|
+
LX.addContextMenu(null, e, m => {
|
|
2679
|
+
m.add( "Graph", () => this.addGraph() );
|
|
2680
|
+
m.add( "Function", () => this.addGraphFunction() );
|
|
2681
|
+
});
|
|
2682
|
+
}
|
|
2683
|
+
|
|
2684
|
+
_showRenameGraphDialog() {
|
|
2685
|
+
|
|
2686
|
+
new LX.Dialog( this.currentGraph.constructor.name, p => {
|
|
2687
|
+
p.addText( "Name", this.currentGraph.name, v => this._updateGraphName(v) );
|
|
2688
|
+
}, { modal: true, size: [ "350px", null ] } );
|
|
2689
|
+
}
|
|
2690
|
+
|
|
2691
|
+
_updateGraphName( name ) {
|
|
2692
|
+
|
|
2693
|
+
this.currentGraph.name = name;
|
|
2694
|
+
|
|
2695
|
+
const nameDom = LX.root.querySelector( '.graph-title button' );
|
|
2696
|
+
console.assert( nameDom );
|
|
2697
|
+
nameDom.innerText = name;
|
|
2698
|
+
|
|
2699
|
+
// TODO:
|
|
2700
|
+
// Update name in sidebar and all references in current nodes..
|
|
2701
|
+
}
|
|
2702
|
+
|
|
2703
|
+
_addGlobalActions() {
|
|
2374
2704
|
|
|
2375
|
-
|
|
2705
|
+
|
|
2376
2706
|
}
|
|
2377
2707
|
}
|
|
2378
2708
|
|
|
@@ -2393,55 +2723,21 @@ class Graph {
|
|
|
2393
2723
|
constructor( name, options = {} ) {
|
|
2394
2724
|
|
|
2395
2725
|
this.name = name ?? "Unnamed Graph";
|
|
2726
|
+
this.type = 'Graph';
|
|
2396
2727
|
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
this.
|
|
2400
|
-
|
|
2401
|
-
this.
|
|
2402
|
-
|
|
2403
|
-
// const mainNode = GraphEditor.addNode( 'system/Main' );
|
|
2404
|
-
// mainNode.position = new LX.vec2( 650, 400 );
|
|
2405
|
-
|
|
2406
|
-
// const addNode = GraphEditor.addNode( 'math/Add' );
|
|
2407
|
-
// addNode.position = new LX.vec2( 425, 250 );
|
|
2408
|
-
|
|
2409
|
-
// const floatNode = GraphEditor.addNode( 'inputs/Float' );
|
|
2410
|
-
// floatNode.position = new LX.vec2( 200, 200 );
|
|
2411
|
-
|
|
2412
|
-
// const stringNode = GraphEditor.addNode( 'inputs/String' );
|
|
2413
|
-
// stringNode.position = new LX.vec2( 400, 50 );
|
|
2414
|
-
|
|
2415
|
-
// const setVarNode = GraphEditor.addNode( 'variables/SetVariable' );
|
|
2416
|
-
// setVarNode.position = new LX.vec2( 650, 50 );
|
|
2417
|
-
|
|
2418
|
-
// // const multNode = new GraphNode( GraphEditor.DEFAULT_NODES[ 'Multiply' ] );
|
|
2419
|
-
// // multNode.position = new LX.vec2( 200, 400 );
|
|
2420
|
-
|
|
2421
|
-
// const keydownNode = GraphEditor.addNode( 'events/KeyDown' );
|
|
2422
|
-
// keydownNode.position = new LX.vec2( 600, 200 );
|
|
2423
|
-
|
|
2424
|
-
// const orNode = GraphEditor.addNode( 'logic/Or' );
|
|
2425
|
-
// orNode.position = new LX.vec2( 435, 435 );
|
|
2426
|
-
|
|
2427
|
-
// const equalNode = GraphEditor.addNode( 'logic/Select' );
|
|
2428
|
-
// equalNode.position = new LX.vec2( 135, 400 );
|
|
2429
|
-
|
|
2430
|
-
// this.nodes = [
|
|
2431
|
-
// mainNode,
|
|
2432
|
-
// addNode,
|
|
2433
|
-
// floatNode,
|
|
2434
|
-
// setVarNode,
|
|
2435
|
-
// stringNode,
|
|
2436
|
-
// keydownNode,
|
|
2437
|
-
// orNode,
|
|
2438
|
-
// equalNode,
|
|
2439
|
-
// // multNode
|
|
2440
|
-
// ];
|
|
2728
|
+
this.nodes = [ ];
|
|
2729
|
+
this.groups = [ ];
|
|
2730
|
+
this.links = { };
|
|
2731
|
+
|
|
2732
|
+
this.scale = 1.0;
|
|
2733
|
+
this.translation = new LX.vec2( 0, 0 );
|
|
2441
2734
|
}
|
|
2442
2735
|
|
|
2443
2736
|
configure( o ) {
|
|
2444
2737
|
|
|
2738
|
+
this.id = o.id;
|
|
2739
|
+
this.name = o.name;
|
|
2740
|
+
|
|
2445
2741
|
this.nodes.length = 0;
|
|
2446
2742
|
|
|
2447
2743
|
for( let node of o.nodes )
|
|
@@ -2453,13 +2749,12 @@ class Graph {
|
|
|
2453
2749
|
newNode.color = node.color;
|
|
2454
2750
|
newNode.position = new LX.vec2( node.position.x, node.position.y );
|
|
2455
2751
|
newNode.type = node.type;
|
|
2456
|
-
|
|
2457
|
-
// newNode.outputs = node.outputs;
|
|
2458
|
-
// newNode.properties = node.properties;
|
|
2752
|
+
newNode.properties = node.properties;
|
|
2459
2753
|
|
|
2460
2754
|
this.nodes.push( newNode );
|
|
2461
2755
|
}
|
|
2462
2756
|
|
|
2757
|
+
this.groups = o.groups;
|
|
2463
2758
|
this.links = o.links;
|
|
2464
2759
|
|
|
2465
2760
|
// editor options?
|
|
@@ -2467,6 +2762,71 @@ class Graph {
|
|
|
2467
2762
|
// zoom/translation ??
|
|
2468
2763
|
}
|
|
2469
2764
|
|
|
2765
|
+
/**
|
|
2766
|
+
* @method _runStep
|
|
2767
|
+
*/
|
|
2768
|
+
|
|
2769
|
+
_runStep( mainId ) {
|
|
2770
|
+
|
|
2771
|
+
if( !mainId )
|
|
2772
|
+
return;
|
|
2773
|
+
|
|
2774
|
+
const nodes = this.nodes.reduce( ( ac, a ) => ( {...ac, [ a.id ] : a } ), {} );
|
|
2775
|
+
|
|
2776
|
+
// Not main graph..
|
|
2777
|
+
if( !nodes[ mainId ] )
|
|
2778
|
+
return;
|
|
2779
|
+
|
|
2780
|
+
const visitedNodes = { };
|
|
2781
|
+
|
|
2782
|
+
this._executionNodes = [ ];
|
|
2783
|
+
|
|
2784
|
+
// Reser variables each step?
|
|
2785
|
+
this.variables = { };
|
|
2786
|
+
|
|
2787
|
+
const addNode = ( id ) => {
|
|
2788
|
+
|
|
2789
|
+
if( visitedNodes[ id ] )
|
|
2790
|
+
return;
|
|
2791
|
+
|
|
2792
|
+
visitedNodes[ id ] = true;
|
|
2793
|
+
|
|
2794
|
+
for( let linkId in this.links )
|
|
2795
|
+
{
|
|
2796
|
+
const idx = linkId.indexOf( '@' + id );
|
|
2797
|
+
|
|
2798
|
+
if( idx < 0 )
|
|
2799
|
+
continue;
|
|
2800
|
+
|
|
2801
|
+
const preNodeId = linkId.substring( 0, idx );
|
|
2802
|
+
|
|
2803
|
+
this._executionNodes.push( preNodeId );
|
|
2804
|
+
|
|
2805
|
+
addNode( preNodeId );
|
|
2806
|
+
}
|
|
2807
|
+
};
|
|
2808
|
+
|
|
2809
|
+
// TODO: Search "no output" nodes and add to the executable list (same as main)..
|
|
2810
|
+
// ...
|
|
2811
|
+
|
|
2812
|
+
this._executionNodes.push( mainId );
|
|
2813
|
+
|
|
2814
|
+
addNode( mainId );
|
|
2815
|
+
|
|
2816
|
+
for( var i = this._executionNodes.length - 1; i >= 0; --i )
|
|
2817
|
+
{
|
|
2818
|
+
const node = nodes[ this._executionNodes[ i ] ];
|
|
2819
|
+
|
|
2820
|
+
if( node.onBeforeStep )
|
|
2821
|
+
node.onBeforeStep();
|
|
2822
|
+
|
|
2823
|
+
node.execute();
|
|
2824
|
+
|
|
2825
|
+
if( node.onBeforeStep )
|
|
2826
|
+
node.onAfterStep();
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2470
2830
|
/**
|
|
2471
2831
|
* @method export
|
|
2472
2832
|
* @param {Boolean} prettify
|
|
@@ -2476,7 +2836,13 @@ class Graph {
|
|
|
2476
2836
|
export( prettify = true ) {
|
|
2477
2837
|
|
|
2478
2838
|
var o = { };
|
|
2839
|
+
|
|
2840
|
+
o.id = this.id;
|
|
2841
|
+
o.name = this.name;
|
|
2842
|
+
o.type = this.type;
|
|
2843
|
+
|
|
2479
2844
|
o.nodes = [ ];
|
|
2845
|
+
o.groups = [ ];
|
|
2480
2846
|
o.links = { };
|
|
2481
2847
|
|
|
2482
2848
|
for( let node of this.nodes )
|
|
@@ -2491,6 +2857,15 @@ class Graph {
|
|
|
2491
2857
|
o.links[ linkId ] = ioLinks;
|
|
2492
2858
|
}
|
|
2493
2859
|
|
|
2860
|
+
for( let group of this.groups )
|
|
2861
|
+
{
|
|
2862
|
+
const groupDom = this.editor.groups[ group.id ];
|
|
2863
|
+
const group_bb = this.editor._getBoundingFromGroup( groupDom );
|
|
2864
|
+
group_bb.id = group.id;
|
|
2865
|
+
group_bb.name = group.name
|
|
2866
|
+
o.groups.push( group_bb );
|
|
2867
|
+
}
|
|
2868
|
+
|
|
2494
2869
|
// editor options?
|
|
2495
2870
|
|
|
2496
2871
|
// zoom/translation ??
|
|
@@ -2562,16 +2937,18 @@ class GraphNode {
|
|
|
2562
2937
|
if( !this.inputs || !this.inputs.length || !this.inputs[ index ] )
|
|
2563
2938
|
return;
|
|
2564
2939
|
|
|
2940
|
+
const graph = this.editor.graphs[ this.graphID ];
|
|
2941
|
+
|
|
2565
2942
|
// Get data from link
|
|
2566
2943
|
|
|
2567
|
-
for( let linkId in
|
|
2944
|
+
for( let linkId in graph.links )
|
|
2568
2945
|
{
|
|
2569
2946
|
const idx = linkId.indexOf( '@' + this.id );
|
|
2570
2947
|
|
|
2571
2948
|
if( idx < 0 )
|
|
2572
2949
|
continue;
|
|
2573
2950
|
|
|
2574
|
-
const nodeLinks =
|
|
2951
|
+
const nodeLinks = graph.links[ linkId ];
|
|
2575
2952
|
|
|
2576
2953
|
for ( var link of nodeLinks )
|
|
2577
2954
|
{
|
|
@@ -2589,23 +2966,27 @@ class GraphNode {
|
|
|
2589
2966
|
if( !this.outputs || !this.outputs.length || !this.outputs[ index ] )
|
|
2590
2967
|
return;
|
|
2591
2968
|
|
|
2969
|
+
const graph = this.editor.graphs[ this.graphID ];
|
|
2970
|
+
|
|
2592
2971
|
// Set data in link
|
|
2593
2972
|
|
|
2594
|
-
for( let linkId in
|
|
2973
|
+
for( let linkId in graph.links )
|
|
2595
2974
|
{
|
|
2596
2975
|
const idx = linkId.indexOf( this.id + '@' );
|
|
2597
2976
|
|
|
2598
2977
|
if( idx < 0 )
|
|
2599
2978
|
continue;
|
|
2600
2979
|
|
|
2601
|
-
const nodeLinks =
|
|
2980
|
+
const nodeLinks = graph.links[ linkId ];
|
|
2602
2981
|
|
|
2603
2982
|
for ( var link of nodeLinks )
|
|
2604
2983
|
{
|
|
2605
2984
|
if( link.outputIdx != index )
|
|
2606
2985
|
continue;
|
|
2607
2986
|
|
|
2608
|
-
|
|
2987
|
+
let innerData = data;
|
|
2988
|
+
|
|
2989
|
+
if( innerData != undefined && link.inputType != link.outputType && link.inputType != "any" && link.outputType != "any" )
|
|
2609
2990
|
{
|
|
2610
2991
|
// In case of supported casting, use function to cast..
|
|
2611
2992
|
|
|
@@ -2613,19 +2994,14 @@ class GraphNode {
|
|
|
2613
2994
|
|
|
2614
2995
|
// Use function if it's possible to cast!
|
|
2615
2996
|
|
|
2616
|
-
|
|
2997
|
+
innerData = fn ? fn( LX.deepCopy( innerData ) ) : null;
|
|
2617
2998
|
}
|
|
2618
2999
|
|
|
2619
|
-
link.data =
|
|
3000
|
+
link.data = innerData;
|
|
2620
3001
|
}
|
|
2621
3002
|
}
|
|
2622
3003
|
}
|
|
2623
3004
|
|
|
2624
|
-
getOutput( index ) {
|
|
2625
|
-
|
|
2626
|
-
return this.outputs[ index ].value;
|
|
2627
|
-
}
|
|
2628
|
-
|
|
2629
3005
|
serialize() {
|
|
2630
3006
|
|
|
2631
3007
|
var o = { };
|
|
@@ -2645,6 +3021,45 @@ class GraphNode {
|
|
|
2645
3021
|
|
|
2646
3022
|
LX.GraphNode = GraphNode;
|
|
2647
3023
|
|
|
3024
|
+
/**
|
|
3025
|
+
* @class GraphFunction
|
|
3026
|
+
*/
|
|
3027
|
+
|
|
3028
|
+
class GraphFunction extends Graph {
|
|
3029
|
+
|
|
3030
|
+
constructor( name, options = { } ) {
|
|
3031
|
+
|
|
3032
|
+
super();
|
|
3033
|
+
|
|
3034
|
+
this.name = name ?? ( "GraphFunction" + GraphEditor.LAST_FUNCTION_ID );
|
|
3035
|
+
this.type = 'GraphFunction';
|
|
3036
|
+
|
|
3037
|
+
GraphEditor.LAST_FUNCTION_ID++
|
|
3038
|
+
|
|
3039
|
+
const nodeInput = GraphEditor.addNode( "function/Input" );
|
|
3040
|
+
nodeInput.position = new LX.vec2( 150, 250 );
|
|
3041
|
+
|
|
3042
|
+
const nodeOutput = GraphEditor.addNode( "function/Output" );
|
|
3043
|
+
nodeOutput.position = new LX.vec2( 650, 350 );
|
|
3044
|
+
|
|
3045
|
+
this.nodes.push( nodeInput, nodeOutput );
|
|
3046
|
+
}
|
|
3047
|
+
|
|
3048
|
+
getOutputData( inputValue ) {
|
|
3049
|
+
|
|
3050
|
+
const inputNode = this.nodes[ 0 ];
|
|
3051
|
+
inputNode.setOutput( 0, inputValue );
|
|
3052
|
+
|
|
3053
|
+
const outputNode = this.nodes[ 1 ];
|
|
3054
|
+
|
|
3055
|
+
this._runStep( outputNode.id );
|
|
3056
|
+
|
|
3057
|
+
return outputNode.getInput( 0 );
|
|
3058
|
+
}
|
|
3059
|
+
}
|
|
3060
|
+
|
|
3061
|
+
LX.GraphFunction = GraphFunction;
|
|
3062
|
+
|
|
2648
3063
|
/*
|
|
2649
3064
|
************ PREDEFINED NODES ************
|
|
2650
3065
|
|
|
@@ -2656,6 +3071,54 @@ LX.GraphNode = GraphNode;
|
|
|
2656
3071
|
- onExecute: Callback for node execution
|
|
2657
3072
|
*/
|
|
2658
3073
|
|
|
3074
|
+
/*
|
|
3075
|
+
Function nodes
|
|
3076
|
+
*/
|
|
3077
|
+
|
|
3078
|
+
class NodeFuncInput extends GraphNode
|
|
3079
|
+
{
|
|
3080
|
+
onCreate() {
|
|
3081
|
+
this.addOutput( null, "float" );
|
|
3082
|
+
this.addProperty( "Outputs", "array", [ "float" ], [ 'float', 'int', 'bool', 'vec2', 'vec3', 'vec4', 'mat44' ] );
|
|
3083
|
+
}
|
|
3084
|
+
|
|
3085
|
+
onExecute() {
|
|
3086
|
+
// var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3087
|
+
// var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3088
|
+
// this.setOutput( 0, a + b );
|
|
3089
|
+
}
|
|
3090
|
+
|
|
3091
|
+
setOutputs( v ) {
|
|
3092
|
+
|
|
3093
|
+
this.outputs.length = 0;
|
|
3094
|
+
|
|
3095
|
+
for( var i of v )
|
|
3096
|
+
{
|
|
3097
|
+
this.outputs.push( { name: null, type: i } );
|
|
3098
|
+
}
|
|
3099
|
+
}
|
|
3100
|
+
}
|
|
3101
|
+
|
|
3102
|
+
NodeFuncInput.blockDelete = true;
|
|
3103
|
+
NodeFuncInput.blockAdd = true;
|
|
3104
|
+
GraphEditor.registerCustomNode( "function/Input", NodeFuncInput );
|
|
3105
|
+
|
|
3106
|
+
class NodeFuncOutput extends GraphNode
|
|
3107
|
+
{
|
|
3108
|
+
onCreate() {
|
|
3109
|
+
this.addInput( null, "any" );
|
|
3110
|
+
}
|
|
3111
|
+
|
|
3112
|
+
onExecute() {
|
|
3113
|
+
// var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3114
|
+
// var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3115
|
+
// this.setOutput( 0, a + b );
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
NodeFuncOutput.blockDelete = true;
|
|
3120
|
+
NodeFuncOutput.blockAdd = true;
|
|
3121
|
+
GraphEditor.registerCustomNode( "function/Output", NodeFuncOutput );
|
|
2659
3122
|
|
|
2660
3123
|
/*
|
|
2661
3124
|
Math nodes
|
|
@@ -3047,7 +3510,7 @@ class NodeSetVariable extends GraphNode
|
|
|
3047
3510
|
var varValue = this.getInput( 1 );
|
|
3048
3511
|
if( varValue == undefined )
|
|
3049
3512
|
return;
|
|
3050
|
-
this.
|
|
3513
|
+
this.editor.setVariable( varName, varValue );
|
|
3051
3514
|
this.setOutput( 0, varValue );
|
|
3052
3515
|
}
|
|
3053
3516
|
}
|
|
@@ -3112,6 +3575,7 @@ class NodeMain extends GraphNode
|
|
|
3112
3575
|
};
|
|
3113
3576
|
}
|
|
3114
3577
|
|
|
3578
|
+
NodeMain.blockDelete = true;
|
|
3115
3579
|
GraphEditor.registerCustomNode( "system/Main", NodeMain );
|
|
3116
3580
|
|
|
3117
3581
|
export { GraphEditor, Graph, GraphNode };
|