lexgui 0.1.28 → 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 +18 -24
- package/build/components/nodegraph.js +3246 -397
- package/build/lexgui.css +340 -56
- package/build/lexgui.js +265 -135
- package/build/lexgui.module.js +251 -121
- package/changelog.md +30 -0
- package/examples/index.html +1 -1
- package/examples/node_graph.html +2 -1
- 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,39 +4,53 @@ if(!LX) {
|
|
|
4
4
|
throw("lexgui.js missing!");
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
LX.components.push( '
|
|
7
|
+
LX.components.push( 'GraphEditor' );
|
|
8
8
|
|
|
9
|
-
|
|
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
|
-
}
|
|
9
|
+
class BoundingBox {
|
|
15
10
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
11
|
+
constructor( o, s )
|
|
12
|
+
{
|
|
13
|
+
this.origin = o ?? new LX.vec2( 0, 0 );
|
|
14
|
+
this.size = s ?? new LX.vec2( 0, 0 );
|
|
15
|
+
}
|
|
19
16
|
|
|
20
|
-
|
|
21
|
-
[array[id0], array[id1]] = [array[id1], array[id0]];
|
|
22
|
-
};
|
|
17
|
+
merge( bb ) {
|
|
23
18
|
|
|
24
|
-
|
|
25
|
-
return str.substr(0, idx) + str.substr(idx + 1);
|
|
26
|
-
}
|
|
19
|
+
console.assert( bb.constructor == BoundingBox );
|
|
27
20
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
21
|
+
const min_0 = this.origin;
|
|
22
|
+
const max_0 = this.origin.add( this.size );
|
|
31
23
|
|
|
32
|
-
|
|
24
|
+
const min_1 = bb.origin;
|
|
25
|
+
const max_1 = bb.origin.add( bb.size );
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
}
|
|
27
|
+
const merge_min = new LX.vec2( Math.min( min_0.x, min_1.x ), Math.min( min_0.y, min_1.y ) );
|
|
28
|
+
const merge_max = new LX.vec2( Math.max( max_0.x, max_1.x ), Math.max( max_0.y, max_1.y ) );
|
|
29
|
+
|
|
30
|
+
this.origin = merge_min;
|
|
31
|
+
this.size = merge_max.sub( merge_min );
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
inside( bb, full = true ) {
|
|
35
|
+
|
|
36
|
+
const min_0 = this.origin;
|
|
37
|
+
const max_0 = this.origin.add( this.size );
|
|
38
|
+
|
|
39
|
+
const min_1 = bb.origin;
|
|
40
|
+
const max_1 = bb.origin.add( bb.size );
|
|
41
|
+
|
|
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
|
+
}
|
|
52
|
+
}
|
|
53
|
+
};
|
|
40
54
|
|
|
41
55
|
/**
|
|
42
56
|
* @class GraphEditor
|
|
@@ -46,25 +60,26 @@ class GraphEditor {
|
|
|
46
60
|
|
|
47
61
|
static __instances = [];
|
|
48
62
|
|
|
49
|
-
//
|
|
63
|
+
// Editor
|
|
50
64
|
|
|
51
|
-
static MIN_SCALE
|
|
52
|
-
static MAX_SCALE
|
|
65
|
+
static MIN_SCALE = 0.25;
|
|
66
|
+
static MAX_SCALE = 4.0;
|
|
53
67
|
|
|
54
68
|
static EVENT_MOUSEMOVE = 0;
|
|
55
69
|
static EVENT_MOUSEWHEEL = 1;
|
|
56
70
|
|
|
57
|
-
|
|
71
|
+
static LAST_GROUP_ID = 0;
|
|
72
|
+
static LAST_FUNCTION_ID = 0;
|
|
73
|
+
|
|
74
|
+
static STOPPED = 0;
|
|
75
|
+
static RUNNING = 1;
|
|
58
76
|
|
|
59
|
-
|
|
60
|
-
static NODE_ROW_HEIGHT = 16;
|
|
77
|
+
// Node Drawing
|
|
61
78
|
|
|
62
|
-
static
|
|
63
|
-
static
|
|
64
|
-
static NODE_BODY_RADIUS = [ GraphEditor.NODE_SHAPE_RADIUS, GraphEditor.NODE_SHAPE_RADIUS, GraphEditor.NODE_SHAPE_RADIUS, GraphEditor.NODE_SHAPE_RADIUS ];
|
|
79
|
+
static NODE_IO_INPUT = 0;
|
|
80
|
+
static NODE_IO_OUTPUT = 1;
|
|
65
81
|
|
|
66
|
-
static
|
|
67
|
-
static DEFAULT_NODE_BODY_COLOR = "#111";
|
|
82
|
+
static NODE_TYPES = { };
|
|
68
83
|
|
|
69
84
|
/**
|
|
70
85
|
* @param {*} options
|
|
@@ -75,6 +90,12 @@ class GraphEditor {
|
|
|
75
90
|
|
|
76
91
|
GraphEditor.__instances.push( this );
|
|
77
92
|
|
|
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 ) } );
|
|
97
|
+
});
|
|
98
|
+
|
|
78
99
|
this.base_area = area;
|
|
79
100
|
this.area = new LX.Area( { className: "lexgraph" } );
|
|
80
101
|
|
|
@@ -82,73 +103,400 @@ class GraphEditor {
|
|
|
82
103
|
|
|
83
104
|
this.root = this.area.root;
|
|
84
105
|
this.root.tabIndex = -1;
|
|
106
|
+
|
|
85
107
|
area.attach( this.root );
|
|
86
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
|
+
|
|
87
116
|
// Bind resize
|
|
88
117
|
|
|
89
118
|
area.onresize = ( bb ) => {
|
|
90
119
|
|
|
91
120
|
};
|
|
92
121
|
|
|
93
|
-
|
|
122
|
+
area.addOverlayButtons( [
|
|
123
|
+
{
|
|
124
|
+
name: "Toggle Sidebar",
|
|
125
|
+
icon: "fa fa-table-columns",
|
|
126
|
+
callback: () => this._toggleSideBar(),
|
|
127
|
+
},
|
|
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
|
+
],
|
|
142
|
+
[
|
|
143
|
+
{
|
|
144
|
+
name: "Enable Snapping",
|
|
145
|
+
icon: "fa fa-table-cells",
|
|
146
|
+
callback: () => this._toggleSnapping(),
|
|
147
|
+
selectable: true
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
name: 1,
|
|
151
|
+
options: [1, 2, 3],
|
|
152
|
+
callback: value => this._setSnappingValue( value ),
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
[
|
|
156
|
+
{
|
|
157
|
+
name: "Import",
|
|
158
|
+
icon: "fa fa-upload",
|
|
159
|
+
callback: (value, event) => { this.loadGraph( "../../data/graph_sample.json" ); }
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
name: "Export",
|
|
163
|
+
icon: "fa fa-diagram-project",
|
|
164
|
+
callback: (value, event) => this.currentGraph.export()
|
|
165
|
+
}
|
|
166
|
+
],
|
|
167
|
+
{
|
|
168
|
+
name: "",
|
|
169
|
+
class: "graph-title",
|
|
170
|
+
callback: (value, event) => this._showRenameGraphDialog()
|
|
171
|
+
}
|
|
172
|
+
], { float: "htc" } );
|
|
173
|
+
|
|
174
|
+
this.root.addEventListener( 'keydown', this._processKeyDown.bind( this ), true );
|
|
175
|
+
this.root.addEventListener( 'keyup', this._processKeyUp.bind( this ), true );
|
|
94
176
|
this.root.addEventListener( 'mousedown', this._processMouse.bind( this ) );
|
|
95
177
|
this.root.addEventListener( 'mouseup', this._processMouse.bind( this ) );
|
|
96
178
|
this.root.addEventListener( 'mousemove', this._processMouse.bind( this ) );
|
|
97
179
|
this.root.addEventListener( 'mousewheel', this._processMouse.bind(this) );
|
|
180
|
+
this.root.addEventListener( 'mouseleave', this._processMouse.bind(this) );
|
|
98
181
|
this.root.addEventListener( 'click', this._processMouse.bind( this ) );
|
|
99
182
|
this.root.addEventListener( 'contextmenu', this._processMouse.bind( this ) );
|
|
100
183
|
this.root.addEventListener( 'focus', this._processFocus.bind( this, true) );
|
|
101
184
|
this.root.addEventListener( 'focusout', this._processFocus.bind( this, false ) );
|
|
102
185
|
|
|
186
|
+
this.propertiesDialog = new LX.PocketDialog( "Properties", null, {
|
|
187
|
+
size: [ "350px", null ],
|
|
188
|
+
position: [ "8px", "8px" ],
|
|
189
|
+
float: "left",
|
|
190
|
+
class: 'lexgraphpropdialog'
|
|
191
|
+
} );
|
|
192
|
+
|
|
193
|
+
// Avoid closing the dialog on click..
|
|
194
|
+
|
|
195
|
+
this.propertiesDialog.root.addEventListener( "mousedown", function( e ) {
|
|
196
|
+
e.stopImmediatePropagation();
|
|
197
|
+
e.stopPropagation();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
this.propertiesDialog.root.addEventListener( "mouseup", function( e ) {
|
|
201
|
+
e.stopImmediatePropagation();
|
|
202
|
+
e.stopPropagation();
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
// Move to root..
|
|
206
|
+
this.root.appendChild( this.propertiesDialog.root );
|
|
207
|
+
|
|
208
|
+
// Editor
|
|
209
|
+
|
|
210
|
+
this._mousePosition = new LX.vec2( 0, 0 );
|
|
211
|
+
this._deltaMousePosition = new LX.vec2( 0, 0 );
|
|
212
|
+
this._snappedDeltaMousePosition = new LX.vec2( 0, 0 );
|
|
103
213
|
this._lastMousePosition = new LX.vec2( 0, 0 );
|
|
214
|
+
this._lastSnappedMousePosition = new LX.vec2( 0, 0 );
|
|
215
|
+
|
|
216
|
+
this._undoSteps = [ ];
|
|
217
|
+
this._redoSteps = [ ];
|
|
218
|
+
|
|
219
|
+
this.keys = { };
|
|
220
|
+
|
|
221
|
+
this._snapToGrid = false;
|
|
222
|
+
this._snapValue = 1.0;
|
|
223
|
+
|
|
224
|
+
// Graphs, Nodes and connections
|
|
225
|
+
|
|
226
|
+
this.currentGraph = null;
|
|
227
|
+
|
|
228
|
+
this.graphs = { };
|
|
229
|
+
this.nodes = { };
|
|
230
|
+
this.groups = { };
|
|
231
|
+
this.variables = { };
|
|
232
|
+
|
|
233
|
+
this.selectedNodes = [ ];
|
|
234
|
+
|
|
235
|
+
this.supportedCastTypes = { };
|
|
236
|
+
|
|
237
|
+
this.addCastType( 'float', 'vec2', ( v ) => { return [ v, v ]; } );
|
|
238
|
+
this.addCastType( 'float', 'vec3', ( v ) => { return [ v, v, v ]; } );
|
|
239
|
+
this.addCastType( 'float', 'vec4', ( v ) => { return [ v, v, v, v ]; } );
|
|
240
|
+
this.addCastType( 'float', 'bool', ( v ) => { return !!v; } );
|
|
241
|
+
|
|
242
|
+
this.addCastType( 'vec4', 'vec3', ( v ) => { v.slice( 0, 3 ); return v; } );
|
|
243
|
+
this.addCastType( 'vec4', 'vec2', ( v ) => { v.slice( 0, 2 ); return v; } );
|
|
244
|
+
this.addCastType( 'vec3', 'vec2', ( v ) => { v.slice( 0, 2 ); return v; } );
|
|
245
|
+
|
|
246
|
+
this.addCastType( 'vec3', 'vec4', ( v ) => { v.push( 1 ); return v; } );
|
|
247
|
+
this.addCastType( 'vec2', 'vec3', ( v ) => { v.push( 1 ); return v; } );
|
|
248
|
+
this.addCastType( 'vec2', 'vec4', ( v ) => { v.push( 0, 1 ); return v; } );
|
|
249
|
+
|
|
250
|
+
this._nodeBackgroundOpacity = options.disableNodeOpacity ? 1.0 : 0.8;
|
|
251
|
+
|
|
252
|
+
this.main = null;
|
|
104
253
|
|
|
105
254
|
// Back pattern
|
|
106
255
|
|
|
107
256
|
const f = 15.0;
|
|
108
|
-
this._patternPosition = new LX.vec2( 0, 0 );
|
|
109
257
|
this._patternSize = new LX.vec2( f );
|
|
110
258
|
this._circlePatternSize = f * 0.04;
|
|
111
259
|
this._circlePatternColor = '#71717a9c';
|
|
112
260
|
|
|
113
261
|
this._generatePattern();
|
|
114
262
|
|
|
115
|
-
//
|
|
263
|
+
// Links
|
|
116
264
|
|
|
117
|
-
this.
|
|
265
|
+
this._domLinks = document.createElement( 'div' );
|
|
266
|
+
this._domLinks.classList.add( 'lexgraphlinks' );
|
|
267
|
+
this.root.appendChild( this._domLinks );
|
|
118
268
|
|
|
119
|
-
//
|
|
269
|
+
// Nodes
|
|
120
270
|
|
|
121
271
|
this._domNodes = document.createElement( 'div' );
|
|
122
272
|
this._domNodes.classList.add( 'lexgraphnodes' );
|
|
123
273
|
this.root.appendChild( this._domNodes );
|
|
124
274
|
|
|
125
|
-
|
|
275
|
+
window.ge = this;
|
|
126
276
|
}
|
|
127
277
|
|
|
128
|
-
static getInstances()
|
|
129
|
-
|
|
278
|
+
static getInstances() {
|
|
279
|
+
|
|
130
280
|
return GraphEditor.__instances;
|
|
131
281
|
}
|
|
132
282
|
|
|
283
|
+
/**
|
|
284
|
+
* Register a node class so it can be listed when the user wants to create a new one
|
|
285
|
+
* @method registerCustomNode
|
|
286
|
+
* @param {String} type: name of the node and path
|
|
287
|
+
* @param {Class} baseClass class containing the structure of the custom node
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
static registerCustomNode( type, baseClass ) {
|
|
291
|
+
|
|
292
|
+
if ( !baseClass.prototype ) {
|
|
293
|
+
throw "Cannot register a simple object, it must be a class with a prototype!";
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Get info from path
|
|
297
|
+
const pos = type.lastIndexOf( "/" );
|
|
298
|
+
|
|
299
|
+
baseClass.category = type.substring( 0, pos );
|
|
300
|
+
baseClass.title = baseClass.title ?? type.substring( pos + 1 );
|
|
301
|
+
baseClass.type = type;
|
|
302
|
+
|
|
303
|
+
if( !baseClass.prototype.onExecute )
|
|
304
|
+
{
|
|
305
|
+
console.warn( `GraphNode [${ this.title }] does not have a callback attached.` );
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const prev = GraphEditor.NODE_TYPES[ type ];
|
|
309
|
+
if(prev) {
|
|
310
|
+
console.warn( `Replacing node type [${ type }]` );
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
GraphEditor.NODE_TYPES[ type ] = baseClass;
|
|
314
|
+
|
|
315
|
+
// Some callbacks..
|
|
316
|
+
|
|
317
|
+
if ( this.onNodeTypeRegistered ) {
|
|
318
|
+
this.onCustomNodeRegistered( type, baseClass);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if ( prev && this.onNodeTypeReplaced ) {
|
|
322
|
+
this.onNodeTypeReplaced( type, baseClass, prev );
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Create a node of a given type with a name. The node is not attached to any graph yet.
|
|
328
|
+
* @method createNode
|
|
329
|
+
* @param {String} type full name of the node class. p.e. "math/sin"
|
|
330
|
+
* @param {String} title a name to distinguish from other nodes
|
|
331
|
+
* @param {Object} options Store node options
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
static addNode( type, title, options ) {
|
|
335
|
+
|
|
336
|
+
var baseClass = GraphEditor.NODE_TYPES[ type ];
|
|
337
|
+
|
|
338
|
+
if (!baseClass) {
|
|
339
|
+
console.warn( `GraphNode type [${ type }] not registered.` );
|
|
340
|
+
return null;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
title = title ?? baseClass.title;
|
|
344
|
+
|
|
345
|
+
const node = new baseClass( title );
|
|
346
|
+
|
|
347
|
+
if( node.onCreate )
|
|
348
|
+
node.onCreate();
|
|
349
|
+
|
|
350
|
+
node.type = type;
|
|
351
|
+
node.title = title;
|
|
352
|
+
node.position = new LX.vec2( 0, 0 );
|
|
353
|
+
node.color = null;
|
|
354
|
+
|
|
355
|
+
// Extra options
|
|
356
|
+
if ( options ) {
|
|
357
|
+
for (var i in options) {
|
|
358
|
+
node[ i ] = options[ i ];
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if ( node.onNodeCreated ) {
|
|
363
|
+
node.onNodeCreated();
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return node;
|
|
367
|
+
}
|
|
368
|
+
|
|
133
369
|
/**
|
|
134
370
|
* @method setGraph
|
|
135
|
-
* @param {Graph} graph
|
|
371
|
+
* @param {Graph} graph
|
|
136
372
|
*/
|
|
137
373
|
|
|
138
374
|
setGraph( graph ) {
|
|
139
375
|
|
|
140
|
-
|
|
376
|
+
// Nothing to do, already there...
|
|
377
|
+
if( this.currentGraph && graph.id == this.currentGraph.id )
|
|
378
|
+
return;
|
|
379
|
+
|
|
380
|
+
this.clear();
|
|
381
|
+
|
|
382
|
+
graph.id = graph.id ?? graph.constructor.name + '-' + LX.UTILS.uidGenerator();
|
|
141
383
|
|
|
142
|
-
|
|
384
|
+
this.graphs[ graph.id ] = graph;
|
|
385
|
+
|
|
386
|
+
if( !graph.nodes )
|
|
143
387
|
{
|
|
144
388
|
console.warn( 'Graph does not contain any node!' );
|
|
145
|
-
return
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
this.currentGraph = graph;
|
|
393
|
+
|
|
394
|
+
this._updatePattern();
|
|
395
|
+
|
|
396
|
+
for( let node of graph.nodes )
|
|
397
|
+
{
|
|
398
|
+
this._createNodeDOM( node );
|
|
399
|
+
}
|
|
400
|
+
|
|
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 )
|
|
409
|
+
{
|
|
410
|
+
const links = graph.links[ linkId ];
|
|
411
|
+
|
|
412
|
+
for( let link of links )
|
|
413
|
+
{
|
|
414
|
+
this._createLink( link );
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
this._updateGraphName( graph.name );
|
|
419
|
+
this._togglePropertiesDialog( false );
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/**
|
|
423
|
+
* @method loadGraph
|
|
424
|
+
* @param {String} url
|
|
425
|
+
* @param {Function} callback Function to call once the graph is loaded
|
|
426
|
+
*/
|
|
427
|
+
|
|
428
|
+
loadGraph( url, callback ) {
|
|
429
|
+
|
|
430
|
+
const onComplete = ( json ) => {
|
|
431
|
+
|
|
432
|
+
let graph = ( json.type == 'Graph' ) ? this.addGraph( json ) : this.addGraphFunction( json );
|
|
433
|
+
|
|
434
|
+
if( callback )
|
|
435
|
+
callback( graph );
|
|
146
436
|
}
|
|
147
437
|
|
|
148
|
-
|
|
438
|
+
const onError = (v) => console.error(v);
|
|
439
|
+
|
|
440
|
+
LX.requestJSON( url, onComplete, onError );
|
|
441
|
+
}
|
|
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
|
|
149
481
|
{
|
|
150
|
-
|
|
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
|
+
}
|
|
151
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 );
|
|
152
500
|
}
|
|
153
501
|
|
|
154
502
|
/**
|
|
@@ -158,52 +506,210 @@ class GraphEditor {
|
|
|
158
506
|
clear() {
|
|
159
507
|
|
|
160
508
|
this._domNodes.innerHTML = "";
|
|
509
|
+
this._domLinks.innerHTML = "";
|
|
510
|
+
|
|
511
|
+
this.nodes = { };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
setVariable( name, value ) {
|
|
515
|
+
|
|
516
|
+
this.variables[ name ] = value;
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
getVariable( name ) {
|
|
520
|
+
|
|
521
|
+
return this.variables[ name ];
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
propagateEventToAllNodes( eventName, params ) {
|
|
525
|
+
|
|
526
|
+
if( !this.currentGraph )
|
|
527
|
+
return;
|
|
528
|
+
|
|
529
|
+
for ( let node of this.currentGraph.nodes )
|
|
530
|
+
{
|
|
531
|
+
if( !node[ eventName ] )
|
|
532
|
+
continue;
|
|
533
|
+
|
|
534
|
+
node[ eventName ].apply( this, params );
|
|
535
|
+
}
|
|
536
|
+
};
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* @method addCastType
|
|
540
|
+
* @param {String} type: Type to cast
|
|
541
|
+
* @param {String} targetType: Types to be casted from original type
|
|
542
|
+
* @param {Function} fn: Function to know how to cast
|
|
543
|
+
*/
|
|
544
|
+
|
|
545
|
+
addCastType( type, targetType, fn ) {
|
|
546
|
+
|
|
547
|
+
this.supportedCastTypes[ type + '@' + targetType ] = fn;
|
|
161
548
|
}
|
|
162
549
|
|
|
163
550
|
/**
|
|
164
551
|
* @method unSelectAll
|
|
165
552
|
*/
|
|
166
553
|
|
|
167
|
-
unSelectAll(
|
|
554
|
+
unSelectAll( keepPropDialog ) {
|
|
168
555
|
|
|
169
|
-
this._domNodes.querySelectorAll( '.lexgraphnode' ).forEach( v =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
556
|
+
this._domNodes.querySelectorAll( '.lexgraphnode' ).forEach( v => v.classList.remove( 'selected' ) );
|
|
557
|
+
|
|
558
|
+
this.selectedNodes.length = 0;
|
|
559
|
+
|
|
560
|
+
if( !keepPropDialog )
|
|
561
|
+
this._togglePropertiesDialog( false );
|
|
174
562
|
}
|
|
175
563
|
|
|
176
|
-
|
|
564
|
+
_createNodeDOM( node ) {
|
|
565
|
+
|
|
566
|
+
node.editor = this;
|
|
567
|
+
node.graphID = this.currentGraph.id;
|
|
177
568
|
|
|
178
569
|
var nodeContainer = document.createElement( 'div' );
|
|
179
570
|
nodeContainer.classList.add( 'lexgraphnode' );
|
|
571
|
+
nodeContainer.style.left = "0";
|
|
572
|
+
nodeContainer.style.top = "0";
|
|
180
573
|
|
|
181
|
-
|
|
182
|
-
|
|
574
|
+
this._translateNode( nodeContainer, node.position );
|
|
575
|
+
|
|
576
|
+
var color;
|
|
577
|
+
|
|
578
|
+
// Get color from type if color if not manually specified
|
|
579
|
+
if( node.type && GraphEditor.NODE_TYPES[ node.type ] )
|
|
580
|
+
{
|
|
581
|
+
const category = node.constructor.category;
|
|
582
|
+
nodeContainer.classList.add( category );
|
|
583
|
+
}
|
|
584
|
+
else
|
|
585
|
+
{
|
|
586
|
+
const pos = node.type.lastIndexOf( "/" );
|
|
587
|
+
const category = node.type.substring( 0, pos );
|
|
588
|
+
nodeContainer.classList.add( category );
|
|
589
|
+
}
|
|
183
590
|
|
|
184
|
-
|
|
591
|
+
// Update with manual color
|
|
592
|
+
|
|
593
|
+
color = node.color ?? color;
|
|
594
|
+
|
|
595
|
+
if( color )
|
|
185
596
|
{
|
|
186
|
-
|
|
597
|
+
// RGB
|
|
598
|
+
if( color.constructor == Array )
|
|
599
|
+
{
|
|
600
|
+
color = color.join( ',' );
|
|
601
|
+
}
|
|
602
|
+
// Hex color..
|
|
603
|
+
else
|
|
604
|
+
{
|
|
605
|
+
color = LX.hexToRgb( color );
|
|
606
|
+
color.forEach( ( v, i ) => color[ i ] = v * 255 );
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
nodeContainer.style.backgroundColor = "rgba(" + color + ", " + this._nodeBackgroundOpacity + ")";
|
|
187
610
|
}
|
|
188
611
|
|
|
189
|
-
|
|
612
|
+
nodeContainer.addEventListener( 'mousedown', e => {
|
|
613
|
+
|
|
614
|
+
// Only for left click..
|
|
615
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
616
|
+
return;
|
|
617
|
+
|
|
618
|
+
if( e.altKey )
|
|
619
|
+
{
|
|
620
|
+
this._unSelectNode( nodeContainer );
|
|
621
|
+
}
|
|
622
|
+
else
|
|
623
|
+
{
|
|
624
|
+
if( this.selectedNodes.length > 1 && ( !e.ctrlKey && !e.shiftKey ) )
|
|
625
|
+
{
|
|
626
|
+
this.unSelectAll( true );
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
if( !nodeContainer.classList.contains( 'selected' ) )
|
|
630
|
+
{
|
|
631
|
+
this._selectNode( nodeContainer, ( e.ctrlKey || e.shiftKey ) );
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
} );
|
|
635
|
+
|
|
636
|
+
nodeContainer.addEventListener( 'contextmenu', e => {
|
|
637
|
+
|
|
638
|
+
e.preventDefault();
|
|
639
|
+
e.stopPropagation();
|
|
640
|
+
e.stopImmediatePropagation();
|
|
641
|
+
|
|
642
|
+
LX.addContextMenu(null, e, m => {
|
|
643
|
+
|
|
644
|
+
m.add( "Copy", () => {
|
|
645
|
+
this._clipboardData = node.id;
|
|
646
|
+
} );
|
|
190
647
|
|
|
191
|
-
|
|
192
|
-
|
|
648
|
+
m.add( "Paste", () => {
|
|
649
|
+
// TODO
|
|
650
|
+
// ...
|
|
651
|
+
} );
|
|
193
652
|
|
|
194
|
-
|
|
195
|
-
// nodeContainer.style.zIndex = "1";
|
|
653
|
+
m.add( "" );
|
|
196
654
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
655
|
+
m.add( "Delete", () => {
|
|
656
|
+
this._deleteNode( nodeContainer.dataset[ 'id' ] );
|
|
657
|
+
} );
|
|
658
|
+
});
|
|
659
|
+
} );
|
|
660
|
+
|
|
661
|
+
nodeContainer.addEventListener( 'dblclick', e => {
|
|
662
|
+
|
|
663
|
+
// Only for left click..
|
|
664
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
665
|
+
return;
|
|
666
|
+
|
|
667
|
+
// Open graph function..
|
|
668
|
+
if( node.constructor.func )
|
|
669
|
+
{
|
|
670
|
+
this._sidebar.select( node.constructor.func.name )
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
} );
|
|
200
674
|
|
|
201
675
|
// Title header
|
|
202
676
|
var nodeHeader = document.createElement( 'div' );
|
|
203
677
|
nodeHeader.classList.add( 'lexgraphnodeheader' );
|
|
204
|
-
nodeHeader.innerText = node.
|
|
678
|
+
nodeHeader.innerText = node.title;
|
|
205
679
|
nodeContainer.appendChild( nodeHeader );
|
|
206
680
|
|
|
681
|
+
// Properties
|
|
682
|
+
// if( node.properties.length )
|
|
683
|
+
// {
|
|
684
|
+
// var nodeProperties = document.createElement( 'div' );
|
|
685
|
+
// nodeProperties.classList.add( 'lexgraphnodeproperties' );
|
|
686
|
+
|
|
687
|
+
// for( let p of node.properties )
|
|
688
|
+
// {
|
|
689
|
+
// var panel = new LX.Panel();
|
|
690
|
+
|
|
691
|
+
// p.signal = "@" + LX.UTILS.uidGenerator() + node.title;
|
|
692
|
+
|
|
693
|
+
// switch( p.type )
|
|
694
|
+
// {
|
|
695
|
+
// case 'float':
|
|
696
|
+
// case 'int':
|
|
697
|
+
// panel.addNumber( p.name, p.value, (v) => p.value = v, { signal: p.signal } );
|
|
698
|
+
// break;
|
|
699
|
+
// case 'string':
|
|
700
|
+
// panel.addText( p.name, p.value, (v) => p.value = v, { signal: p.signal } );
|
|
701
|
+
// break;
|
|
702
|
+
// }
|
|
703
|
+
|
|
704
|
+
// // var prop = document.createElement( 'div' );
|
|
705
|
+
// // prop.innerText = p.type;
|
|
706
|
+
// // prop.classList.add( 'lexgraphnodeproperty' );
|
|
707
|
+
// nodeProperties.appendChild( panel.root );
|
|
708
|
+
// }
|
|
709
|
+
|
|
710
|
+
// nodeContainer.appendChild( nodeProperties );
|
|
711
|
+
// }
|
|
712
|
+
|
|
207
713
|
// Inputs and outputs
|
|
208
714
|
var nodeIO = document.createElement( 'div' );
|
|
209
715
|
nodeIO.classList.add( 'lexgraphnodeios' );
|
|
@@ -216,7 +722,7 @@ class GraphEditor {
|
|
|
216
722
|
{
|
|
217
723
|
var nodeInputs = null;
|
|
218
724
|
|
|
219
|
-
if(
|
|
725
|
+
if( hasInputs )
|
|
220
726
|
{
|
|
221
727
|
nodeInputs = document.createElement( 'div' );
|
|
222
728
|
nodeInputs.classList.add( 'lexgraphnodeinputs' );
|
|
@@ -228,18 +734,25 @@ class GraphEditor {
|
|
|
228
734
|
{
|
|
229
735
|
if( !i.type )
|
|
230
736
|
{
|
|
231
|
-
console.warn( `Missing type for node [${ node.
|
|
737
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
232
738
|
continue;
|
|
233
739
|
}
|
|
234
740
|
|
|
235
741
|
var input = document.createElement( 'div' );
|
|
236
|
-
input.
|
|
742
|
+
input.className = 'lexgraphnodeio ioinput';
|
|
743
|
+
input.dataset[ 'index' ] = nodeInputs.childElementCount;
|
|
237
744
|
|
|
238
745
|
var type = document.createElement( 'span' );
|
|
239
746
|
type.className = 'io__type input ' + i.type;
|
|
240
|
-
type.
|
|
747
|
+
type.dataset[ 'type' ] = i.type;
|
|
748
|
+
type.innerHTML = '<span>' + i.type[ 0 ].toUpperCase() + '</span>';
|
|
241
749
|
input.appendChild( type );
|
|
242
750
|
|
|
751
|
+
var typeDesc = document.createElement( 'span' );
|
|
752
|
+
typeDesc.className = 'io__typedesc input ' + i.type;
|
|
753
|
+
typeDesc.innerHTML = i.type;
|
|
754
|
+
input.appendChild( typeDesc );
|
|
755
|
+
|
|
243
756
|
if( i.name )
|
|
244
757
|
{
|
|
245
758
|
var name = document.createElement( 'span' );
|
|
@@ -256,7 +769,7 @@ class GraphEditor {
|
|
|
256
769
|
{
|
|
257
770
|
var nodeOutputs = null;
|
|
258
771
|
|
|
259
|
-
if(
|
|
772
|
+
if( hasOutputs )
|
|
260
773
|
{
|
|
261
774
|
nodeOutputs = document.createElement( 'div' );
|
|
262
775
|
nodeOutputs.classList.add( 'lexgraphnodeoutputs' );
|
|
@@ -268,11 +781,12 @@ class GraphEditor {
|
|
|
268
781
|
{
|
|
269
782
|
if( !o.type )
|
|
270
783
|
{
|
|
271
|
-
console.warn( `Missing type for node [${ node.
|
|
784
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
272
785
|
}
|
|
273
786
|
|
|
274
787
|
var output = document.createElement( 'div' );
|
|
275
|
-
output.className = 'lexgraphnodeio
|
|
788
|
+
output.className = 'lexgraphnodeio iooutput';
|
|
789
|
+
output.dataset[ 'index' ] = nodeOutputs.childElementCount;
|
|
276
790
|
|
|
277
791
|
if( o.name )
|
|
278
792
|
{
|
|
@@ -284,449 +798,2784 @@ class GraphEditor {
|
|
|
284
798
|
|
|
285
799
|
var type = document.createElement( 'span' );
|
|
286
800
|
type.className = 'io__type output ' + o.type;
|
|
287
|
-
type.
|
|
801
|
+
type.dataset[ 'type' ] = o.type;
|
|
802
|
+
type.innerHTML = '<span>' + o.type[ 0 ].toUpperCase() + '</span>';
|
|
288
803
|
output.appendChild( type );
|
|
289
804
|
|
|
805
|
+
var typeDesc = document.createElement( 'span' );
|
|
806
|
+
typeDesc.className = 'io__typedesc output ' + o.type;
|
|
807
|
+
typeDesc.innerHTML = o.type;
|
|
808
|
+
output.appendChild( typeDesc );
|
|
809
|
+
|
|
290
810
|
nodeOutputs.appendChild( output );
|
|
291
811
|
}
|
|
292
812
|
}
|
|
293
813
|
|
|
294
|
-
//
|
|
814
|
+
// Move nodes
|
|
815
|
+
|
|
816
|
+
LX.makeDraggable( nodeContainer, {
|
|
817
|
+
onMove: this._onMoveNodes.bind( this ),
|
|
818
|
+
onDragStart: this._onDragNode.bind( this )
|
|
819
|
+
} );
|
|
820
|
+
|
|
821
|
+
this._addNodeIOEvents( nodeContainer );
|
|
295
822
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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;
|
|
301
828
|
|
|
302
829
|
this._domNodes.appendChild( nodeContainer );
|
|
303
|
-
}
|
|
304
830
|
|
|
305
|
-
|
|
831
|
+
// Only 1 main per graph!
|
|
832
|
+
if( node.title == 'Main' )
|
|
833
|
+
{
|
|
834
|
+
this.main = id;
|
|
835
|
+
}
|
|
306
836
|
|
|
307
|
-
|
|
308
|
-
}
|
|
837
|
+
node.size = new LX.vec2( nodeContainer.offsetWidth, nodeContainer.offsetHeight );
|
|
309
838
|
|
|
310
|
-
|
|
839
|
+
node.resizeObserver = new ResizeObserver( entries => {
|
|
311
840
|
|
|
312
|
-
|
|
313
|
-
|
|
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;
|
|
314
852
|
}
|
|
315
853
|
|
|
316
|
-
|
|
854
|
+
_updateNodeDOMIOs( dom, node ) {
|
|
317
855
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this._mousePosition = new LX.vec2( e.clientX - rect.x , e.clientY - rect.y );
|
|
321
|
-
this._deltaMousePosition = this._mousePosition.sub( this._lastMousePosition );
|
|
856
|
+
// Inputs and outputs
|
|
857
|
+
var nodeIO = dom.querySelector( '.lexgraphnodeios' );
|
|
322
858
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
else if( e.type == 'mouseup' )
|
|
859
|
+
const hasInputs = node.inputs && node.inputs.length;
|
|
860
|
+
const hasOutputs = node.outputs && node.outputs.length;
|
|
861
|
+
|
|
862
|
+
// Inputs
|
|
329
863
|
{
|
|
330
|
-
|
|
331
|
-
|
|
864
|
+
var nodeInputs = null;
|
|
865
|
+
|
|
866
|
+
if( hasInputs )
|
|
867
|
+
{
|
|
868
|
+
nodeInputs = nodeIO.querySelector( '.lexgraphnodeinputs' );
|
|
869
|
+
nodeInputs.innerHTML = "";
|
|
332
870
|
}
|
|
333
|
-
}
|
|
334
871
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
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
|
+
}
|
|
338
904
|
}
|
|
339
905
|
|
|
340
|
-
|
|
906
|
+
// Outputs
|
|
341
907
|
{
|
|
342
|
-
|
|
908
|
+
var nodeOutputs = null;
|
|
909
|
+
|
|
910
|
+
if( hasOutputs )
|
|
343
911
|
{
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
case LX.MOUSE_TRIPLE_CLICK:
|
|
347
|
-
break;
|
|
912
|
+
nodeOutputs = nodeIO.querySelector( '.lexgraphnodeoutputs' );
|
|
913
|
+
nodeOutputs.innerHTML = "";
|
|
348
914
|
}
|
|
349
|
-
}
|
|
350
915
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
916
|
+
for( let o of node.outputs )
|
|
917
|
+
{
|
|
918
|
+
if( !o.type )
|
|
919
|
+
{
|
|
920
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
921
|
+
}
|
|
355
922
|
|
|
356
|
-
|
|
923
|
+
var output = document.createElement( 'div' );
|
|
924
|
+
output.className = 'lexgraphnodeio iooutput';
|
|
925
|
+
output.dataset[ 'index' ] = nodeOutputs.childElementCount;
|
|
357
926
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
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
|
+
}
|
|
364
934
|
|
|
365
|
-
|
|
366
|
-
|
|
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 );
|
|
367
939
|
|
|
368
|
-
|
|
940
|
+
var typeDesc = document.createElement( 'span' );
|
|
941
|
+
typeDesc.className = 'io__typedesc output ' + o.type;
|
|
942
|
+
typeDesc.innerHTML = o.type;
|
|
943
|
+
output.appendChild( typeDesc );
|
|
369
944
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
this._processBackgroundClick( e );
|
|
373
|
-
return;
|
|
945
|
+
nodeOutputs.appendChild( output );
|
|
946
|
+
}
|
|
374
947
|
}
|
|
375
948
|
|
|
376
|
-
|
|
949
|
+
this._addNodeIOEvents( dom );
|
|
377
950
|
}
|
|
378
951
|
|
|
379
|
-
|
|
952
|
+
_addNodeIOEvents( nodeContainer ) {
|
|
380
953
|
|
|
381
|
-
|
|
382
|
-
}
|
|
954
|
+
const nodeIO = nodeContainer.querySelector( '.lexgraphnodeios' );
|
|
383
955
|
|
|
384
|
-
|
|
956
|
+
// Manage links
|
|
385
957
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
if( rightPressed )
|
|
389
|
-
{
|
|
390
|
-
this._patternPosition.add( this._deltaMousePosition.div( this._scale ), this._patternPosition );
|
|
958
|
+
nodeIO.querySelectorAll( '.lexgraphnodeio' ).forEach( el => {
|
|
391
959
|
|
|
392
|
-
|
|
393
|
-
}
|
|
394
|
-
}
|
|
960
|
+
el.addEventListener( 'mousedown', e => {
|
|
395
961
|
|
|
396
|
-
|
|
962
|
+
// Only for left click..
|
|
963
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
964
|
+
return;
|
|
397
965
|
|
|
398
|
-
|
|
966
|
+
this.lastMouseDown = LX.getTime();
|
|
967
|
+
|
|
968
|
+
this._generatingLink = {
|
|
969
|
+
index: parseInt( el.dataset[ 'index' ] ),
|
|
970
|
+
io: el,
|
|
971
|
+
ioType: el.classList.contains( 'ioinput' ) ? GraphEditor.NODE_IO_INPUT : GraphEditor.NODE_IO_OUTPUT,
|
|
972
|
+
domEl: nodeContainer
|
|
973
|
+
};
|
|
399
974
|
|
|
400
|
-
|
|
401
|
-
|
|
975
|
+
e.stopPropagation();
|
|
976
|
+
e.stopImmediatePropagation();
|
|
977
|
+
} );
|
|
402
978
|
|
|
403
|
-
|
|
979
|
+
el.addEventListener( 'mouseup', e => {
|
|
404
980
|
|
|
405
|
-
|
|
981
|
+
e.stopPropagation();
|
|
982
|
+
e.stopImmediatePropagation();
|
|
406
983
|
|
|
407
|
-
|
|
408
|
-
|
|
984
|
+
// Single click..
|
|
985
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 200 ) {
|
|
986
|
+
delete this._generatingLink;
|
|
987
|
+
return;
|
|
988
|
+
}
|
|
409
989
|
|
|
410
|
-
|
|
990
|
+
if( this._generatingLink )
|
|
991
|
+
{
|
|
992
|
+
// Check for IO
|
|
993
|
+
if( !this._onLink( e ) )
|
|
994
|
+
{
|
|
995
|
+
// Delete entire SVG if not a successful connection..
|
|
996
|
+
LX.UTILS.deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
997
|
+
}
|
|
411
998
|
|
|
412
|
-
|
|
413
|
-
|
|
999
|
+
delete this._generatingLink;
|
|
1000
|
+
}
|
|
1001
|
+
} );
|
|
414
1002
|
|
|
415
|
-
|
|
1003
|
+
el.addEventListener( 'click', e => {
|
|
416
1004
|
|
|
417
|
-
|
|
1005
|
+
if( !el.links )
|
|
1006
|
+
return;
|
|
418
1007
|
|
|
419
|
-
|
|
1008
|
+
const nodeId = nodeContainer.dataset[ 'id' ];
|
|
420
1009
|
|
|
421
|
-
|
|
422
|
-
|
|
1010
|
+
this._deleteLinks( nodeId, el );
|
|
1011
|
+
} );
|
|
423
1012
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
LX.addContextMenu( "Test", e, m => {
|
|
427
|
-
m.add( "option 1", () => { } );
|
|
428
|
-
m.add( "option 2", () => { } );
|
|
429
|
-
});
|
|
1013
|
+
} );
|
|
430
1014
|
}
|
|
431
1015
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
1016
|
+
_getAllDOMNodes( includeGroups, exclude ) {
|
|
1017
|
+
|
|
1018
|
+
var elements = null;
|
|
435
1019
|
|
|
436
|
-
|
|
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' ) );
|
|
437
1024
|
|
|
438
|
-
|
|
1025
|
+
if( exclude )
|
|
1026
|
+
{
|
|
1027
|
+
elements = elements.filter( v => v != exclude );
|
|
1028
|
+
}
|
|
439
1029
|
|
|
440
|
-
|
|
1030
|
+
return elements;
|
|
441
1031
|
}
|
|
442
1032
|
|
|
443
|
-
|
|
444
|
-
* @method update
|
|
445
|
-
*/
|
|
446
|
-
|
|
447
|
-
_update() {
|
|
448
|
-
|
|
449
|
-
console.log("Update");
|
|
450
|
-
}
|
|
1033
|
+
_onMoveNodes( target ) {
|
|
451
1034
|
|
|
452
|
-
|
|
1035
|
+
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
1036
|
+
dT.div( this.currentGraph.scale, dT);
|
|
453
1037
|
|
|
454
|
-
|
|
1038
|
+
for( let nodeId of this.selectedNodes )
|
|
455
1039
|
{
|
|
456
|
-
|
|
457
|
-
pattern.setAttribute( 'id', 'pattern-0' );
|
|
458
|
-
pattern.setAttribute( 'x', this._patternPosition.x );
|
|
459
|
-
pattern.setAttribute( 'y', this._patternPosition.y );
|
|
460
|
-
pattern.setAttribute( 'width', this._patternSize.x )
|
|
461
|
-
pattern.setAttribute( 'height', this._patternSize.y );
|
|
462
|
-
pattern.setAttribute( 'patternUnits', 'userSpaceOnUse' );
|
|
1040
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
463
1041
|
|
|
464
|
-
|
|
465
|
-
circle.setAttribute( 'cx', this._circlePatternSize );
|
|
466
|
-
circle.setAttribute( 'cy', this._circlePatternSize );
|
|
467
|
-
circle.setAttribute( 'r', this._circlePatternSize );
|
|
468
|
-
circle.setAttribute( 'fill', this._circlePatternColor );
|
|
1042
|
+
this._translateNode( el, dT );
|
|
469
1043
|
|
|
470
|
-
|
|
1044
|
+
this._updateNodeLinks( nodeId );
|
|
471
1045
|
}
|
|
472
|
-
|
|
473
|
-
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
474
|
-
svg.classList.add( "background-svg" );
|
|
475
|
-
svg.style.width = "100%";
|
|
476
|
-
svg.style.height = "100%";
|
|
1046
|
+
}
|
|
477
1047
|
|
|
478
|
-
|
|
1048
|
+
_onDragNode( target, e ) {
|
|
479
1049
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
rect.setAttribute( 'y', '0' );
|
|
483
|
-
rect.setAttribute( 'width', '100%' );
|
|
484
|
-
rect.setAttribute( 'height', '100%' );
|
|
485
|
-
rect.setAttribute( 'fill', 'url(#pattern-0)' );
|
|
1050
|
+
if( !e.shiftKey )
|
|
1051
|
+
return;
|
|
486
1052
|
|
|
487
|
-
|
|
1053
|
+
this._cloneNodes();
|
|
1054
|
+
}
|
|
488
1055
|
|
|
489
|
-
|
|
1056
|
+
_onMoveGroup( target ) {
|
|
490
1057
|
|
|
491
|
-
|
|
492
|
-
}
|
|
1058
|
+
// Move nodes inside the group
|
|
493
1059
|
|
|
494
|
-
|
|
1060
|
+
const groupNodeIds = target.nodes;
|
|
495
1061
|
|
|
496
|
-
if( !
|
|
1062
|
+
if( !groupNodeIds )
|
|
497
1063
|
return;
|
|
498
1064
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
const patternPosition = this._patternPosition.mul( this._scale );
|
|
502
|
-
|
|
503
|
-
let pattern = this._background.querySelector( 'pattern' );
|
|
504
|
-
pattern.setAttribute( 'x', patternPosition.x );
|
|
505
|
-
pattern.setAttribute( 'y', patternPosition.y );
|
|
506
|
-
pattern.setAttribute( 'width', patternSize.x )
|
|
507
|
-
pattern.setAttribute( 'height', patternSize.y );
|
|
1065
|
+
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
1066
|
+
dT.div( this.currentGraph.scale, dT);
|
|
508
1067
|
|
|
509
|
-
|
|
510
|
-
circle.setAttribute( 'cx', circlePatternSize );
|
|
511
|
-
circle.setAttribute( 'cy', circlePatternSize );
|
|
512
|
-
circle.setAttribute( 'r', circlePatternSize );
|
|
1068
|
+
this._translateNode( target, dT );
|
|
513
1069
|
|
|
514
|
-
|
|
1070
|
+
for( let nodeId of groupNodeIds )
|
|
1071
|
+
{
|
|
1072
|
+
const isGroup = nodeId.constructor !== String;
|
|
515
1073
|
|
|
516
|
-
|
|
517
|
-
const h = this._domNodes.offsetHeight * 0.5;
|
|
1074
|
+
const el = isGroup ? nodeId : this._getNodeDOMElement( nodeId );
|
|
518
1075
|
|
|
519
|
-
|
|
520
|
-
const dh = h - h * this._scale;
|
|
1076
|
+
this._translateNode( el, dT, !isGroup );
|
|
521
1077
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
`;
|
|
1078
|
+
if( !isGroup )
|
|
1079
|
+
this._updateNodeLinks( nodeId );
|
|
1080
|
+
}
|
|
526
1081
|
}
|
|
527
1082
|
|
|
528
|
-
|
|
1083
|
+
_onDragGroup( target ) {
|
|
529
1084
|
|
|
530
|
-
|
|
531
|
-
}
|
|
1085
|
+
// Get nodes inside the group to be moved
|
|
532
1086
|
|
|
533
|
-
|
|
1087
|
+
const group_bb = this._getBoundingFromGroup( target );
|
|
534
1088
|
|
|
535
|
-
const
|
|
536
|
-
var textMetrics = ctx.measureText( node.name );
|
|
1089
|
+
const groupNodeIds = [ ];
|
|
537
1090
|
|
|
538
|
-
let
|
|
1091
|
+
for( let dom of this._getAllDOMNodes( true, target ) )
|
|
1092
|
+
{
|
|
1093
|
+
const x = parseFloat( dom.style.left );
|
|
1094
|
+
const y = parseFloat( dom.style.top );
|
|
1095
|
+
const node_bb = new BoundingBox( new LX.vec2( x, y ), new LX.vec2( dom.offsetWidth - 6, dom.offsetHeight - 6 ) );
|
|
1096
|
+
|
|
1097
|
+
if( !group_bb.inside( node_bb ) )
|
|
1098
|
+
continue;
|
|
539
1099
|
|
|
540
|
-
|
|
541
|
-
|
|
1100
|
+
groupNodeIds.push( dom.dataset[ 'id' ] ?? dom );
|
|
1101
|
+
}
|
|
542
1102
|
|
|
543
|
-
|
|
1103
|
+
target.nodes = groupNodeIds;
|
|
544
1104
|
}
|
|
545
1105
|
|
|
546
|
-
|
|
1106
|
+
_selectNode( dom, multiSelection, forceOrder = true ) {
|
|
547
1107
|
|
|
548
|
-
|
|
1108
|
+
if( !multiSelection )
|
|
1109
|
+
this.unSelectAll( true );
|
|
549
1110
|
|
|
550
|
-
|
|
1111
|
+
dom.classList.add( 'selected' );
|
|
551
1112
|
|
|
552
|
-
|
|
1113
|
+
const id = dom.dataset[ 'id' ];
|
|
553
1114
|
|
|
554
|
-
|
|
555
|
-
// let cp1 = { x: 230, y: 30 };
|
|
556
|
-
// let cp2 = { x: 150, y: 80 };
|
|
557
|
-
// let end = { x: 250, y: 100 };
|
|
1115
|
+
this.selectedNodes.push( id );
|
|
558
1116
|
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
1117
|
+
if( forceOrder )
|
|
1118
|
+
{
|
|
1119
|
+
// Reorder nodes to draw on top..
|
|
1120
|
+
this._domNodes.appendChild( dom );
|
|
1121
|
+
}
|
|
564
1122
|
|
|
565
|
-
|
|
566
|
-
// {
|
|
567
|
-
// // Discard nodes without inputs...
|
|
568
|
-
// if (!node.inputs || !node.inputs.length) {
|
|
569
|
-
// continue;
|
|
570
|
-
// }
|
|
1123
|
+
const node = this.nodes[ id ].data;
|
|
571
1124
|
|
|
572
|
-
|
|
1125
|
+
this.propertiesDialog.setTitle( node.title );
|
|
573
1126
|
|
|
1127
|
+
var panel = this.propertiesDialog.panel;
|
|
1128
|
+
panel.clear();
|
|
574
1129
|
|
|
575
|
-
//
|
|
576
|
-
|
|
577
|
-
|
|
1130
|
+
// Add description
|
|
1131
|
+
if( node.constructor.description )
|
|
1132
|
+
{
|
|
1133
|
+
panel.addText( null, node.constructor.description, null, { disabled: true } );
|
|
1134
|
+
}
|
|
578
1135
|
|
|
579
|
-
|
|
580
|
-
|
|
1136
|
+
// Allow change name if input
|
|
1137
|
+
if( node.constructor.category == 'inputs' )
|
|
1138
|
+
{
|
|
1139
|
+
panel.addText( 'Name', node.title, (v) => {
|
|
1140
|
+
node.title = v;
|
|
1141
|
+
dom.querySelector( '.lexgraphnodeheader' ).innerText = v;
|
|
1142
|
+
} );
|
|
1143
|
+
}
|
|
581
1144
|
|
|
582
|
-
|
|
1145
|
+
for( let p of node.properties )
|
|
583
1146
|
{
|
|
584
|
-
|
|
585
|
-
|
|
1147
|
+
switch( p.type )
|
|
1148
|
+
{
|
|
1149
|
+
case 'float':
|
|
1150
|
+
case 'int':
|
|
1151
|
+
panel.addNumber( p.name, p.value, (v) => { p.value = v } );
|
|
1152
|
+
break;
|
|
1153
|
+
case 'string':
|
|
1154
|
+
panel.addText( p.name, p.value, (v) => { p.value = v } );
|
|
1155
|
+
break;
|
|
1156
|
+
case 'vec2':
|
|
1157
|
+
panel.addVector2( p.name, p.value, (v) => { p.value = v } );
|
|
1158
|
+
break;
|
|
1159
|
+
case 'vec3':
|
|
1160
|
+
panel.addVector3( p.name, p.value, (v) => { p.value = v } );
|
|
1161
|
+
break;
|
|
1162
|
+
case 'vec4':
|
|
1163
|
+
panel.addVector4( p.name, p.value, (v) => { p.value = v } );
|
|
1164
|
+
break;
|
|
1165
|
+
case 'select':
|
|
1166
|
+
panel.addDropdown( p.name, p.options, p.value, (v) => { p.value = v } );
|
|
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;
|
|
1178
|
+
}
|
|
586
1179
|
}
|
|
587
1180
|
|
|
588
|
-
|
|
1181
|
+
this._togglePropertiesDialog( true );
|
|
589
1182
|
}
|
|
590
1183
|
|
|
591
|
-
|
|
1184
|
+
_unSelectNode( dom ) {
|
|
592
1185
|
|
|
593
|
-
|
|
1186
|
+
dom.classList.remove( 'selected' );
|
|
594
1187
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
1188
|
+
// Delete from selected..
|
|
1189
|
+
const idx = this.selectedNodes.indexOf( dom.dataset[ 'id' ] );
|
|
1190
|
+
this.selectedNodes.splice( idx, 1 );
|
|
598
1191
|
|
|
599
|
-
|
|
1192
|
+
if( !this.selectedNodes.length )
|
|
1193
|
+
this._togglePropertiesDialog( false );
|
|
1194
|
+
}
|
|
600
1195
|
|
|
601
|
-
|
|
602
|
-
* @param {*} options
|
|
603
|
-
*
|
|
604
|
-
*/
|
|
1196
|
+
_translateNode( dom, deltaTranslation, updateBasePosition = true ) {
|
|
605
1197
|
|
|
606
|
-
|
|
1198
|
+
const translation = deltaTranslation.add( new LX.vec2( parseFloat( dom.style.left ), parseFloat( dom.style.top ) ) );
|
|
607
1199
|
|
|
608
|
-
|
|
1200
|
+
if( this._snapToGrid && dom.mustSnap )
|
|
1201
|
+
{
|
|
1202
|
+
const snapSize = this._patternSize.x * this._snapValue * this._snapValue;
|
|
1203
|
+
translation.x = Math.floor( translation.x / snapSize ) * snapSize;
|
|
1204
|
+
translation.y = Math.floor( translation.y / snapSize ) * snapSize;
|
|
1205
|
+
dom.mustSnap = false;
|
|
1206
|
+
}
|
|
609
1207
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
{
|
|
620
|
-
name: "Offset",
|
|
621
|
-
type: "vec2"
|
|
622
|
-
}
|
|
623
|
-
]
|
|
624
|
-
}),
|
|
625
|
-
new GraphNode({
|
|
626
|
-
name: "Node 2",
|
|
627
|
-
size: new LX.vec2( 120, 100 ),
|
|
628
|
-
position: new LX.vec2( 500, 350 ),
|
|
629
|
-
color: "#f7884c",
|
|
630
|
-
inputs: [],
|
|
631
|
-
outputs: [
|
|
632
|
-
{
|
|
633
|
-
name: "Speed",
|
|
634
|
-
type: "float"
|
|
635
|
-
},
|
|
636
|
-
{
|
|
637
|
-
name: "Offset",
|
|
638
|
-
type: "vec2"
|
|
639
|
-
},
|
|
640
|
-
{
|
|
641
|
-
name: "Loop",
|
|
642
|
-
type: "bool"
|
|
643
|
-
}
|
|
644
|
-
]
|
|
645
|
-
}),
|
|
646
|
-
new GraphNode({
|
|
647
|
-
name: "Node 3",
|
|
648
|
-
position: new LX.vec2( 200, 400 ),
|
|
649
|
-
inputs: [
|
|
650
|
-
{
|
|
651
|
-
name: "Speed",
|
|
652
|
-
type: "float"
|
|
653
|
-
},
|
|
654
|
-
{
|
|
655
|
-
name: "Offset",
|
|
656
|
-
type: "vec2"
|
|
657
|
-
}
|
|
658
|
-
],
|
|
659
|
-
outputs: [
|
|
660
|
-
{
|
|
661
|
-
name: "Speed",
|
|
662
|
-
type: "float"
|
|
663
|
-
},
|
|
664
|
-
{
|
|
665
|
-
name: "Offset",
|
|
666
|
-
type: "vec3"
|
|
667
|
-
},
|
|
668
|
-
{
|
|
669
|
-
name: "Loop",
|
|
670
|
-
type: "bool"
|
|
671
|
-
}
|
|
672
|
-
]
|
|
673
|
-
}),
|
|
674
|
-
new GraphNode({
|
|
675
|
-
name: "Add",
|
|
676
|
-
position: new LX.vec2( 300, 300 ),
|
|
677
|
-
inputs: [
|
|
678
|
-
{
|
|
679
|
-
type: "float"
|
|
680
|
-
},
|
|
681
|
-
{
|
|
682
|
-
type: "float"
|
|
683
|
-
}
|
|
684
|
-
],
|
|
685
|
-
outputs: [
|
|
686
|
-
{
|
|
687
|
-
type: "float"
|
|
688
|
-
}
|
|
689
|
-
]
|
|
690
|
-
})
|
|
691
|
-
];
|
|
1208
|
+
dom.style.left = ( translation.x ) + "px";
|
|
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
|
+
}
|
|
692
1217
|
}
|
|
693
|
-
}
|
|
694
1218
|
|
|
695
|
-
|
|
1219
|
+
_deleteNode( nodeId ) {
|
|
696
1220
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
1221
|
+
const nodeInfo = this.nodes[ nodeId ];
|
|
1222
|
+
const node = nodeInfo.data;
|
|
1223
|
+
const el = nodeInfo.dom;
|
|
700
1224
|
|
|
701
|
-
|
|
1225
|
+
console.assert( el );
|
|
702
1226
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
1227
|
+
if( node.constructor.blockDelete )
|
|
1228
|
+
{
|
|
1229
|
+
console.warn( `Can't delete node!` );
|
|
1230
|
+
return;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
LX.UTILS.deleteElement( el );
|
|
1234
|
+
|
|
1235
|
+
// Delete from the editor
|
|
1236
|
+
|
|
1237
|
+
delete this.nodes[ nodeId ];
|
|
707
1238
|
|
|
708
|
-
|
|
1239
|
+
// Delete from the graph data
|
|
709
1240
|
|
|
710
|
-
this.
|
|
711
|
-
|
|
712
|
-
this.
|
|
713
|
-
|
|
1241
|
+
const idx = this.currentGraph.nodes.findIndex( v => v.id === nodeId );
|
|
1242
|
+
console.assert( idx >= 0 );
|
|
1243
|
+
this.currentGraph.nodes.splice( idx, 1 );
|
|
1244
|
+
|
|
1245
|
+
// Delete connected links..
|
|
714
1246
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
1247
|
+
for( let key in this.currentGraph.links )
|
|
1248
|
+
{
|
|
1249
|
+
if( !key.includes( nodeId ) )
|
|
1250
|
+
continue;
|
|
718
1251
|
|
|
719
|
-
|
|
1252
|
+
const aIdx = key.indexOf( '@' );
|
|
1253
|
+
const targetIsInput = key.substring( aIdx + 1 ) != nodeId;
|
|
720
1254
|
|
|
721
|
-
|
|
1255
|
+
// Remove the connection from the other before deleting..
|
|
722
1256
|
|
|
723
|
-
|
|
724
|
-
let sY = rows * GraphEditor.NODE_ROW_HEIGHT + GraphEditor.NODE_TITLE_HEIGHT;
|
|
1257
|
+
const numLinks = this.currentGraph.links[ key ].length;
|
|
725
1258
|
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
1259
|
+
for( var i = 0; i < numLinks; ++i )
|
|
1260
|
+
{
|
|
1261
|
+
var link = this.currentGraph.links[ key ][ i ];
|
|
729
1262
|
|
|
730
|
-
LX.
|
|
1263
|
+
LX.UTILS.deleteElement( link.path.parentElement );
|
|
1264
|
+
|
|
1265
|
+
const targetNodeId = targetIsInput ? link.inputNode : link.outputNode;
|
|
1266
|
+
|
|
1267
|
+
const targetNodeDOM = this._getNodeDOMElement( targetNodeId );
|
|
1268
|
+
const ios = targetNodeDOM.querySelector( targetIsInput ? '.lexgraphnodeinputs' : '.lexgraphnodeoutputs' );
|
|
1269
|
+
const io = ios.childNodes[ targetIsInput ? link.inputIdx : link.outputIdx ];
|
|
1270
|
+
|
|
1271
|
+
const ioIndex = targetIsInput ? link.outputIdx : link.inputIdx;
|
|
1272
|
+
const nodelinkidx = io.links[ ioIndex ].indexOf( nodeId );
|
|
1273
|
+
io.links[ ioIndex ].splice( nodelinkidx, 1 );
|
|
1274
|
+
|
|
1275
|
+
// Unique link, so it's done..
|
|
1276
|
+
if( targetIsInput )
|
|
1277
|
+
{
|
|
1278
|
+
delete io.dataset[ 'active' ];
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// Check if any link left in case of output
|
|
1282
|
+
else
|
|
1283
|
+
{
|
|
1284
|
+
var active = false;
|
|
1285
|
+
for( var links of io.links )
|
|
1286
|
+
{
|
|
1287
|
+
if( !links )
|
|
1288
|
+
continue;
|
|
1289
|
+
for( var j of links ) {
|
|
1290
|
+
active |= ( !!j );
|
|
1291
|
+
}
|
|
1292
|
+
}
|
|
1293
|
+
if( !active )
|
|
1294
|
+
delete io.dataset[ 'active' ];
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
delete this.currentGraph.links[ key ];
|
|
1299
|
+
}
|
|
1300
|
+
}
|
|
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
|
+
|
|
1318
|
+
_cloneNodes() {
|
|
1319
|
+
|
|
1320
|
+
// Clone all selected nodes
|
|
1321
|
+
const selectedIds = LX.deepCopy( this.selectedNodes );
|
|
1322
|
+
|
|
1323
|
+
this.unSelectAll();
|
|
1324
|
+
|
|
1325
|
+
for( let nodeId of selectedIds )
|
|
1326
|
+
{
|
|
1327
|
+
const nodeInfo = this.nodes[ nodeId ];
|
|
1328
|
+
if( !nodeInfo )
|
|
1329
|
+
return;
|
|
1330
|
+
|
|
1331
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
1332
|
+
const data = nodeInfo.data;
|
|
1333
|
+
const newNode = GraphEditor.addNode( data.type );
|
|
1334
|
+
const newDom = this._createNodeDOM( newNode );
|
|
1335
|
+
|
|
1336
|
+
this._translateNode( newDom, this._getNodePosition( el ) );
|
|
1337
|
+
|
|
1338
|
+
this._selectNode( newDom, true );
|
|
1339
|
+
|
|
1340
|
+
this.currentGraph.nodes.push( newNode );
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
// This is in pattern space!
|
|
1345
|
+
_getNodePosition( dom ) {
|
|
1346
|
+
|
|
1347
|
+
return new LX.vec2( parseFloat( dom.style.left ), parseFloat( dom.style.top ) );
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
_getNodeDOMElement( nodeId ) {
|
|
1351
|
+
|
|
1352
|
+
return this.nodes[ nodeId ] ? this.nodes[ nodeId ].dom : null;
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
_getLinks( nodeSrcId, nodeDstId ) {
|
|
1356
|
+
|
|
1357
|
+
const str = nodeSrcId + '@' + nodeDstId;
|
|
1358
|
+
return this.currentGraph.links[ str ];
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
_deleteLinks( nodeId, io ) {
|
|
1362
|
+
|
|
1363
|
+
const isInput = io.classList.contains( 'ioinput' );
|
|
1364
|
+
const srcIndex = parseInt( io.dataset[ 'index' ] );
|
|
1365
|
+
|
|
1366
|
+
if( isInput ) // Only one "link to output" to delete
|
|
1367
|
+
{
|
|
1368
|
+
let targetIndex;
|
|
1369
|
+
|
|
1370
|
+
const targets = io.links.filter( (v, i) => { targetIndex = i; return v !== undefined; } )[ 0 ];
|
|
1371
|
+
const targetId = targets[ 0 ];
|
|
1372
|
+
|
|
1373
|
+
// It has been deleted..
|
|
1374
|
+
if( !targetId )
|
|
1375
|
+
return;
|
|
1376
|
+
|
|
1377
|
+
var links = this._getLinks( targetId, nodeId );
|
|
1378
|
+
|
|
1379
|
+
var linkIdx = links.findIndex( i => ( i.inputIdx == srcIndex && i.outputIdx == targetIndex ) );
|
|
1380
|
+
LX.UTILS.deleteElement( links[ linkIdx ].path.parentElement );
|
|
1381
|
+
links.splice( linkIdx, 1 );
|
|
1382
|
+
|
|
1383
|
+
// Input has no longer any connected link
|
|
1384
|
+
|
|
1385
|
+
delete io.links;
|
|
1386
|
+
delete io.dataset[ 'active' ];
|
|
1387
|
+
|
|
1388
|
+
// Remove a connection from the target connections
|
|
1389
|
+
|
|
1390
|
+
const targetDOM = this._getNodeDOMElement( targetId );
|
|
1391
|
+
const ios = targetDOM.querySelector( '.lexgraphnodeoutputs' );
|
|
1392
|
+
const targetIO = ios.childNodes[ targetIndex ];
|
|
1393
|
+
|
|
1394
|
+
const idx = targetIO.links[ srcIndex ].findIndex( v => v == nodeId );
|
|
1395
|
+
targetIO.links[ srcIndex ].splice( idx, 1 );
|
|
1396
|
+
|
|
1397
|
+
let active = false;
|
|
1398
|
+
|
|
1399
|
+
for( var ls of targetIO.links )
|
|
1400
|
+
{
|
|
1401
|
+
if( !ls ) continue;
|
|
1402
|
+
// Check links left per io
|
|
1403
|
+
active |= ls.reduce( c => c !== undefined, 0 );
|
|
1404
|
+
}
|
|
1405
|
+
|
|
1406
|
+
if( !active )
|
|
1407
|
+
{
|
|
1408
|
+
delete targetIO.links;
|
|
1409
|
+
delete targetIO.dataset[ 'active' ];
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
else // Delete ALL "to input links"
|
|
1413
|
+
{
|
|
1414
|
+
|
|
1415
|
+
const numLinks = io.links.length;
|
|
1416
|
+
|
|
1417
|
+
for( let targetIndex = 0; targetIndex < numLinks; ++targetIndex )
|
|
1418
|
+
{
|
|
1419
|
+
const targets = io.links[ targetIndex ];
|
|
1420
|
+
|
|
1421
|
+
if( !targets )
|
|
1422
|
+
continue;
|
|
1423
|
+
|
|
1424
|
+
for( let it = ( targets.length - 1 ); it >= 0 ; --it )
|
|
1425
|
+
{
|
|
1426
|
+
const targetId = targets[ it ];
|
|
1427
|
+
var links = this._getLinks( nodeId, targetId );
|
|
1428
|
+
|
|
1429
|
+
var linkIdx = links.findIndex( i => ( i.inputIdx == targetIndex && i.outputIdx == srcIndex ) );
|
|
1430
|
+
LX.UTILS.deleteElement( links[ linkIdx ].path.parentElement );
|
|
1431
|
+
links.splice( linkIdx, 1 );
|
|
1432
|
+
|
|
1433
|
+
// Remove a connection from the output connections
|
|
1434
|
+
|
|
1435
|
+
io.links[ targetIndex ].splice( it, 1 );
|
|
1436
|
+
|
|
1437
|
+
// Input has no longer any connected link
|
|
1438
|
+
|
|
1439
|
+
const targetDOM = this._getNodeDOMElement( targetId );
|
|
1440
|
+
const ios = targetDOM.querySelector( '.lexgraphnodeinputs' );
|
|
1441
|
+
const targetIO = ios.childNodes[ targetIndex ];
|
|
1442
|
+
|
|
1443
|
+
delete targetIO.links;
|
|
1444
|
+
delete targetIO.dataset[ 'active' ];
|
|
1445
|
+
}
|
|
1446
|
+
}
|
|
1447
|
+
|
|
1448
|
+
delete io.links;
|
|
1449
|
+
delete io.dataset[ 'active' ];
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
|
|
1453
|
+
_processFocus( active ) {
|
|
1454
|
+
|
|
1455
|
+
this.isFocused = active;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
_processKeyDown( e ) {
|
|
1459
|
+
|
|
1460
|
+
// Prevent processing keys on inputs and others
|
|
1461
|
+
if( document.activeElement != this.root )
|
|
1462
|
+
return;
|
|
1463
|
+
|
|
1464
|
+
var key = e.key ?? e.detail.key;
|
|
1465
|
+
|
|
1466
|
+
switch( key ) {
|
|
1467
|
+
case 'Escape':
|
|
1468
|
+
this.unSelectAll();
|
|
1469
|
+
break;
|
|
1470
|
+
case 'Delete':
|
|
1471
|
+
case 'Backspace':
|
|
1472
|
+
e.preventDefault();
|
|
1473
|
+
this._deleteSelection( e );
|
|
1474
|
+
break;
|
|
1475
|
+
case 'g':
|
|
1476
|
+
if( e.ctrlKey )
|
|
1477
|
+
{
|
|
1478
|
+
e.preventDefault();
|
|
1479
|
+
this._createGroup();
|
|
1480
|
+
}
|
|
1481
|
+
break;
|
|
1482
|
+
case 'y':
|
|
1483
|
+
if( e.ctrlKey )
|
|
1484
|
+
{
|
|
1485
|
+
e.preventDefault();
|
|
1486
|
+
this._doRedo();
|
|
1487
|
+
}
|
|
1488
|
+
break;
|
|
1489
|
+
case 'z':
|
|
1490
|
+
if( e.ctrlKey )
|
|
1491
|
+
{
|
|
1492
|
+
e.preventDefault();
|
|
1493
|
+
this._doUndo();
|
|
1494
|
+
}
|
|
1495
|
+
break;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
this.keys[ key ] = true;
|
|
1499
|
+
}
|
|
1500
|
+
|
|
1501
|
+
_processKeyUp( e ) {
|
|
1502
|
+
|
|
1503
|
+
// Prevent processing keys on inputs and others
|
|
1504
|
+
if( document.activeElement != this.root )
|
|
1505
|
+
return;
|
|
1506
|
+
|
|
1507
|
+
var key = e.key ?? e.detail.key;
|
|
1508
|
+
|
|
1509
|
+
delete this.keys[ key ];
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
_processMouse( e ) {
|
|
1513
|
+
|
|
1514
|
+
const rect = this.root.getBoundingClientRect();
|
|
1515
|
+
|
|
1516
|
+
this._mousePosition = new LX.vec2( e.clientX - rect.x , e.clientY - rect.y );
|
|
1517
|
+
|
|
1518
|
+
const snapPosition = new LX.vec2( this._mousePosition.x, this._mousePosition.y );
|
|
1519
|
+
|
|
1520
|
+
if( this._snapToGrid )
|
|
1521
|
+
{
|
|
1522
|
+
const snapSize = this._patternSize.x * this._snapValue * this.currentGraph.scale;
|
|
1523
|
+
snapPosition.x = Math.floor( snapPosition.x / snapSize ) * snapSize;
|
|
1524
|
+
snapPosition.y = Math.floor( snapPosition.y / snapSize ) * snapSize;
|
|
1525
|
+
this._snappedDeltaMousePosition = snapPosition.sub( this._lastSnappedMousePosition );
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
this._deltaMousePosition = this._mousePosition.sub( this._lastMousePosition );
|
|
1529
|
+
|
|
1530
|
+
if( e.type == 'mousedown' )
|
|
1531
|
+
{
|
|
1532
|
+
this.lastMouseDown = LX.getTime();
|
|
1533
|
+
|
|
1534
|
+
this._processMouseDown( e );
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
else if( e.type == 'mouseup' )
|
|
1538
|
+
{
|
|
1539
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 200 ) {
|
|
1540
|
+
|
|
1541
|
+
this._processClick( e );
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
this._processMouseUp( e );
|
|
1545
|
+
}
|
|
1546
|
+
|
|
1547
|
+
else if( e.type == 'mousemove' )
|
|
1548
|
+
{
|
|
1549
|
+
this._processMouseMove( e );
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
else if ( e.type == 'click' ) // trick
|
|
1553
|
+
{
|
|
1554
|
+
switch( e.detail )
|
|
1555
|
+
{
|
|
1556
|
+
case LX.MOUSE_DOUBLE_CLICK:
|
|
1557
|
+
break;
|
|
1558
|
+
case LX.MOUSE_TRIPLE_CLICK:
|
|
1559
|
+
break;
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
else if ( e.type == 'mousewheel' ) {
|
|
1564
|
+
e.preventDefault();
|
|
1565
|
+
this._processWheel( e );
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
else if ( e.type == 'contextmenu' ) {
|
|
1569
|
+
|
|
1570
|
+
e.preventDefault();
|
|
1571
|
+
|
|
1572
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 300 ) {
|
|
1573
|
+
this._processContextMenu( e );
|
|
1574
|
+
}
|
|
1575
|
+
}
|
|
1576
|
+
|
|
1577
|
+
else if ( e.type == 'mouseleave' ) {
|
|
1578
|
+
|
|
1579
|
+
if( this._generatingLink )
|
|
1580
|
+
{
|
|
1581
|
+
this._processMouseUp( e );
|
|
1582
|
+
}
|
|
1583
|
+
}
|
|
1584
|
+
|
|
1585
|
+
if( this._snapToGrid )
|
|
1586
|
+
{
|
|
1587
|
+
this._lastSnappedMousePosition = snapPosition;
|
|
1588
|
+
}
|
|
1589
|
+
|
|
1590
|
+
this._lastMousePosition = this._mousePosition;
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
_processClick( e ) {
|
|
1594
|
+
|
|
1595
|
+
if( e.target.classList.contains( 'lexgraphnodes' ) || e.target.classList.contains( 'lexgraphgroup' ) )
|
|
1596
|
+
{
|
|
1597
|
+
this._processBackgroundClick( e );
|
|
1598
|
+
return;
|
|
1599
|
+
}
|
|
1600
|
+
}
|
|
1601
|
+
|
|
1602
|
+
_processBackgroundClick( e ) {
|
|
1603
|
+
|
|
1604
|
+
this.unSelectAll();
|
|
1605
|
+
}
|
|
1606
|
+
|
|
1607
|
+
_processMouseDown( e ) {
|
|
1608
|
+
|
|
1609
|
+
// Don't box select over a node..
|
|
1610
|
+
if( !e.target.classList.contains( 'lexgraphnode' ) && !e.target.classList.contains( 'lexgraphgroup' )
|
|
1611
|
+
&& e.button == LX.MOUSE_LEFT_CLICK )
|
|
1612
|
+
{
|
|
1613
|
+
this._boxSelecting = this._mousePosition;
|
|
1614
|
+
this._boxSelectRemoving = e.altKey;
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
_processMouseUp( e ) {
|
|
1619
|
+
|
|
1620
|
+
// It the event reaches this, the link isn't valid..
|
|
1621
|
+
if( this._generatingLink )
|
|
1622
|
+
{
|
|
1623
|
+
const linkInfo = Object.assign( { }, this._generatingLink );
|
|
1624
|
+
|
|
1625
|
+
// Delete old link
|
|
1626
|
+
LX.UTILS.deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
1627
|
+
delete this._generatingLink;
|
|
1628
|
+
|
|
1629
|
+
// Open contextmenu to auto-connect something..
|
|
1630
|
+
this._processContextMenu( e, linkInfo );
|
|
1631
|
+
}
|
|
1632
|
+
|
|
1633
|
+
else if( this._boxSelecting )
|
|
1634
|
+
{
|
|
1635
|
+
if( !e.ctrlKey && !e.altKey )
|
|
1636
|
+
this.unSelectAll();
|
|
1637
|
+
|
|
1638
|
+
this._selectNodesInBox( this._boxSelecting, this._mousePosition, e.altKey );
|
|
1639
|
+
|
|
1640
|
+
LX.UTILS.deleteElement( this._currentBoxSelectionSVG );
|
|
1641
|
+
|
|
1642
|
+
delete this._currentBoxSelectionSVG;
|
|
1643
|
+
delete this._boxSelecting;
|
|
1644
|
+
delete this._boxSelectRemoving;
|
|
1645
|
+
}
|
|
1646
|
+
}
|
|
1647
|
+
|
|
1648
|
+
_processMouseMove( e ) {
|
|
1649
|
+
|
|
1650
|
+
const rightPressed = ( e.which == 3 );
|
|
1651
|
+
|
|
1652
|
+
if( rightPressed )
|
|
1653
|
+
{
|
|
1654
|
+
this.currentGraph.translation.add( this._deltaMousePosition.div( this.currentGraph.scale ), this.currentGraph.translation );
|
|
1655
|
+
|
|
1656
|
+
this._updatePattern();
|
|
1657
|
+
|
|
1658
|
+
return;
|
|
1659
|
+
}
|
|
1660
|
+
|
|
1661
|
+
else if( this._generatingLink )
|
|
1662
|
+
{
|
|
1663
|
+
this._updatePreviewLink( e );
|
|
1664
|
+
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
|
|
1668
|
+
else if( this._boxSelecting )
|
|
1669
|
+
{
|
|
1670
|
+
this._drawBoxSelection( e );
|
|
1671
|
+
|
|
1672
|
+
return;
|
|
1673
|
+
}
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
_processWheel( e ) {
|
|
1677
|
+
|
|
1678
|
+
if( this._boxSelecting )
|
|
1679
|
+
return;
|
|
1680
|
+
|
|
1681
|
+
// Compute zoom center in pattern space using current scale
|
|
1682
|
+
|
|
1683
|
+
const rect = this.root.getBoundingClientRect();
|
|
1684
|
+
const zoomCenter = this._mousePosition ?? new LX.vec2( rect.width * 0.5, rect.height * 0.5 );
|
|
1685
|
+
|
|
1686
|
+
const center = this._getPatternPosition( zoomCenter );
|
|
1687
|
+
|
|
1688
|
+
const delta = e.deltaY;
|
|
1689
|
+
|
|
1690
|
+
if( delta > 0.0 ) this.currentGraph.scale *= 0.9;
|
|
1691
|
+
else this.currentGraph.scale *= ( 1.0 / 0.9 );
|
|
1692
|
+
|
|
1693
|
+
this.currentGraph.scale = LX.UTILS.clamp( this.currentGraph.scale, GraphEditor.MIN_SCALE, GraphEditor.MAX_SCALE );
|
|
1694
|
+
|
|
1695
|
+
// Compute zoom center in pattern space using new scale
|
|
1696
|
+
// and get delta..
|
|
1697
|
+
|
|
1698
|
+
const newCenter = this._getPatternPosition( zoomCenter );
|
|
1699
|
+
|
|
1700
|
+
const deltaCenter = newCenter.sub( center );
|
|
1701
|
+
|
|
1702
|
+
this.currentGraph.translation.add( deltaCenter, this.currentGraph.translation );
|
|
1703
|
+
|
|
1704
|
+
this._updatePattern();
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
_processContextMenu( e, autoConnect ) {
|
|
1708
|
+
|
|
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 => {
|
|
1721
|
+
|
|
1722
|
+
for( let type in GraphEditor.NODE_TYPES )
|
|
1723
|
+
{
|
|
1724
|
+
const baseClass = GraphEditor.NODE_TYPES[ type ];
|
|
1725
|
+
|
|
1726
|
+
if( baseClass.blockAdd )
|
|
1727
|
+
continue;
|
|
1728
|
+
|
|
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
|
+
}
|
|
1783
|
+
}
|
|
1784
|
+
|
|
1785
|
+
/**
|
|
1786
|
+
* @method start
|
|
1787
|
+
*/
|
|
1788
|
+
|
|
1789
|
+
start() {
|
|
1790
|
+
|
|
1791
|
+
this.mustStop = false;
|
|
1792
|
+
this.state = GraphEditor.RUNNING;
|
|
1793
|
+
|
|
1794
|
+
this.propagateEventToAllNodes( 'onStart' );
|
|
1795
|
+
|
|
1796
|
+
requestAnimationFrame( this._frame.bind(this) );
|
|
1797
|
+
}
|
|
1798
|
+
|
|
1799
|
+
/**
|
|
1800
|
+
* @method stop
|
|
1801
|
+
*/
|
|
1802
|
+
|
|
1803
|
+
stop() {
|
|
1804
|
+
|
|
1805
|
+
this.mustStop = true;
|
|
1806
|
+
this.state = GraphEditor.STOPPED;
|
|
1807
|
+
|
|
1808
|
+
this.propagateEventToAllNodes( 'onStop' );
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
/**
|
|
1812
|
+
* @method _frame
|
|
1813
|
+
*/
|
|
1814
|
+
|
|
1815
|
+
_frame() {
|
|
1816
|
+
|
|
1817
|
+
if( this.mustStop )
|
|
1818
|
+
{
|
|
1819
|
+
return;
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
requestAnimationFrame( this._frame.bind(this) );
|
|
1823
|
+
|
|
1824
|
+
// Only run here main graph!
|
|
1825
|
+
this.currentGraph._runStep( this.main );
|
|
1826
|
+
}
|
|
1827
|
+
|
|
1828
|
+
_generatePattern() {
|
|
1829
|
+
|
|
1830
|
+
// Generate pattern
|
|
1831
|
+
{
|
|
1832
|
+
var pattern = document.createElementNS( 'http://www.w3.org/2000/svg', 'pattern' );
|
|
1833
|
+
pattern.setAttribute( 'id', 'pattern-0' );
|
|
1834
|
+
pattern.setAttribute( 'x', 0.0 );
|
|
1835
|
+
pattern.setAttribute( 'y', 0.0 );
|
|
1836
|
+
pattern.setAttribute( 'width', this._patternSize.x )
|
|
1837
|
+
pattern.setAttribute( 'height', this._patternSize.y );
|
|
1838
|
+
pattern.setAttribute( 'patternUnits', 'userSpaceOnUse' );
|
|
1839
|
+
|
|
1840
|
+
var circle = document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' );
|
|
1841
|
+
circle.setAttribute( 'cx', this._circlePatternSize );
|
|
1842
|
+
circle.setAttribute( 'cy', this._circlePatternSize );
|
|
1843
|
+
circle.setAttribute( 'r', this._circlePatternSize );
|
|
1844
|
+
circle.setAttribute( 'fill', this._circlePatternColor );
|
|
1845
|
+
|
|
1846
|
+
pattern.appendChild( circle );
|
|
1847
|
+
}
|
|
1848
|
+
|
|
1849
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
1850
|
+
svg.classList.add( "background-svg" );
|
|
1851
|
+
svg.style.width = "100%";
|
|
1852
|
+
svg.style.height = "100%";
|
|
1853
|
+
|
|
1854
|
+
svg.appendChild( pattern );
|
|
1855
|
+
|
|
1856
|
+
var rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' );
|
|
1857
|
+
rect.setAttribute( 'x', '0' );
|
|
1858
|
+
rect.setAttribute( 'y', '0' );
|
|
1859
|
+
rect.setAttribute( 'width', '100%' );
|
|
1860
|
+
rect.setAttribute( 'height', '100%' );
|
|
1861
|
+
rect.setAttribute( 'fill', 'url(#pattern-0)' );
|
|
1862
|
+
|
|
1863
|
+
svg.appendChild( rect );
|
|
1864
|
+
|
|
1865
|
+
this._background = svg;
|
|
1866
|
+
|
|
1867
|
+
this.root.appendChild( this._background );
|
|
1868
|
+
}
|
|
1869
|
+
|
|
1870
|
+
_updatePattern() {
|
|
1871
|
+
|
|
1872
|
+
if( !this._background )
|
|
1873
|
+
return;
|
|
1874
|
+
|
|
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 );
|
|
1878
|
+
|
|
1879
|
+
let pattern = this._background.querySelector( 'pattern' );
|
|
1880
|
+
pattern.setAttribute( 'x', patternPosition.x );
|
|
1881
|
+
pattern.setAttribute( 'y', patternPosition.y );
|
|
1882
|
+
pattern.setAttribute( 'width', patternSize.x )
|
|
1883
|
+
pattern.setAttribute( 'height', patternSize.y );
|
|
1884
|
+
|
|
1885
|
+
var circle = this._background.querySelector( 'circle' );
|
|
1886
|
+
circle.setAttribute( 'cx', circlePatternSize );
|
|
1887
|
+
circle.setAttribute( 'cy', circlePatternSize );
|
|
1888
|
+
circle.setAttribute( 'r', circlePatternSize );
|
|
1889
|
+
|
|
1890
|
+
// Nodes
|
|
1891
|
+
|
|
1892
|
+
const w = this._domNodes.offsetWidth * 0.5;
|
|
1893
|
+
const h = this._domNodes.offsetHeight * 0.5;
|
|
1894
|
+
|
|
1895
|
+
const dw = w - w * this.currentGraph.scale;
|
|
1896
|
+
const dh = h - h * this.currentGraph.scale;
|
|
1897
|
+
|
|
1898
|
+
this._domNodes.style.transform = `
|
|
1899
|
+
translate(` + ( patternPosition.x - dw ) + `px, ` + ( patternPosition.y - dh ) + `px)
|
|
1900
|
+
scale(` + this.currentGraph.scale + `)
|
|
1901
|
+
`;
|
|
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
|
+
}
|
|
1913
|
+
}
|
|
1914
|
+
|
|
1915
|
+
_getPatternPosition( renderPosition ) {
|
|
1916
|
+
|
|
1917
|
+
return renderPosition.div( this.currentGraph.scale ).sub( this.currentGraph.translation );
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
_getRenderPosition( patternPosition ) {
|
|
1921
|
+
|
|
1922
|
+
return patternPosition.add( this.currentGraph.translation ).mul( this.currentGraph.scale );
|
|
1923
|
+
}
|
|
1924
|
+
|
|
1925
|
+
_onLink( e ) {
|
|
1926
|
+
|
|
1927
|
+
const linkData = this._generatingLink;
|
|
1928
|
+
const ioType = e.target.classList.contains( 'input' ) ? GraphEditor.NODE_IO_INPUT : GraphEditor.NODE_IO_OUTPUT;
|
|
1929
|
+
|
|
1930
|
+
// Discard same IO type
|
|
1931
|
+
if( linkData.ioType == ioType )
|
|
1932
|
+
{
|
|
1933
|
+
console.warn( `Can't link same type of data` );
|
|
1934
|
+
return;
|
|
1935
|
+
}
|
|
1936
|
+
|
|
1937
|
+
// Info about src node
|
|
1938
|
+
const src_nodeContainer = linkData.domEl;
|
|
1939
|
+
const src_nodeId = src_nodeContainer.dataset[ 'id' ];
|
|
1940
|
+
const src_node = this.nodes[ src_nodeId ].data;
|
|
1941
|
+
const src_ioIndex = this._generatingLink.index
|
|
1942
|
+
|
|
1943
|
+
// Info about dst node
|
|
1944
|
+
const dst_nodeContainer = e.target.offsetParent;
|
|
1945
|
+
const dst_nodeId = dst_nodeContainer.dataset[ 'id' ];
|
|
1946
|
+
const dst_node = this.nodes[ dst_nodeId ].data;
|
|
1947
|
+
const dst_ioIndex = parseInt( e.target.parentElement.dataset[ 'index' ] );
|
|
1948
|
+
|
|
1949
|
+
// Discard different types
|
|
1950
|
+
const srcIsInput = ( linkData.ioType == GraphEditor.NODE_IO_INPUT );
|
|
1951
|
+
const src_ios = src_node[ srcIsInput ? 'inputs' : 'outputs' ];
|
|
1952
|
+
const src_ioType = src_ios[ src_ioIndex ].type;
|
|
1953
|
+
|
|
1954
|
+
const dst_ios = dst_node[ ioType == GraphEditor.NODE_IO_INPUT ? 'inputs' : 'outputs' ];
|
|
1955
|
+
const dst_ioType = dst_ios[ dst_ioIndex ].type;
|
|
1956
|
+
|
|
1957
|
+
if( src_ioType != dst_ioType && src_ioType != "any" && dst_ioType != "any" )
|
|
1958
|
+
{
|
|
1959
|
+
// Different types, but it might be possible to cast types
|
|
1960
|
+
|
|
1961
|
+
const inputType = srcIsInput ? src_ioType : dst_ioType;
|
|
1962
|
+
const outputType = srcIsInput ? dst_ioType : src_ioType;
|
|
1963
|
+
|
|
1964
|
+
if( !this.supportedCastTypes[ outputType + '@' + inputType ] )
|
|
1965
|
+
{
|
|
1966
|
+
console.warn( `Can't link ${ src_ioType } to ${ dst_ioType }.` );
|
|
1967
|
+
return;
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
|
|
1971
|
+
// Check if target it's an active input and remove the old link
|
|
1972
|
+
|
|
1973
|
+
if( ioType == GraphEditor.NODE_IO_INPUT && e.target.parentElement.dataset[ 'active' ] )
|
|
1974
|
+
{
|
|
1975
|
+
this._deleteLinks( dst_nodeId, e.target.parentElement );
|
|
1976
|
+
}
|
|
1977
|
+
|
|
1978
|
+
// Check if source it's an active input and remove the old link
|
|
1979
|
+
|
|
1980
|
+
else if( linkData.ioType == GraphEditor.NODE_IO_INPUT && linkData.io.dataset[ 'active' ] )
|
|
1981
|
+
{
|
|
1982
|
+
this._deleteLinks( src_nodeId, linkData.io );
|
|
1983
|
+
}
|
|
1984
|
+
|
|
1985
|
+
// Store the end io..
|
|
1986
|
+
|
|
1987
|
+
var srcDom = linkData.io;
|
|
1988
|
+
srcDom.links = srcDom.links ?? [ ];
|
|
1989
|
+
srcDom.links[ dst_ioIndex ] = srcDom.links[ dst_ioIndex ] ?? [ ];
|
|
1990
|
+
srcDom.links[ dst_ioIndex ].push( dst_nodeId );
|
|
1991
|
+
|
|
1992
|
+
var dstDom = e.target.parentElement;
|
|
1993
|
+
dstDom.links = dstDom.links ?? [ ];
|
|
1994
|
+
dstDom.links[ src_ioIndex ] = dstDom.links[ src_ioIndex ] ?? [ ];
|
|
1995
|
+
dstDom.links[ src_ioIndex ].push( src_nodeId );
|
|
1996
|
+
|
|
1997
|
+
// Call this using the io target to set the connection to the center of the input DOM element..
|
|
1998
|
+
|
|
1999
|
+
let path = this._updatePreviewLink( null, e.target.parentElement );
|
|
2000
|
+
|
|
2001
|
+
// Store link
|
|
2002
|
+
|
|
2003
|
+
const pathId = ( srcIsInput ? dst_nodeId : src_nodeId ) + '@' + ( srcIsInput ? src_nodeId : dst_nodeId );
|
|
2004
|
+
|
|
2005
|
+
if( !this.currentGraph.links[ pathId ] ) this.currentGraph.links[ pathId ] = [];
|
|
2006
|
+
|
|
2007
|
+
this.currentGraph.links[ pathId ].push( {
|
|
2008
|
+
path: path,
|
|
2009
|
+
inputNode: srcIsInput ? src_nodeId : dst_nodeId,
|
|
2010
|
+
inputIdx: srcIsInput ? src_ioIndex : dst_ioIndex,
|
|
2011
|
+
inputType: srcIsInput ? src_ioType : dst_ioType,
|
|
2012
|
+
outputNode: srcIsInput ? dst_nodeId : src_nodeId,
|
|
2013
|
+
outputIdx: srcIsInput ? dst_ioIndex : src_ioIndex,
|
|
2014
|
+
outputType: srcIsInput ? dst_ioType : src_ioType,
|
|
2015
|
+
} );
|
|
2016
|
+
|
|
2017
|
+
path.dataset[ 'id' ] = pathId;
|
|
2018
|
+
|
|
2019
|
+
// Mark as active links...
|
|
2020
|
+
|
|
2021
|
+
linkData.io.dataset[ 'active' ] = true;
|
|
2022
|
+
e.target.parentElement.dataset[ 'active' ] = true;
|
|
2023
|
+
|
|
2024
|
+
// Successful link..
|
|
2025
|
+
return true;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
_updatePreviewLink( e, endIO ) {
|
|
2029
|
+
|
|
2030
|
+
var path = this._generatingLink.path;
|
|
2031
|
+
|
|
2032
|
+
if( !path )
|
|
2033
|
+
{
|
|
2034
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
2035
|
+
svg.classList.add( "link-svg" );
|
|
2036
|
+
svg.style.width = "100%";
|
|
2037
|
+
svg.style.height = "100%";
|
|
2038
|
+
this._domLinks.appendChild( svg );
|
|
2039
|
+
|
|
2040
|
+
path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
2041
|
+
path.setAttribute( 'fill', 'none' );
|
|
2042
|
+
svg.appendChild( path );
|
|
2043
|
+
this._generatingLink.path = path;
|
|
2044
|
+
}
|
|
2045
|
+
|
|
2046
|
+
// Generate bezier curve
|
|
2047
|
+
|
|
2048
|
+
const index = this._generatingLink.index;
|
|
2049
|
+
const type = this._generatingLink.ioType;
|
|
2050
|
+
const domEl = this._generatingLink.domEl;
|
|
2051
|
+
|
|
2052
|
+
const offsetX = this.root.getBoundingClientRect().x;
|
|
2053
|
+
const offsetY = this.root.getBoundingClientRect().y;
|
|
2054
|
+
|
|
2055
|
+
const ios = domEl.querySelector( type == GraphEditor.NODE_IO_INPUT ? '.lexgraphnodeinputs' : '.lexgraphnodeoutputs' );
|
|
2056
|
+
const ioEl = ios.childNodes[ index ].querySelector( '.io__type' );
|
|
2057
|
+
const startRect = ioEl.getBoundingClientRect();
|
|
2058
|
+
|
|
2059
|
+
let startPos = new LX.vec2( startRect.x - offsetX, startRect.y - offsetY );
|
|
2060
|
+
let endPos = null;
|
|
2061
|
+
let endioEl = null;
|
|
2062
|
+
|
|
2063
|
+
if( e )
|
|
2064
|
+
{
|
|
2065
|
+
endPos = new LX.vec2( e.offsetX, e.offsetY );
|
|
2066
|
+
|
|
2067
|
+
// Add node position, since I can't get the correct position directly from the event..
|
|
2068
|
+
if( e.target.hasClass( [ 'lexgraphnode', 'lexgraphgroup' ] ) )
|
|
2069
|
+
{
|
|
2070
|
+
endPos.add( this._getNodePosition( e.target ), endPos );
|
|
2071
|
+
endPos.add( new LX.vec2( 3, 3 ), endPos );
|
|
2072
|
+
}
|
|
2073
|
+
else if( e.target.hasClass( [ 'io__type', 'lexgraphgroupresizer' ] ) )
|
|
2074
|
+
{
|
|
2075
|
+
var parent = e.target.offsetParent;
|
|
2076
|
+
// Add parent offset
|
|
2077
|
+
endPos.add( this._getNodePosition( parent ), endPos );
|
|
2078
|
+
// Add own offset
|
|
2079
|
+
endPos.add( new LX.vec2( e.target.offsetLeft, e.target.offsetTop ), endPos );
|
|
2080
|
+
endPos.add( new LX.vec2( 3, 3 ), endPos );
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
endPos = this._getRenderPosition( endPos );
|
|
2084
|
+
}
|
|
2085
|
+
else
|
|
2086
|
+
{
|
|
2087
|
+
endioEl = endIO.querySelector( '.io__type' );
|
|
2088
|
+
const ioRect = endioEl.getBoundingClientRect();
|
|
2089
|
+
endPos = new LX.vec2( ioRect.x - offsetX, ioRect.y - offsetY );
|
|
2090
|
+
}
|
|
2091
|
+
|
|
2092
|
+
if( type == GraphEditor.NODE_IO_INPUT )
|
|
2093
|
+
{
|
|
2094
|
+
var tmp = endPos;
|
|
2095
|
+
endPos = startPos;
|
|
2096
|
+
startPos = tmp;
|
|
2097
|
+
}
|
|
2098
|
+
|
|
2099
|
+
let color = getComputedStyle( ioEl ).backgroundColor;
|
|
2100
|
+
|
|
2101
|
+
if( type == GraphEditor.NODE_IO_OUTPUT && endioEl )
|
|
2102
|
+
color = getComputedStyle( endioEl ).backgroundColor;
|
|
2103
|
+
|
|
2104
|
+
this._createLinkPath( path, startPos, endPos, color, !!e );
|
|
2105
|
+
|
|
2106
|
+
return path;
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
_createLink( link ) {
|
|
2110
|
+
|
|
2111
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
2112
|
+
svg.classList.add( "link-svg" );
|
|
2113
|
+
svg.style.width = "100%";
|
|
2114
|
+
svg.style.height = "100%";
|
|
2115
|
+
this._domLinks.appendChild( svg );
|
|
2116
|
+
|
|
2117
|
+
var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
2118
|
+
path.setAttribute( 'fill', 'none' );
|
|
2119
|
+
svg.appendChild( path );
|
|
2120
|
+
|
|
2121
|
+
const inputNodeDom = this._getNodeDOMElement( link.inputNode );
|
|
2122
|
+
const outputNodeDom = this._getNodeDOMElement( link.outputNode );
|
|
2123
|
+
|
|
2124
|
+
// Start pos
|
|
2125
|
+
|
|
2126
|
+
const offsetX = this.root.getBoundingClientRect().x;
|
|
2127
|
+
const offsetY = this.root.getBoundingClientRect().y;
|
|
2128
|
+
|
|
2129
|
+
const outputs = outputNodeDom.querySelector( '.lexgraphnodeoutputs' );
|
|
2130
|
+
const io0 = outputs.childNodes[ link.outputIdx ];
|
|
2131
|
+
const startRect = io0.querySelector( '.io__type' ).getBoundingClientRect();
|
|
2132
|
+
|
|
2133
|
+
let startPos = new LX.vec2( startRect.x - offsetX, startRect.y - offsetY + 6 );
|
|
2134
|
+
|
|
2135
|
+
// End pos
|
|
2136
|
+
|
|
2137
|
+
const inputs = inputNodeDom.querySelector( '.lexgraphnodeinputs' );
|
|
2138
|
+
const io1 = inputs.childNodes[ link.inputIdx ];
|
|
2139
|
+
const endRect = io1.querySelector( '.io__type' ).getBoundingClientRect();
|
|
2140
|
+
|
|
2141
|
+
let endPos = new LX.vec2( endRect.x - offsetX, endRect.y - offsetY + 6 );
|
|
2142
|
+
|
|
2143
|
+
// Generate bezier curve
|
|
2144
|
+
|
|
2145
|
+
const color = getComputedStyle( io1.querySelector( '.io__type' ) ).backgroundColor;
|
|
2146
|
+
this._createLinkPath( path, startPos, endPos, color );
|
|
2147
|
+
|
|
2148
|
+
link.path = path;
|
|
2149
|
+
|
|
2150
|
+
// Store data in each IO
|
|
2151
|
+
|
|
2152
|
+
io0.links = [ ];
|
|
2153
|
+
io0.links[ link.inputIdx ] = io0.links[ link.inputIdx ] ?? [ ];
|
|
2154
|
+
io0.links[ link.inputIdx ].push( link.inputNode );
|
|
2155
|
+
|
|
2156
|
+
io1.links = [ ];
|
|
2157
|
+
io1.links[ link.outputIdx ] = io1.links[ link.outputIdx ] ?? [ ];
|
|
2158
|
+
io1.links[ link.outputIdx ].push( link.outputNode );
|
|
2159
|
+
|
|
2160
|
+
io0.dataset[ 'active' ] = true;
|
|
2161
|
+
io1.dataset[ 'active' ] = true;
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
_createLinkPath( path, startPos, endPos, color, exactEnd ) {
|
|
2165
|
+
|
|
2166
|
+
const dist = 6 * this.currentGraph.scale;
|
|
2167
|
+
startPos.add( new LX.vec2( dist, dist ), startPos );
|
|
2168
|
+
|
|
2169
|
+
if( !exactEnd )
|
|
2170
|
+
{
|
|
2171
|
+
endPos.add( new LX.vec2( dist, dist ), endPos );
|
|
2172
|
+
}
|
|
2173
|
+
|
|
2174
|
+
startPos = this._getPatternPosition( startPos );
|
|
2175
|
+
endPos = this._getPatternPosition( endPos );
|
|
2176
|
+
|
|
2177
|
+
const distanceX = LX.UTILS.clamp( Math.abs( startPos.x - endPos.x ), 0.0, 256.0 );
|
|
2178
|
+
const cPDistance = 128.0 * Math.pow( distanceX / 256.0, 0.5 );
|
|
2179
|
+
|
|
2180
|
+
let cPoint1 = startPos.add( new LX.vec2( cPDistance, 0 ) );
|
|
2181
|
+
let cPoint2 = endPos.sub( new LX.vec2( cPDistance, 0 ) );
|
|
2182
|
+
|
|
2183
|
+
path.setAttribute( 'd', `
|
|
2184
|
+
M ${ startPos.x },${ startPos.y }
|
|
2185
|
+
C ${ cPoint1.x },${ cPoint1.y } ${ cPoint2.x },${ cPoint2.y } ${ endPos.x },${ endPos.y }
|
|
2186
|
+
` );
|
|
2187
|
+
|
|
2188
|
+
path.setAttribute( 'stroke', color );
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
_updateNodeLinks( nodeId ) {
|
|
2192
|
+
|
|
2193
|
+
var node = this.nodes[ nodeId ] ? this.nodes[ nodeId ].data : null;
|
|
2194
|
+
|
|
2195
|
+
if( !node ) {
|
|
2196
|
+
console.warn( `Can't finde node [${ nodeId }]` );
|
|
2197
|
+
return;
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
const nodeDOM = this._getNodeDOMElement( nodeId );
|
|
2201
|
+
|
|
2202
|
+
// Update input links
|
|
2203
|
+
|
|
2204
|
+
for( let input of nodeDOM.querySelectorAll( '.ioinput' ) )
|
|
2205
|
+
{
|
|
2206
|
+
if( !input.links )
|
|
2207
|
+
continue;
|
|
2208
|
+
|
|
2209
|
+
// Get first and only target output..
|
|
2210
|
+
const targets = input.links.filter( v => v !== undefined )[ 0 ];
|
|
2211
|
+
const targetNodeId = targets[ 0 ];
|
|
2212
|
+
|
|
2213
|
+
// It has been deleted..
|
|
2214
|
+
if( !targetNodeId )
|
|
2215
|
+
continue;
|
|
2216
|
+
|
|
2217
|
+
const ioIndex = parseInt( input.dataset[ 'index' ] );
|
|
2218
|
+
|
|
2219
|
+
var links = this._getLinks( targetNodeId, nodeId );
|
|
2220
|
+
|
|
2221
|
+
// Inputs only have 1 possible output connected
|
|
2222
|
+
var link = links.find( i => ( i.inputIdx == ioIndex ) );
|
|
2223
|
+
|
|
2224
|
+
this._generatingLink = {
|
|
2225
|
+
index: ioIndex,
|
|
2226
|
+
io: input,
|
|
2227
|
+
ioType: GraphEditor.NODE_IO_INPUT,
|
|
2228
|
+
domEl: nodeDOM,
|
|
2229
|
+
path: link.path
|
|
2230
|
+
};
|
|
2231
|
+
|
|
2232
|
+
// Get end io
|
|
2233
|
+
|
|
2234
|
+
const outputNode = this._getNodeDOMElement( targetNodeId );
|
|
2235
|
+
const io = outputNode.querySelector( '.lexgraphnodeoutputs' ).childNodes[ link.outputIdx ]
|
|
2236
|
+
|
|
2237
|
+
this._updatePreviewLink( null, io );
|
|
2238
|
+
}
|
|
2239
|
+
|
|
2240
|
+
// Update output links
|
|
2241
|
+
|
|
2242
|
+
|
|
2243
|
+
for( let output of nodeDOM.querySelectorAll( '.iooutput' ) )
|
|
2244
|
+
{
|
|
2245
|
+
if( !output.links )
|
|
2246
|
+
continue;
|
|
2247
|
+
|
|
2248
|
+
const srcIndex = parseInt( output.dataset[ 'index' ] );
|
|
2249
|
+
|
|
2250
|
+
for( let targetIndex = 0; targetIndex < output.links.length; ++targetIndex )
|
|
2251
|
+
{
|
|
2252
|
+
const targets = output.links[ targetIndex ];
|
|
2253
|
+
|
|
2254
|
+
if( !targets )
|
|
2255
|
+
continue;
|
|
2256
|
+
|
|
2257
|
+
for( let targetId of targets )
|
|
2258
|
+
{
|
|
2259
|
+
var links = this._getLinks( nodeId, targetId );
|
|
2260
|
+
var link = links.find( i => ( i.inputIdx == targetIndex && i.outputIdx == srcIndex ) );
|
|
2261
|
+
|
|
2262
|
+
// Outputs can have different inputs connected
|
|
2263
|
+
this._generatingLink = {
|
|
2264
|
+
index: link.outputIdx,
|
|
2265
|
+
io: output,
|
|
2266
|
+
ioType: GraphEditor.NODE_IO_OUTPUT,
|
|
2267
|
+
domEl: nodeDOM,
|
|
2268
|
+
path: link.path
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
// Get end io
|
|
2272
|
+
|
|
2273
|
+
const inputNode = this._getNodeDOMElement( targetId );
|
|
2274
|
+
const io = inputNode.querySelector( '.lexgraphnodeinputs' ).childNodes[ link.inputIdx ]
|
|
2275
|
+
|
|
2276
|
+
this._updatePreviewLink( null, io );
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
}
|
|
2280
|
+
|
|
2281
|
+
delete this._generatingLink;
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
_drawBoxSelection( e ) {
|
|
2285
|
+
|
|
2286
|
+
var svg = this._currentBoxSelectionSVG;
|
|
2287
|
+
|
|
2288
|
+
if( !svg )
|
|
2289
|
+
{
|
|
2290
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
2291
|
+
svg.classList.add( "box-selection-svg" );
|
|
2292
|
+
if( this._boxSelectRemoving )
|
|
2293
|
+
svg.classList.add( "removing" );
|
|
2294
|
+
svg.style.width = "100%";
|
|
2295
|
+
svg.style.height = "100%";
|
|
2296
|
+
this._domLinks.appendChild( svg );
|
|
2297
|
+
this._currentBoxSelectionSVG = svg;
|
|
2298
|
+
}
|
|
2299
|
+
|
|
2300
|
+
// Generate box
|
|
2301
|
+
|
|
2302
|
+
let startPos = this._getPatternPosition( this._boxSelecting );
|
|
2303
|
+
let size = this._getPatternPosition( this._mousePosition ).sub( startPos );
|
|
2304
|
+
|
|
2305
|
+
if( size.x < 0 ) startPos.x += size.x;
|
|
2306
|
+
if( size.y < 0 ) startPos.y += size.y;
|
|
2307
|
+
|
|
2308
|
+
size = size.abs();
|
|
2309
|
+
|
|
2310
|
+
svg.innerHTML = `<rect
|
|
2311
|
+
x="${ startPos.x }" y="${ startPos.y }"
|
|
2312
|
+
rx="${ 6 }" ry="${ 6 }"
|
|
2313
|
+
width="${ size.x }" height="${ size.y }"
|
|
2314
|
+
"/>`;
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
_getNonVisibleNodes() {
|
|
2318
|
+
|
|
2319
|
+
const nonVisibleNodes = [ ];
|
|
2320
|
+
|
|
2321
|
+
if( !this.currentGraph )
|
|
2322
|
+
{
|
|
2323
|
+
console.warn( "No graph set" );
|
|
2324
|
+
return [];
|
|
2325
|
+
}
|
|
2326
|
+
|
|
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;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
_selectNodesInBox( lt, rb, remove ) {
|
|
2358
|
+
|
|
2359
|
+
lt = this._getPatternPosition( lt );
|
|
2360
|
+
rb = this._getPatternPosition( rb );
|
|
2361
|
+
|
|
2362
|
+
let size = rb.sub( lt );
|
|
2363
|
+
|
|
2364
|
+
if( size.x < 0 )
|
|
2365
|
+
{
|
|
2366
|
+
var tmp = lt.x;
|
|
2367
|
+
lt.x = rb.x;
|
|
2368
|
+
rb.x = tmp;
|
|
2369
|
+
}
|
|
2370
|
+
|
|
2371
|
+
if( size.y < 0 )
|
|
2372
|
+
{
|
|
2373
|
+
var tmp = lt.y;
|
|
2374
|
+
lt.y = rb.y;
|
|
2375
|
+
rb.y = tmp;
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
for( let nodeEl of this._getAllDOMNodes() )
|
|
2379
|
+
{
|
|
2380
|
+
let pos = this._getNodePosition( nodeEl );
|
|
2381
|
+
let size = new LX.vec2( nodeEl.offsetWidth, nodeEl.offsetHeight );
|
|
2382
|
+
|
|
2383
|
+
if( ( !( pos.x < lt.x && ( pos.x + size.x ) < lt.x ) && !( pos.x > rb.x && ( pos.x + size.x ) > rb.x ) ) &&
|
|
2384
|
+
( !( pos.y < lt.y && ( pos.y + size.y ) < lt.y ) && !( pos.y > rb.y && ( pos.y + size.y ) > rb.y ) ) )
|
|
2385
|
+
{
|
|
2386
|
+
if( remove )
|
|
2387
|
+
this._unSelectNode( nodeEl );
|
|
2388
|
+
else
|
|
2389
|
+
this._selectNode( nodeEl, true, false );
|
|
2390
|
+
}
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
|
|
2394
|
+
_deleteSelection( e ) {
|
|
2395
|
+
|
|
2396
|
+
const lastNodeCount = this._domNodes.childElementCount;
|
|
2397
|
+
|
|
2398
|
+
for( let nodeId of this.selectedNodes )
|
|
2399
|
+
{
|
|
2400
|
+
this._deleteNode( nodeId );
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
this.selectedNodes.length = 0;
|
|
2404
|
+
|
|
2405
|
+
// We delete something, so add undo step..
|
|
2406
|
+
|
|
2407
|
+
if( this._domNodes.childElementCount != lastNodeCount )
|
|
2408
|
+
{
|
|
2409
|
+
this._addUndoStep();
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
}
|
|
2413
|
+
|
|
2414
|
+
_getBoundingFromGroup( groupDOM ) {
|
|
2415
|
+
|
|
2416
|
+
const x = parseFloat( groupDOM.style.left );
|
|
2417
|
+
const y = parseFloat( groupDOM.style.top );
|
|
2418
|
+
|
|
2419
|
+
return new BoundingBox( new LX.vec2( x, y ), new LX.vec2( groupDOM.offsetWidth - 2, groupDOM.offsetHeight - 2 ) );
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
_getBoundingFromNodes( nodeIds ) {
|
|
2423
|
+
|
|
2424
|
+
let group_bb = null;
|
|
2425
|
+
|
|
2426
|
+
for( let nodeId of nodeIds )
|
|
2427
|
+
{
|
|
2428
|
+
const node = this.nodes[ nodeId ].data;
|
|
2429
|
+
const node_bb = new BoundingBox( node.position, node.size );
|
|
2430
|
+
|
|
2431
|
+
if( group_bb )
|
|
2432
|
+
{
|
|
2433
|
+
group_bb.merge( node_bb );
|
|
2434
|
+
}
|
|
2435
|
+
else
|
|
2436
|
+
{
|
|
2437
|
+
group_bb = node_bb;
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
|
|
2441
|
+
// Add padding
|
|
2442
|
+
|
|
2443
|
+
const groupContentPadding = 8;
|
|
2444
|
+
|
|
2445
|
+
group_bb.origin.sub( new LX.vec2( groupContentPadding ), group_bb.origin );
|
|
2446
|
+
group_bb.origin.sub( new LX.vec2( groupContentPadding ), group_bb.origin );
|
|
2447
|
+
|
|
2448
|
+
group_bb.size.add( new LX.vec2( groupContentPadding * 2.0 ), group_bb.size );
|
|
2449
|
+
group_bb.size.add( new LX.vec2( groupContentPadding * 2.0 ), group_bb.size );
|
|
2450
|
+
|
|
2451
|
+
return group_bb;
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
/**
|
|
2455
|
+
* @method _createGroup
|
|
2456
|
+
* @description Creates a node group from the bounding box of the selected nodes
|
|
2457
|
+
* @returns JSON data from the serialized graph
|
|
2458
|
+
*/
|
|
2459
|
+
|
|
2460
|
+
_createGroup( bb ) {
|
|
2461
|
+
|
|
2462
|
+
const group_bb = bb ?? this._getBoundingFromNodes( this.selectedNodes );
|
|
2463
|
+
const group_id = bb ? bb.id : "group-" + LX.UTILS.uidGenerator();
|
|
2464
|
+
|
|
2465
|
+
let groupDOM = document.createElement( 'div' );
|
|
2466
|
+
groupDOM.id = group_id;
|
|
2467
|
+
groupDOM.classList.add( 'lexgraphgroup' );
|
|
2468
|
+
groupDOM.style.left = group_bb.origin.x + "px";
|
|
2469
|
+
groupDOM.style.top = group_bb.origin.y + "px";
|
|
2470
|
+
groupDOM.style.width = group_bb.size.x + "px";
|
|
2471
|
+
groupDOM.style.height = group_bb.size.y + "px";
|
|
2472
|
+
|
|
2473
|
+
let groupResizer = document.createElement( 'div' );
|
|
2474
|
+
groupResizer.classList.add( 'lexgraphgroupresizer' );
|
|
2475
|
+
|
|
2476
|
+
groupResizer.addEventListener( 'mousedown', inner_mousedown );
|
|
2477
|
+
|
|
2478
|
+
this.groups[ group_id ] = groupDOM;
|
|
2479
|
+
|
|
2480
|
+
var that = this;
|
|
2481
|
+
var lastPos = [0,0];
|
|
2482
|
+
|
|
2483
|
+
function inner_mousedown( e )
|
|
2484
|
+
{
|
|
2485
|
+
var doc = that.root.ownerDocument;
|
|
2486
|
+
doc.addEventListener( 'mousemove', inner_mousemove );
|
|
2487
|
+
doc.addEventListener( 'mouseup', inner_mouseup );
|
|
2488
|
+
lastPos[0] = e.x;
|
|
2489
|
+
lastPos[1] = e.y;
|
|
2490
|
+
e.stopPropagation();
|
|
2491
|
+
e.preventDefault();
|
|
2492
|
+
document.body.classList.add( 'nocursor' );
|
|
2493
|
+
groupResizer.classList.add( 'nocursor' );
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
function inner_mousemove( e )
|
|
2497
|
+
{
|
|
2498
|
+
let dt = new LX.vec2( lastPos[0] - e.x, lastPos[1] - e.y );
|
|
2499
|
+
dt.div( that.currentGraph.scale, dt);
|
|
2500
|
+
|
|
2501
|
+
groupDOM.style.width = ( parseFloat( groupDOM.style.width ) - dt.x ) + "px";
|
|
2502
|
+
groupDOM.style.height = ( parseFloat( groupDOM.style.height ) - dt.y ) + "px";
|
|
2503
|
+
|
|
2504
|
+
lastPos[0] = e.x;
|
|
2505
|
+
lastPos[1] = e.y;
|
|
2506
|
+
|
|
2507
|
+
e.stopPropagation();
|
|
2508
|
+
e.preventDefault();
|
|
2509
|
+
}
|
|
2510
|
+
|
|
2511
|
+
function inner_mouseup( e )
|
|
2512
|
+
{
|
|
2513
|
+
var doc = that.root.ownerDocument;
|
|
2514
|
+
doc.removeEventListener( 'mousemove', inner_mousemove );
|
|
2515
|
+
doc.removeEventListener( 'mouseup', inner_mouseup );
|
|
2516
|
+
document.body.classList.remove( 'nocursor' );
|
|
2517
|
+
groupResizer.classList.remove( 'nocursor' );
|
|
2518
|
+
}
|
|
2519
|
+
|
|
2520
|
+
let groupTitle = document.createElement( 'input' );
|
|
2521
|
+
let defaultName = `Group ${ GraphEditor.LAST_GROUP_ID }`;
|
|
2522
|
+
groupTitle.value = defaultName;
|
|
2523
|
+
groupTitle.classList.add( 'lexgraphgrouptitle' );
|
|
2524
|
+
groupTitle.disabled = true;
|
|
2525
|
+
|
|
2526
|
+
// Dbl click to rename
|
|
2527
|
+
|
|
2528
|
+
groupTitle.addEventListener( 'mousedown', e => {
|
|
2529
|
+
e.stopPropagation();
|
|
2530
|
+
e.stopImmediatePropagation();
|
|
2531
|
+
} );
|
|
2532
|
+
|
|
2533
|
+
groupTitle.addEventListener( 'focusout', e => {
|
|
2534
|
+
groupTitle.disabled = true;
|
|
2535
|
+
if( !groupTitle.value.length )
|
|
2536
|
+
groupTitle.value = defaultName;
|
|
2537
|
+
} );
|
|
2538
|
+
|
|
2539
|
+
groupTitle.addEventListener( 'keyup', e => {
|
|
2540
|
+
if( e.key == 'Enter' ) {
|
|
2541
|
+
groupTitle.blur();
|
|
2542
|
+
}
|
|
2543
|
+
else if( e.key == 'Escape' ) {
|
|
2544
|
+
groupTitle.value = "";
|
|
2545
|
+
groupTitle.blur();
|
|
2546
|
+
}
|
|
2547
|
+
});
|
|
2548
|
+
|
|
2549
|
+
groupDOM.addEventListener( 'dblclick', e => {
|
|
2550
|
+
// Only for left click..
|
|
2551
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
2552
|
+
return;
|
|
2553
|
+
groupTitle.disabled = false;
|
|
2554
|
+
groupTitle.focus();
|
|
2555
|
+
} );
|
|
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
|
+
|
|
2570
|
+
groupDOM.appendChild( groupResizer );
|
|
2571
|
+
groupDOM.appendChild( groupTitle );
|
|
2572
|
+
|
|
2573
|
+
this._domNodes.prepend( groupDOM );
|
|
2574
|
+
|
|
2575
|
+
// Move group!!
|
|
2576
|
+
|
|
2577
|
+
LX.makeDraggable( groupDOM, {
|
|
2578
|
+
onMove: this._onMoveGroup.bind( this ),
|
|
2579
|
+
onDragStart: this._onDragGroup.bind( this )
|
|
2580
|
+
} );
|
|
2581
|
+
|
|
2582
|
+
GraphEditor.LAST_GROUP_ID++;
|
|
2583
|
+
|
|
2584
|
+
return groupDOM;
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
_addUndoStep( deleteRedo = true ) {
|
|
2588
|
+
|
|
2589
|
+
if( deleteRedo )
|
|
2590
|
+
{
|
|
2591
|
+
// Remove all redo steps
|
|
2592
|
+
this._redoSteps.length = 0;
|
|
2593
|
+
}
|
|
2594
|
+
|
|
2595
|
+
this._undoSteps.push( {
|
|
2596
|
+
// TODO: Add graph state
|
|
2597
|
+
} );
|
|
2598
|
+
}
|
|
2599
|
+
|
|
2600
|
+
_doUndo() {
|
|
2601
|
+
|
|
2602
|
+
if( !this._undoSteps.length )
|
|
2603
|
+
return;
|
|
2604
|
+
|
|
2605
|
+
this._addRedoStep();
|
|
2606
|
+
|
|
2607
|
+
// Extract info from the last state
|
|
2608
|
+
const step = this._undoSteps.pop();
|
|
2609
|
+
|
|
2610
|
+
// Set old state
|
|
2611
|
+
// TODO
|
|
2612
|
+
|
|
2613
|
+
console.log( "Undo!!" );
|
|
2614
|
+
}
|
|
2615
|
+
|
|
2616
|
+
_addRedoStep() {
|
|
2617
|
+
|
|
2618
|
+
this._redoSteps.push( {
|
|
2619
|
+
// TODO: Add graph state
|
|
2620
|
+
} );
|
|
2621
|
+
}
|
|
2622
|
+
|
|
2623
|
+
_doRedo() {
|
|
2624
|
+
|
|
2625
|
+
if( !this._redoSteps.length )
|
|
2626
|
+
return;
|
|
2627
|
+
|
|
2628
|
+
this._addUndoStep( false );
|
|
2629
|
+
|
|
2630
|
+
// Extract info from the next saved code state
|
|
2631
|
+
const step = this._redoSteps.pop();
|
|
2632
|
+
|
|
2633
|
+
// Set old state
|
|
2634
|
+
// TODO
|
|
2635
|
+
|
|
2636
|
+
console.log( "Redo!!" );
|
|
2637
|
+
}
|
|
2638
|
+
|
|
2639
|
+
_togglePropertiesDialog( force ) {
|
|
2640
|
+
|
|
2641
|
+
this.propertiesDialog.root.classList.toggle( 'opened', force );
|
|
2642
|
+
|
|
2643
|
+
if( !force )
|
|
2644
|
+
{
|
|
2645
|
+
this.propertiesDialog.panel.clear();
|
|
2646
|
+
}
|
|
2647
|
+
}
|
|
2648
|
+
|
|
2649
|
+
_setSnappingValue( value ) {
|
|
2650
|
+
|
|
2651
|
+
this._snapValue = value;
|
|
2652
|
+
}
|
|
2653
|
+
|
|
2654
|
+
_toggleSnapping() {
|
|
2655
|
+
|
|
2656
|
+
this._snapToGrid = !this._snapToGrid;
|
|
2657
|
+
|
|
2658
|
+
// Trigger position snapping for each node if needed
|
|
2659
|
+
|
|
2660
|
+
if( this._snapToGrid )
|
|
2661
|
+
{
|
|
2662
|
+
for( let nodeDom of this._getAllDOMNodes( true ) )
|
|
2663
|
+
{
|
|
2664
|
+
nodeDom.mustSnap = true;
|
|
2665
|
+
}
|
|
2666
|
+
}
|
|
2667
|
+
}
|
|
2668
|
+
|
|
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() {
|
|
2704
|
+
|
|
2705
|
+
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
LX.GraphEditor = GraphEditor;
|
|
2710
|
+
|
|
2711
|
+
|
|
2712
|
+
/**
|
|
2713
|
+
* @class Graph
|
|
2714
|
+
*/
|
|
2715
|
+
|
|
2716
|
+
class Graph {
|
|
2717
|
+
|
|
2718
|
+
/**
|
|
2719
|
+
* @param {*} options
|
|
2720
|
+
*
|
|
2721
|
+
*/
|
|
2722
|
+
|
|
2723
|
+
constructor( name, options = {} ) {
|
|
2724
|
+
|
|
2725
|
+
this.name = name ?? "Unnamed Graph";
|
|
2726
|
+
this.type = 'Graph';
|
|
2727
|
+
|
|
2728
|
+
this.nodes = [ ];
|
|
2729
|
+
this.groups = [ ];
|
|
2730
|
+
this.links = { };
|
|
2731
|
+
|
|
2732
|
+
this.scale = 1.0;
|
|
2733
|
+
this.translation = new LX.vec2( 0, 0 );
|
|
2734
|
+
}
|
|
2735
|
+
|
|
2736
|
+
configure( o ) {
|
|
2737
|
+
|
|
2738
|
+
this.id = o.id;
|
|
2739
|
+
this.name = o.name;
|
|
2740
|
+
|
|
2741
|
+
this.nodes.length = 0;
|
|
2742
|
+
|
|
2743
|
+
for( let node of o.nodes )
|
|
2744
|
+
{
|
|
2745
|
+
const newNode = GraphEditor.addNode( node.type );
|
|
2746
|
+
|
|
2747
|
+
newNode.id = node.id;
|
|
2748
|
+
newNode.title = node.title;
|
|
2749
|
+
newNode.color = node.color;
|
|
2750
|
+
newNode.position = new LX.vec2( node.position.x, node.position.y );
|
|
2751
|
+
newNode.type = node.type;
|
|
2752
|
+
newNode.properties = node.properties;
|
|
2753
|
+
|
|
2754
|
+
this.nodes.push( newNode );
|
|
2755
|
+
}
|
|
2756
|
+
|
|
2757
|
+
this.groups = o.groups;
|
|
2758
|
+
this.links = o.links;
|
|
2759
|
+
|
|
2760
|
+
// editor options?
|
|
2761
|
+
|
|
2762
|
+
// zoom/translation ??
|
|
2763
|
+
}
|
|
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
|
+
|
|
2830
|
+
/**
|
|
2831
|
+
* @method export
|
|
2832
|
+
* @param {Boolean} prettify
|
|
2833
|
+
* @returns JSON data from the serialized graph
|
|
2834
|
+
*/
|
|
2835
|
+
|
|
2836
|
+
export( prettify = true ) {
|
|
2837
|
+
|
|
2838
|
+
var o = { };
|
|
2839
|
+
|
|
2840
|
+
o.id = this.id;
|
|
2841
|
+
o.name = this.name;
|
|
2842
|
+
o.type = this.type;
|
|
2843
|
+
|
|
2844
|
+
o.nodes = [ ];
|
|
2845
|
+
o.groups = [ ];
|
|
2846
|
+
o.links = { };
|
|
2847
|
+
|
|
2848
|
+
for( let node of this.nodes )
|
|
2849
|
+
{
|
|
2850
|
+
o.nodes.push( node.serialize() );
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
for( let linkId in this.links )
|
|
2854
|
+
{
|
|
2855
|
+
const ioLinks = LX.deepCopy( this.links[ linkId ] );
|
|
2856
|
+
ioLinks.forEach( v => delete v.path );
|
|
2857
|
+
o.links[ linkId ] = ioLinks;
|
|
2858
|
+
}
|
|
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
|
+
|
|
2869
|
+
// editor options?
|
|
2870
|
+
|
|
2871
|
+
// zoom/translation ??
|
|
2872
|
+
|
|
2873
|
+
try
|
|
2874
|
+
{
|
|
2875
|
+
o = JSON.stringify( o, null, prettify ? 2 : null );
|
|
2876
|
+
}
|
|
2877
|
+
catch( e )
|
|
2878
|
+
{
|
|
2879
|
+
o = null;
|
|
2880
|
+
console.error( `Can't export GraphNode [${ this.title }] of type [${ this.type }].` );
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
LX.downloadFile( this.name + ".json", o );
|
|
2884
|
+
|
|
2885
|
+
return o;
|
|
2886
|
+
}
|
|
2887
|
+
}
|
|
2888
|
+
|
|
2889
|
+
LX.Graph = Graph;
|
|
2890
|
+
|
|
2891
|
+
/**
|
|
2892
|
+
* @class GraphNode
|
|
2893
|
+
*/
|
|
2894
|
+
|
|
2895
|
+
class GraphNode {
|
|
2896
|
+
|
|
2897
|
+
constructor() {
|
|
2898
|
+
|
|
2899
|
+
this.inputs = [ ];
|
|
2900
|
+
this.outputs = [ ];
|
|
2901
|
+
this.properties = [ ];
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
_hasOutputsConnected() {
|
|
2905
|
+
|
|
2906
|
+
return true;
|
|
2907
|
+
}
|
|
2908
|
+
|
|
2909
|
+
execute() {
|
|
2910
|
+
|
|
2911
|
+
if( !this._hasOutputsConnected() )
|
|
2912
|
+
return;
|
|
2913
|
+
|
|
2914
|
+
if( this.onExecute )
|
|
2915
|
+
{
|
|
2916
|
+
this.onExecute();
|
|
2917
|
+
}
|
|
2918
|
+
}
|
|
2919
|
+
|
|
2920
|
+
addInput( name, type ) {
|
|
2921
|
+
|
|
2922
|
+
this.inputs.push( { name: name, type: type } );
|
|
2923
|
+
}
|
|
2924
|
+
|
|
2925
|
+
addOutput( name, type ) {
|
|
2926
|
+
|
|
2927
|
+
this.outputs.push( { name: name, type: type } );
|
|
2928
|
+
}
|
|
2929
|
+
|
|
2930
|
+
addProperty( name, type, value, selectOptions ) {
|
|
2931
|
+
|
|
2932
|
+
this.properties.push( { name: name, type: type, value: value, options: selectOptions } );
|
|
2933
|
+
}
|
|
2934
|
+
|
|
2935
|
+
getInput( index ) {
|
|
2936
|
+
|
|
2937
|
+
if( !this.inputs || !this.inputs.length || !this.inputs[ index ] )
|
|
2938
|
+
return;
|
|
2939
|
+
|
|
2940
|
+
const graph = this.editor.graphs[ this.graphID ];
|
|
2941
|
+
|
|
2942
|
+
// Get data from link
|
|
2943
|
+
|
|
2944
|
+
for( let linkId in graph.links )
|
|
2945
|
+
{
|
|
2946
|
+
const idx = linkId.indexOf( '@' + this.id );
|
|
2947
|
+
|
|
2948
|
+
if( idx < 0 )
|
|
2949
|
+
continue;
|
|
2950
|
+
|
|
2951
|
+
const nodeLinks = graph.links[ linkId ];
|
|
2952
|
+
|
|
2953
|
+
for ( var link of nodeLinks )
|
|
2954
|
+
{
|
|
2955
|
+
if( link.inputIdx != index )
|
|
2956
|
+
continue;
|
|
2957
|
+
|
|
2958
|
+
// This is the value!!
|
|
2959
|
+
return link.data;
|
|
2960
|
+
}
|
|
2961
|
+
}
|
|
2962
|
+
}
|
|
2963
|
+
|
|
2964
|
+
setOutput( index, data ) {
|
|
2965
|
+
|
|
2966
|
+
if( !this.outputs || !this.outputs.length || !this.outputs[ index ] )
|
|
2967
|
+
return;
|
|
2968
|
+
|
|
2969
|
+
const graph = this.editor.graphs[ this.graphID ];
|
|
2970
|
+
|
|
2971
|
+
// Set data in link
|
|
2972
|
+
|
|
2973
|
+
for( let linkId in graph.links )
|
|
2974
|
+
{
|
|
2975
|
+
const idx = linkId.indexOf( this.id + '@' );
|
|
2976
|
+
|
|
2977
|
+
if( idx < 0 )
|
|
2978
|
+
continue;
|
|
2979
|
+
|
|
2980
|
+
const nodeLinks = graph.links[ linkId ];
|
|
2981
|
+
|
|
2982
|
+
for ( var link of nodeLinks )
|
|
2983
|
+
{
|
|
2984
|
+
if( link.outputIdx != index )
|
|
2985
|
+
continue;
|
|
2986
|
+
|
|
2987
|
+
let innerData = data;
|
|
2988
|
+
|
|
2989
|
+
if( innerData != undefined && link.inputType != link.outputType && link.inputType != "any" && link.outputType != "any" )
|
|
2990
|
+
{
|
|
2991
|
+
// In case of supported casting, use function to cast..
|
|
2992
|
+
|
|
2993
|
+
var fn = this.editor.supportedCastTypes[ link.outputType + '@' + link.inputType ];
|
|
2994
|
+
|
|
2995
|
+
// Use function if it's possible to cast!
|
|
2996
|
+
|
|
2997
|
+
innerData = fn ? fn( LX.deepCopy( innerData ) ) : null;
|
|
2998
|
+
}
|
|
2999
|
+
|
|
3000
|
+
link.data = innerData;
|
|
3001
|
+
}
|
|
3002
|
+
}
|
|
3003
|
+
}
|
|
3004
|
+
|
|
3005
|
+
serialize() {
|
|
3006
|
+
|
|
3007
|
+
var o = { };
|
|
3008
|
+
|
|
3009
|
+
o.id = this.id;
|
|
3010
|
+
o.title = this.title;
|
|
3011
|
+
o.color = this.color;
|
|
3012
|
+
o.position = this.position;
|
|
3013
|
+
o.type = this.type;
|
|
3014
|
+
o.inputs = this.inputs;
|
|
3015
|
+
o.outputs = this.outputs;
|
|
3016
|
+
o.properties = this.properties;
|
|
3017
|
+
|
|
3018
|
+
return o;
|
|
3019
|
+
}
|
|
3020
|
+
}
|
|
3021
|
+
|
|
3022
|
+
LX.GraphNode = GraphNode;
|
|
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
|
+
|
|
3063
|
+
/*
|
|
3064
|
+
************ PREDEFINED NODES ************
|
|
3065
|
+
|
|
3066
|
+
Nodes can override the following methods:
|
|
3067
|
+
|
|
3068
|
+
- onCreate: Add inputs, outputs and properties
|
|
3069
|
+
- onStart: Callback on graph starts
|
|
3070
|
+
- onStop: Callback on graph stops
|
|
3071
|
+
- onExecute: Callback for node execution
|
|
3072
|
+
*/
|
|
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 );
|
|
3122
|
+
|
|
3123
|
+
/*
|
|
3124
|
+
Math nodes
|
|
3125
|
+
*/
|
|
3126
|
+
|
|
3127
|
+
class NodeAdd extends GraphNode
|
|
3128
|
+
{
|
|
3129
|
+
onCreate() {
|
|
3130
|
+
this.addInput( null, "float" );
|
|
3131
|
+
this.addInput( null, "float" );
|
|
3132
|
+
this.addOutput( null, "float" );
|
|
3133
|
+
this.addProperty( "A", "float", 0 );
|
|
3134
|
+
this.addProperty( "B", "float", 0 );
|
|
3135
|
+
}
|
|
3136
|
+
|
|
3137
|
+
onExecute() {
|
|
3138
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3139
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3140
|
+
this.setOutput( 0, a + b );
|
|
3141
|
+
}
|
|
3142
|
+
}
|
|
3143
|
+
|
|
3144
|
+
NodeAdd.description = "Addition of 2 values (A+B)."
|
|
3145
|
+
GraphEditor.registerCustomNode( "math/Add", NodeAdd );
|
|
3146
|
+
|
|
3147
|
+
class NodeSubstract extends GraphNode
|
|
3148
|
+
{
|
|
3149
|
+
onCreate() {
|
|
3150
|
+
this.addInput( null, "float" );
|
|
3151
|
+
this.addInput( null, "float" );
|
|
3152
|
+
this.addOutput( null, "float" );
|
|
3153
|
+
this.addProperty( "A", "float", 0 );
|
|
3154
|
+
this.addProperty( "B", "float", 0 );
|
|
3155
|
+
}
|
|
3156
|
+
|
|
3157
|
+
onExecute() {
|
|
3158
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3159
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3160
|
+
this.setOutput( 0, a - b );
|
|
3161
|
+
}
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
NodeSubstract.description = "Substraction of 2 values (A-B)."
|
|
3165
|
+
GraphEditor.registerCustomNode( "math/Substract", NodeSubstract );
|
|
3166
|
+
|
|
3167
|
+
class NodeMultiply extends GraphNode
|
|
3168
|
+
{
|
|
3169
|
+
onCreate() {
|
|
3170
|
+
this.addInput( null, "float" );
|
|
3171
|
+
this.addInput( null, "float" );
|
|
3172
|
+
this.addOutput( null, "float" );
|
|
3173
|
+
this.addProperty( "A", "float", 0 );
|
|
3174
|
+
this.addProperty( "B", "float", 0 );
|
|
3175
|
+
}
|
|
3176
|
+
|
|
3177
|
+
onExecute() {
|
|
3178
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3179
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3180
|
+
this.setOutput( 0, a * b );
|
|
3181
|
+
}
|
|
3182
|
+
}
|
|
3183
|
+
|
|
3184
|
+
NodeMultiply.description = "Multiplication of 2 values (A*B)."
|
|
3185
|
+
GraphEditor.registerCustomNode( "math/Multiply", NodeMultiply );
|
|
3186
|
+
|
|
3187
|
+
class NodeDivide extends GraphNode
|
|
3188
|
+
{
|
|
3189
|
+
onCreate() {
|
|
3190
|
+
this.addInput( null, "float" );
|
|
3191
|
+
this.addInput( null, "float" );
|
|
3192
|
+
this.addOutput( null, "float" );
|
|
3193
|
+
this.addProperty( "A", "float", 0 );
|
|
3194
|
+
this.addProperty( "B", "float", 0 );
|
|
3195
|
+
}
|
|
3196
|
+
|
|
3197
|
+
onExecute() {
|
|
3198
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3199
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
3200
|
+
this.setOutput( 0, a / b );
|
|
3201
|
+
}
|
|
3202
|
+
}
|
|
3203
|
+
|
|
3204
|
+
NodeDivide.description = "Division of 2 values (A/B)."
|
|
3205
|
+
GraphEditor.registerCustomNode( "math/Divide", NodeDivide );
|
|
3206
|
+
|
|
3207
|
+
class NodeSqrt extends GraphNode
|
|
3208
|
+
{
|
|
3209
|
+
onCreate() {
|
|
3210
|
+
this.addInput( null, "float" );
|
|
3211
|
+
this.addOutput( null, "float" );
|
|
3212
|
+
this.addProperty( "Value", "float", 0 );
|
|
3213
|
+
}
|
|
3214
|
+
|
|
3215
|
+
onExecute() {
|
|
3216
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
3217
|
+
this.setOutput( 0, Math.sqrt( a ) );
|
|
3218
|
+
}
|
|
3219
|
+
}
|
|
3220
|
+
|
|
3221
|
+
NodeSqrt.description = "Square root of a scalar."
|
|
3222
|
+
GraphEditor.registerCustomNode( "math/SQRT", NodeSqrt );
|
|
3223
|
+
|
|
3224
|
+
/*
|
|
3225
|
+
Math Missing:
|
|
3226
|
+
- abs
|
|
3227
|
+
- ceil
|
|
3228
|
+
- clamp
|
|
3229
|
+
- floor
|
|
3230
|
+
- fract
|
|
3231
|
+
- lerp
|
|
3232
|
+
- log
|
|
3233
|
+
- max
|
|
3234
|
+
- min
|
|
3235
|
+
- negate
|
|
3236
|
+
- pow
|
|
3237
|
+
- remainder
|
|
3238
|
+
- round
|
|
3239
|
+
- remap range
|
|
3240
|
+
- saturate
|
|
3241
|
+
- step
|
|
3242
|
+
*/
|
|
3243
|
+
|
|
3244
|
+
|
|
3245
|
+
/*
|
|
3246
|
+
Logical operator nodes
|
|
3247
|
+
*/
|
|
3248
|
+
|
|
3249
|
+
class NodeAnd extends GraphNode
|
|
3250
|
+
{
|
|
3251
|
+
onCreate() {
|
|
3252
|
+
this.addInput( null, "bool" );
|
|
3253
|
+
this.addInput( null, "bool" );
|
|
3254
|
+
this.addOutput( null, "bool" );
|
|
3255
|
+
}
|
|
3256
|
+
|
|
3257
|
+
onExecute() {
|
|
3258
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
3259
|
+
if( a == undefined || b == undefined )
|
|
3260
|
+
return;
|
|
3261
|
+
this.setOutput( 0, !!( a ) && !!( b ) );
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
|
|
3265
|
+
GraphEditor.registerCustomNode( "logic/And", NodeAnd );
|
|
3266
|
+
|
|
3267
|
+
class NodeOr extends GraphNode
|
|
3268
|
+
{
|
|
3269
|
+
onCreate() {
|
|
3270
|
+
this.addInput( null, "bool" );
|
|
3271
|
+
this.addInput( null, "bool" );
|
|
3272
|
+
this.addOutput( null, "bool" );
|
|
3273
|
+
}
|
|
3274
|
+
|
|
3275
|
+
onExecute() {
|
|
3276
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
3277
|
+
if( a == undefined || b == undefined )
|
|
3278
|
+
return;
|
|
3279
|
+
this.setOutput( 0, !!( a ) || !!( b ) );
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
|
|
3283
|
+
GraphEditor.registerCustomNode( "logic/Or", NodeOr );
|
|
3284
|
+
|
|
3285
|
+
class NodeEqual extends GraphNode
|
|
3286
|
+
{
|
|
3287
|
+
onCreate() {
|
|
3288
|
+
this.addInput( null, "float" );
|
|
3289
|
+
this.addInput( null, "float" );
|
|
3290
|
+
this.addOutput( null, "bool" );
|
|
3291
|
+
}
|
|
3292
|
+
|
|
3293
|
+
logicOp( a, b ) {
|
|
3294
|
+
return a == b;
|
|
3295
|
+
}
|
|
3296
|
+
|
|
3297
|
+
onExecute() {
|
|
3298
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
3299
|
+
if( a == undefined || b == undefined )
|
|
3300
|
+
return;
|
|
3301
|
+
this.setOutput( 0, this.logicOp( a, b ) );
|
|
3302
|
+
}
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
GraphEditor.registerCustomNode( "logic/Equal", NodeEqual );
|
|
3306
|
+
|
|
3307
|
+
class NodeNotEqual extends NodeEqual
|
|
3308
|
+
{
|
|
3309
|
+
logicOp( a, b ) {
|
|
3310
|
+
return a != b;
|
|
3311
|
+
}
|
|
3312
|
+
}
|
|
3313
|
+
|
|
3314
|
+
GraphEditor.registerCustomNode( "logic/NotEqual", NodeNotEqual );
|
|
3315
|
+
|
|
3316
|
+
class NodeLess extends NodeEqual
|
|
3317
|
+
{
|
|
3318
|
+
logicOp( a, b ) {
|
|
3319
|
+
return a < b;
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
GraphEditor.registerCustomNode( "logic/Less", NodeLess );
|
|
3324
|
+
|
|
3325
|
+
class NodeLessEqual extends NodeEqual
|
|
3326
|
+
{
|
|
3327
|
+
logicOp( a, b ) {
|
|
3328
|
+
return a <= b;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
|
|
3332
|
+
GraphEditor.registerCustomNode( "logic/LessEqual", NodeLessEqual );
|
|
3333
|
+
|
|
3334
|
+
class NodeGreater extends NodeEqual
|
|
3335
|
+
{
|
|
3336
|
+
logicOp( a, b ) {
|
|
3337
|
+
return a > b;
|
|
3338
|
+
}
|
|
3339
|
+
}
|
|
3340
|
+
|
|
3341
|
+
GraphEditor.registerCustomNode( "logic/Greater", NodeGreater );
|
|
3342
|
+
|
|
3343
|
+
class NodeGreaterEqual extends NodeEqual
|
|
3344
|
+
{
|
|
3345
|
+
logicOp( a, b ) {
|
|
3346
|
+
return a >= b;
|
|
3347
|
+
}
|
|
3348
|
+
}
|
|
3349
|
+
|
|
3350
|
+
GraphEditor.registerCustomNode( "logic/GreaterEqual", NodeGreaterEqual );
|
|
3351
|
+
|
|
3352
|
+
class NodeSelect extends GraphNode
|
|
3353
|
+
{
|
|
3354
|
+
onCreate() {
|
|
3355
|
+
this.addInput( "True", "any" );
|
|
3356
|
+
this.addInput( "False", "any" );
|
|
3357
|
+
this.addInput( "Condition", "bool" );
|
|
3358
|
+
this.addOutput( null, "any" );
|
|
3359
|
+
}
|
|
3360
|
+
|
|
3361
|
+
onExecute() {
|
|
3362
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 ), cond = this.getInput( 2 );
|
|
3363
|
+
if( a == undefined || b == undefined || cond == undefined )
|
|
3364
|
+
return;
|
|
3365
|
+
this.setOutput( 0, cond ? a : b );
|
|
3366
|
+
}
|
|
3367
|
+
}
|
|
3368
|
+
|
|
3369
|
+
GraphEditor.registerCustomNode( "logic/Select", NodeSelect );
|
|
3370
|
+
|
|
3371
|
+
class NodeCompare extends GraphNode
|
|
3372
|
+
{
|
|
3373
|
+
onCreate() {
|
|
3374
|
+
this.addInput( "A", "any" );
|
|
3375
|
+
this.addInput( "B", "any" );
|
|
3376
|
+
this.addInput( "True", "any" );
|
|
3377
|
+
this.addInput( "False", "any" );
|
|
3378
|
+
this.addProperty( "Condition", "select", 'Equal', [ 'Equal', 'Not Equal', 'Less', 'Less Equal', 'Greater', 'Greater Equal' ] );
|
|
3379
|
+
this.addOutput( null, "any" );
|
|
3380
|
+
}
|
|
3381
|
+
|
|
3382
|
+
onExecute() {
|
|
3383
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 ), TrueVal = this.getInput( 2 ), FalseVal = this.getInput( 3 );;
|
|
3384
|
+
var cond = this.properties[ 0 ].value;
|
|
3385
|
+
if( a == undefined || b == undefined || TrueVal == undefined || FalseVal == undefined )
|
|
3386
|
+
return;
|
|
3387
|
+
var output;
|
|
3388
|
+
switch( cond ) {
|
|
3389
|
+
case 'Equal': output = ( a == b ? TrueVal : FalseVal ); break;
|
|
3390
|
+
case 'Not Equal': output = ( a != b ? TrueVal : FalseVal ); break;
|
|
3391
|
+
case 'Less': output = ( a < b ? TrueVal : FalseVal ); break;
|
|
3392
|
+
case 'Less Equal': output = ( a <= b ? TrueVal : FalseVal ); break;
|
|
3393
|
+
case 'Greater': output = ( a > b ? TrueVal : FalseVal ); break;
|
|
3394
|
+
case 'Greater Equal': output = ( a >= b ? TrueVal : FalseVal ); break;
|
|
3395
|
+
}
|
|
3396
|
+
this.setOutput( 0, output );
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
NodeCompare.description = "Compare A to B given the selected operator. If true, return value of True else return value of False."
|
|
3400
|
+
GraphEditor.registerCustomNode( "logic/Compare", NodeCompare );
|
|
3401
|
+
|
|
3402
|
+
/*
|
|
3403
|
+
Event nodes
|
|
3404
|
+
*/
|
|
3405
|
+
|
|
3406
|
+
class NodeKeyDown extends GraphNode
|
|
3407
|
+
{
|
|
3408
|
+
onCreate() {
|
|
3409
|
+
this.addOutput( null, "bool" );
|
|
3410
|
+
this.addProperty( "Key", "string", " " );
|
|
3411
|
+
}
|
|
3412
|
+
|
|
3413
|
+
onExecute() {
|
|
3414
|
+
this.setOutput( 0, !!this.editor.keys[ this.properties[ 0 ].value ] );
|
|
3415
|
+
}
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
GraphEditor.registerCustomNode( "events/KeyDown", NodeKeyDown );
|
|
3419
|
+
|
|
3420
|
+
/*
|
|
3421
|
+
Input nodes
|
|
3422
|
+
*/
|
|
3423
|
+
|
|
3424
|
+
class NodeString extends GraphNode
|
|
3425
|
+
{
|
|
3426
|
+
onCreate() {
|
|
3427
|
+
this.addOutput( null, "string" );
|
|
3428
|
+
this.addProperty( null, "string", "text" );
|
|
3429
|
+
}
|
|
3430
|
+
|
|
3431
|
+
onExecute() {
|
|
3432
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3433
|
+
}
|
|
3434
|
+
}
|
|
3435
|
+
|
|
3436
|
+
GraphEditor.registerCustomNode( "inputs/String", NodeString );
|
|
3437
|
+
|
|
3438
|
+
class NodeFloat extends GraphNode
|
|
3439
|
+
{
|
|
3440
|
+
onCreate() {
|
|
3441
|
+
this.addOutput( null, "float" );
|
|
3442
|
+
this.addProperty( null, "float", 0.0 );
|
|
3443
|
+
}
|
|
3444
|
+
|
|
3445
|
+
onExecute() {
|
|
3446
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3447
|
+
}
|
|
3448
|
+
}
|
|
3449
|
+
|
|
3450
|
+
GraphEditor.registerCustomNode( "inputs/Float", NodeFloat );
|
|
3451
|
+
|
|
3452
|
+
class NodeVector2 extends GraphNode
|
|
3453
|
+
{
|
|
3454
|
+
onCreate() {
|
|
3455
|
+
this.addOutput( "Value", "vec2" );
|
|
3456
|
+
this.addProperty( "Value", "vec2", [ 0, 0 ] );
|
|
3457
|
+
}
|
|
3458
|
+
|
|
3459
|
+
onExecute() {
|
|
3460
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3461
|
+
}
|
|
3462
|
+
}
|
|
3463
|
+
|
|
3464
|
+
GraphEditor.registerCustomNode( "inputs/Vector2", NodeVector2 );
|
|
3465
|
+
|
|
3466
|
+
class NodeVector3 extends GraphNode
|
|
3467
|
+
{
|
|
3468
|
+
onCreate() {
|
|
3469
|
+
this.addOutput( "Value", "vec3" );
|
|
3470
|
+
this.addProperty( "Value", "vec3", [ 0, 0, 0 ] );
|
|
3471
|
+
}
|
|
3472
|
+
|
|
3473
|
+
onExecute() {
|
|
3474
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3475
|
+
}
|
|
3476
|
+
}
|
|
3477
|
+
|
|
3478
|
+
GraphEditor.registerCustomNode( "inputs/Vector3", NodeVector3 );
|
|
3479
|
+
|
|
3480
|
+
class NodeVector4 extends GraphNode
|
|
3481
|
+
{
|
|
3482
|
+
onCreate() {
|
|
3483
|
+
this.addOutput( "Value", "vec4" );
|
|
3484
|
+
this.addProperty( "Value", "vec4", [ 0, 0, 0, 0 ] );
|
|
3485
|
+
}
|
|
3486
|
+
|
|
3487
|
+
onExecute() {
|
|
3488
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3489
|
+
}
|
|
3490
|
+
}
|
|
3491
|
+
|
|
3492
|
+
GraphEditor.registerCustomNode( "inputs/Vector4", NodeVector4 );
|
|
3493
|
+
|
|
3494
|
+
/*
|
|
3495
|
+
Variable nodes
|
|
3496
|
+
*/
|
|
3497
|
+
|
|
3498
|
+
class NodeSetVariable extends GraphNode
|
|
3499
|
+
{
|
|
3500
|
+
onCreate() {
|
|
3501
|
+
this.addInput( "Value", "any" );
|
|
3502
|
+
this.addOutput( null, "any" );
|
|
3503
|
+
this.addProperty( "Name", "string", "" );
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3506
|
+
onExecute() {
|
|
3507
|
+
var varName = this.getInput( 0 );
|
|
3508
|
+
if( varName == undefined )
|
|
3509
|
+
return;
|
|
3510
|
+
var varValue = this.getInput( 1 );
|
|
3511
|
+
if( varValue == undefined )
|
|
3512
|
+
return;
|
|
3513
|
+
this.editor.setVariable( varName, varValue );
|
|
3514
|
+
this.setOutput( 0, varValue );
|
|
3515
|
+
}
|
|
3516
|
+
}
|
|
3517
|
+
|
|
3518
|
+
NodeSetVariable.title = "Set Variable";
|
|
3519
|
+
GraphEditor.registerCustomNode( "variables/SetVariable", NodeSetVariable );
|
|
3520
|
+
|
|
3521
|
+
class NodeGetVariable extends GraphNode
|
|
3522
|
+
{
|
|
3523
|
+
onCreate() {
|
|
3524
|
+
this.addOutput( null, "any" );
|
|
3525
|
+
this.addProperty( "Name", "string", "" );
|
|
3526
|
+
}
|
|
3527
|
+
|
|
3528
|
+
onExecute() {
|
|
3529
|
+
var varName = this.getInput( 0 );
|
|
3530
|
+
if( varName == undefined )
|
|
3531
|
+
return;
|
|
3532
|
+
var data = this.editor.getVariable( varName );
|
|
3533
|
+
if( data != undefined )
|
|
3534
|
+
this.setOutput( 0, data );
|
|
3535
|
+
}
|
|
3536
|
+
}
|
|
3537
|
+
|
|
3538
|
+
NodeGetVariable.title = "Get Variable";
|
|
3539
|
+
GraphEditor.registerCustomNode( "variables/GetVariable", NodeGetVariable );
|
|
3540
|
+
|
|
3541
|
+
/*
|
|
3542
|
+
System nodes
|
|
3543
|
+
*/
|
|
3544
|
+
|
|
3545
|
+
class NodeConsoleLog extends GraphNode
|
|
3546
|
+
{
|
|
3547
|
+
onCreate() {
|
|
3548
|
+
this.addInput( null, "any" );
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3551
|
+
onExecute() {
|
|
3552
|
+
var data = this.getInput( 0 );
|
|
3553
|
+
if( data == undefined )
|
|
3554
|
+
return;
|
|
3555
|
+
console.log( data );
|
|
3556
|
+
}
|
|
3557
|
+
}
|
|
3558
|
+
|
|
3559
|
+
NodeConsoleLog.title = "Console Log";
|
|
3560
|
+
GraphEditor.registerCustomNode( "system/ConsoleLog", NodeConsoleLog );
|
|
3561
|
+
|
|
3562
|
+
class NodeMain extends GraphNode
|
|
3563
|
+
{
|
|
3564
|
+
onCreate() {
|
|
3565
|
+
this.addInput( "a", "float" );
|
|
3566
|
+
this.addInput( "b", "bool" );
|
|
3567
|
+
this.addInput( "Color", "vec4" );
|
|
3568
|
+
}
|
|
3569
|
+
|
|
3570
|
+
onExecute() {
|
|
3571
|
+
var data = this.getInput( 2 );
|
|
3572
|
+
if( data == undefined )
|
|
3573
|
+
return;
|
|
3574
|
+
console.log( data );
|
|
3575
|
+
};
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3578
|
+
NodeMain.blockDelete = true;
|
|
3579
|
+
GraphEditor.registerCustomNode( "system/Main", NodeMain );
|
|
731
3580
|
|
|
732
3581
|
export { GraphEditor, Graph, GraphNode };
|