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