lexgui 0.7.15 → 8.1.0

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