lexgui 0.1.28 → 0.1.29
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 +7 -2
- package/build/components/nodegraph.js +2746 -361
- package/build/lexgui.css +286 -36
- package/build/lexgui.js +156 -107
- package/build/lexgui.module.js +144 -95
- package/changelog.md +11 -0
- package/examples/code_editor.html +13 -10
- package/examples/node_graph.html +1 -1
- package/package.json +1 -1
|
@@ -29,6 +29,10 @@ function firstNonspaceIndex(str) {
|
|
|
29
29
|
return str.search(/\S|$/);
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
function deleteElement( el ) {
|
|
33
|
+
if( el ) el.remove();
|
|
34
|
+
}
|
|
35
|
+
|
|
32
36
|
let ASYNC_ENABLED = true;
|
|
33
37
|
|
|
34
38
|
function doAsync( fn, ms ) {
|
|
@@ -38,6 +42,44 @@ function doAsync( fn, ms ) {
|
|
|
38
42
|
fn();
|
|
39
43
|
}
|
|
40
44
|
|
|
45
|
+
class BoundingBox {
|
|
46
|
+
|
|
47
|
+
constructor( o, s )
|
|
48
|
+
{
|
|
49
|
+
this.origin = o ?? new LX.vec2( 0, 0 );
|
|
50
|
+
this.size = s ?? new LX.vec2( 0, 0 );
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
merge ( bb ) {
|
|
54
|
+
|
|
55
|
+
console.assert( bb.constructor == BoundingBox );
|
|
56
|
+
|
|
57
|
+
const min_0 = this.origin;
|
|
58
|
+
const max_0 = this.origin.add( this.size );
|
|
59
|
+
|
|
60
|
+
const min_1 = bb.origin;
|
|
61
|
+
const max_1 = bb.origin.add( bb.size );
|
|
62
|
+
|
|
63
|
+
const merge_min = new LX.vec2( Math.min( min_0.x, min_1.x ), Math.min( min_0.y, min_1.y ) );
|
|
64
|
+
const merge_max = new LX.vec2( Math.max( max_0.x, max_1.x ), Math.max( max_0.y, max_1.y ) );
|
|
65
|
+
|
|
66
|
+
this.origin = merge_min;
|
|
67
|
+
this.size = merge_max.sub( merge_min );
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
inside ( bb ) {
|
|
71
|
+
|
|
72
|
+
const min_0 = this.origin;
|
|
73
|
+
const max_0 = this.origin.add( this.size );
|
|
74
|
+
|
|
75
|
+
const min_1 = bb.origin;
|
|
76
|
+
const max_1 = bb.origin.add( bb.size );
|
|
77
|
+
|
|
78
|
+
return min_1.x >= min_0.x && max_1.x <= max_0.x
|
|
79
|
+
&& min_1.y >= min_0.y && max_1.y <= max_0.y;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
41
83
|
/**
|
|
42
84
|
* @class GraphEditor
|
|
43
85
|
*/
|
|
@@ -46,25 +88,22 @@ class GraphEditor {
|
|
|
46
88
|
|
|
47
89
|
static __instances = [];
|
|
48
90
|
|
|
49
|
-
//
|
|
91
|
+
// Editor
|
|
50
92
|
|
|
51
|
-
static MIN_SCALE
|
|
52
|
-
static MAX_SCALE
|
|
93
|
+
static MIN_SCALE = 0.25;
|
|
94
|
+
static MAX_SCALE = 4.0;
|
|
53
95
|
|
|
54
96
|
static EVENT_MOUSEMOVE = 0;
|
|
55
97
|
static EVENT_MOUSEWHEEL = 1;
|
|
56
98
|
|
|
57
|
-
|
|
99
|
+
static LAST_GROUP_ID = 0;
|
|
58
100
|
|
|
59
|
-
|
|
60
|
-
static NODE_ROW_HEIGHT = 16;
|
|
101
|
+
// Node Drawing
|
|
61
102
|
|
|
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 ];
|
|
103
|
+
static NODE_IO_INPUT = 0;
|
|
104
|
+
static NODE_IO_OUTPUT = 1;
|
|
65
105
|
|
|
66
|
-
static
|
|
67
|
-
static DEFAULT_NODE_BODY_COLOR = "#111";
|
|
106
|
+
static NODE_TYPES = { };
|
|
68
107
|
|
|
69
108
|
/**
|
|
70
109
|
* @param {*} options
|
|
@@ -75,6 +114,12 @@ class GraphEditor {
|
|
|
75
114
|
|
|
76
115
|
GraphEditor.__instances.push( this );
|
|
77
116
|
|
|
117
|
+
area.addSidebar( m => {
|
|
118
|
+
m.add( "Scene", { icon: "fa fa-cube", callback: () => { codeArea.hide(); show( canvas ); } } );
|
|
119
|
+
m.add( "Code", { icon: "fa fa-code", callback: () => { hide( canvas ); codeArea.show(); } } );
|
|
120
|
+
m.add( "Search", { icon: "fa fa-search", bottom: true, callback: () => { } } );
|
|
121
|
+
});
|
|
122
|
+
|
|
78
123
|
this.base_area = area;
|
|
79
124
|
this.area = new LX.Area( { className: "lexgraph" } );
|
|
80
125
|
|
|
@@ -87,20 +132,143 @@ class GraphEditor {
|
|
|
87
132
|
// Bind resize
|
|
88
133
|
|
|
89
134
|
area.onresize = ( bb ) => {
|
|
90
|
-
|
|
135
|
+
console.log(bb);
|
|
91
136
|
};
|
|
92
137
|
|
|
93
|
-
|
|
138
|
+
area.addOverlayButtons( [
|
|
139
|
+
{
|
|
140
|
+
name: "Toggle Sidebar",
|
|
141
|
+
icon: "fa fa-table-columns",
|
|
142
|
+
callback: () => this._toggleSideBar(),
|
|
143
|
+
},
|
|
144
|
+
// [
|
|
145
|
+
// {
|
|
146
|
+
// name: "Select",
|
|
147
|
+
// icon: "fa fa-arrow-pointer",
|
|
148
|
+
// callback: (value, event) => console.log(value),
|
|
149
|
+
// selectable: true
|
|
150
|
+
// },
|
|
151
|
+
// {
|
|
152
|
+
// name: "Move",
|
|
153
|
+
// icon: "fa-solid fa-arrows-up-down-left-right",
|
|
154
|
+
// img: "https://webglstudio.org/latest/imgs/mini-icon-gizmo.png",
|
|
155
|
+
// callback: (value, event) => console.log(value),
|
|
156
|
+
// selectable: true
|
|
157
|
+
// },
|
|
158
|
+
// {
|
|
159
|
+
// name: "Rotate",
|
|
160
|
+
// icon: "fa-solid fa-rotate-right",
|
|
161
|
+
// callback: (value, event) => console.log(value),
|
|
162
|
+
// selectable: true
|
|
163
|
+
// }
|
|
164
|
+
// ],
|
|
165
|
+
[
|
|
166
|
+
{
|
|
167
|
+
name: "Enable Snapping",
|
|
168
|
+
icon: "fa fa-table-cells",
|
|
169
|
+
callback: () => this._toggleSnapping(),
|
|
170
|
+
selectable: true
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 1,
|
|
174
|
+
options: [1, 2, 3],
|
|
175
|
+
callback: value => this._setSnappingValue( value ),
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
[
|
|
179
|
+
{
|
|
180
|
+
name: "Import",
|
|
181
|
+
icon: "fa fa-upload",
|
|
182
|
+
callback: (value, event) => { this.loadGraph( "../../data/graph_sample.json" ); }
|
|
183
|
+
},
|
|
184
|
+
{
|
|
185
|
+
name: "Export",
|
|
186
|
+
icon: "fa fa-diagram-project",
|
|
187
|
+
callback: (value, event) => this.graph.export()
|
|
188
|
+
}
|
|
189
|
+
]
|
|
190
|
+
], { float: "htc" } );
|
|
191
|
+
|
|
192
|
+
this.root.addEventListener( 'keydown', this._processKeyDown.bind( this ), true );
|
|
193
|
+
this.root.addEventListener( 'keyup', this._processKeyUp.bind( this ), true );
|
|
94
194
|
this.root.addEventListener( 'mousedown', this._processMouse.bind( this ) );
|
|
95
195
|
this.root.addEventListener( 'mouseup', this._processMouse.bind( this ) );
|
|
96
196
|
this.root.addEventListener( 'mousemove', this._processMouse.bind( this ) );
|
|
97
197
|
this.root.addEventListener( 'mousewheel', this._processMouse.bind(this) );
|
|
198
|
+
this.root.addEventListener( 'mouseleave', this._processMouse.bind(this) );
|
|
98
199
|
this.root.addEventListener( 'click', this._processMouse.bind( this ) );
|
|
99
200
|
this.root.addEventListener( 'contextmenu', this._processMouse.bind( this ) );
|
|
100
201
|
this.root.addEventListener( 'focus', this._processFocus.bind( this, true) );
|
|
101
202
|
this.root.addEventListener( 'focusout', this._processFocus.bind( this, false ) );
|
|
102
203
|
|
|
204
|
+
this.propertiesDialog = new LX.PocketDialog( "Properties", null, {
|
|
205
|
+
size: [ "300px", null ],
|
|
206
|
+
position: [ "8px", "8px" ],
|
|
207
|
+
float: "left",
|
|
208
|
+
class: 'lexgraphpropdialog'
|
|
209
|
+
} );
|
|
210
|
+
|
|
211
|
+
// Avoid closing the dialog on click..
|
|
212
|
+
|
|
213
|
+
this.propertiesDialog.root.addEventListener( "mousedown", function( e ) {
|
|
214
|
+
e.stopImmediatePropagation();
|
|
215
|
+
e.stopPropagation();
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
this.propertiesDialog.root.addEventListener( "mouseup", function( e ) {
|
|
219
|
+
e.stopImmediatePropagation();
|
|
220
|
+
e.stopPropagation();
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Move to root..
|
|
224
|
+
this.root.appendChild( this.propertiesDialog.root );
|
|
225
|
+
|
|
226
|
+
// Editor
|
|
227
|
+
|
|
228
|
+
this._mousePosition = new LX.vec2( 0, 0 );
|
|
229
|
+
this._deltaMousePosition = new LX.vec2( 0, 0 );
|
|
230
|
+
this._snappedDeltaMousePosition = new LX.vec2( 0, 0 );
|
|
103
231
|
this._lastMousePosition = new LX.vec2( 0, 0 );
|
|
232
|
+
this._lastSnappedMousePosition = new LX.vec2( 0, 0 );
|
|
233
|
+
|
|
234
|
+
this._undoSteps = [ ];
|
|
235
|
+
this._redoSteps = [ ];
|
|
236
|
+
|
|
237
|
+
this.keys = { };
|
|
238
|
+
|
|
239
|
+
this._sidebar = area.sections[ 0 ].root;
|
|
240
|
+
this._sidebarActive = false;
|
|
241
|
+
|
|
242
|
+
this._snapToGrid = false;
|
|
243
|
+
this._snapValue = 1.0;
|
|
244
|
+
|
|
245
|
+
this._scale = 1.0;
|
|
246
|
+
|
|
247
|
+
// Nodes and connections
|
|
248
|
+
|
|
249
|
+
this.nodes = { };
|
|
250
|
+
this.variables = { };
|
|
251
|
+
|
|
252
|
+
this.selectedNodes = [ ];
|
|
253
|
+
|
|
254
|
+
this.supportedCastTypes = { };
|
|
255
|
+
|
|
256
|
+
this.addCastType( 'float', 'vec2', ( v ) => { return [ v, v ]; } );
|
|
257
|
+
this.addCastType( 'float', 'vec3', ( v ) => { return [ v, v, v ]; } );
|
|
258
|
+
this.addCastType( 'float', 'vec4', ( v ) => { return [ v, v, v, v ]; } );
|
|
259
|
+
this.addCastType( 'float', 'bool', ( v ) => { return !!v; } );
|
|
260
|
+
|
|
261
|
+
this.addCastType( 'vec4', 'vec3', ( v ) => { v.slice( 0, 3 ); return v; } );
|
|
262
|
+
this.addCastType( 'vec4', 'vec2', ( v ) => { v.slice( 0, 2 ); return v; } );
|
|
263
|
+
this.addCastType( 'vec3', 'vec2', ( v ) => { v.slice( 0, 2 ); return v; } );
|
|
264
|
+
|
|
265
|
+
this.addCastType( 'vec3', 'vec4', ( v ) => { v.push( 1 ); return v; } );
|
|
266
|
+
this.addCastType( 'vec2', 'vec3', ( v ) => { v.push( 1 ); return v; } );
|
|
267
|
+
this.addCastType( 'vec2', 'vec4', ( v ) => { v.push( 0, 1 ); return v; } );
|
|
268
|
+
|
|
269
|
+
this._nodeBackgroundOpacity = options.disableNodeOpacity ? 1.0 : 0.8;
|
|
270
|
+
|
|
271
|
+
this.main = null;
|
|
104
272
|
|
|
105
273
|
// Back pattern
|
|
106
274
|
|
|
@@ -112,43 +280,170 @@ class GraphEditor {
|
|
|
112
280
|
|
|
113
281
|
this._generatePattern();
|
|
114
282
|
|
|
115
|
-
//
|
|
283
|
+
// Links
|
|
116
284
|
|
|
117
|
-
this.
|
|
285
|
+
this._domLinks = document.createElement( 'div' );
|
|
286
|
+
this._domLinks.classList.add( 'lexgraphlinks' );
|
|
287
|
+
this.root.appendChild( this._domLinks );
|
|
118
288
|
|
|
119
|
-
//
|
|
289
|
+
// Nodes
|
|
120
290
|
|
|
121
291
|
this._domNodes = document.createElement( 'div' );
|
|
122
292
|
this._domNodes.classList.add( 'lexgraphnodes' );
|
|
123
293
|
this.root.appendChild( this._domNodes );
|
|
124
294
|
|
|
125
|
-
|
|
295
|
+
window.ge = this;
|
|
126
296
|
}
|
|
127
297
|
|
|
128
|
-
static getInstances()
|
|
129
|
-
|
|
298
|
+
static getInstances() {
|
|
299
|
+
|
|
130
300
|
return GraphEditor.__instances;
|
|
131
301
|
}
|
|
132
302
|
|
|
303
|
+
/**
|
|
304
|
+
* Register a node class so it can be listed when the user wants to create a new one
|
|
305
|
+
* @method registerCustomNode
|
|
306
|
+
* @param {String} type: name of the node and path
|
|
307
|
+
* @param {Class} baseClass class containing the structure of the custom node
|
|
308
|
+
*/
|
|
309
|
+
|
|
310
|
+
static registerCustomNode( type, baseClass ) {
|
|
311
|
+
|
|
312
|
+
if ( !baseClass.prototype ) {
|
|
313
|
+
throw "Cannot register a simple object, it must be a class with a prototype!";
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Get info from path
|
|
317
|
+
const pos = type.lastIndexOf( "/" );
|
|
318
|
+
|
|
319
|
+
baseClass.category = type.substring( 0, pos );
|
|
320
|
+
baseClass.title = baseClass.title ?? type.substring( pos + 1 );
|
|
321
|
+
baseClass.type = type;
|
|
322
|
+
|
|
323
|
+
if( !baseClass.prototype.onExecute )
|
|
324
|
+
{
|
|
325
|
+
console.warn( `GraphNode [${ this.title }] does not have a callback attached.` );
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const prev = GraphEditor.NODE_TYPES[ type ];
|
|
329
|
+
if(prev) {
|
|
330
|
+
console.warn( `Replacing node type [${ type }]` );
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
GraphEditor.NODE_TYPES[ type ] = baseClass;
|
|
334
|
+
|
|
335
|
+
// Some callbacks..
|
|
336
|
+
|
|
337
|
+
if ( this.onNodeTypeRegistered ) {
|
|
338
|
+
this.onCustomNodeRegistered( type, baseClass);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
if ( prev && this.onNodeTypeReplaced ) {
|
|
342
|
+
this.onNodeTypeReplaced( type, baseClass, prev );
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Create a node of a given type with a name. The node is not attached to any graph yet.
|
|
348
|
+
* @method createNode
|
|
349
|
+
* @param {String} type full name of the node class. p.e. "math/sin"
|
|
350
|
+
* @param {String} title a name to distinguish from other nodes
|
|
351
|
+
* @param {Object} options Store node options
|
|
352
|
+
*/
|
|
353
|
+
|
|
354
|
+
static addNode( type, title, options ) {
|
|
355
|
+
|
|
356
|
+
var baseClass = GraphEditor.NODE_TYPES[ type ];
|
|
357
|
+
|
|
358
|
+
if (!baseClass) {
|
|
359
|
+
console.warn( `GraphNode type [${ type }] not registered.` );
|
|
360
|
+
return null;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
title = title ?? baseClass.title;
|
|
364
|
+
|
|
365
|
+
const node = new baseClass( title );
|
|
366
|
+
|
|
367
|
+
if( node.onCreate )
|
|
368
|
+
node.onCreate();
|
|
369
|
+
|
|
370
|
+
node.type = type;
|
|
371
|
+
node.title = title;
|
|
372
|
+
node.position = new LX.vec2( 0, 0 );
|
|
373
|
+
node.color = null;
|
|
374
|
+
|
|
375
|
+
// Extra options
|
|
376
|
+
if ( options ) {
|
|
377
|
+
for (var i in options) {
|
|
378
|
+
node[ i ] = options[ i ];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
if ( node.onNodeCreated ) {
|
|
383
|
+
node.onNodeCreated();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
return node;
|
|
387
|
+
}
|
|
388
|
+
|
|
133
389
|
/**
|
|
134
390
|
* @method setGraph
|
|
135
|
-
* @param {Graph} graph
|
|
391
|
+
* @param {Graph} graph
|
|
136
392
|
*/
|
|
137
393
|
|
|
138
394
|
setGraph( graph ) {
|
|
139
395
|
|
|
396
|
+
this.clear();
|
|
397
|
+
|
|
140
398
|
this.graph = graph;
|
|
141
399
|
|
|
142
400
|
if( !this.graph.nodes )
|
|
143
401
|
{
|
|
144
402
|
console.warn( 'Graph does not contain any node!' );
|
|
145
|
-
return
|
|
403
|
+
return;
|
|
146
404
|
}
|
|
147
405
|
|
|
148
406
|
for( let node of this.graph.nodes )
|
|
149
407
|
{
|
|
150
|
-
this.
|
|
408
|
+
this._createNodeDOM( node );
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
for( let linkId in this.graph.links )
|
|
412
|
+
{
|
|
413
|
+
const links = this.graph.links[ linkId ];
|
|
414
|
+
|
|
415
|
+
for( let link of links )
|
|
416
|
+
{
|
|
417
|
+
this._createLink( link );
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// TODO: REMOVE THIS (DEBUG)
|
|
422
|
+
this.start();
|
|
423
|
+
//
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
/**
|
|
427
|
+
* @method loadGraph
|
|
428
|
+
* @param {Graph} graph
|
|
429
|
+
*/
|
|
430
|
+
|
|
431
|
+
loadGraph( url, callback ) {
|
|
432
|
+
|
|
433
|
+
const onComplete = ( json ) => {
|
|
434
|
+
|
|
435
|
+
var graph = new Graph();
|
|
436
|
+
graph.configure( json );
|
|
437
|
+
|
|
438
|
+
this.setGraph( graph );
|
|
439
|
+
|
|
440
|
+
if( callback )
|
|
441
|
+
callback( graph );
|
|
151
442
|
}
|
|
443
|
+
|
|
444
|
+
const onError = (v) => console.error(v);
|
|
445
|
+
|
|
446
|
+
LX.requestJSON( url, onComplete, onError );
|
|
152
447
|
}
|
|
153
448
|
|
|
154
449
|
/**
|
|
@@ -158,52 +453,197 @@ class GraphEditor {
|
|
|
158
453
|
clear() {
|
|
159
454
|
|
|
160
455
|
this._domNodes.innerHTML = "";
|
|
456
|
+
this._domLinks.innerHTML = "";
|
|
457
|
+
|
|
458
|
+
this.nodes = { };
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
setVariable( name, value ) {
|
|
462
|
+
|
|
463
|
+
this.variables[ name ] = value;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
getVariable( name ) {
|
|
467
|
+
|
|
468
|
+
return this.variables[ name ];
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
propagateEventToAllNodes( eventName, params ) {
|
|
472
|
+
|
|
473
|
+
if( !this.graph )
|
|
474
|
+
return;
|
|
475
|
+
|
|
476
|
+
for ( let node of this.graph.nodes )
|
|
477
|
+
{
|
|
478
|
+
if( !node[ eventName ] )
|
|
479
|
+
continue;
|
|
480
|
+
|
|
481
|
+
node[ eventName ].apply( this, params );
|
|
482
|
+
}
|
|
483
|
+
};
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* @method addCastType
|
|
487
|
+
* @param {String} type: Type to cast
|
|
488
|
+
* @param {String} targetType: Types to be casted from original type
|
|
489
|
+
* @param {Function} fn: Function to know how to cast
|
|
490
|
+
*/
|
|
491
|
+
|
|
492
|
+
addCastType( type, targetType, fn ) {
|
|
493
|
+
|
|
494
|
+
this.supportedCastTypes[ type + '@' + targetType ] = fn;
|
|
161
495
|
}
|
|
162
496
|
|
|
163
497
|
/**
|
|
164
498
|
* @method unSelectAll
|
|
165
499
|
*/
|
|
166
500
|
|
|
167
|
-
unSelectAll(
|
|
501
|
+
unSelectAll( keepPropDialog ) {
|
|
168
502
|
|
|
169
|
-
this._domNodes.querySelectorAll( '.lexgraphnode' ).forEach( v =>
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
503
|
+
this._domNodes.querySelectorAll( '.lexgraphnode' ).forEach( v => v.classList.remove( 'selected' ) );
|
|
504
|
+
|
|
505
|
+
this.selectedNodes.length = 0;
|
|
506
|
+
|
|
507
|
+
if( !keepPropDialog )
|
|
508
|
+
this._togglePropertiesDialog( false );
|
|
174
509
|
}
|
|
175
510
|
|
|
176
|
-
|
|
511
|
+
_createNodeDOM( node ) {
|
|
512
|
+
|
|
513
|
+
node.editor = this;
|
|
177
514
|
|
|
178
515
|
var nodeContainer = document.createElement( 'div' );
|
|
179
516
|
nodeContainer.classList.add( 'lexgraphnode' );
|
|
517
|
+
nodeContainer.style.left = "0";
|
|
518
|
+
nodeContainer.style.top = "0";
|
|
180
519
|
|
|
181
|
-
|
|
182
|
-
nodeContainer.style.top = node.position.y + "px";
|
|
520
|
+
this._translateNode( nodeContainer, node.position );
|
|
183
521
|
|
|
184
|
-
|
|
522
|
+
var color;
|
|
523
|
+
|
|
524
|
+
// Get color from type if color if not manually specified
|
|
525
|
+
if( node.type && GraphEditor.NODE_TYPES[ node.type ] )
|
|
526
|
+
{
|
|
527
|
+
const category = node.constructor.category;
|
|
528
|
+
nodeContainer.classList.add( category );
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
// Update with manual color
|
|
532
|
+
|
|
533
|
+
color = node.color ?? color;
|
|
534
|
+
|
|
535
|
+
if( color )
|
|
185
536
|
{
|
|
186
|
-
|
|
537
|
+
// RGB
|
|
538
|
+
if( color.constructor == Array )
|
|
539
|
+
{
|
|
540
|
+
color = color.join( ',' );
|
|
541
|
+
}
|
|
542
|
+
// Hex color..
|
|
543
|
+
else
|
|
544
|
+
{
|
|
545
|
+
color = LX.hexToRgb( color );
|
|
546
|
+
color.forEach( ( v, i ) => color[ i ] = v * 255 );
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
nodeContainer.style.backgroundColor = "rgba(" + color + ", " + this._nodeBackgroundOpacity + ")";
|
|
187
550
|
}
|
|
188
551
|
|
|
189
|
-
|
|
552
|
+
nodeContainer.addEventListener( 'mousedown', e => {
|
|
553
|
+
|
|
554
|
+
// Only for left click..
|
|
555
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
556
|
+
return;
|
|
557
|
+
|
|
558
|
+
if( e.altKey )
|
|
559
|
+
{
|
|
560
|
+
this._unSelectNode( nodeContainer );
|
|
561
|
+
}
|
|
562
|
+
else
|
|
563
|
+
{
|
|
564
|
+
if( this.selectedNodes.length > 1 && ( !e.ctrlKey && !e.shiftKey ) )
|
|
565
|
+
{
|
|
566
|
+
this.unSelectAll( true );
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
if( !nodeContainer.classList.contains( 'selected' ) )
|
|
570
|
+
{
|
|
571
|
+
this._selectNode( nodeContainer, ( e.ctrlKey || e.shiftKey ) );
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
} );
|
|
575
|
+
|
|
576
|
+
nodeContainer.addEventListener( 'contextmenu', e => {
|
|
190
577
|
|
|
191
|
-
|
|
192
|
-
|
|
578
|
+
e.preventDefault();
|
|
579
|
+
e.stopPropagation();
|
|
580
|
+
e.stopImmediatePropagation();
|
|
581
|
+
|
|
582
|
+
LX.addContextMenu(null, e, m => {
|
|
583
|
+
|
|
584
|
+
m.add( "Copy", () => {
|
|
585
|
+
// TODO
|
|
586
|
+
// ...
|
|
587
|
+
} );
|
|
588
|
+
|
|
589
|
+
m.add( "Paste", () => {
|
|
590
|
+
// TODO
|
|
591
|
+
// ...
|
|
592
|
+
} );
|
|
193
593
|
|
|
194
|
-
|
|
195
|
-
// nodeContainer.style.zIndex = "1";
|
|
594
|
+
m.add( "" );
|
|
196
595
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
596
|
+
m.add( "Delete", () => {
|
|
597
|
+
this._deleteNode( nodeContainer.dataset[ 'id' ] );
|
|
598
|
+
} );
|
|
599
|
+
});
|
|
600
|
+
} );
|
|
601
|
+
|
|
602
|
+
nodeContainer.addEventListener( 'dblclick', e => {
|
|
603
|
+
|
|
604
|
+
// Only for left click..
|
|
605
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
606
|
+
return;
|
|
607
|
+
} );
|
|
200
608
|
|
|
201
609
|
// Title header
|
|
202
610
|
var nodeHeader = document.createElement( 'div' );
|
|
203
611
|
nodeHeader.classList.add( 'lexgraphnodeheader' );
|
|
204
|
-
nodeHeader.innerText = node.
|
|
612
|
+
nodeHeader.innerText = node.title;
|
|
205
613
|
nodeContainer.appendChild( nodeHeader );
|
|
206
614
|
|
|
615
|
+
// Properties
|
|
616
|
+
// if( node.properties.length && node.constructor.category == 'inputs' )
|
|
617
|
+
// {
|
|
618
|
+
// var nodeProperties = document.createElement( 'div' );
|
|
619
|
+
// nodeProperties.classList.add( 'lexgraphnodeproperties' );
|
|
620
|
+
|
|
621
|
+
// for( let p of node.properties )
|
|
622
|
+
// {
|
|
623
|
+
// var panel = new LX.Panel();
|
|
624
|
+
|
|
625
|
+
// p.signal = "@" + LX.UTILS.uidGenerator() + node.title;
|
|
626
|
+
|
|
627
|
+
// switch( p.type )
|
|
628
|
+
// {
|
|
629
|
+
// case 'float':
|
|
630
|
+
// case 'int':
|
|
631
|
+
// panel.addNumber( p.name, p.value, (v) => p.value = v, { signal: p.signal } );
|
|
632
|
+
// break;
|
|
633
|
+
// case 'string':
|
|
634
|
+
// panel.addText( p.name, p.value, (v) => p.value = v, { signal: p.signal } );
|
|
635
|
+
// break;
|
|
636
|
+
// }
|
|
637
|
+
|
|
638
|
+
// // var prop = document.createElement( 'div' );
|
|
639
|
+
// // prop.innerText = p.type;
|
|
640
|
+
// // prop.classList.add( 'lexgraphnodeproperty' );
|
|
641
|
+
// nodeProperties.appendChild( panel.root );
|
|
642
|
+
// }
|
|
643
|
+
|
|
644
|
+
// nodeContainer.appendChild( nodeProperties );
|
|
645
|
+
// }
|
|
646
|
+
|
|
207
647
|
// Inputs and outputs
|
|
208
648
|
var nodeIO = document.createElement( 'div' );
|
|
209
649
|
nodeIO.classList.add( 'lexgraphnodeios' );
|
|
@@ -228,18 +668,24 @@ class GraphEditor {
|
|
|
228
668
|
{
|
|
229
669
|
if( !i.type )
|
|
230
670
|
{
|
|
231
|
-
console.warn( `Missing type for node [${ node.
|
|
671
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
232
672
|
continue;
|
|
233
673
|
}
|
|
234
674
|
|
|
235
675
|
var input = document.createElement( 'div' );
|
|
236
|
-
input.
|
|
676
|
+
input.className = 'lexgraphnodeio ioinput';
|
|
677
|
+
input.dataset[ 'index' ] = nodeInputs.childElementCount;
|
|
237
678
|
|
|
238
679
|
var type = document.createElement( 'span' );
|
|
239
680
|
type.className = 'io__type input ' + i.type;
|
|
240
|
-
type.innerHTML =
|
|
681
|
+
type.innerHTML = '<span>' + i.type[ 0 ].toUpperCase() + '</span>';
|
|
241
682
|
input.appendChild( type );
|
|
242
683
|
|
|
684
|
+
var typeDesc = document.createElement( 'span' );
|
|
685
|
+
typeDesc.className = 'io__typedesc input ' + i.type;
|
|
686
|
+
typeDesc.innerHTML = i.type;
|
|
687
|
+
input.appendChild( typeDesc );
|
|
688
|
+
|
|
243
689
|
if( i.name )
|
|
244
690
|
{
|
|
245
691
|
var name = document.createElement( 'span' );
|
|
@@ -268,11 +714,12 @@ class GraphEditor {
|
|
|
268
714
|
{
|
|
269
715
|
if( !o.type )
|
|
270
716
|
{
|
|
271
|
-
console.warn( `Missing type for node [${ node.
|
|
717
|
+
console.warn( `Missing type for node [${ node.title }], skipping...` );
|
|
272
718
|
}
|
|
273
719
|
|
|
274
720
|
var output = document.createElement( 'div' );
|
|
275
|
-
output.className = 'lexgraphnodeio
|
|
721
|
+
output.className = 'lexgraphnodeio iooutput';
|
|
722
|
+
output.dataset[ 'index' ] = nodeOutputs.childElementCount;
|
|
276
723
|
|
|
277
724
|
if( o.name )
|
|
278
725
|
{
|
|
@@ -284,449 +731,2387 @@ class GraphEditor {
|
|
|
284
731
|
|
|
285
732
|
var type = document.createElement( 'span' );
|
|
286
733
|
type.className = 'io__type output ' + o.type;
|
|
287
|
-
type.innerHTML =
|
|
734
|
+
type.innerHTML = '<span>' + o.type[ 0 ].toUpperCase() + '</span>';
|
|
288
735
|
output.appendChild( type );
|
|
289
736
|
|
|
737
|
+
var typeDesc = document.createElement( 'span' );
|
|
738
|
+
typeDesc.className = 'io__typedesc output ' + o.type;
|
|
739
|
+
typeDesc.innerHTML = o.type;
|
|
740
|
+
output.appendChild( typeDesc );
|
|
741
|
+
|
|
290
742
|
nodeOutputs.appendChild( output );
|
|
291
743
|
}
|
|
292
744
|
}
|
|
293
745
|
|
|
294
|
-
//
|
|
746
|
+
// Move nodes
|
|
295
747
|
|
|
296
|
-
LX.makeDraggable( nodeContainer, {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} } );
|
|
748
|
+
LX.makeDraggable( nodeContainer, {
|
|
749
|
+
onMove: this._onMoveNodes.bind( this ),
|
|
750
|
+
onDragStart: this._onDragNode.bind( this )
|
|
751
|
+
} );
|
|
301
752
|
|
|
302
|
-
|
|
303
|
-
}
|
|
753
|
+
// Manage links
|
|
304
754
|
|
|
305
|
-
|
|
755
|
+
nodeIO.querySelectorAll( '.lexgraphnodeio' ).forEach( el => {
|
|
306
756
|
|
|
307
|
-
|
|
308
|
-
}
|
|
757
|
+
el.addEventListener( 'mousedown', e => {
|
|
309
758
|
|
|
310
|
-
|
|
759
|
+
// Only for left click..
|
|
760
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
761
|
+
return;
|
|
311
762
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
763
|
+
this.lastMouseDown = LX.getTime();
|
|
764
|
+
|
|
765
|
+
this._generatingLink = {
|
|
766
|
+
index: parseInt( el.dataset[ 'index' ] ),
|
|
767
|
+
io: el,
|
|
768
|
+
ioType: el.classList.contains( 'ioinput' ) ? GraphEditor.NODE_IO_INPUT : GraphEditor.NODE_IO_OUTPUT,
|
|
769
|
+
domEl: nodeContainer
|
|
770
|
+
};
|
|
315
771
|
|
|
316
|
-
|
|
772
|
+
e.stopPropagation();
|
|
773
|
+
e.stopImmediatePropagation();
|
|
774
|
+
} );
|
|
317
775
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
this._mousePosition = new LX.vec2( e.clientX - rect.x , e.clientY - rect.y );
|
|
321
|
-
this._deltaMousePosition = this._mousePosition.sub( this._lastMousePosition );
|
|
776
|
+
el.addEventListener( 'mouseup', e => {
|
|
322
777
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
else if( e.type == 'mouseup' )
|
|
329
|
-
{
|
|
330
|
-
if( (LX.getTime() - this.lastMouseDown) < 120 ) {
|
|
331
|
-
this._processClick( e );
|
|
332
|
-
}
|
|
333
|
-
}
|
|
778
|
+
// Single click..
|
|
779
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 120 ) {
|
|
780
|
+
delete this._generatingLink;
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
334
783
|
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
784
|
+
if( this._generatingLink )
|
|
785
|
+
{
|
|
786
|
+
// Check for IO
|
|
787
|
+
if( !this._onLink( e ) )
|
|
788
|
+
{
|
|
789
|
+
// Delete entire SVG if not a successful connection..
|
|
790
|
+
deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
791
|
+
}
|
|
339
792
|
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
switch( e.detail )
|
|
343
|
-
{
|
|
344
|
-
case LX.MOUSE_DOUBLE_CLICK:
|
|
345
|
-
break;
|
|
346
|
-
case LX.MOUSE_TRIPLE_CLICK:
|
|
347
|
-
break;
|
|
348
|
-
}
|
|
349
|
-
}
|
|
793
|
+
delete this._generatingLink;
|
|
794
|
+
}
|
|
350
795
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
}
|
|
796
|
+
e.stopPropagation();
|
|
797
|
+
e.stopImmediatePropagation();
|
|
798
|
+
} );
|
|
355
799
|
|
|
356
|
-
|
|
800
|
+
el.addEventListener( 'click', e => {
|
|
357
801
|
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
if( (LX.getTime() - this.lastMouseDown) < 120 ) {
|
|
361
|
-
this._processContextMenu( e );
|
|
362
|
-
}
|
|
363
|
-
}
|
|
802
|
+
if( !el.links )
|
|
803
|
+
return;
|
|
364
804
|
|
|
365
|
-
|
|
366
|
-
}
|
|
805
|
+
const nodeId = nodeContainer.dataset[ 'id' ];
|
|
367
806
|
|
|
368
|
-
|
|
807
|
+
this._deleteLinks( nodeId, el );
|
|
808
|
+
} );
|
|
809
|
+
|
|
810
|
+
} );
|
|
811
|
+
|
|
812
|
+
const id = node.id ?? node.title.toLowerCase().replaceAll( /\s/g, '-' ) + '-' + LX.UTILS.uidGenerator();
|
|
813
|
+
this.nodes[ id ] = { data: node, dom: nodeContainer };
|
|
814
|
+
|
|
815
|
+
node.id = id;
|
|
816
|
+
nodeContainer.dataset[ 'id' ] = id;
|
|
817
|
+
|
|
818
|
+
this._domNodes.appendChild( nodeContainer );
|
|
369
819
|
|
|
370
|
-
|
|
820
|
+
// Only 1 main per graph!
|
|
821
|
+
if( node.title == 'Main' )
|
|
371
822
|
{
|
|
372
|
-
this.
|
|
373
|
-
return;
|
|
823
|
+
this.main = nodeContainer;
|
|
374
824
|
}
|
|
375
825
|
|
|
376
|
-
|
|
826
|
+
return nodeContainer;
|
|
377
827
|
}
|
|
378
828
|
|
|
379
|
-
|
|
829
|
+
_getAllDOMNodes( includeGroups ) {
|
|
830
|
+
|
|
831
|
+
if( includeGroups )
|
|
832
|
+
return this._domNodes.childNodes;
|
|
380
833
|
|
|
381
|
-
this.
|
|
834
|
+
return Array.from( this._domNodes.childNodes ).filter( v => v.classList.contains( 'lexgraphnode' ) );
|
|
382
835
|
}
|
|
383
836
|
|
|
384
|
-
|
|
837
|
+
_onMoveNodes( target ) {
|
|
385
838
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
839
|
+
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
840
|
+
dT.div( this._scale, dT);
|
|
841
|
+
|
|
842
|
+
for( let nodeId of this.selectedNodes )
|
|
389
843
|
{
|
|
390
|
-
|
|
844
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
391
845
|
|
|
392
|
-
this.
|
|
846
|
+
this._translateNode( el, dT );
|
|
847
|
+
|
|
848
|
+
this._updateNodeLinks( nodeId );
|
|
393
849
|
}
|
|
394
850
|
}
|
|
395
851
|
|
|
396
|
-
|
|
852
|
+
_onDragNode( target, e ) {
|
|
853
|
+
|
|
854
|
+
if( !e.shiftKey )
|
|
855
|
+
return;
|
|
856
|
+
|
|
857
|
+
this._cloneNodes();
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
_onMoveGroup( target ) {
|
|
861
|
+
|
|
862
|
+
// Move nodes inside the group
|
|
863
|
+
|
|
864
|
+
const groupNodeIds = target.nodes;
|
|
865
|
+
|
|
866
|
+
if( !groupNodeIds )
|
|
867
|
+
return;
|
|
868
|
+
|
|
869
|
+
let dT = this._snapToGrid ? this._snappedDeltaMousePosition : this._deltaMousePosition;
|
|
870
|
+
dT.div( this._scale, dT);
|
|
871
|
+
|
|
872
|
+
this._translateNode( target, dT );
|
|
873
|
+
|
|
874
|
+
for( let nodeId of groupNodeIds )
|
|
875
|
+
{
|
|
876
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
877
|
+
|
|
878
|
+
this._translateNode( el, dT );
|
|
879
|
+
|
|
880
|
+
this._updateNodeLinks( nodeId );
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
_onDragGroup( target ) {
|
|
885
|
+
|
|
886
|
+
// Get nodes inside the group to be moved
|
|
887
|
+
|
|
888
|
+
const group_bb = this._getBoundingFromGroup( target );
|
|
889
|
+
|
|
890
|
+
const groupNodeIds = [ ];
|
|
891
|
+
|
|
892
|
+
for( let dom of this._getAllDOMNodes() )
|
|
893
|
+
{
|
|
894
|
+
const x = parseFloat( dom.style.left );
|
|
895
|
+
const y = parseFloat( dom.style.top );
|
|
896
|
+
const node_bb = new BoundingBox( new LX.vec2( x, y ), new LX.vec2( dom.offsetWidth - 6, dom.offsetHeight - 6 ) );
|
|
897
|
+
|
|
898
|
+
if( !group_bb.inside( node_bb ) )
|
|
899
|
+
continue;
|
|
900
|
+
|
|
901
|
+
groupNodeIds.push( dom.dataset[ 'id' ] );
|
|
902
|
+
}
|
|
903
|
+
|
|
904
|
+
target.nodes = groupNodeIds;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
_selectNode( dom, multiSelection, forceOrder = true ) {
|
|
908
|
+
|
|
909
|
+
if( !multiSelection )
|
|
910
|
+
this.unSelectAll( true );
|
|
911
|
+
|
|
912
|
+
dom.classList.add( 'selected' );
|
|
913
|
+
|
|
914
|
+
const id = dom.dataset[ 'id' ];
|
|
915
|
+
|
|
916
|
+
this.selectedNodes.push( id );
|
|
917
|
+
|
|
918
|
+
if( forceOrder )
|
|
919
|
+
{
|
|
920
|
+
// Reorder nodes to draw on top..
|
|
921
|
+
this._domNodes.appendChild( dom );
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
const node = this.nodes[ id ].data;
|
|
925
|
+
|
|
926
|
+
this.propertiesDialog.setTitle( node.title );
|
|
927
|
+
|
|
928
|
+
var panel = this.propertiesDialog.panel;
|
|
929
|
+
panel.clear();
|
|
930
|
+
|
|
931
|
+
// Add description
|
|
932
|
+
if( node.constructor.description )
|
|
933
|
+
{
|
|
934
|
+
panel.addText( null, node.constructor.description, null, { disabled: true } );
|
|
935
|
+
}
|
|
936
|
+
|
|
937
|
+
// Allow change name if input
|
|
938
|
+
if( node.constructor.category == 'inputs' )
|
|
939
|
+
{
|
|
940
|
+
panel.addText( 'Name', node.title, (v) => {
|
|
941
|
+
node.title = v;
|
|
942
|
+
dom.querySelector( '.lexgraphnodeheader' ).innerText = v;
|
|
943
|
+
} );
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
for( let p of node.properties )
|
|
947
|
+
{
|
|
948
|
+
switch( p.type )
|
|
949
|
+
{
|
|
950
|
+
case 'float':
|
|
951
|
+
case 'int':
|
|
952
|
+
panel.addNumber( p.name, p.value, (v) => { p.value = v } );
|
|
953
|
+
break;
|
|
954
|
+
case 'string':
|
|
955
|
+
panel.addText( p.name, p.value, (v) => { p.value = v } );
|
|
956
|
+
break;
|
|
957
|
+
case 'vec2':
|
|
958
|
+
panel.addVector2( p.name, p.value, (v) => { p.value = v } );
|
|
959
|
+
break;
|
|
960
|
+
case 'vec3':
|
|
961
|
+
panel.addVector3( p.name, p.value, (v) => { p.value = v } );
|
|
962
|
+
break;
|
|
963
|
+
case 'vec4':
|
|
964
|
+
panel.addVector4( p.name, p.value, (v) => { p.value = v } );
|
|
965
|
+
break;
|
|
966
|
+
case 'select':
|
|
967
|
+
panel.addDropdown( p.name, p.options, p.value, (v) => { p.value = v } );
|
|
968
|
+
break;
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
this._togglePropertiesDialog( true );
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
_unSelectNode( dom ) {
|
|
976
|
+
|
|
977
|
+
dom.classList.remove( 'selected' );
|
|
978
|
+
|
|
979
|
+
// Delete from selected..
|
|
980
|
+
const idx = this.selectedNodes.indexOf( dom.dataset[ 'id' ] );
|
|
981
|
+
this.selectedNodes.splice( idx, 1 );
|
|
982
|
+
|
|
983
|
+
if( !this.selectedNodes.length )
|
|
984
|
+
this._togglePropertiesDialog( false );
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
_translateNode( dom, deltaTranslation ) {
|
|
988
|
+
|
|
989
|
+
const translation = deltaTranslation.add( new LX.vec2( parseFloat( dom.style.left ), parseFloat( dom.style.top ) ) );
|
|
990
|
+
|
|
991
|
+
if( this._snapToGrid && dom.mustSnap )
|
|
992
|
+
{
|
|
993
|
+
const snapSize = this._patternSize.x * this._snapValue * this._snapValue;
|
|
994
|
+
translation.x = Math.floor( translation.x / snapSize ) * snapSize;
|
|
995
|
+
translation.y = Math.floor( translation.y / snapSize ) * snapSize;
|
|
996
|
+
dom.mustSnap = false;
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
dom.style.left = ( translation.x ) + "px";
|
|
1000
|
+
dom.style.top = ( translation.y ) + "px";
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
_deleteNode( nodeId ) {
|
|
1004
|
+
|
|
1005
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
1006
|
+
|
|
1007
|
+
console.assert( el );
|
|
1008
|
+
|
|
1009
|
+
if( el == this.main )
|
|
1010
|
+
{
|
|
1011
|
+
console.warn( `Can't delete MAIN node!` );
|
|
1012
|
+
return;
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
deleteElement( el );
|
|
1016
|
+
|
|
1017
|
+
delete this.nodes[ nodeId ];
|
|
1018
|
+
|
|
1019
|
+
// Delete connected links..
|
|
1020
|
+
|
|
1021
|
+
for( let key in this.graph.links )
|
|
1022
|
+
{
|
|
1023
|
+
if( !key.includes( nodeId ) )
|
|
1024
|
+
continue;
|
|
1025
|
+
|
|
1026
|
+
const aIdx = key.indexOf( '@' );
|
|
1027
|
+
const targetIsInput = key.substring( aIdx + 1 ) != nodeId;
|
|
1028
|
+
|
|
1029
|
+
// Remove the connection from the other before deleting..
|
|
1030
|
+
|
|
1031
|
+
const numLinks = this.graph.links[ key ].length;
|
|
1032
|
+
|
|
1033
|
+
for( var i = 0; i < numLinks; ++i )
|
|
1034
|
+
{
|
|
1035
|
+
var link = this.graph.links[ key ][ i ];
|
|
1036
|
+
|
|
1037
|
+
deleteElement( link.path.parentElement );
|
|
1038
|
+
|
|
1039
|
+
const targetNodeId = targetIsInput ? link.inputNode : link.outputNode;
|
|
1040
|
+
|
|
1041
|
+
const targetNodeDOM = this._getNodeDOMElement( targetNodeId );
|
|
1042
|
+
const ios = targetNodeDOM.querySelector( targetIsInput ? '.lexgraphnodeinputs' : '.lexgraphnodeoutputs' );
|
|
1043
|
+
const io = ios.childNodes[ targetIsInput ? link.inputIdx : link.outputIdx ];
|
|
1044
|
+
|
|
1045
|
+
const ioIndex = targetIsInput ? link.outputIdx : link.inputIdx;
|
|
1046
|
+
const nodelinkidx = io.links[ ioIndex ].indexOf( nodeId );
|
|
1047
|
+
io.links[ ioIndex ].splice( nodelinkidx, 1 );
|
|
1048
|
+
|
|
1049
|
+
// Unique link, so it's done..
|
|
1050
|
+
if( targetIsInput )
|
|
1051
|
+
{
|
|
1052
|
+
delete io.dataset[ 'active' ];
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
// Check if any link left in case of output
|
|
1056
|
+
else
|
|
1057
|
+
{
|
|
1058
|
+
var active = false;
|
|
1059
|
+
for( var links of io.links )
|
|
1060
|
+
for( var j of links ){
|
|
1061
|
+
console.log(j)
|
|
1062
|
+
active |= ( !!j );
|
|
1063
|
+
}
|
|
1064
|
+
if( !active )
|
|
1065
|
+
delete io.dataset[ 'active' ];
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
delete this.graph.links[ key ];
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
_cloneNodes() {
|
|
1074
|
+
|
|
1075
|
+
// Clone all selected nodes
|
|
1076
|
+
const selectedIds = LX.deepCopy( this.selectedNodes );
|
|
1077
|
+
|
|
1078
|
+
this.unSelectAll();
|
|
1079
|
+
|
|
1080
|
+
for( let nodeId of selectedIds )
|
|
1081
|
+
{
|
|
1082
|
+
const nodeInfo = this.nodes[ nodeId ];
|
|
1083
|
+
if( !nodeInfo )
|
|
1084
|
+
return;
|
|
1085
|
+
|
|
1086
|
+
const el = this._getNodeDOMElement( nodeId );
|
|
1087
|
+
const data = nodeInfo.data;
|
|
1088
|
+
const newNode = GraphEditor.addNode( data.type );
|
|
1089
|
+
const newDom = this._createNodeDOM( newNode );
|
|
1090
|
+
|
|
1091
|
+
this._translateNode( newDom, this._getNodePosition( el ) );
|
|
1092
|
+
|
|
1093
|
+
this._selectNode( newDom, true );
|
|
1094
|
+
|
|
1095
|
+
this.graph.nodes.push( newNode );
|
|
1096
|
+
}
|
|
1097
|
+
}
|
|
1098
|
+
|
|
1099
|
+
// This is in pattern space!
|
|
1100
|
+
_getNodePosition( dom ) {
|
|
1101
|
+
|
|
1102
|
+
return new LX.vec2( parseFloat( dom.style.left ), parseFloat( dom.style.top ) );
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
_getNodeDOMElement( nodeId ) {
|
|
1106
|
+
|
|
1107
|
+
return this.nodes[ nodeId ] ? this.nodes[ nodeId ].dom : null;
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
_getLinks( nodeSrcId, nodeDstId ) {
|
|
1111
|
+
|
|
1112
|
+
const str = nodeSrcId + '@' + nodeDstId;
|
|
1113
|
+
return this.graph.links[ str ];
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
_deleteLinks( nodeId, io ) {
|
|
1117
|
+
|
|
1118
|
+
const isInput = io.classList.contains( 'ioinput' );
|
|
1119
|
+
const srcIndex = parseInt( io.dataset[ 'index' ] );
|
|
1120
|
+
|
|
1121
|
+
if( isInput ) // Only one "link to output" to delete
|
|
1122
|
+
{
|
|
1123
|
+
let targetIndex;
|
|
1124
|
+
|
|
1125
|
+
const targets = io.links.filter( (v, i) => { targetIndex = i; return v !== undefined; } )[ 0 ];
|
|
1126
|
+
const targetId = targets[ 0 ];
|
|
1127
|
+
|
|
1128
|
+
// It has been deleted..
|
|
1129
|
+
if( !targetId )
|
|
1130
|
+
return;
|
|
1131
|
+
|
|
1132
|
+
var links = this._getLinks( targetId, nodeId );
|
|
1133
|
+
|
|
1134
|
+
var linkIdx = links.findIndex( i => ( i.inputIdx == srcIndex && i.outputIdx == targetIndex ) );
|
|
1135
|
+
deleteElement( links[ linkIdx ].path.parentElement );
|
|
1136
|
+
links.splice( linkIdx, 1 );
|
|
1137
|
+
|
|
1138
|
+
// Input has no longer any connected link
|
|
1139
|
+
|
|
1140
|
+
delete io.links;
|
|
1141
|
+
delete io.dataset[ 'active' ];
|
|
1142
|
+
|
|
1143
|
+
// Remove a connection from the target connections
|
|
1144
|
+
|
|
1145
|
+
const targetDOM = this._getNodeDOMElement( targetId );
|
|
1146
|
+
const ios = targetDOM.querySelector( '.lexgraphnodeoutputs' );
|
|
1147
|
+
const targetIO = ios.childNodes[ targetIndex ];
|
|
1148
|
+
|
|
1149
|
+
const idx = targetIO.links[ srcIndex ].findIndex( v => v == nodeId );
|
|
1150
|
+
targetIO.links[ srcIndex ].splice( idx, 1 );
|
|
1151
|
+
|
|
1152
|
+
let active = false;
|
|
1153
|
+
|
|
1154
|
+
for( var ls of targetIO.links )
|
|
1155
|
+
{
|
|
1156
|
+
if( !ls ) continue;
|
|
1157
|
+
// Check links left per io
|
|
1158
|
+
active |= ls.reduce( c => c !== undefined, 0 );
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
if( !active )
|
|
1162
|
+
{
|
|
1163
|
+
delete targetIO.links;
|
|
1164
|
+
delete targetIO.dataset[ 'active' ];
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
else // Delete ALL "to input links"
|
|
1168
|
+
{
|
|
1169
|
+
|
|
1170
|
+
const numLinks = io.links.length;
|
|
1171
|
+
|
|
1172
|
+
for( let targetIndex = 0; targetIndex < numLinks; ++targetIndex )
|
|
1173
|
+
{
|
|
1174
|
+
const targets = io.links[ targetIndex ];
|
|
1175
|
+
|
|
1176
|
+
if( !targets )
|
|
1177
|
+
continue;
|
|
1178
|
+
|
|
1179
|
+
for( let it = ( targets.length - 1 ); it >= 0 ; --it )
|
|
1180
|
+
{
|
|
1181
|
+
const targetId = targets[ it ];
|
|
1182
|
+
var links = this._getLinks( nodeId, targetId );
|
|
1183
|
+
|
|
1184
|
+
var linkIdx = links.findIndex( i => ( i.inputIdx == targetIndex && i.outputIdx == srcIndex ) );
|
|
1185
|
+
deleteElement( links[ linkIdx ].path.parentElement );
|
|
1186
|
+
links.splice( linkIdx, 1 );
|
|
1187
|
+
|
|
1188
|
+
// Remove a connection from the output connections
|
|
1189
|
+
|
|
1190
|
+
io.links[ targetIndex ].splice( it, 1 );
|
|
1191
|
+
|
|
1192
|
+
// Input has no longer any connected link
|
|
1193
|
+
|
|
1194
|
+
const targetDOM = this._getNodeDOMElement( targetId );
|
|
1195
|
+
const ios = targetDOM.querySelector( '.lexgraphnodeinputs' );
|
|
1196
|
+
const targetIO = ios.childNodes[ targetIndex ];
|
|
1197
|
+
|
|
1198
|
+
delete targetIO.links;
|
|
1199
|
+
delete targetIO.dataset[ 'active' ];
|
|
1200
|
+
}
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
delete io.links;
|
|
1204
|
+
delete io.dataset[ 'active' ];
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
_processFocus( active ) {
|
|
1209
|
+
|
|
1210
|
+
this.isFocused = active;
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
_processKeyDown( e ) {
|
|
1214
|
+
|
|
1215
|
+
// Prevent processing keys on inputs and others
|
|
1216
|
+
if( document.activeElement != this.root )
|
|
1217
|
+
return;
|
|
1218
|
+
|
|
1219
|
+
var key = e.key ?? e.detail.key;
|
|
1220
|
+
|
|
1221
|
+
switch( key ) {
|
|
1222
|
+
case 'Escape':
|
|
1223
|
+
this.unSelectAll();
|
|
1224
|
+
break;
|
|
1225
|
+
case 'Delete':
|
|
1226
|
+
case 'Backspace':
|
|
1227
|
+
e.preventDefault();
|
|
1228
|
+
this._deleteSelection( e );
|
|
1229
|
+
break;
|
|
1230
|
+
case 'g':
|
|
1231
|
+
if( e.ctrlKey )
|
|
1232
|
+
{
|
|
1233
|
+
e.preventDefault();
|
|
1234
|
+
this._createGroup();
|
|
1235
|
+
}
|
|
1236
|
+
break;
|
|
1237
|
+
case 'y':
|
|
1238
|
+
if( e.ctrlKey )
|
|
1239
|
+
{
|
|
1240
|
+
e.preventDefault();
|
|
1241
|
+
this._doRedo();
|
|
1242
|
+
}
|
|
1243
|
+
break;
|
|
1244
|
+
case 'z':
|
|
1245
|
+
if( e.ctrlKey )
|
|
1246
|
+
{
|
|
1247
|
+
e.preventDefault();
|
|
1248
|
+
this._doUndo();
|
|
1249
|
+
}
|
|
1250
|
+
break;
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
this.keys[ key ] = true;
|
|
1254
|
+
}
|
|
1255
|
+
|
|
1256
|
+
_processKeyUp( e ) {
|
|
1257
|
+
|
|
1258
|
+
// Prevent processing keys on inputs and others
|
|
1259
|
+
if( document.activeElement != this.root )
|
|
1260
|
+
return;
|
|
1261
|
+
|
|
1262
|
+
var key = e.key ?? e.detail.key;
|
|
1263
|
+
|
|
1264
|
+
delete this.keys[ key ];
|
|
1265
|
+
}
|
|
1266
|
+
|
|
1267
|
+
_processMouse( e ) {
|
|
1268
|
+
|
|
1269
|
+
const rect = this.root.getBoundingClientRect();
|
|
1270
|
+
|
|
1271
|
+
this._mousePosition = new LX.vec2( e.clientX - rect.x , e.clientY - rect.y );
|
|
1272
|
+
|
|
1273
|
+
const snapPosition = new LX.vec2( this._mousePosition.x, this._mousePosition.y );
|
|
1274
|
+
|
|
1275
|
+
if( this._snapToGrid )
|
|
1276
|
+
{
|
|
1277
|
+
const snapSize = this._patternSize.x * this._snapValue * this._scale;
|
|
1278
|
+
snapPosition.x = Math.floor( snapPosition.x / snapSize ) * snapSize;
|
|
1279
|
+
snapPosition.y = Math.floor( snapPosition.y / snapSize ) * snapSize;
|
|
1280
|
+
this._snappedDeltaMousePosition = snapPosition.sub( this._lastSnappedMousePosition );
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
this._deltaMousePosition = this._mousePosition.sub( this._lastMousePosition );
|
|
1284
|
+
|
|
1285
|
+
if( e.type == 'mousedown' )
|
|
1286
|
+
{
|
|
1287
|
+
this.lastMouseDown = LX.getTime();
|
|
1288
|
+
|
|
1289
|
+
this._processMouseDown( e );
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
else if( e.type == 'mouseup' )
|
|
1293
|
+
{
|
|
1294
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 120 ) {
|
|
1295
|
+
this._processClick( e );
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
this._processMouseUp( e );
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
else if( e.type == 'mousemove' )
|
|
1302
|
+
{
|
|
1303
|
+
this._processMouseMove( e );
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
else if ( e.type == 'click' ) // trick
|
|
1307
|
+
{
|
|
1308
|
+
switch( e.detail )
|
|
1309
|
+
{
|
|
1310
|
+
case LX.MOUSE_DOUBLE_CLICK:
|
|
1311
|
+
break;
|
|
1312
|
+
case LX.MOUSE_TRIPLE_CLICK:
|
|
1313
|
+
break;
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
else if ( e.type == 'mousewheel' ) {
|
|
1318
|
+
e.preventDefault();
|
|
1319
|
+
this._processWheel( e );
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
else if ( e.type == 'contextmenu' ) {
|
|
1323
|
+
|
|
1324
|
+
e.preventDefault();
|
|
1325
|
+
|
|
1326
|
+
if( ( LX.getTime() - this.lastMouseDown ) < 120 ) {
|
|
1327
|
+
this._processContextMenu( e );
|
|
1328
|
+
}
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
else if ( e.type == 'mouseleave' ) {
|
|
1332
|
+
|
|
1333
|
+
if( this._generatingLink )
|
|
1334
|
+
{
|
|
1335
|
+
this._processMouseUp( e );
|
|
1336
|
+
}
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
if( this._snapToGrid )
|
|
1340
|
+
{
|
|
1341
|
+
this._lastSnappedMousePosition = snapPosition;
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
this._lastMousePosition = this._mousePosition;
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
_processClick( e ) {
|
|
1348
|
+
|
|
1349
|
+
if( e.target.classList.contains( 'lexgraphnodes' ) || e.target.classList.contains( 'lexgraphgroup' ) )
|
|
1350
|
+
{
|
|
1351
|
+
this._processBackgroundClick( e );
|
|
1352
|
+
return;
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
|
|
1356
|
+
_processBackgroundClick( e ) {
|
|
1357
|
+
|
|
1358
|
+
this.unSelectAll();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
_processMouseDown( e ) {
|
|
1362
|
+
|
|
1363
|
+
// Don't box select over a node..
|
|
1364
|
+
if( !e.target.classList.contains( 'lexgraphnode' ) && !e.target.classList.contains( 'lexgraphgroup' )
|
|
1365
|
+
&& e.button == LX.MOUSE_LEFT_CLICK )
|
|
1366
|
+
{
|
|
1367
|
+
this._boxSelecting = this._mousePosition;
|
|
1368
|
+
this._boxSelectRemoving = e.altKey;
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
_processMouseUp( e ) {
|
|
1373
|
+
|
|
1374
|
+
// It the event reaches this, the link isn't valid..
|
|
1375
|
+
if( this._generatingLink )
|
|
1376
|
+
{
|
|
1377
|
+
deleteElement( this._generatingLink.path ? this._generatingLink.path.parentElement : null );
|
|
1378
|
+
delete this._generatingLink;
|
|
1379
|
+
}
|
|
1380
|
+
|
|
1381
|
+
else if( this._boxSelecting )
|
|
1382
|
+
{
|
|
1383
|
+
if( !e.ctrlKey && !e.altKey )
|
|
1384
|
+
this.unSelectAll();
|
|
1385
|
+
|
|
1386
|
+
this._selectNodesInBox( this._boxSelecting, this._mousePosition, e.altKey );
|
|
1387
|
+
|
|
1388
|
+
deleteElement( this._currentBoxSelectionSVG );
|
|
1389
|
+
|
|
1390
|
+
delete this._currentBoxSelectionSVG;
|
|
1391
|
+
delete this._boxSelecting;
|
|
1392
|
+
delete this._boxSelectRemoving;
|
|
1393
|
+
}
|
|
1394
|
+
}
|
|
1395
|
+
|
|
1396
|
+
_processMouseMove( e ) {
|
|
1397
|
+
|
|
1398
|
+
const rightPressed = ( e.which == 3 );
|
|
1399
|
+
|
|
1400
|
+
if( rightPressed )
|
|
1401
|
+
{
|
|
1402
|
+
this._patternPosition.add( this._deltaMousePosition.div( this._scale ), this._patternPosition );
|
|
1403
|
+
|
|
1404
|
+
this._updatePattern();
|
|
1405
|
+
|
|
1406
|
+
return;
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
else if( this._generatingLink )
|
|
1410
|
+
{
|
|
1411
|
+
this._updatePreviewLink( e );
|
|
1412
|
+
|
|
1413
|
+
return;
|
|
1414
|
+
}
|
|
1415
|
+
|
|
1416
|
+
else if( this._boxSelecting )
|
|
1417
|
+
{
|
|
1418
|
+
this._drawBoxSelection( e );
|
|
1419
|
+
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
_processWheel( e ) {
|
|
1425
|
+
|
|
1426
|
+
if( this._boxSelecting )
|
|
1427
|
+
return;
|
|
397
1428
|
|
|
398
1429
|
// Compute zoom center in pattern space using current scale
|
|
399
1430
|
|
|
400
|
-
const rect = this.root.getBoundingClientRect();
|
|
401
|
-
const zoomCenter = this._mousePosition ?? new LX.vec2( rect.width * 0.5, rect.height * 0.5 );
|
|
1431
|
+
const rect = this.root.getBoundingClientRect();
|
|
1432
|
+
const zoomCenter = this._mousePosition ?? new LX.vec2( rect.width * 0.5, rect.height * 0.5 );
|
|
1433
|
+
|
|
1434
|
+
const center = this._getPatternPosition( zoomCenter );
|
|
1435
|
+
|
|
1436
|
+
const delta = e.deltaY;
|
|
1437
|
+
|
|
1438
|
+
if( delta > 0.0 ) this._scale *= 0.9;
|
|
1439
|
+
else this._scale *= ( 1.0 / 0.9 );
|
|
1440
|
+
|
|
1441
|
+
this._scale = LX.UTILS.clamp( this._scale, GraphEditor.MIN_SCALE, GraphEditor.MAX_SCALE );
|
|
1442
|
+
|
|
1443
|
+
// Compute zoom center in pattern space using new scale
|
|
1444
|
+
// and get delta..
|
|
1445
|
+
|
|
1446
|
+
const newCenter = this._getPatternPosition( zoomCenter );
|
|
1447
|
+
|
|
1448
|
+
const deltaCenter = newCenter.sub( center );
|
|
1449
|
+
|
|
1450
|
+
this._patternPosition = this._patternPosition.add( deltaCenter );
|
|
1451
|
+
|
|
1452
|
+
this._updatePattern( GraphEditor.EVENT_MOUSEWHEEL );
|
|
1453
|
+
}
|
|
1454
|
+
|
|
1455
|
+
_processContextMenu( e ) {
|
|
1456
|
+
|
|
1457
|
+
LX.addContextMenu( "ADD NODE", e, m => {
|
|
1458
|
+
|
|
1459
|
+
for( let type in GraphEditor.NODE_TYPES )
|
|
1460
|
+
{
|
|
1461
|
+
m.add( type, () => {
|
|
1462
|
+
|
|
1463
|
+
const newNode = GraphEditor.addNode( type );
|
|
1464
|
+
|
|
1465
|
+
const dom = this._createNodeDOM( newNode );
|
|
1466
|
+
|
|
1467
|
+
if( this._snapToGrid )
|
|
1468
|
+
{
|
|
1469
|
+
dom.mustSnap = true;
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
if( e )
|
|
1473
|
+
{
|
|
1474
|
+
const rect = this.root.getBoundingClientRect();
|
|
1475
|
+
|
|
1476
|
+
let position = new LX.vec2( e.clientX - rect.x, e.clientY - rect.y );
|
|
1477
|
+
|
|
1478
|
+
position = this._getPatternPosition( position );
|
|
1479
|
+
|
|
1480
|
+
this._translateNode( dom, position );
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
this.graph.nodes.push( newNode );
|
|
1484
|
+
|
|
1485
|
+
} );
|
|
1486
|
+
}
|
|
1487
|
+
});
|
|
1488
|
+
}
|
|
1489
|
+
|
|
1490
|
+
/**
|
|
1491
|
+
* @method start
|
|
1492
|
+
*/
|
|
1493
|
+
|
|
1494
|
+
start() {
|
|
1495
|
+
|
|
1496
|
+
this.mustStop = false;
|
|
1497
|
+
|
|
1498
|
+
this.propagateEventToAllNodes( 'onStart' );
|
|
1499
|
+
|
|
1500
|
+
requestAnimationFrame( this._frame.bind(this) );
|
|
1501
|
+
}
|
|
1502
|
+
|
|
1503
|
+
/**
|
|
1504
|
+
* @method stop
|
|
1505
|
+
*/
|
|
1506
|
+
|
|
1507
|
+
stop() {
|
|
1508
|
+
|
|
1509
|
+
this.mustStop = true;
|
|
1510
|
+
this.state = GraphEditor.STOPPED;
|
|
1511
|
+
|
|
1512
|
+
this.propagateEventToAllNodes( 'onStop' );
|
|
1513
|
+
}
|
|
1514
|
+
|
|
1515
|
+
/**
|
|
1516
|
+
* @method _frame
|
|
1517
|
+
*/
|
|
1518
|
+
|
|
1519
|
+
_frame() {
|
|
1520
|
+
|
|
1521
|
+
if( this.mustStop )
|
|
1522
|
+
{
|
|
1523
|
+
return;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
requestAnimationFrame( this._frame.bind(this) );
|
|
1527
|
+
|
|
1528
|
+
this._runStep();
|
|
1529
|
+
}
|
|
1530
|
+
|
|
1531
|
+
/**
|
|
1532
|
+
* @method _runStep
|
|
1533
|
+
*/
|
|
1534
|
+
|
|
1535
|
+
_runStep() {
|
|
1536
|
+
|
|
1537
|
+
const main = this.main;
|
|
1538
|
+
|
|
1539
|
+
if( !main )
|
|
1540
|
+
return;
|
|
1541
|
+
|
|
1542
|
+
const visitedNodes = { };
|
|
1543
|
+
|
|
1544
|
+
this._executionNodes = [ ];
|
|
1545
|
+
|
|
1546
|
+
// Reser variables each step?
|
|
1547
|
+
this.variables = { };
|
|
1548
|
+
|
|
1549
|
+
const mainId = this.main.dataset[ 'id' ];
|
|
1550
|
+
|
|
1551
|
+
const addNode = ( id ) => {
|
|
1552
|
+
|
|
1553
|
+
if( visitedNodes[ id ] )
|
|
1554
|
+
return;
|
|
1555
|
+
|
|
1556
|
+
visitedNodes[ id ] = true;
|
|
1557
|
+
|
|
1558
|
+
for( let linkId in this.graph.links )
|
|
1559
|
+
{
|
|
1560
|
+
const idx = linkId.indexOf( '@' + id );
|
|
1561
|
+
|
|
1562
|
+
if( idx < 0 )
|
|
1563
|
+
continue;
|
|
1564
|
+
|
|
1565
|
+
const preNodeId = linkId.substring( 0, idx );
|
|
1566
|
+
|
|
1567
|
+
this._executionNodes.push( preNodeId );
|
|
1568
|
+
|
|
1569
|
+
addNode( preNodeId );
|
|
1570
|
+
}
|
|
1571
|
+
};
|
|
1572
|
+
|
|
1573
|
+
// TODO: Search "no output" nodes and add to the executable list (same as main)..
|
|
1574
|
+
// ...
|
|
1575
|
+
|
|
1576
|
+
this._executionNodes.push( mainId );
|
|
1577
|
+
|
|
1578
|
+
addNode( mainId );
|
|
1579
|
+
|
|
1580
|
+
for( var i = this._executionNodes.length - 1; i >= 0; --i )
|
|
1581
|
+
{
|
|
1582
|
+
const node = this.nodes[ this._executionNodes[ i ] ];
|
|
1583
|
+
|
|
1584
|
+
if( node.data.onBeforeStep )
|
|
1585
|
+
node.data.onBeforeStep();
|
|
1586
|
+
|
|
1587
|
+
node.data.execute();
|
|
1588
|
+
|
|
1589
|
+
if( node.data.onBeforeStep )
|
|
1590
|
+
node.data.onAfterStep();
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
|
|
1594
|
+
_generatePattern() {
|
|
1595
|
+
|
|
1596
|
+
// Generate pattern
|
|
1597
|
+
{
|
|
1598
|
+
var pattern = document.createElementNS( 'http://www.w3.org/2000/svg', 'pattern' );
|
|
1599
|
+
pattern.setAttribute( 'id', 'pattern-0' );
|
|
1600
|
+
pattern.setAttribute( 'x', this._patternPosition.x );
|
|
1601
|
+
pattern.setAttribute( 'y', this._patternPosition.y );
|
|
1602
|
+
pattern.setAttribute( 'width', this._patternSize.x )
|
|
1603
|
+
pattern.setAttribute( 'height', this._patternSize.y );
|
|
1604
|
+
pattern.setAttribute( 'patternUnits', 'userSpaceOnUse' );
|
|
1605
|
+
|
|
1606
|
+
var circle = document.createElementNS( 'http://www.w3.org/2000/svg', 'circle' );
|
|
1607
|
+
circle.setAttribute( 'cx', this._circlePatternSize );
|
|
1608
|
+
circle.setAttribute( 'cy', this._circlePatternSize );
|
|
1609
|
+
circle.setAttribute( 'r', this._circlePatternSize );
|
|
1610
|
+
circle.setAttribute( 'fill', this._circlePatternColor );
|
|
1611
|
+
|
|
1612
|
+
pattern.appendChild( circle );
|
|
1613
|
+
}
|
|
1614
|
+
|
|
1615
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
1616
|
+
svg.classList.add( "background-svg" );
|
|
1617
|
+
svg.style.width = "100%";
|
|
1618
|
+
svg.style.height = "100%";
|
|
1619
|
+
|
|
1620
|
+
svg.appendChild( pattern );
|
|
1621
|
+
|
|
1622
|
+
var rect = document.createElementNS( 'http://www.w3.org/2000/svg', 'rect' );
|
|
1623
|
+
rect.setAttribute( 'x', '0' );
|
|
1624
|
+
rect.setAttribute( 'y', '0' );
|
|
1625
|
+
rect.setAttribute( 'width', '100%' );
|
|
1626
|
+
rect.setAttribute( 'height', '100%' );
|
|
1627
|
+
rect.setAttribute( 'fill', 'url(#pattern-0)' );
|
|
1628
|
+
|
|
1629
|
+
svg.appendChild( rect );
|
|
1630
|
+
|
|
1631
|
+
this._background = svg;
|
|
1632
|
+
|
|
1633
|
+
this.root.appendChild( this._background );
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
_updatePattern() {
|
|
1637
|
+
|
|
1638
|
+
if( !this._background )
|
|
1639
|
+
return;
|
|
1640
|
+
|
|
1641
|
+
const patternSize = this._patternSize.mul( this._scale );
|
|
1642
|
+
const circlePatternSize = this._circlePatternSize * this._scale;
|
|
1643
|
+
const patternPosition = this._patternPosition.mul( this._scale );
|
|
1644
|
+
|
|
1645
|
+
let pattern = this._background.querySelector( 'pattern' );
|
|
1646
|
+
pattern.setAttribute( 'x', patternPosition.x );
|
|
1647
|
+
pattern.setAttribute( 'y', patternPosition.y );
|
|
1648
|
+
pattern.setAttribute( 'width', patternSize.x )
|
|
1649
|
+
pattern.setAttribute( 'height', patternSize.y );
|
|
1650
|
+
|
|
1651
|
+
var circle = this._background.querySelector( 'circle' );
|
|
1652
|
+
circle.setAttribute( 'cx', circlePatternSize );
|
|
1653
|
+
circle.setAttribute( 'cy', circlePatternSize );
|
|
1654
|
+
circle.setAttribute( 'r', circlePatternSize );
|
|
1655
|
+
|
|
1656
|
+
// Nodes
|
|
1657
|
+
|
|
1658
|
+
const w = this._domNodes.offsetWidth * 0.5;
|
|
1659
|
+
const h = this._domNodes.offsetHeight * 0.5;
|
|
1660
|
+
|
|
1661
|
+
const dw = w - w * this._scale;
|
|
1662
|
+
const dh = h - h * this._scale;
|
|
1663
|
+
|
|
1664
|
+
this._domNodes.style.transform = `
|
|
1665
|
+
translate(` + ( patternPosition.x - dw ) + `px, ` + ( patternPosition.y - dh ) + `px)
|
|
1666
|
+
scale(` + this._scale + `)
|
|
1667
|
+
`;
|
|
1668
|
+
this._domLinks.style.transform = this._domNodes.style.transform;
|
|
1669
|
+
}
|
|
1670
|
+
|
|
1671
|
+
_getPatternPosition( renderPosition ) {
|
|
1672
|
+
|
|
1673
|
+
return renderPosition.div( this._scale ).sub( this._patternPosition );
|
|
1674
|
+
}
|
|
1675
|
+
|
|
1676
|
+
_getRenderPosition( patternPosition ) {
|
|
1677
|
+
|
|
1678
|
+
return patternPosition.add( this._patternPosition ).mul( this._scale );
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1681
|
+
_onLink( e ) {
|
|
1682
|
+
|
|
1683
|
+
const linkData = this._generatingLink;
|
|
1684
|
+
const ioType = e.target.classList.contains( 'input' ) ? GraphEditor.NODE_IO_INPUT : GraphEditor.NODE_IO_OUTPUT;
|
|
1685
|
+
|
|
1686
|
+
// Discard same IO type
|
|
1687
|
+
if( linkData.ioType == ioType )
|
|
1688
|
+
{
|
|
1689
|
+
console.warn( `Can't link same type of data` );
|
|
1690
|
+
return;
|
|
1691
|
+
}
|
|
1692
|
+
|
|
1693
|
+
// Info about src node
|
|
1694
|
+
const src_nodeContainer = linkData.domEl;
|
|
1695
|
+
const src_nodeId = src_nodeContainer.dataset[ 'id' ];
|
|
1696
|
+
const src_node = this.nodes[ src_nodeId ].data;
|
|
1697
|
+
const src_ioIndex = this._generatingLink.index
|
|
1698
|
+
|
|
1699
|
+
// Info about dst node
|
|
1700
|
+
const dst_nodeContainer = e.target.offsetParent;
|
|
1701
|
+
const dst_nodeId = dst_nodeContainer.dataset[ 'id' ];
|
|
1702
|
+
const dst_node = this.nodes[ dst_nodeId ].data;
|
|
1703
|
+
const dst_ioIndex = parseInt( e.target.parentElement.dataset[ 'index' ] );
|
|
1704
|
+
|
|
1705
|
+
// Discard different types
|
|
1706
|
+
const srcIsInput = ( linkData.ioType == GraphEditor.NODE_IO_INPUT );
|
|
1707
|
+
const src_ios = src_node[ srcIsInput ? 'inputs' : 'outputs' ];
|
|
1708
|
+
const src_ioType = src_ios[ src_ioIndex ].type;
|
|
1709
|
+
|
|
1710
|
+
const dst_ios = dst_node[ ioType == GraphEditor.NODE_IO_INPUT ? 'inputs' : 'outputs' ];
|
|
1711
|
+
const dst_ioType = dst_ios[ dst_ioIndex ].type;
|
|
1712
|
+
|
|
1713
|
+
if( src_ioType != dst_ioType && src_ioType != "any" && dst_ioType != "any" )
|
|
1714
|
+
{
|
|
1715
|
+
// Different types, but it might be possible to cast types
|
|
1716
|
+
|
|
1717
|
+
const inputType = srcIsInput ? src_ioType : dst_ioType;
|
|
1718
|
+
const outputType = srcIsInput ? dst_ioType : src_ioType;
|
|
1719
|
+
|
|
1720
|
+
if( !this.supportedCastTypes[ outputType + '@' + inputType ] )
|
|
1721
|
+
{
|
|
1722
|
+
console.warn( `Can't link ${ src_ioType } to ${ dst_ioType }.` );
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// Check if target it's an active input and remove the old link
|
|
1728
|
+
|
|
1729
|
+
if( ioType == GraphEditor.NODE_IO_INPUT && e.target.parentElement.dataset[ 'active' ] )
|
|
1730
|
+
{
|
|
1731
|
+
this._deleteLinks( dst_nodeId, e.target.parentElement );
|
|
1732
|
+
}
|
|
1733
|
+
|
|
1734
|
+
// Check if source it's an active input and remove the old link
|
|
1735
|
+
|
|
1736
|
+
else if( linkData.ioType == GraphEditor.NODE_IO_INPUT && linkData.io.dataset[ 'active' ] )
|
|
1737
|
+
{
|
|
1738
|
+
this._deleteLinks( src_nodeId, linkData.io );
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
// Mark as active
|
|
1742
|
+
|
|
1743
|
+
linkData.io.dataset[ 'active' ] = true;
|
|
1744
|
+
e.target.parentElement.dataset[ 'active' ] = true;
|
|
1745
|
+
|
|
1746
|
+
// Store the end io..
|
|
1747
|
+
|
|
1748
|
+
var srcDom = linkData.io;
|
|
1749
|
+
srcDom.links = srcDom.links ?? [ ];
|
|
1750
|
+
srcDom.links[ dst_ioIndex ] = srcDom.links[ dst_ioIndex ] ?? [ ];
|
|
1751
|
+
srcDom.links[ dst_ioIndex ].push( dst_nodeId );
|
|
1752
|
+
|
|
1753
|
+
var dstDom = e.target.parentElement;
|
|
1754
|
+
dstDom.links = dstDom.links ?? [ ];
|
|
1755
|
+
dstDom.links[ src_ioIndex ] = dstDom.links[ src_ioIndex ] ?? [ ];
|
|
1756
|
+
dstDom.links[ src_ioIndex ].push( src_nodeId );
|
|
1757
|
+
|
|
1758
|
+
// Call this using the io target to set the connection to the center of the input DOM element..
|
|
1759
|
+
|
|
1760
|
+
let path = this._updatePreviewLink( null, e.target.parentElement );
|
|
1761
|
+
|
|
1762
|
+
// Store link
|
|
1763
|
+
|
|
1764
|
+
const pathId = ( srcIsInput ? dst_nodeId : src_nodeId ) + '@' + ( srcIsInput ? src_nodeId : dst_nodeId );
|
|
1765
|
+
|
|
1766
|
+
if( !this.graph.links[ pathId ] ) this.graph.links[ pathId ] = [];
|
|
1767
|
+
|
|
1768
|
+
this.graph.links[ pathId ].push( {
|
|
1769
|
+
path: path,
|
|
1770
|
+
inputNode: srcIsInput ? src_nodeId : dst_nodeId,
|
|
1771
|
+
inputIdx: srcIsInput ? src_ioIndex : dst_ioIndex,
|
|
1772
|
+
inputType: srcIsInput ? src_ioType : dst_ioType,
|
|
1773
|
+
outputNode: srcIsInput ? dst_nodeId : src_nodeId,
|
|
1774
|
+
outputIdx: srcIsInput ? dst_ioIndex : src_ioIndex,
|
|
1775
|
+
outputType: srcIsInput ? dst_ioType : src_ioType,
|
|
1776
|
+
} );
|
|
1777
|
+
|
|
1778
|
+
path.dataset[ 'id' ] = pathId;
|
|
1779
|
+
|
|
1780
|
+
// Successful link..
|
|
1781
|
+
return true;
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1784
|
+
_updatePreviewLink( e, endIO ) {
|
|
1785
|
+
|
|
1786
|
+
var path = this._generatingLink.path;
|
|
1787
|
+
|
|
1788
|
+
if( !path )
|
|
1789
|
+
{
|
|
1790
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
1791
|
+
svg.classList.add( "link-svg" );
|
|
1792
|
+
svg.style.width = "100%";
|
|
1793
|
+
svg.style.height = "100%";
|
|
1794
|
+
this._domLinks.appendChild( svg );
|
|
1795
|
+
|
|
1796
|
+
path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
1797
|
+
path.setAttribute( 'fill', 'none' );
|
|
1798
|
+
svg.appendChild( path );
|
|
1799
|
+
this._generatingLink.path = path;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// Generate bezier curve
|
|
1803
|
+
|
|
1804
|
+
const index = this._generatingLink.index;
|
|
1805
|
+
const type = this._generatingLink.ioType;
|
|
1806
|
+
const domEl = this._generatingLink.domEl;
|
|
1807
|
+
|
|
1808
|
+
const offsetX = this.root.getBoundingClientRect().x;
|
|
1809
|
+
const offsetY = this.root.getBoundingClientRect().y;
|
|
1810
|
+
|
|
1811
|
+
const ios = domEl.querySelector( type == GraphEditor.NODE_IO_INPUT ? '.lexgraphnodeinputs' : '.lexgraphnodeoutputs' );
|
|
1812
|
+
const ioEl = ios.childNodes[ index ].querySelector( '.io__type' );
|
|
1813
|
+
const startRect = ioEl.getBoundingClientRect();
|
|
1814
|
+
|
|
1815
|
+
let startPos = new LX.vec2( startRect.x - offsetX, startRect.y - offsetY );
|
|
1816
|
+
let endPos = null;
|
|
1817
|
+
|
|
1818
|
+
if( e )
|
|
1819
|
+
{
|
|
1820
|
+
endPos = new LX.vec2( e.offsetX, e.offsetY );
|
|
1821
|
+
|
|
1822
|
+
// Add node position, since I can't get the correct position directly from the event..
|
|
1823
|
+
if( e.target.hasClass( [ 'lexgraphnode', 'lexgraphgroup' ] ) )
|
|
1824
|
+
{
|
|
1825
|
+
console.log( this._getNodePosition( e.target ) );
|
|
1826
|
+
endPos.add( this._getNodePosition( e.target ), endPos );
|
|
1827
|
+
endPos.add( new LX.vec2( 3, 3 ), endPos );
|
|
1828
|
+
}
|
|
1829
|
+
else if( e.target.hasClass( [ 'io__type', 'lexgraphgroupresizer' ] ) )
|
|
1830
|
+
{
|
|
1831
|
+
var parent = e.target.offsetParent;
|
|
1832
|
+
// Add parent offset
|
|
1833
|
+
endPos.add( this._getNodePosition( parent ), endPos );
|
|
1834
|
+
// Add own offset
|
|
1835
|
+
endPos.add( new LX.vec2( e.target.offsetLeft, e.target.offsetTop ), endPos );
|
|
1836
|
+
endPos.add( new LX.vec2( 3, 3 ), endPos );
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
endPos = this._getRenderPosition( endPos );
|
|
1840
|
+
}
|
|
1841
|
+
else
|
|
1842
|
+
{
|
|
1843
|
+
const ioRect = endIO.querySelector( '.io__type' ).getBoundingClientRect();
|
|
1844
|
+
endPos = new LX.vec2( ioRect.x - offsetX, ioRect.y - offsetY );
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
if( type == GraphEditor.NODE_IO_INPUT )
|
|
1848
|
+
{
|
|
1849
|
+
var tmp = endPos;
|
|
1850
|
+
endPos = startPos;
|
|
1851
|
+
startPos = tmp;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1854
|
+
const color = getComputedStyle( ioEl ).backgroundColor;
|
|
1855
|
+
this._createLinkPath( path, startPos, endPos, color, !!e );
|
|
1856
|
+
|
|
1857
|
+
return path;
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
_createLink( link ) {
|
|
1861
|
+
|
|
1862
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
1863
|
+
svg.classList.add( "link-svg" );
|
|
1864
|
+
svg.style.width = "100%";
|
|
1865
|
+
svg.style.height = "100%";
|
|
1866
|
+
this._domLinks.appendChild( svg );
|
|
1867
|
+
|
|
1868
|
+
var path = document.createElementNS( 'http://www.w3.org/2000/svg', 'path' );
|
|
1869
|
+
path.setAttribute( 'fill', 'none' );
|
|
1870
|
+
svg.appendChild( path );
|
|
1871
|
+
|
|
1872
|
+
const inputNodeDom = this._getNodeDOMElement( link.inputNode );
|
|
1873
|
+
const outputNodeDom = this._getNodeDOMElement( link.outputNode );
|
|
1874
|
+
|
|
1875
|
+
// Start pos
|
|
1876
|
+
|
|
1877
|
+
const offsetX = this.root.getBoundingClientRect().x;
|
|
1878
|
+
const offsetY = this.root.getBoundingClientRect().y;
|
|
1879
|
+
|
|
1880
|
+
const outputs = outputNodeDom.querySelector( '.lexgraphnodeoutputs' );
|
|
1881
|
+
const io0 = outputs.childNodes[ link.outputIdx ];
|
|
1882
|
+
const startRect = io0.querySelector( '.io__type' ).getBoundingClientRect();
|
|
1883
|
+
|
|
1884
|
+
let startPos = new LX.vec2( startRect.x - offsetX, startRect.y - offsetY + 6 );
|
|
1885
|
+
|
|
1886
|
+
// End pos
|
|
1887
|
+
|
|
1888
|
+
const inputs = inputNodeDom.querySelector( '.lexgraphnodeinputs' );
|
|
1889
|
+
const io1 = inputs.childNodes[ link.inputIdx ];
|
|
1890
|
+
const endRect = io1.querySelector( '.io__type' ).getBoundingClientRect();
|
|
1891
|
+
|
|
1892
|
+
let endPos = new LX.vec2( endRect.x - offsetX, endRect.y - offsetY + 6 );
|
|
1893
|
+
|
|
1894
|
+
// Generate bezier curve
|
|
1895
|
+
|
|
1896
|
+
const color = getComputedStyle( io1.querySelector( '.io__type' ) ).backgroundColor;
|
|
1897
|
+
this._createLinkPath( path, startPos, endPos, color );
|
|
1898
|
+
|
|
1899
|
+
link.path = path;
|
|
1900
|
+
|
|
1901
|
+
// Store data in each IO
|
|
1902
|
+
|
|
1903
|
+
io0.links = [ ];
|
|
1904
|
+
io0.links[ link.inputIdx ] = io0.links[ link.inputIdx ] ?? [ ];
|
|
1905
|
+
io0.links[ link.inputIdx ].push( link.inputNode );
|
|
1906
|
+
|
|
1907
|
+
io1.links = [ ];
|
|
1908
|
+
io1.links[ link.outputIdx ] = io1.links[ link.outputIdx ] ?? [ ];
|
|
1909
|
+
io1.links[ link.outputIdx ].push( link.outputNode );
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1912
|
+
_createLinkPath( path, startPos, endPos, color, exactEnd ) {
|
|
1913
|
+
|
|
1914
|
+
const dist = 6 * this._scale;
|
|
1915
|
+
startPos.add( new LX.vec2( dist, dist ), startPos );
|
|
1916
|
+
|
|
1917
|
+
if( !exactEnd )
|
|
1918
|
+
{
|
|
1919
|
+
endPos.add( new LX.vec2( dist, dist ), endPos );
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
startPos = this._getPatternPosition( startPos );
|
|
1923
|
+
endPos = this._getPatternPosition( endPos );
|
|
1924
|
+
|
|
1925
|
+
const distanceX = LX.UTILS.clamp( Math.abs( startPos.x - endPos.x ), 0.0, 256.0 );
|
|
1926
|
+
const cPDistance = 128.0 * Math.pow( distanceX / 256.0, 0.5 );
|
|
1927
|
+
|
|
1928
|
+
let cPoint1 = startPos.add( new LX.vec2( cPDistance, 0 ) );
|
|
1929
|
+
let cPoint2 = endPos.sub( new LX.vec2( cPDistance, 0 ) );
|
|
1930
|
+
|
|
1931
|
+
path.setAttribute( 'd', `
|
|
1932
|
+
M ${ startPos.x },${ startPos.y }
|
|
1933
|
+
C ${ cPoint1.x },${ cPoint1.y } ${ cPoint2.x },${ cPoint2.y } ${ endPos.x },${ endPos.y }
|
|
1934
|
+
` );
|
|
1935
|
+
|
|
1936
|
+
path.setAttribute( 'stroke', color );
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1939
|
+
_updateNodeLinks( nodeId ) {
|
|
1940
|
+
|
|
1941
|
+
var node = this.nodes[ nodeId ] ? this.nodes[ nodeId ].data : null;
|
|
1942
|
+
|
|
1943
|
+
if( !node ) {
|
|
1944
|
+
console.warn( `Can't finde node [${ nodeId }]` );
|
|
1945
|
+
return;
|
|
1946
|
+
}
|
|
1947
|
+
|
|
1948
|
+
const nodeDOM = this._getNodeDOMElement( nodeId );
|
|
1949
|
+
|
|
1950
|
+
// Update input links
|
|
1951
|
+
|
|
1952
|
+
for( let input of nodeDOM.querySelectorAll( '.ioinput' ) )
|
|
1953
|
+
{
|
|
1954
|
+
if( !input.links )
|
|
1955
|
+
continue;
|
|
1956
|
+
|
|
1957
|
+
// Get first and only target output..
|
|
1958
|
+
const targets = input.links.filter( v => v !== undefined )[ 0 ];
|
|
1959
|
+
const targetNodeId = targets[ 0 ];
|
|
1960
|
+
|
|
1961
|
+
// It has been deleted..
|
|
1962
|
+
if( !targetNodeId )
|
|
1963
|
+
continue;
|
|
1964
|
+
|
|
1965
|
+
const ioIndex = parseInt( input.dataset[ 'index' ] );
|
|
1966
|
+
|
|
1967
|
+
var links = this._getLinks( targetNodeId, nodeId );
|
|
1968
|
+
|
|
1969
|
+
// Inputs only have 1 possible output connected
|
|
1970
|
+
var link = links.find( i => ( i.inputIdx == ioIndex ) );
|
|
1971
|
+
|
|
1972
|
+
this._generatingLink = {
|
|
1973
|
+
index: ioIndex,
|
|
1974
|
+
io: input,
|
|
1975
|
+
ioType: GraphEditor.NODE_IO_INPUT,
|
|
1976
|
+
domEl: nodeDOM,
|
|
1977
|
+
path: link.path
|
|
1978
|
+
};
|
|
1979
|
+
|
|
1980
|
+
// Get end io
|
|
1981
|
+
|
|
1982
|
+
const outputNode = this._getNodeDOMElement( targetNodeId );
|
|
1983
|
+
const io = outputNode.querySelector( '.lexgraphnodeoutputs' ).childNodes[ link.outputIdx ]
|
|
1984
|
+
|
|
1985
|
+
this._updatePreviewLink( null, io );
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1988
|
+
// Update output links
|
|
1989
|
+
|
|
1990
|
+
|
|
1991
|
+
for( let output of nodeDOM.querySelectorAll( '.iooutput' ) )
|
|
1992
|
+
{
|
|
1993
|
+
if( !output.links )
|
|
1994
|
+
continue;
|
|
1995
|
+
|
|
1996
|
+
const srcIndex = parseInt( output.dataset[ 'index' ] );
|
|
1997
|
+
|
|
1998
|
+
for( let targetIndex = 0; targetIndex < output.links.length; ++targetIndex )
|
|
1999
|
+
{
|
|
2000
|
+
const targets = output.links[ targetIndex ];
|
|
2001
|
+
|
|
2002
|
+
if( !targets )
|
|
2003
|
+
continue;
|
|
2004
|
+
|
|
2005
|
+
for( let targetId of targets )
|
|
2006
|
+
{
|
|
2007
|
+
var links = this._getLinks( nodeId, targetId );
|
|
2008
|
+
var link = links.find( i => ( i.inputIdx == targetIndex && i.outputIdx == srcIndex ) );
|
|
2009
|
+
|
|
2010
|
+
// Outputs can have different inputs connected
|
|
2011
|
+
this._generatingLink = {
|
|
2012
|
+
index: link.outputIdx,
|
|
2013
|
+
io: output,
|
|
2014
|
+
ioType: GraphEditor.NODE_IO_OUTPUT,
|
|
2015
|
+
domEl: nodeDOM,
|
|
2016
|
+
path: link.path
|
|
2017
|
+
};
|
|
2018
|
+
|
|
2019
|
+
// Get end io
|
|
2020
|
+
|
|
2021
|
+
const inputNode = this._getNodeDOMElement( targetId );
|
|
2022
|
+
const io = inputNode.querySelector( '.lexgraphnodeinputs' ).childNodes[ link.inputIdx ]
|
|
2023
|
+
|
|
2024
|
+
this._updatePreviewLink( null, io );
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
delete this._generatingLink;
|
|
2030
|
+
}
|
|
2031
|
+
|
|
2032
|
+
_drawBoxSelection( e ) {
|
|
2033
|
+
|
|
2034
|
+
var svg = this._currentBoxSelectionSVG;
|
|
2035
|
+
|
|
2036
|
+
if( !svg )
|
|
2037
|
+
{
|
|
2038
|
+
var svg = document.createElementNS( 'http://www.w3.org/2000/svg', 'svg' );
|
|
2039
|
+
svg.classList.add( "box-selection-svg" );
|
|
2040
|
+
if( this._boxSelectRemoving )
|
|
2041
|
+
svg.classList.add( "removing" );
|
|
2042
|
+
svg.style.width = "100%";
|
|
2043
|
+
svg.style.height = "100%";
|
|
2044
|
+
this._domLinks.appendChild( svg );
|
|
2045
|
+
this._currentBoxSelectionSVG = svg;
|
|
2046
|
+
}
|
|
2047
|
+
|
|
2048
|
+
// Generate box
|
|
2049
|
+
|
|
2050
|
+
let startPos = this._getPatternPosition( this._boxSelecting );
|
|
2051
|
+
let size = this._getPatternPosition( this._mousePosition ).sub( startPos );
|
|
2052
|
+
|
|
2053
|
+
if( size.x < 0 ) startPos.x += size.x;
|
|
2054
|
+
if( size.y < 0 ) startPos.y += size.y;
|
|
2055
|
+
|
|
2056
|
+
size = size.abs();
|
|
2057
|
+
|
|
2058
|
+
svg.innerHTML = `<rect
|
|
2059
|
+
x="${ startPos.x }" y="${ startPos.y }"
|
|
2060
|
+
rx="${ 6 }" ry="${ 6 }"
|
|
2061
|
+
width="${ size.x }" height="${ size.y }"
|
|
2062
|
+
"/>`;
|
|
2063
|
+
}
|
|
2064
|
+
|
|
2065
|
+
// TODO: Return the ones in the viewport
|
|
2066
|
+
_getVisibleNodes() {
|
|
2067
|
+
|
|
2068
|
+
if( !this.graph )
|
|
2069
|
+
{
|
|
2070
|
+
console.warn( "No graph set" );
|
|
2071
|
+
return [];
|
|
2072
|
+
}
|
|
2073
|
+
|
|
2074
|
+
return this.graph.nodes;
|
|
2075
|
+
}
|
|
2076
|
+
|
|
2077
|
+
_selectNodesInBox( lt, rb, remove ) {
|
|
2078
|
+
|
|
2079
|
+
lt = this._getPatternPosition( lt );
|
|
2080
|
+
rb = this._getPatternPosition( rb );
|
|
2081
|
+
|
|
2082
|
+
let size = rb.sub( lt );
|
|
2083
|
+
|
|
2084
|
+
if( size.x < 0 )
|
|
2085
|
+
{
|
|
2086
|
+
var tmp = lt.x;
|
|
2087
|
+
lt.x = rb.x;
|
|
2088
|
+
rb.x = tmp;
|
|
2089
|
+
}
|
|
2090
|
+
|
|
2091
|
+
if( size.y < 0 )
|
|
2092
|
+
{
|
|
2093
|
+
var tmp = lt.y;
|
|
2094
|
+
lt.y = rb.y;
|
|
2095
|
+
rb.y = tmp;
|
|
2096
|
+
}
|
|
2097
|
+
|
|
2098
|
+
for( let nodeEl of this._getAllDOMNodes() )
|
|
2099
|
+
{
|
|
2100
|
+
let pos = this._getNodePosition( nodeEl );
|
|
2101
|
+
let size = new LX.vec2( nodeEl.offsetWidth, nodeEl.offsetHeight );
|
|
2102
|
+
|
|
2103
|
+
if( ( !( pos.x < lt.x && ( pos.x + size.x ) < lt.x ) && !( pos.x > rb.x && ( pos.x + size.x ) > rb.x ) ) &&
|
|
2104
|
+
( !( pos.y < lt.y && ( pos.y + size.y ) < lt.y ) && !( pos.y > rb.y && ( pos.y + size.y ) > rb.y ) ) )
|
|
2105
|
+
{
|
|
2106
|
+
if( remove )
|
|
2107
|
+
this._unSelectNode( nodeEl );
|
|
2108
|
+
else
|
|
2109
|
+
this._selectNode( nodeEl, true, false );
|
|
2110
|
+
}
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
|
|
2114
|
+
_deleteSelection( e ) {
|
|
2115
|
+
|
|
2116
|
+
const lastNodeCount = this._domNodes.childElementCount;
|
|
2117
|
+
|
|
2118
|
+
for( let nodeId of this.selectedNodes )
|
|
2119
|
+
{
|
|
2120
|
+
this._deleteNode( nodeId );
|
|
2121
|
+
}
|
|
2122
|
+
|
|
2123
|
+
this.selectedNodes.length = 0;
|
|
2124
|
+
|
|
2125
|
+
// We delete something, so add undo step..
|
|
2126
|
+
|
|
2127
|
+
if( this._domNodes.childElementCount != lastNodeCount )
|
|
2128
|
+
{
|
|
2129
|
+
this._addUndoStep();
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
_getBoundingFromGroup( groupDOM ) {
|
|
2135
|
+
|
|
2136
|
+
const x = parseFloat( groupDOM.style.left );
|
|
2137
|
+
const y = parseFloat( groupDOM.style.top );
|
|
2138
|
+
|
|
2139
|
+
return new BoundingBox( new LX.vec2( x, y ), new LX.vec2( groupDOM.offsetWidth - 2, groupDOM.offsetHeight - 2 ) );
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
_getBoundingFromNodes( nodeIds ) {
|
|
2143
|
+
|
|
2144
|
+
let group_bb = null;
|
|
2145
|
+
|
|
2146
|
+
for( let nodeId of nodeIds )
|
|
2147
|
+
{
|
|
2148
|
+
const dom = this._getNodeDOMElement( nodeId );
|
|
2149
|
+
|
|
2150
|
+
const x = parseFloat( dom.style.left );
|
|
2151
|
+
const y = parseFloat( dom.style.top );
|
|
2152
|
+
const node_bb = new BoundingBox( new LX.vec2( x, y ), new LX.vec2( dom.offsetWidth - 6, dom.offsetHeight - 6 ) );
|
|
2153
|
+
|
|
2154
|
+
if( group_bb )
|
|
2155
|
+
{
|
|
2156
|
+
group_bb.merge( node_bb );
|
|
2157
|
+
}
|
|
2158
|
+
else
|
|
2159
|
+
{
|
|
2160
|
+
group_bb = node_bb;
|
|
2161
|
+
}
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
// Add padding
|
|
2165
|
+
|
|
2166
|
+
const groupContentPadding = 8;
|
|
2167
|
+
|
|
2168
|
+
group_bb.origin.sub( new LX.vec2( groupContentPadding ), group_bb.origin );
|
|
2169
|
+
group_bb.origin.sub( new LX.vec2( groupContentPadding ), group_bb.origin );
|
|
2170
|
+
|
|
2171
|
+
group_bb.size.add( new LX.vec2( groupContentPadding * 2.0 ), group_bb.size );
|
|
2172
|
+
group_bb.size.add( new LX.vec2( groupContentPadding * 2.0 ), group_bb.size );
|
|
2173
|
+
|
|
2174
|
+
return group_bb;
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
/**
|
|
2178
|
+
* @method _createGroup
|
|
2179
|
+
* @description Creates a node group from the bounding box of the selected nodes
|
|
2180
|
+
* @returns JSON data from the serialized graph
|
|
2181
|
+
*/
|
|
2182
|
+
|
|
2183
|
+
_createGroup() {
|
|
2184
|
+
|
|
2185
|
+
const group_bb = this._getBoundingFromNodes( this.selectedNodes );
|
|
2186
|
+
|
|
2187
|
+
let groupDOM = document.createElement( 'div' );
|
|
2188
|
+
groupDOM.classList.add( 'lexgraphgroup' );
|
|
2189
|
+
groupDOM.style.left = group_bb.origin.x + "px";
|
|
2190
|
+
groupDOM.style.top = group_bb.origin.y + "px";
|
|
2191
|
+
groupDOM.style.width = group_bb.size.x + "px";
|
|
2192
|
+
groupDOM.style.height = group_bb.size.y + "px";
|
|
2193
|
+
|
|
2194
|
+
let groupResizer = document.createElement( 'div' );
|
|
2195
|
+
groupResizer.classList.add( 'lexgraphgroupresizer' );
|
|
2196
|
+
|
|
2197
|
+
groupResizer.addEventListener( 'mousedown', inner_mousedown );
|
|
2198
|
+
|
|
2199
|
+
var that = this;
|
|
2200
|
+
var lastPos = [0,0];
|
|
2201
|
+
|
|
2202
|
+
function inner_mousedown( e )
|
|
2203
|
+
{
|
|
2204
|
+
var doc = that.root.ownerDocument;
|
|
2205
|
+
doc.addEventListener( 'mousemove', inner_mousemove );
|
|
2206
|
+
doc.addEventListener( 'mouseup', inner_mouseup );
|
|
2207
|
+
lastPos[0] = e.x;
|
|
2208
|
+
lastPos[1] = e.y;
|
|
2209
|
+
e.stopPropagation();
|
|
2210
|
+
e.preventDefault();
|
|
2211
|
+
document.body.classList.add( 'nocursor' );
|
|
2212
|
+
groupResizer.classList.add( 'nocursor' );
|
|
2213
|
+
}
|
|
2214
|
+
|
|
2215
|
+
function inner_mousemove( e )
|
|
2216
|
+
{
|
|
2217
|
+
let dt = new LX.vec2( lastPos[0] - e.x, lastPos[1] - e.y );
|
|
2218
|
+
dt.div( that._scale, dt);
|
|
2219
|
+
|
|
2220
|
+
groupDOM.style.width = ( parseFloat( groupDOM.style.width ) - dt.x ) + "px";
|
|
2221
|
+
groupDOM.style.height = ( parseFloat( groupDOM.style.height ) - dt.y ) + "px";
|
|
2222
|
+
|
|
2223
|
+
lastPos[0] = e.x;
|
|
2224
|
+
lastPos[1] = e.y;
|
|
2225
|
+
|
|
2226
|
+
e.stopPropagation();
|
|
2227
|
+
e.preventDefault();
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function inner_mouseup( e )
|
|
2231
|
+
{
|
|
2232
|
+
var doc = that.root.ownerDocument;
|
|
2233
|
+
doc.removeEventListener( 'mousemove', inner_mousemove );
|
|
2234
|
+
doc.removeEventListener( 'mouseup', inner_mouseup );
|
|
2235
|
+
document.body.classList.remove( 'nocursor' );
|
|
2236
|
+
groupResizer.classList.remove( 'nocursor' );
|
|
2237
|
+
}
|
|
2238
|
+
|
|
2239
|
+
let groupTitle = document.createElement( 'input' );
|
|
2240
|
+
let defaultName = `Group ${ GraphEditor.LAST_GROUP_ID }`;
|
|
2241
|
+
groupTitle.value = defaultName;
|
|
2242
|
+
groupTitle.classList.add( 'lexgraphgrouptitle' );
|
|
2243
|
+
groupTitle.disabled = true;
|
|
2244
|
+
|
|
2245
|
+
// Dbl click to rename
|
|
2246
|
+
|
|
2247
|
+
groupTitle.addEventListener( 'mousedown', e => {
|
|
2248
|
+
e.stopPropagation();
|
|
2249
|
+
e.stopImmediatePropagation();
|
|
2250
|
+
} );
|
|
2251
|
+
|
|
2252
|
+
groupTitle.addEventListener( 'focusout', e => {
|
|
2253
|
+
groupTitle.disabled = true;
|
|
2254
|
+
if( !groupTitle.value.length )
|
|
2255
|
+
groupTitle.value = defaultName;
|
|
2256
|
+
} );
|
|
2257
|
+
|
|
2258
|
+
groupTitle.addEventListener( 'keyup', e => {
|
|
2259
|
+
if( e.key == 'Enter' ) {
|
|
2260
|
+
groupTitle.blur();
|
|
2261
|
+
}
|
|
2262
|
+
else if( e.key == 'Escape' ) {
|
|
2263
|
+
groupTitle.value = "";
|
|
2264
|
+
groupTitle.blur();
|
|
2265
|
+
}
|
|
2266
|
+
});
|
|
2267
|
+
|
|
2268
|
+
groupDOM.addEventListener( 'dblclick', e => {
|
|
2269
|
+
// Only for left click..
|
|
2270
|
+
if( e.button != LX.MOUSE_LEFT_CLICK )
|
|
2271
|
+
return;
|
|
2272
|
+
groupTitle.disabled = false;
|
|
2273
|
+
groupTitle.focus();
|
|
2274
|
+
} );
|
|
2275
|
+
|
|
2276
|
+
groupDOM.appendChild( groupResizer );
|
|
2277
|
+
groupDOM.appendChild( groupTitle );
|
|
2278
|
+
|
|
2279
|
+
this._domNodes.prepend( groupDOM );
|
|
2280
|
+
|
|
2281
|
+
// Move group!!
|
|
2282
|
+
|
|
2283
|
+
LX.makeDraggable( groupDOM, {
|
|
2284
|
+
onMove: this._onMoveGroup.bind( this ),
|
|
2285
|
+
onDragStart: this._onDragGroup.bind( this )
|
|
2286
|
+
} );
|
|
2287
|
+
|
|
2288
|
+
GraphEditor.LAST_GROUP_ID++;
|
|
2289
|
+
}
|
|
2290
|
+
|
|
2291
|
+
_addUndoStep( deleteRedo = true ) {
|
|
2292
|
+
|
|
2293
|
+
if( deleteRedo )
|
|
2294
|
+
{
|
|
2295
|
+
// Remove all redo steps
|
|
2296
|
+
this._redoSteps.length = 0;
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
this._undoSteps.push( {
|
|
2300
|
+
// TODO: Add graph state
|
|
2301
|
+
} );
|
|
2302
|
+
}
|
|
2303
|
+
|
|
2304
|
+
_doUndo() {
|
|
2305
|
+
|
|
2306
|
+
if( !this._undoSteps.length )
|
|
2307
|
+
return;
|
|
2308
|
+
|
|
2309
|
+
this._addRedoStep();
|
|
2310
|
+
|
|
2311
|
+
// Extract info from the last state
|
|
2312
|
+
const step = this._undoSteps.pop();
|
|
2313
|
+
|
|
2314
|
+
// Set old state
|
|
2315
|
+
// TODO
|
|
2316
|
+
|
|
2317
|
+
console.log( "Undo!!" );
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
_addRedoStep() {
|
|
2321
|
+
|
|
2322
|
+
this._redoSteps.push( {
|
|
2323
|
+
// TODO: Add graph state
|
|
2324
|
+
} );
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
_doRedo() {
|
|
2328
|
+
|
|
2329
|
+
if( !this._redoSteps.length )
|
|
2330
|
+
return;
|
|
2331
|
+
|
|
2332
|
+
this._addUndoStep( false );
|
|
2333
|
+
|
|
2334
|
+
// Extract info from the next saved code state
|
|
2335
|
+
const step = this._redoSteps.pop();
|
|
2336
|
+
|
|
2337
|
+
// Set old state
|
|
2338
|
+
// TODO
|
|
2339
|
+
|
|
2340
|
+
console.log( "Redo!!" );
|
|
2341
|
+
}
|
|
2342
|
+
|
|
2343
|
+
_togglePropertiesDialog( force ) {
|
|
2344
|
+
|
|
2345
|
+
this.propertiesDialog.root.classList.toggle( 'opened', force );
|
|
2346
|
+
|
|
2347
|
+
if( !force )
|
|
2348
|
+
{
|
|
2349
|
+
this.propertiesDialog.panel.clear();
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2352
|
+
|
|
2353
|
+
_setSnappingValue( value ) {
|
|
2354
|
+
|
|
2355
|
+
this._snapValue = value;
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
_toggleSnapping() {
|
|
2359
|
+
|
|
2360
|
+
this._snapToGrid = !this._snapToGrid;
|
|
2361
|
+
|
|
2362
|
+
// Trigger position snapping for each node if needed
|
|
2363
|
+
|
|
2364
|
+
if( this._snapToGrid )
|
|
2365
|
+
{
|
|
2366
|
+
for( let nodeDom of this._getAllDOMNodes( true ) )
|
|
2367
|
+
{
|
|
2368
|
+
nodeDom.mustSnap = true;
|
|
2369
|
+
}
|
|
2370
|
+
}
|
|
2371
|
+
}
|
|
2372
|
+
|
|
2373
|
+
_toggleSideBar() {
|
|
2374
|
+
|
|
2375
|
+
this._sidebar.classList.toggle( 'hidden' );
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
|
|
2379
|
+
LX.GraphEditor = GraphEditor;
|
|
2380
|
+
|
|
2381
|
+
|
|
2382
|
+
/**
|
|
2383
|
+
* @class Graph
|
|
2384
|
+
*/
|
|
2385
|
+
|
|
2386
|
+
class Graph {
|
|
2387
|
+
|
|
2388
|
+
/**
|
|
2389
|
+
* @param {*} options
|
|
2390
|
+
*
|
|
2391
|
+
*/
|
|
2392
|
+
|
|
2393
|
+
constructor( name, options = {} ) {
|
|
2394
|
+
|
|
2395
|
+
this.name = name ?? "Unnamed Graph";
|
|
2396
|
+
|
|
2397
|
+
// Nodes
|
|
2398
|
+
|
|
2399
|
+
this.nodes = [ ];
|
|
2400
|
+
|
|
2401
|
+
this.links = { };
|
|
2402
|
+
|
|
2403
|
+
// const mainNode = GraphEditor.addNode( 'system/Main' );
|
|
2404
|
+
// mainNode.position = new LX.vec2( 650, 400 );
|
|
2405
|
+
|
|
2406
|
+
// const addNode = GraphEditor.addNode( 'math/Add' );
|
|
2407
|
+
// addNode.position = new LX.vec2( 425, 250 );
|
|
2408
|
+
|
|
2409
|
+
// const floatNode = GraphEditor.addNode( 'inputs/Float' );
|
|
2410
|
+
// floatNode.position = new LX.vec2( 200, 200 );
|
|
2411
|
+
|
|
2412
|
+
// const stringNode = GraphEditor.addNode( 'inputs/String' );
|
|
2413
|
+
// stringNode.position = new LX.vec2( 400, 50 );
|
|
2414
|
+
|
|
2415
|
+
// const setVarNode = GraphEditor.addNode( 'variables/SetVariable' );
|
|
2416
|
+
// setVarNode.position = new LX.vec2( 650, 50 );
|
|
2417
|
+
|
|
2418
|
+
// // const multNode = new GraphNode( GraphEditor.DEFAULT_NODES[ 'Multiply' ] );
|
|
2419
|
+
// // multNode.position = new LX.vec2( 200, 400 );
|
|
2420
|
+
|
|
2421
|
+
// const keydownNode = GraphEditor.addNode( 'events/KeyDown' );
|
|
2422
|
+
// keydownNode.position = new LX.vec2( 600, 200 );
|
|
2423
|
+
|
|
2424
|
+
// const orNode = GraphEditor.addNode( 'logic/Or' );
|
|
2425
|
+
// orNode.position = new LX.vec2( 435, 435 );
|
|
2426
|
+
|
|
2427
|
+
// const equalNode = GraphEditor.addNode( 'logic/Select' );
|
|
2428
|
+
// equalNode.position = new LX.vec2( 135, 400 );
|
|
2429
|
+
|
|
2430
|
+
// this.nodes = [
|
|
2431
|
+
// mainNode,
|
|
2432
|
+
// addNode,
|
|
2433
|
+
// floatNode,
|
|
2434
|
+
// setVarNode,
|
|
2435
|
+
// stringNode,
|
|
2436
|
+
// keydownNode,
|
|
2437
|
+
// orNode,
|
|
2438
|
+
// equalNode,
|
|
2439
|
+
// // multNode
|
|
2440
|
+
// ];
|
|
2441
|
+
}
|
|
2442
|
+
|
|
2443
|
+
configure( o ) {
|
|
2444
|
+
|
|
2445
|
+
this.nodes.length = 0;
|
|
2446
|
+
|
|
2447
|
+
for( let node of o.nodes )
|
|
2448
|
+
{
|
|
2449
|
+
const newNode = GraphEditor.addNode( node.type );
|
|
2450
|
+
|
|
2451
|
+
newNode.id = node.id;
|
|
2452
|
+
newNode.title = node.title;
|
|
2453
|
+
newNode.color = node.color;
|
|
2454
|
+
newNode.position = new LX.vec2( node.position.x, node.position.y );
|
|
2455
|
+
newNode.type = node.type;
|
|
2456
|
+
// newNode.inputs = node.inputs;
|
|
2457
|
+
// newNode.outputs = node.outputs;
|
|
2458
|
+
// newNode.properties = node.properties;
|
|
2459
|
+
|
|
2460
|
+
this.nodes.push( newNode );
|
|
2461
|
+
}
|
|
2462
|
+
|
|
2463
|
+
this.links = o.links;
|
|
2464
|
+
|
|
2465
|
+
// editor options?
|
|
2466
|
+
|
|
2467
|
+
// zoom/translation ??
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
/**
|
|
2471
|
+
* @method export
|
|
2472
|
+
* @param {Boolean} prettify
|
|
2473
|
+
* @returns JSON data from the serialized graph
|
|
2474
|
+
*/
|
|
2475
|
+
|
|
2476
|
+
export( prettify = true ) {
|
|
2477
|
+
|
|
2478
|
+
var o = { };
|
|
2479
|
+
o.nodes = [ ];
|
|
2480
|
+
o.links = { };
|
|
2481
|
+
|
|
2482
|
+
for( let node of this.nodes )
|
|
2483
|
+
{
|
|
2484
|
+
o.nodes.push( node.serialize() );
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2487
|
+
for( let linkId in this.links )
|
|
2488
|
+
{
|
|
2489
|
+
const ioLinks = LX.deepCopy( this.links[ linkId ] );
|
|
2490
|
+
ioLinks.forEach( v => delete v.path );
|
|
2491
|
+
o.links[ linkId ] = ioLinks;
|
|
2492
|
+
}
|
|
2493
|
+
|
|
2494
|
+
// editor options?
|
|
2495
|
+
|
|
2496
|
+
// zoom/translation ??
|
|
2497
|
+
|
|
2498
|
+
try
|
|
2499
|
+
{
|
|
2500
|
+
o = JSON.stringify( o, null, prettify ? 2 : null );
|
|
2501
|
+
}
|
|
2502
|
+
catch( e )
|
|
2503
|
+
{
|
|
2504
|
+
o = null;
|
|
2505
|
+
console.error( `Can't export GraphNode [${ this.title }] of type [${ this.type }].` );
|
|
2506
|
+
}
|
|
2507
|
+
|
|
2508
|
+
LX.downloadFile( this.name + ".json", o );
|
|
2509
|
+
|
|
2510
|
+
return o;
|
|
2511
|
+
}
|
|
2512
|
+
}
|
|
402
2513
|
|
|
403
|
-
|
|
2514
|
+
LX.Graph = Graph;
|
|
404
2515
|
|
|
405
|
-
|
|
2516
|
+
/**
|
|
2517
|
+
* @class GraphNode
|
|
2518
|
+
*/
|
|
406
2519
|
|
|
407
|
-
|
|
408
|
-
else this._scale *= ( 1.0 / 0.9 );
|
|
2520
|
+
class GraphNode {
|
|
409
2521
|
|
|
410
|
-
|
|
2522
|
+
constructor() {
|
|
411
2523
|
|
|
412
|
-
|
|
413
|
-
|
|
2524
|
+
this.inputs = [ ];
|
|
2525
|
+
this.outputs = [ ];
|
|
2526
|
+
this.properties = [ ];
|
|
2527
|
+
}
|
|
414
2528
|
|
|
415
|
-
|
|
2529
|
+
_hasOutputsConnected() {
|
|
416
2530
|
|
|
417
|
-
|
|
2531
|
+
return true;
|
|
2532
|
+
}
|
|
418
2533
|
|
|
419
|
-
|
|
2534
|
+
execute() {
|
|
420
2535
|
|
|
421
|
-
this.
|
|
2536
|
+
if( !this._hasOutputsConnected() )
|
|
2537
|
+
return;
|
|
2538
|
+
|
|
2539
|
+
if( this.onExecute )
|
|
2540
|
+
{
|
|
2541
|
+
this.onExecute();
|
|
2542
|
+
}
|
|
422
2543
|
}
|
|
423
2544
|
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
m.add( "option 1", () => { } );
|
|
428
|
-
m.add( "option 2", () => { } );
|
|
429
|
-
});
|
|
2545
|
+
addInput( name, type ) {
|
|
2546
|
+
|
|
2547
|
+
this.inputs.push( { name: name, type: type } );
|
|
430
2548
|
}
|
|
431
2549
|
|
|
432
|
-
|
|
433
|
-
* @method frame
|
|
434
|
-
*/
|
|
2550
|
+
addOutput( name, type ) {
|
|
435
2551
|
|
|
436
|
-
|
|
2552
|
+
this.outputs.push( { name: name, type: type } );
|
|
2553
|
+
}
|
|
437
2554
|
|
|
438
|
-
|
|
2555
|
+
addProperty( name, type, value, selectOptions ) {
|
|
439
2556
|
|
|
440
|
-
|
|
2557
|
+
this.properties.push( { name: name, type: type, value: value, options: selectOptions } );
|
|
441
2558
|
}
|
|
442
2559
|
|
|
443
|
-
|
|
444
|
-
* @method update
|
|
445
|
-
*/
|
|
2560
|
+
getInput( index ) {
|
|
446
2561
|
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
2562
|
+
if( !this.inputs || !this.inputs.length || !this.inputs[ index ] )
|
|
2563
|
+
return;
|
|
2564
|
+
|
|
2565
|
+
// Get data from link
|
|
2566
|
+
|
|
2567
|
+
for( let linkId in this.editor.graph.links )
|
|
2568
|
+
{
|
|
2569
|
+
const idx = linkId.indexOf( '@' + this.id );
|
|
2570
|
+
|
|
2571
|
+
if( idx < 0 )
|
|
2572
|
+
continue;
|
|
2573
|
+
|
|
2574
|
+
const nodeLinks = this.editor.graph.links[ linkId ];
|
|
2575
|
+
|
|
2576
|
+
for ( var link of nodeLinks )
|
|
2577
|
+
{
|
|
2578
|
+
if( link.inputIdx != index )
|
|
2579
|
+
continue;
|
|
2580
|
+
|
|
2581
|
+
// This is the value!!
|
|
2582
|
+
return link.data;
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
450
2585
|
}
|
|
451
2586
|
|
|
452
|
-
|
|
2587
|
+
setOutput( index, data ) {
|
|
453
2588
|
|
|
454
|
-
|
|
2589
|
+
if( !this.outputs || !this.outputs.length || !this.outputs[ index ] )
|
|
2590
|
+
return;
|
|
2591
|
+
|
|
2592
|
+
// Set data in link
|
|
2593
|
+
|
|
2594
|
+
for( let linkId in this.editor.graph.links )
|
|
455
2595
|
{
|
|
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' );
|
|
2596
|
+
const idx = linkId.indexOf( this.id + '@' );
|
|
463
2597
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
circle.setAttribute( 'cy', this._circlePatternSize );
|
|
467
|
-
circle.setAttribute( 'r', this._circlePatternSize );
|
|
468
|
-
circle.setAttribute( 'fill', this._circlePatternColor );
|
|
2598
|
+
if( idx < 0 )
|
|
2599
|
+
continue;
|
|
469
2600
|
|
|
470
|
-
|
|
2601
|
+
const nodeLinks = this.editor.graph.links[ linkId ];
|
|
2602
|
+
|
|
2603
|
+
for ( var link of nodeLinks )
|
|
2604
|
+
{
|
|
2605
|
+
if( link.outputIdx != index )
|
|
2606
|
+
continue;
|
|
2607
|
+
|
|
2608
|
+
if( data != undefined && link.inputType != link.outputType && link.inputType != "any" && link.outputType != "any" )
|
|
2609
|
+
{
|
|
2610
|
+
// In case of supported casting, use function to cast..
|
|
2611
|
+
|
|
2612
|
+
var fn = this.editor.supportedCastTypes[ link.outputType + '@' + link.inputType ];
|
|
2613
|
+
|
|
2614
|
+
// Use function if it's possible to cast!
|
|
2615
|
+
|
|
2616
|
+
data = fn ? fn( LX.deepCopy( data ) ) : null;
|
|
2617
|
+
}
|
|
2618
|
+
|
|
2619
|
+
link.data = data;
|
|
2620
|
+
}
|
|
471
2621
|
}
|
|
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%";
|
|
2622
|
+
}
|
|
477
2623
|
|
|
478
|
-
|
|
2624
|
+
getOutput( index ) {
|
|
479
2625
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
rect.setAttribute( 'y', '0' );
|
|
483
|
-
rect.setAttribute( 'width', '100%' );
|
|
484
|
-
rect.setAttribute( 'height', '100%' );
|
|
485
|
-
rect.setAttribute( 'fill', 'url(#pattern-0)' );
|
|
2626
|
+
return this.outputs[ index ].value;
|
|
2627
|
+
}
|
|
486
2628
|
|
|
487
|
-
|
|
2629
|
+
serialize() {
|
|
488
2630
|
|
|
489
|
-
|
|
2631
|
+
var o = { };
|
|
490
2632
|
|
|
491
|
-
|
|
2633
|
+
o.id = this.id;
|
|
2634
|
+
o.title = this.title;
|
|
2635
|
+
o.color = this.color;
|
|
2636
|
+
o.position = this.position;
|
|
2637
|
+
o.type = this.type;
|
|
2638
|
+
o.inputs = this.inputs;
|
|
2639
|
+
o.outputs = this.outputs;
|
|
2640
|
+
o.properties = this.properties;
|
|
2641
|
+
|
|
2642
|
+
return o;
|
|
492
2643
|
}
|
|
2644
|
+
}
|
|
493
2645
|
|
|
494
|
-
|
|
2646
|
+
LX.GraphNode = GraphNode;
|
|
495
2647
|
|
|
496
|
-
|
|
497
|
-
|
|
2648
|
+
/*
|
|
2649
|
+
************ PREDEFINED NODES ************
|
|
498
2650
|
|
|
499
|
-
|
|
500
|
-
const circlePatternSize = this._circlePatternSize * this._scale;
|
|
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 );
|
|
2651
|
+
Nodes can override the following methods:
|
|
508
2652
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
2653
|
+
- onCreate: Add inputs, outputs and properties
|
|
2654
|
+
- onStart: Callback on graph starts
|
|
2655
|
+
- onStop: Callback on graph stops
|
|
2656
|
+
- onExecute: Callback for node execution
|
|
2657
|
+
*/
|
|
513
2658
|
|
|
514
|
-
// Nodes
|
|
515
2659
|
|
|
516
|
-
|
|
517
|
-
|
|
2660
|
+
/*
|
|
2661
|
+
Math nodes
|
|
2662
|
+
*/
|
|
518
2663
|
|
|
519
|
-
|
|
520
|
-
|
|
2664
|
+
class NodeAdd extends GraphNode
|
|
2665
|
+
{
|
|
2666
|
+
onCreate() {
|
|
2667
|
+
this.addInput( null, "float" );
|
|
2668
|
+
this.addInput( null, "float" );
|
|
2669
|
+
this.addOutput( null, "float" );
|
|
2670
|
+
this.addProperty( "A", "float", 0 );
|
|
2671
|
+
this.addProperty( "B", "float", 0 );
|
|
2672
|
+
}
|
|
2673
|
+
|
|
2674
|
+
onExecute() {
|
|
2675
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
2676
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
2677
|
+
this.setOutput( 0, a + b );
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
521
2680
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
2681
|
+
NodeAdd.description = "Addition of 2 values (A+B)."
|
|
2682
|
+
GraphEditor.registerCustomNode( "math/Add", NodeAdd );
|
|
2683
|
+
|
|
2684
|
+
class NodeSubstract extends GraphNode
|
|
2685
|
+
{
|
|
2686
|
+
onCreate() {
|
|
2687
|
+
this.addInput( null, "float" );
|
|
2688
|
+
this.addInput( null, "float" );
|
|
2689
|
+
this.addOutput( null, "float" );
|
|
2690
|
+
this.addProperty( "A", "float", 0 );
|
|
2691
|
+
this.addProperty( "B", "float", 0 );
|
|
2692
|
+
}
|
|
2693
|
+
|
|
2694
|
+
onExecute() {
|
|
2695
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
2696
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
2697
|
+
this.setOutput( 0, a - b );
|
|
2698
|
+
}
|
|
2699
|
+
}
|
|
2700
|
+
|
|
2701
|
+
NodeSubstract.description = "Substraction of 2 values (A-B)."
|
|
2702
|
+
GraphEditor.registerCustomNode( "math/Substract", NodeSubstract );
|
|
2703
|
+
|
|
2704
|
+
class NodeMultiply extends GraphNode
|
|
2705
|
+
{
|
|
2706
|
+
onCreate() {
|
|
2707
|
+
this.addInput( null, "float" );
|
|
2708
|
+
this.addInput( null, "float" );
|
|
2709
|
+
this.addOutput( null, "float" );
|
|
2710
|
+
this.addProperty( "A", "float", 0 );
|
|
2711
|
+
this.addProperty( "B", "float", 0 );
|
|
526
2712
|
}
|
|
2713
|
+
|
|
2714
|
+
onExecute() {
|
|
2715
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
2716
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
2717
|
+
this.setOutput( 0, a * b );
|
|
2718
|
+
}
|
|
2719
|
+
}
|
|
527
2720
|
|
|
528
|
-
|
|
2721
|
+
NodeMultiply.description = "Multiplication of 2 values (A*B)."
|
|
2722
|
+
GraphEditor.registerCustomNode( "math/Multiply", NodeMultiply );
|
|
2723
|
+
|
|
2724
|
+
class NodeDivide extends GraphNode
|
|
2725
|
+
{
|
|
2726
|
+
onCreate() {
|
|
2727
|
+
this.addInput( null, "float" );
|
|
2728
|
+
this.addInput( null, "float" );
|
|
2729
|
+
this.addOutput( null, "float" );
|
|
2730
|
+
this.addProperty( "A", "float", 0 );
|
|
2731
|
+
this.addProperty( "B", "float", 0 );
|
|
2732
|
+
}
|
|
2733
|
+
|
|
2734
|
+
onExecute() {
|
|
2735
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
2736
|
+
var b = this.getInput( 1 ) ?? this.properties[ 1 ].value;
|
|
2737
|
+
this.setOutput( 0, a / b );
|
|
2738
|
+
}
|
|
2739
|
+
}
|
|
529
2740
|
|
|
530
|
-
|
|
2741
|
+
NodeDivide.description = "Division of 2 values (A/B)."
|
|
2742
|
+
GraphEditor.registerCustomNode( "math/Divide", NodeDivide );
|
|
2743
|
+
|
|
2744
|
+
class NodeSqrt extends GraphNode
|
|
2745
|
+
{
|
|
2746
|
+
onCreate() {
|
|
2747
|
+
this.addInput( null, "float" );
|
|
2748
|
+
this.addOutput( null, "float" );
|
|
2749
|
+
this.addProperty( "Value", "float", 0 );
|
|
531
2750
|
}
|
|
2751
|
+
|
|
2752
|
+
onExecute() {
|
|
2753
|
+
var a = this.getInput( 0 ) ?? this.properties[ 0 ].value;
|
|
2754
|
+
this.setOutput( 0, Math.sqrt( a ) );
|
|
2755
|
+
}
|
|
2756
|
+
}
|
|
532
2757
|
|
|
533
|
-
|
|
2758
|
+
NodeSqrt.description = "Square root of a scalar."
|
|
2759
|
+
GraphEditor.registerCustomNode( "math/SQRT", NodeSqrt );
|
|
2760
|
+
|
|
2761
|
+
/*
|
|
2762
|
+
Math Missing:
|
|
2763
|
+
- abs
|
|
2764
|
+
- ceil
|
|
2765
|
+
- clamp
|
|
2766
|
+
- floor
|
|
2767
|
+
- fract
|
|
2768
|
+
- lerp
|
|
2769
|
+
- log
|
|
2770
|
+
- max
|
|
2771
|
+
- min
|
|
2772
|
+
- negate
|
|
2773
|
+
- pow
|
|
2774
|
+
- remainder
|
|
2775
|
+
- round
|
|
2776
|
+
- remap range
|
|
2777
|
+
- saturate
|
|
2778
|
+
- step
|
|
2779
|
+
*/
|
|
2780
|
+
|
|
2781
|
+
|
|
2782
|
+
/*
|
|
2783
|
+
Logical operator nodes
|
|
2784
|
+
*/
|
|
2785
|
+
|
|
2786
|
+
class NodeAnd extends GraphNode
|
|
2787
|
+
{
|
|
2788
|
+
onCreate() {
|
|
2789
|
+
this.addInput( null, "bool" );
|
|
2790
|
+
this.addInput( null, "bool" );
|
|
2791
|
+
this.addOutput( null, "bool" );
|
|
2792
|
+
}
|
|
2793
|
+
|
|
2794
|
+
onExecute() {
|
|
2795
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
2796
|
+
if( a == undefined || b == undefined )
|
|
2797
|
+
return;
|
|
2798
|
+
this.setOutput( 0, !!( a ) && !!( b ) );
|
|
2799
|
+
}
|
|
2800
|
+
}
|
|
534
2801
|
|
|
535
|
-
|
|
536
|
-
var textMetrics = ctx.measureText( node.name );
|
|
2802
|
+
GraphEditor.registerCustomNode( "logic/And", NodeAnd );
|
|
537
2803
|
|
|
538
|
-
|
|
2804
|
+
class NodeOr extends GraphNode
|
|
2805
|
+
{
|
|
2806
|
+
onCreate() {
|
|
2807
|
+
this.addInput( null, "bool" );
|
|
2808
|
+
this.addInput( null, "bool" );
|
|
2809
|
+
this.addOutput( null, "bool" );
|
|
2810
|
+
}
|
|
2811
|
+
|
|
2812
|
+
onExecute() {
|
|
2813
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
2814
|
+
if( a == undefined || b == undefined )
|
|
2815
|
+
return;
|
|
2816
|
+
this.setOutput( 0, !!( a ) || !!( b ) );
|
|
2817
|
+
}
|
|
2818
|
+
}
|
|
539
2819
|
|
|
540
|
-
|
|
541
|
-
let sY = rows * GraphEditor.NODE_ROW_HEIGHT + GraphEditor.NODE_TITLE_HEIGHT;
|
|
2820
|
+
GraphEditor.registerCustomNode( "logic/Or", NodeOr );
|
|
542
2821
|
|
|
543
|
-
|
|
2822
|
+
class NodeEqual extends GraphNode
|
|
2823
|
+
{
|
|
2824
|
+
onCreate() {
|
|
2825
|
+
this.addInput( null, "float" );
|
|
2826
|
+
this.addInput( null, "float" );
|
|
2827
|
+
this.addOutput( null, "bool" );
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
logicOp( a, b ) {
|
|
2831
|
+
return a == b;
|
|
544
2832
|
}
|
|
545
2833
|
|
|
546
|
-
|
|
2834
|
+
onExecute() {
|
|
2835
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 );
|
|
2836
|
+
if( a == undefined || b == undefined )
|
|
2837
|
+
return;
|
|
2838
|
+
this.setOutput( 0, this.logicOp( a, b ) );
|
|
2839
|
+
}
|
|
2840
|
+
}
|
|
547
2841
|
|
|
548
|
-
|
|
2842
|
+
GraphEditor.registerCustomNode( "logic/Equal", NodeEqual );
|
|
549
2843
|
|
|
550
|
-
|
|
2844
|
+
class NodeNotEqual extends NodeEqual
|
|
2845
|
+
{
|
|
2846
|
+
logicOp( a, b ) {
|
|
2847
|
+
return a != b;
|
|
2848
|
+
}
|
|
2849
|
+
}
|
|
551
2850
|
|
|
552
|
-
|
|
2851
|
+
GraphEditor.registerCustomNode( "logic/NotEqual", NodeNotEqual );
|
|
553
2852
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
2853
|
+
class NodeLess extends NodeEqual
|
|
2854
|
+
{
|
|
2855
|
+
logicOp( a, b ) {
|
|
2856
|
+
return a < b;
|
|
2857
|
+
}
|
|
2858
|
+
}
|
|
558
2859
|
|
|
559
|
-
|
|
560
|
-
// ctx.beginPath();
|
|
561
|
-
// ctx.moveTo(start.x, start.y);
|
|
562
|
-
// ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
|
|
563
|
-
// ctx.stroke();
|
|
2860
|
+
GraphEditor.registerCustomNode( "logic/Less", NodeLess );
|
|
564
2861
|
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
2862
|
+
class NodeLessEqual extends NodeEqual
|
|
2863
|
+
{
|
|
2864
|
+
logicOp( a, b ) {
|
|
2865
|
+
return a <= b;
|
|
2866
|
+
}
|
|
2867
|
+
}
|
|
571
2868
|
|
|
572
|
-
|
|
2869
|
+
GraphEditor.registerCustomNode( "logic/LessEqual", NodeLessEqual );
|
|
573
2870
|
|
|
2871
|
+
class NodeGreater extends NodeEqual
|
|
2872
|
+
{
|
|
2873
|
+
logicOp( a, b ) {
|
|
2874
|
+
return a > b;
|
|
2875
|
+
}
|
|
2876
|
+
}
|
|
574
2877
|
|
|
575
|
-
|
|
576
|
-
|
|
2878
|
+
GraphEditor.registerCustomNode( "logic/Greater", NodeGreater );
|
|
2879
|
+
|
|
2880
|
+
class NodeGreaterEqual extends NodeEqual
|
|
2881
|
+
{
|
|
2882
|
+
logicOp( a, b ) {
|
|
2883
|
+
return a >= b;
|
|
577
2884
|
}
|
|
2885
|
+
}
|
|
578
2886
|
|
|
579
|
-
|
|
580
|
-
_getVisibleNodes() {
|
|
2887
|
+
GraphEditor.registerCustomNode( "logic/GreaterEqual", NodeGreaterEqual );
|
|
581
2888
|
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
2889
|
+
class NodeSelect extends GraphNode
|
|
2890
|
+
{
|
|
2891
|
+
onCreate() {
|
|
2892
|
+
this.addInput( "True", "any" );
|
|
2893
|
+
this.addInput( "False", "any" );
|
|
2894
|
+
this.addInput( "Condition", "bool" );
|
|
2895
|
+
this.addOutput( null, "any" );
|
|
2896
|
+
}
|
|
2897
|
+
|
|
2898
|
+
onExecute() {
|
|
2899
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 ), cond = this.getInput( 2 );
|
|
2900
|
+
if( a == undefined || b == undefined || cond == undefined )
|
|
2901
|
+
return;
|
|
2902
|
+
this.setOutput( 0, cond ? a : b );
|
|
2903
|
+
}
|
|
2904
|
+
}
|
|
587
2905
|
|
|
588
|
-
|
|
2906
|
+
GraphEditor.registerCustomNode( "logic/Select", NodeSelect );
|
|
2907
|
+
|
|
2908
|
+
class NodeCompare extends GraphNode
|
|
2909
|
+
{
|
|
2910
|
+
onCreate() {
|
|
2911
|
+
this.addInput( "A", "any" );
|
|
2912
|
+
this.addInput( "B", "any" );
|
|
2913
|
+
this.addInput( "True", "any" );
|
|
2914
|
+
this.addInput( "False", "any" );
|
|
2915
|
+
this.addProperty( "Condition", "select", 'Equal', [ 'Equal', 'Not Equal', 'Less', 'Less Equal', 'Greater', 'Greater Equal' ] );
|
|
2916
|
+
this.addOutput( null, "any" );
|
|
2917
|
+
}
|
|
2918
|
+
|
|
2919
|
+
onExecute() {
|
|
2920
|
+
var a = this.getInput( 0 ), b = this.getInput( 1 ), TrueVal = this.getInput( 2 ), FalseVal = this.getInput( 3 );;
|
|
2921
|
+
var cond = this.properties[ 0 ].value;
|
|
2922
|
+
if( a == undefined || b == undefined || TrueVal == undefined || FalseVal == undefined )
|
|
2923
|
+
return;
|
|
2924
|
+
var output;
|
|
2925
|
+
switch( cond ) {
|
|
2926
|
+
case 'Equal': output = ( a == b ? TrueVal : FalseVal ); break;
|
|
2927
|
+
case 'Not Equal': output = ( a != b ? TrueVal : FalseVal ); break;
|
|
2928
|
+
case 'Less': output = ( a < b ? TrueVal : FalseVal ); break;
|
|
2929
|
+
case 'Less Equal': output = ( a <= b ? TrueVal : FalseVal ); break;
|
|
2930
|
+
case 'Greater': output = ( a > b ? TrueVal : FalseVal ); break;
|
|
2931
|
+
case 'Greater Equal': output = ( a >= b ? TrueVal : FalseVal ); break;
|
|
2932
|
+
}
|
|
2933
|
+
this.setOutput( 0, output );
|
|
2934
|
+
}
|
|
2935
|
+
}
|
|
2936
|
+
NodeCompare.description = "Compare A to B given the selected operator. If true, return value of True else return value of False."
|
|
2937
|
+
GraphEditor.registerCustomNode( "logic/Compare", NodeCompare );
|
|
2938
|
+
|
|
2939
|
+
/*
|
|
2940
|
+
Event nodes
|
|
2941
|
+
*/
|
|
2942
|
+
|
|
2943
|
+
class NodeKeyDown extends GraphNode
|
|
2944
|
+
{
|
|
2945
|
+
onCreate() {
|
|
2946
|
+
this.addOutput( null, "bool" );
|
|
2947
|
+
this.addProperty( "Key", "string", " " );
|
|
2948
|
+
}
|
|
2949
|
+
|
|
2950
|
+
onExecute() {
|
|
2951
|
+
this.setOutput( 0, !!this.editor.keys[ this.properties[ 0 ].value ] );
|
|
589
2952
|
}
|
|
2953
|
+
}
|
|
2954
|
+
|
|
2955
|
+
GraphEditor.registerCustomNode( "events/KeyDown", NodeKeyDown );
|
|
590
2956
|
|
|
2957
|
+
/*
|
|
2958
|
+
Input nodes
|
|
2959
|
+
*/
|
|
2960
|
+
|
|
2961
|
+
class NodeString extends GraphNode
|
|
2962
|
+
{
|
|
2963
|
+
onCreate() {
|
|
2964
|
+
this.addOutput( null, "string" );
|
|
2965
|
+
this.addProperty( null, "string", "text" );
|
|
2966
|
+
}
|
|
2967
|
+
|
|
2968
|
+
onExecute() {
|
|
2969
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
2970
|
+
}
|
|
591
2971
|
}
|
|
592
2972
|
|
|
593
|
-
|
|
2973
|
+
GraphEditor.registerCustomNode( "inputs/String", NodeString );
|
|
594
2974
|
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
2975
|
+
class NodeFloat extends GraphNode
|
|
2976
|
+
{
|
|
2977
|
+
onCreate() {
|
|
2978
|
+
this.addOutput( null, "float" );
|
|
2979
|
+
this.addProperty( null, "float", 0.0 );
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2982
|
+
onExecute() {
|
|
2983
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
2984
|
+
}
|
|
2985
|
+
}
|
|
598
2986
|
|
|
599
|
-
|
|
2987
|
+
GraphEditor.registerCustomNode( "inputs/Float", NodeFloat );
|
|
600
2988
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
2989
|
+
class NodeVector2 extends GraphNode
|
|
2990
|
+
{
|
|
2991
|
+
onCreate() {
|
|
2992
|
+
this.addOutput( "Value", "vec2" );
|
|
2993
|
+
this.addProperty( "Value", "vec2", [ 0, 0 ] );
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2996
|
+
onExecute() {
|
|
2997
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
2998
|
+
}
|
|
2999
|
+
}
|
|
605
3000
|
|
|
606
|
-
|
|
3001
|
+
GraphEditor.registerCustomNode( "inputs/Vector2", NodeVector2 );
|
|
607
3002
|
|
|
608
|
-
|
|
3003
|
+
class NodeVector3 extends GraphNode
|
|
3004
|
+
{
|
|
3005
|
+
onCreate() {
|
|
3006
|
+
this.addOutput( "Value", "vec3" );
|
|
3007
|
+
this.addProperty( "Value", "vec3", [ 0, 0, 0 ] );
|
|
3008
|
+
}
|
|
3009
|
+
|
|
3010
|
+
onExecute() {
|
|
3011
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
3012
|
+
}
|
|
3013
|
+
}
|
|
609
3014
|
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
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
|
-
];
|
|
3015
|
+
GraphEditor.registerCustomNode( "inputs/Vector3", NodeVector3 );
|
|
3016
|
+
|
|
3017
|
+
class NodeVector4 extends GraphNode
|
|
3018
|
+
{
|
|
3019
|
+
onCreate() {
|
|
3020
|
+
this.addOutput( "Value", "vec4" );
|
|
3021
|
+
this.addProperty( "Value", "vec4", [ 0, 0, 0, 0 ] );
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
onExecute() {
|
|
3025
|
+
this.setOutput( 0, this.properties[ 0 ].value );
|
|
692
3026
|
}
|
|
693
3027
|
}
|
|
694
3028
|
|
|
695
|
-
|
|
3029
|
+
GraphEditor.registerCustomNode( "inputs/Vector4", NodeVector4 );
|
|
696
3030
|
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
3031
|
+
/*
|
|
3032
|
+
Variable nodes
|
|
3033
|
+
*/
|
|
700
3034
|
|
|
701
|
-
class GraphNode
|
|
3035
|
+
class NodeSetVariable extends GraphNode
|
|
3036
|
+
{
|
|
3037
|
+
onCreate() {
|
|
3038
|
+
this.addInput( "Value", "any" );
|
|
3039
|
+
this.addOutput( null, "any" );
|
|
3040
|
+
this.addProperty( "Name", "string", "" );
|
|
3041
|
+
}
|
|
3042
|
+
|
|
3043
|
+
onExecute() {
|
|
3044
|
+
var varName = this.getInput( 0 );
|
|
3045
|
+
if( varName == undefined )
|
|
3046
|
+
return;
|
|
3047
|
+
var varValue = this.getInput( 1 );
|
|
3048
|
+
if( varValue == undefined )
|
|
3049
|
+
return;
|
|
3050
|
+
this.graph.setVariable( varName, varValue );
|
|
3051
|
+
this.setOutput( 0, varValue );
|
|
3052
|
+
}
|
|
3053
|
+
}
|
|
702
3054
|
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
*
|
|
706
|
-
*/
|
|
3055
|
+
NodeSetVariable.title = "Set Variable";
|
|
3056
|
+
GraphEditor.registerCustomNode( "variables/SetVariable", NodeSetVariable );
|
|
707
3057
|
|
|
708
|
-
|
|
3058
|
+
class NodeGetVariable extends GraphNode
|
|
3059
|
+
{
|
|
3060
|
+
onCreate() {
|
|
3061
|
+
this.addOutput( null, "any" );
|
|
3062
|
+
this.addProperty( "Name", "string", "" );
|
|
3063
|
+
}
|
|
709
3064
|
|
|
710
|
-
|
|
711
|
-
this.
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
3065
|
+
onExecute() {
|
|
3066
|
+
var varName = this.getInput( 0 );
|
|
3067
|
+
if( varName == undefined )
|
|
3068
|
+
return;
|
|
3069
|
+
var data = this.editor.getVariable( varName );
|
|
3070
|
+
if( data != undefined )
|
|
3071
|
+
this.setOutput( 0, data );
|
|
717
3072
|
}
|
|
3073
|
+
}
|
|
3074
|
+
|
|
3075
|
+
NodeGetVariable.title = "Get Variable";
|
|
3076
|
+
GraphEditor.registerCustomNode( "variables/GetVariable", NodeGetVariable );
|
|
718
3077
|
|
|
719
|
-
|
|
3078
|
+
/*
|
|
3079
|
+
System nodes
|
|
3080
|
+
*/
|
|
720
3081
|
|
|
721
|
-
|
|
3082
|
+
class NodeConsoleLog extends GraphNode
|
|
3083
|
+
{
|
|
3084
|
+
onCreate() {
|
|
3085
|
+
this.addInput( null, "any" );
|
|
3086
|
+
}
|
|
3087
|
+
|
|
3088
|
+
onExecute() {
|
|
3089
|
+
var data = this.getInput( 0 );
|
|
3090
|
+
if( data == undefined )
|
|
3091
|
+
return;
|
|
3092
|
+
console.log( data );
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
722
3095
|
|
|
723
|
-
|
|
724
|
-
|
|
3096
|
+
NodeConsoleLog.title = "Console Log";
|
|
3097
|
+
GraphEditor.registerCustomNode( "system/ConsoleLog", NodeConsoleLog );
|
|
725
3098
|
|
|
726
|
-
|
|
3099
|
+
class NodeMain extends GraphNode
|
|
3100
|
+
{
|
|
3101
|
+
onCreate() {
|
|
3102
|
+
this.addInput( "a", "float" );
|
|
3103
|
+
this.addInput( "b", "bool" );
|
|
3104
|
+
this.addInput( "Color", "vec4" );
|
|
727
3105
|
}
|
|
3106
|
+
|
|
3107
|
+
onExecute() {
|
|
3108
|
+
var data = this.getInput( 2 );
|
|
3109
|
+
if( data == undefined )
|
|
3110
|
+
return;
|
|
3111
|
+
console.log( data );
|
|
3112
|
+
};
|
|
728
3113
|
}
|
|
729
3114
|
|
|
730
|
-
|
|
3115
|
+
GraphEditor.registerCustomNode( "system/Main", NodeMain );
|
|
731
3116
|
|
|
732
3117
|
export { GraphEditor, Graph, GraphNode };
|