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