lexgui 0.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.
@@ -0,0 +1,534 @@
1
+ (function(global){
2
+
3
+ if(!global.LX) {
4
+ throw("lexgui.js missing!");
5
+ }
6
+
7
+ LX.components.push( 'Graph' );
8
+
9
+ function flushCss(element) {
10
+ // By reading the offsetHeight property, we are forcing
11
+ // the browser to flush the pending CSS changes (which it
12
+ // does to ensure the value obtained is accurate).
13
+ element.offsetHeight;
14
+ }
15
+
16
+ function swapElements (obj, a, b) {
17
+ [obj[a], obj[b]] = [obj[b], obj[a]];
18
+ }
19
+
20
+ function swapArrayElements (array, id0, id1) {
21
+ [array[id0], array[id1]] = [array[id1], array[id0]];
22
+ };
23
+
24
+ function sliceChar(str, idx) {
25
+ return str.substr(0, idx) + str.substr(idx + 1);
26
+ }
27
+
28
+ function firstNonspaceIndex(str) {
29
+ return str.search(/\S|$/);
30
+ }
31
+
32
+ let ASYNC_ENABLED = true;
33
+
34
+ function doAsync( fn, ms ) {
35
+ if( ASYNC_ENABLED )
36
+ setTimeout( fn, ms ?? 0 );
37
+ else
38
+ fn();
39
+ }
40
+
41
+ /**
42
+ * @class GraphCanvas
43
+ */
44
+
45
+ class GraphCanvas {
46
+
47
+ static __instances = [];
48
+
49
+ static BACK_IMAGE_SRC = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAAAXNSR0IArs4c6QAAAplJREFUeF7tm8FqwzAQRG2DDynEvaT//5fNKXFRCqZQkjLSyEzxC/Qmb5eZN9pYasfL5WMd+MQoMBZDxnEc5nl+2tS6rkP5mabpZeNlzf1+f6wrNZ99jrau6HC73f7UpazbDDmfFwsl1+vncDq9WWqVIkerhyGN6LiBwRAM0RRwE5hej4RofPxa7TYYQzBEU8BNYHo9EqLxsc+WVX7Lsrw3tvb9eDqB6f2RkEYM3QZjCIZoCrgJTK9HQjQ+9hnq5WSWw8U6Z9yJIyF1PmxPYUiYgBiCIZoCbmKOVu8xQ4rkr65wNUtY3aIAQ71FvQ5HRRiCIZoCh5whvBhqkPxc7QaGLaveiy7XDRiCIZoC7i0hvR4J0fjgtDedaHd/25s6d+p1UeliCF9768zo8UcdzJB6L/ja26hdFwHZshpdcQvorseWFWYwhmCIpoB7S0ivR0I0PnhTTyfa3R936o0JcT/OltWoaJeEcHRS7wqG1GvHm3qjdv9CQBLS6LJbQHc9hnqYwRiCIZoC7i0hvR4J0fjY5+ik/Bbu1OuccSeOhNT5sD2FIWECYgiGaAq4iTlaPWaIxts+37I47a13xZ1gElLvRZfDTwzBEE0B95aQXo87dY2P7qvZsholdicOQzBEU8BNYHo9EqLxwYthOtHu/kgICdEUcBOYXo+EaHwwQ9KJdvfH/6mTEE0BN4Hp9ZghGh/MkHSi3f2REBKiKeAmML0eCdH4YIakE+3uj4SQEE0BN4Hp9bhT1/jovpotq1Fid+IwBEM0BdwEptcjIRofvIekE+3uj4SQEE0BN4Hp9UiIxgczJJ1od38khIRoCrgJTK9HQjQ+mCHpRLv7IyEkRFPATWB6PRKi8cEMSSfa3R8JISGaAm4C0+t9AfQNJzgm7/oIAAAAAElFTkSuQmCC";
50
+
51
+ // Node Drawing
52
+ static NODE_TITLE_HEIGHT = 24;
53
+ static NODE_ROW_HEIGHT = 16;
54
+
55
+ static NODE_SHAPE_RADIUS = 4;
56
+ static NODE_TITLE_RADIUS = [GraphCanvas.NODE_SHAPE_RADIUS, GraphCanvas.NODE_SHAPE_RADIUS, 0, 0];
57
+ static NODE_BODY_RADIUS = [GraphCanvas.NODE_SHAPE_RADIUS, GraphCanvas.NODE_SHAPE_RADIUS, GraphCanvas.NODE_SHAPE_RADIUS, GraphCanvas.NODE_SHAPE_RADIUS];
58
+
59
+ static DEFAULT_NODE_TITLE_COLOR = "#4a59b0";
60
+ static DEFAULT_NODE_BODY_COLOR = "#111";
61
+
62
+ /**
63
+ * @param {*} options
64
+ *
65
+ */
66
+
67
+ constructor( area, options = {} ) {
68
+
69
+ GraphCanvas.__instances.push( this );
70
+
71
+ this.base_area = area;
72
+ this.area = new LX.Area( { className: "lexGraph" } );
73
+
74
+ area.root.classList.add('grapharea');
75
+
76
+ this.root = this.area.root;
77
+ area.attach( this.root );
78
+
79
+ // Bind resize
80
+ area.onresize = ( bb ) => {
81
+ this.dom.width = bb.width;
82
+ this.dom.height = bb.height;
83
+ this._backDirty = true;
84
+ this._frontDirty = true;
85
+ };
86
+
87
+ this.root.addEventListener( 'keydown', this._processKey.bind(this), true);
88
+ this.root.addEventListener( 'mousedown', this._processMouse.bind(this) );
89
+ this.root.addEventListener( 'mouseup', this._processMouse.bind(this) );
90
+ this.root.addEventListener( 'mousemove', this._processMouse.bind(this) );
91
+ this.root.addEventListener( 'click', this._processMouse.bind(this) );
92
+ this.root.addEventListener( 'contextmenu', this._processMouse.bind(this) );
93
+ this.root.addEventListener( 'focus', this._processFocus.bind(this, true) );
94
+ this.root.addEventListener( 'focusout', this._processFocus.bind(this, false) );
95
+
96
+ // State
97
+
98
+ this.drawAllFrames = false;
99
+ this.isFocused = false;
100
+ this._backDirty = true;
101
+ this._frontDirty = true;
102
+
103
+ // Canvas
104
+
105
+ this.dom = document.createElement('canvas');
106
+ this.dom.width = area.size[0];
107
+ this.dom.height = area.size[1];
108
+ this.dom.tabIndex = -1;
109
+ this.area.attach( this.dom );
110
+
111
+ this.frames = 0;
112
+ this.fps = 0;
113
+ this._lastDrawTime = 0;
114
+ this._drawTime = 0;
115
+
116
+ this.font = new FontFace("Ubuntu", "url(../data/Ubuntu-Bold.ttf)");
117
+ this.font.load().then(
118
+ ( font ) => {
119
+ document.fonts.add( font );
120
+ requestAnimationFrame( this.frame.bind(this) );
121
+ },
122
+ (err) => {
123
+ console.error(err);
124
+ },
125
+ );
126
+ }
127
+
128
+ static getInstances()
129
+ {
130
+ return GraphCanvas.__instances;
131
+ }
132
+
133
+ _processFocus( active ) {
134
+
135
+ this.isFocused = active;
136
+ }
137
+
138
+ _processKey(e) {
139
+
140
+ var key = e.key ?? e.detail.key;
141
+ console.log( key );
142
+ }
143
+
144
+ _processMouse(e) {
145
+
146
+ if( e.type == 'mousedown' )
147
+ {
148
+ this.lastMouseDown = LX.getTime();
149
+ }
150
+
151
+ else if( e.type == 'mouseup' )
152
+ {
153
+ if( (LX.getTime() - this.lastMouseDown) < 300 ) {
154
+ this._processClick(e);
155
+ }
156
+ }
157
+
158
+ else if( e.type == 'mousemove' )
159
+ {
160
+
161
+ }
162
+
163
+ else if ( e.type == 'click' ) // trip
164
+ {
165
+ switch( e.detail )
166
+ {
167
+ case LX.MOUSE_DOUBLE_CLICK:
168
+ break;
169
+ case LX.MOUSE_TRIPLE_CLICK:
170
+ break;
171
+ }
172
+ }
173
+
174
+ else if ( e.type == 'contextmenu' ) {
175
+ e.preventDefault()
176
+ this._processContextMenu( e );
177
+ }
178
+ }
179
+
180
+ _processClick( e ) {
181
+
182
+
183
+ }
184
+
185
+ _processContextMenu( e ) {
186
+
187
+ LX.addContextMenu( "Test", e, m => {
188
+ m.add( "option 1", () => { } );
189
+ m.add( "option 2", () => { } );
190
+ });
191
+ }
192
+
193
+ _forceDraw() {
194
+
195
+ this._backDirty = true;
196
+ this._frontDirty = true;
197
+ }
198
+
199
+ /**
200
+ * @method setGraph
201
+ * @param {Graph} graph:
202
+ */
203
+
204
+ setGraph( graph ) {
205
+
206
+ this.graph = graph;
207
+ }
208
+
209
+ /**
210
+ * @method clear
211
+ */
212
+
213
+ clear( ) {
214
+
215
+ }
216
+
217
+ /**
218
+ * @method frame
219
+ */
220
+
221
+ frame() {
222
+
223
+ // this.update();
224
+ this.draw();
225
+
226
+ requestAnimationFrame( this.frame.bind(this) );
227
+ }
228
+
229
+ /**
230
+ * @method update
231
+ */
232
+
233
+ update() {
234
+
235
+ console.log("Update");
236
+ }
237
+
238
+ /**
239
+ * @method draw
240
+ */
241
+
242
+ draw() {
243
+
244
+ if (!this.dom || !this.dom.width || !this.dom.height)
245
+ return;
246
+
247
+ // Count Fps
248
+ var now = LX.getTime();
249
+ this._drawTime = (now - this._lastDrawTime) * 0.001;
250
+ this._lastDrawTime = now;
251
+
252
+ // if (this.graph) {
253
+ // this.ds.computeVisibleArea(this.viewport);
254
+ // }
255
+
256
+ const forceDraw = this.drawAllFrames || (this._backDirty || this._frontDirty);
257
+
258
+ if ( forceDraw )
259
+ {
260
+ if( this._backDirty )
261
+ this._drawBack();
262
+ if ( this._frontDirty )
263
+ this._drawFront();
264
+ }
265
+
266
+ this.fps = this._drawTime ? (1.0 / this._drawTime) : 0;
267
+ this.frames += 1;
268
+ }
269
+
270
+ _drawBack() {
271
+
272
+ console.log( "_drawBack" );
273
+
274
+ var ctx = this.dom.getContext("2d");
275
+
276
+ if ( !GraphCanvas.BACK_IMAGE_SRC )
277
+ return;
278
+
279
+ ctx.imageSmoothingEnabled = false;
280
+
281
+ if ( !this._backImage ) {
282
+ this._backImage = new Image();
283
+ this._backImage.src = GraphCanvas.BACK_IMAGE_SRC;
284
+ this._backImage.onload = this._forceDraw.bind(this);
285
+ }
286
+
287
+ if ( !this._pattern && this._backImage.width > 0) {
288
+ this._pattern = ctx.createPattern(this._backImage, "repeat");
289
+ }
290
+
291
+ // Draw background
292
+
293
+ if (this._pattern) {
294
+ ctx.fillStyle = this._pattern;
295
+ ctx.fillRect(0, 0, this.dom.width, this.dom.height);
296
+ ctx.fillStyle = "transparent";
297
+ }
298
+
299
+ ctx.globalAlpha = 1.0;
300
+ ctx.imageSmoothingEnabled = true;
301
+
302
+ // Draw node connections
303
+
304
+ this._drawConnections();
305
+
306
+ this._backDirty = false;
307
+ }
308
+
309
+ _drawFront() {
310
+
311
+ console.log( "_drawFront" );
312
+
313
+ let nodes = this._getVisibleNodes();
314
+
315
+ for( let node of nodes )
316
+ {
317
+ this._drawNode( node );
318
+ }
319
+
320
+ this._frontDirty = false;
321
+ }
322
+
323
+ _resetCanvasShadows( ctx ) {
324
+
325
+ ctx.shadowOffsetX = 0;
326
+ ctx.shadowOffsetY = 0;
327
+ ctx.shadowBlur = 0;
328
+ ctx.shadowColor = "rgba(0,0,0,0)";
329
+ }
330
+
331
+ _computeNodeSize( node ) {
332
+
333
+ const ctx = this.dom.getContext("2d");
334
+ var textMetrics = ctx.measureText( node.name );
335
+
336
+ let sX = 32 + textMetrics.width * 1.475;
337
+
338
+ const rows = Math.max(1, Math.max(node.inputs.length, node.outputs.length));
339
+ let sY = rows * GraphCanvas.NODE_ROW_HEIGHT + GraphCanvas.NODE_TITLE_HEIGHT;
340
+
341
+ return [sX, sY];
342
+ }
343
+
344
+ _drawConnections() {
345
+
346
+ console.log( "_drawConnections" );
347
+
348
+ const ctx = this.dom.getContext("2d");
349
+
350
+ let nodes = this._getVisibleNodes();
351
+
352
+ let start = { x: 50, y: 20 };
353
+ let cp1 = { x: 230, y: 30 };
354
+ let cp2 = { x: 150, y: 80 };
355
+ let end = { x: 250, y: 100 };
356
+
357
+ // Cubic Bézier curve
358
+ ctx.beginPath();
359
+ ctx.moveTo(start.x, start.y);
360
+ ctx.bezierCurveTo(cp1.x, cp1.y, cp2.x, cp2.y, end.x, end.y);
361
+ ctx.stroke();
362
+
363
+ // for( let node of nodes )
364
+ // {
365
+ // // Discard nodes without inputs...
366
+ // if (!node.inputs || !node.inputs.length) {
367
+ // continue;
368
+ // }
369
+
370
+ // for (let input of node.inputs) {
371
+
372
+
373
+ // }
374
+ // }
375
+ }
376
+
377
+ _drawNode( node ) {
378
+
379
+ console.log( node.name );
380
+
381
+ // Process some attributes
382
+ node.size = node.size ?? this._computeNodeSize( node );
383
+ node.color = node.color ?? GraphCanvas.DEFAULT_NODE_BODY_COLOR;
384
+ node.titleColor = node.titleColor ?? GraphCanvas.DEFAULT_NODE_TITLE_COLOR;
385
+
386
+ let [pX, pY] = node.position;
387
+ let [sX, sY] = node.size;
388
+
389
+ const ctx = this.dom.getContext("2d");
390
+ const offsetY = GraphCanvas.NODE_TITLE_HEIGHT;
391
+
392
+ // Body
393
+
394
+ ctx.shadowBlur = 8;
395
+ ctx.shadowColor = "#000";
396
+
397
+ ctx.beginPath();
398
+ ctx.fillStyle = node.color;
399
+ ctx.roundRect( pX, pY, sX, sY, GraphCanvas.NODE_BODY_RADIUS );
400
+ ctx.fill();
401
+
402
+ this._resetCanvasShadows( ctx );
403
+
404
+ // Draw border
405
+ ctx.beginPath();
406
+ ctx.strokeStyle = "#555";
407
+ ctx.roundRect( pX, pY, sX, sY, GraphCanvas.NODE_BODY_RADIUS );
408
+ ctx.stroke();
409
+
410
+ // Title
411
+
412
+ ctx.beginPath();
413
+ var titleGrd = ctx.createLinearGradient(pX, pY, pX + sX, pY + offsetY);
414
+ titleGrd.addColorStop(0, node.color);
415
+ titleGrd.addColorStop(1, node.titleColor);
416
+
417
+ ctx.fillStyle = titleGrd;
418
+ ctx.roundRect( pX + 1, pY, sX - 2, offsetY, GraphCanvas.NODE_TITLE_RADIUS );
419
+ ctx.fill();
420
+
421
+ ctx.font = "14px Ubuntu";
422
+ ctx.fillStyle = "#ddd";
423
+ ctx.fillText( node.name, pX + 16, pY + offsetY * 0.75);
424
+ }
425
+
426
+ _getVisibleNodes() {
427
+
428
+ if( !this.graph )
429
+ {
430
+ console.warn( "No graph set" );
431
+ return [];
432
+ }
433
+
434
+ // TODO: Return the ones in the viewport
435
+ return this.graph.nodes;
436
+ }
437
+
438
+ }
439
+
440
+ LX.GraphCanvas = GraphCanvas;
441
+
442
+ /**
443
+ * @class Graph
444
+ */
445
+
446
+ class Graph {
447
+
448
+ /**
449
+ * @param {*} options
450
+ *
451
+ */
452
+
453
+ constructor( options = {} ) {
454
+
455
+ // Nodes
456
+
457
+ this.nodes = [
458
+ new GraphNode({
459
+ name: "Node 1",
460
+ xsize: [120, 100],
461
+ position: [200, 200],
462
+ inputs: [
463
+ {
464
+ name: "Speed",
465
+ type: "number"
466
+ },
467
+ {
468
+ name: "Offset",
469
+ type: "number"
470
+ }
471
+ ],
472
+ outputs: [
473
+ {
474
+ name: "Speed",
475
+ type: "number"
476
+ },
477
+ {
478
+ name: "Offset",
479
+ type: "number"
480
+ },
481
+ {
482
+ name: "Loop",
483
+ type: "bool"
484
+ }
485
+ ]
486
+ }),
487
+ new GraphNode({
488
+ name: "Node 2",
489
+ size: [120, 100],
490
+ position: [500, 350],
491
+ inputs: [],
492
+ outputs: []
493
+ })
494
+ ];
495
+ }
496
+ }
497
+
498
+ LX.Graph = Graph;
499
+
500
+ /**
501
+ * @class GraphNode
502
+ */
503
+
504
+ class GraphNode {
505
+
506
+ /**
507
+ * @param {*} options
508
+ *
509
+ */
510
+
511
+ constructor( options = {} ) {
512
+
513
+ this.name = options.name ?? "Unnamed";
514
+ this.size = options.size;
515
+ this.position = options.position ?? [0, 0];
516
+
517
+ this.inputs = options.inputs ?? [];
518
+ this.outputs = options.outputs ?? [];
519
+ }
520
+
521
+ computeSize() {
522
+
523
+ let sX = 16 + this.name.length * 10;
524
+
525
+ const rows = Math.max(1, Math.max(this.inputs.length, this.outputs.length));
526
+ let sY = rows * GraphCanvas.NODE_ROW_HEIGHT + GraphCanvas.NODE_TITLE_HEIGHT;
527
+
528
+ return [sX, sY];
529
+ }
530
+ }
531
+
532
+ LX.GraphNode = GraphNode;
533
+
534
+ })( typeof(window) != "undefined" ? window : (typeof(self) != "undefined" ? self : global ) );