lexgui 8.1.2 → 8.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (65) hide show
  1. package/build/components/AlertDialog.d.ts +7 -7
  2. package/build/components/Avatar.d.ts +15 -0
  3. package/build/components/Counter.d.ts +9 -9
  4. package/build/components/Dialog.d.ts +20 -20
  5. package/build/components/Footer.d.ts +14 -14
  6. package/build/components/Menubar.d.ts +59 -59
  7. package/build/components/NodeTree.d.ts +26 -1
  8. package/build/components/Vector.d.ts +1 -0
  9. package/build/core/Area.d.ts +143 -143
  10. package/build/core/Event.d.ts +0 -20
  11. package/build/core/Namespace.js +1 -1
  12. package/build/core/Namespace.js.map +1 -1
  13. package/build/core/Panel.d.ts +538 -538
  14. package/build/extensions/AssetView.d.ts +137 -136
  15. package/build/extensions/AssetView.js +193 -155
  16. package/build/extensions/AssetView.js.map +1 -1
  17. package/build/extensions/Audio.js +163 -163
  18. package/build/extensions/Audio.js.map +1 -1
  19. package/build/extensions/CodeEditor.d.ts +358 -350
  20. package/build/extensions/CodeEditor.js +302 -270
  21. package/build/extensions/CodeEditor.js.map +1 -1
  22. package/build/extensions/DocMaker.d.ts +27 -27
  23. package/build/extensions/DocMaker.js +15 -11
  24. package/build/extensions/DocMaker.js.map +1 -1
  25. package/build/extensions/GraphEditor.js +2754 -2760
  26. package/build/extensions/GraphEditor.js.map +1 -1
  27. package/build/extensions/ImUi.js +227 -227
  28. package/build/extensions/Timeline.d.ts +668 -670
  29. package/build/extensions/Timeline.js +71 -79
  30. package/build/extensions/Timeline.js.map +1 -1
  31. package/build/extensions/VideoEditor.d.ts +38 -16
  32. package/build/extensions/VideoEditor.js +294 -180
  33. package/build/extensions/VideoEditor.js.map +1 -1
  34. package/build/extensions/index.d.ts +8 -8
  35. package/build/extensions/index.js +10 -10
  36. package/build/index.all.d.ts +2 -2
  37. package/build/index.css.d.ts +3 -4
  38. package/build/index.d.ts +57 -56
  39. package/build/lexgui.all.js +1877 -1520
  40. package/build/lexgui.all.js.map +1 -1
  41. package/build/lexgui.all.min.js +1 -1
  42. package/build/lexgui.all.module.js +1875 -1516
  43. package/build/lexgui.all.module.js.map +1 -1
  44. package/build/lexgui.all.module.min.js +1 -1
  45. package/build/lexgui.css +6123 -5556
  46. package/build/lexgui.js +997 -814
  47. package/build/lexgui.js.map +1 -1
  48. package/build/lexgui.min.css +2 -3
  49. package/build/lexgui.min.js +1 -1
  50. package/build/lexgui.module.js +995 -810
  51. package/build/lexgui.module.js.map +1 -1
  52. package/build/lexgui.module.min.js +1 -1
  53. package/changelog.md +65 -2
  54. package/demo.js +167 -65
  55. package/examples/all-components.html +40 -55
  56. package/examples/asset-view.html +27 -0
  57. package/examples/code-editor.html +12 -1
  58. package/examples/dialogs.html +13 -2
  59. package/examples/editor.html +9 -49
  60. package/examples/index.html +2 -2
  61. package/examples/side-bar.html +1 -1
  62. package/examples/timeline.html +2 -2
  63. package/examples/video-editor.html +1 -1
  64. package/examples/video-editor2.html +2 -2
  65. package/package.json +7 -4
@@ -15,24 +15,29 @@ LX.Panel;
15
15
  class TimeBar {
16
16
  static TIMEBAR_PLAY = 1;
17
17
  static TIMEBAR_TRIM = 2;
18
- static BACKGROUND_COLOR = LX.getThemeColor('global-branch-darker');
19
- static COLOR = LX.getThemeColor('global-button-color');
20
- static ACTIVE_COLOR = '#668ee4';
18
+ static BACKGROUND_COLOR = LX.getCSSVariable('secondary');
19
+ static COLOR = LX.getCSSVariable('accent');
20
+ static ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
21
21
  type = TimeBar.TIMEBAR_PLAY;
22
22
  duration = 1.0;
23
23
  canvas;
24
24
  ctx;
25
+ options;
25
26
  markerWidth = 8;
26
27
  markerHeight;
27
28
  offset;
28
29
  lineWidth;
29
30
  lineHeight;
30
- position;
31
+ linePosition;
31
32
  startX;
32
33
  endX;
33
34
  currentX;
34
35
  hovering;
35
36
  dragging;
37
+ _onMouseUpListener;
38
+ _onMouseMoveListener;
39
+ _mouseDownCanvasRect = null;
40
+ updateTheme;
36
41
  onChangeCurrent;
37
42
  onChangeStart;
38
43
  onChangeEnd;
@@ -40,46 +45,65 @@ class TimeBar {
40
45
  onMouse;
41
46
  constructor(area, type, options = {}) {
42
47
  this.type = type ?? TimeBar.TIMEBAR_PLAY;
48
+ this.options = options ?? {};
43
49
  this.duration = options.duration ?? this.duration;
44
50
  // Create canvas
45
51
  this.canvas = document.createElement('canvas');
52
+ this.canvas.style.borderRadius = '6px';
46
53
  this.canvas.width = area.size[0];
47
54
  this.canvas.height = area.size[1];
48
55
  area.attach(this.canvas);
49
56
  this.ctx = this.canvas.getContext('2d');
50
57
  this.markerWidth = options.markerWidth ?? this.markerWidth;
51
- this.markerHeight = options.markerHeight ?? (this.canvas.height * 0.5);
52
- this.offset = options.offset || (this.markerWidth * 0.5 + 5);
58
+ this.markerHeight = (options.markerHeight ?? 0.5) * this.canvas.height;
59
+ const defaultOffset = this.markerWidth * 0.5 + 5;
60
+ if (typeof (options.offset) == 'number') {
61
+ this.offset = new vec2(options.offset, options.offset);
62
+ }
63
+ else if (Array.isArray(options.offset)) {
64
+ this.offset = new vec2(options.offset[0] ?? defaultOffset, options.offset[1] ?? defaultOffset);
65
+ }
66
+ else {
67
+ this.offset = new vec2(defaultOffset, defaultOffset);
68
+ }
53
69
  // dimensions of line (not canvas)
54
- this.lineWidth = this.canvas.width - this.offset * 2;
70
+ this.lineWidth = this.canvas.width - this.offset.x * 2;
55
71
  this.lineHeight = options.barHeight ?? 5;
56
- this.position = new vec2(this.offset, this.canvas.height * 0.5 - this.lineHeight * 0.5);
57
- this.startX = this.position.x;
58
- this.endX = this.position.x + this.lineWidth;
72
+ this.linePosition = new vec2(this.offset.x, this.canvas.height * 0.5 - this.lineHeight * 0.5);
73
+ this.startX = this.linePosition.x;
74
+ this.endX = this.linePosition.x + this.lineWidth;
59
75
  this.currentX = this.startX;
60
76
  this._draw();
77
+ function updateTheme() {
78
+ TimeBar.BACKGROUND_COLOR = LX.getCSSVariable('secondary');
79
+ TimeBar.COLOR = LX.getCSSVariable('accent');
80
+ TimeBar.ACTIVE_COLOR = LX.getCSSVariable('color-blue-400');
81
+ }
82
+ this.updateTheme = updateTheme.bind(this);
83
+ LX.addSignal('@on_new_color_scheme', this.updateTheme);
61
84
  this.updateTheme();
62
- LX.addSignal('@on_new_color_scheme', () => {
63
- // Retrieve again the color using LX.getThemeColor, which checks the applied theme
64
- this.updateTheme();
65
- });
85
+ // prepare event listeners' functions
86
+ this._onMouseUpListener = this.onMouseUp.bind(this);
87
+ this._onMouseMoveListener = this.onMouseMove.bind(this);
66
88
  this.canvas.onmousedown = (e) => this.onMouseDown(e);
67
- this.canvas.onmousemove = (e) => this.onMouseMove(e);
68
- this.canvas.onmouseup = (e) => this.onMouseUp(e);
89
+ this.canvas.onmousemove = (e) => {
90
+ if (this.dragging)
91
+ return; // already handled by _onMouseMoveListener
92
+ this.onMouseMove(e);
93
+ };
69
94
  }
70
- updateTheme() {
71
- TimeBar.BACKGROUND_COLOR = LX.getThemeColor('global-color-secondary');
72
- TimeBar.COLOR = LX.getThemeColor('global-color-quaternary');
73
- TimeBar.ACTIVE_COLOR = '#668ee4';
95
+ unbind() {
96
+ removeEventListener('mousemove', this._onMouseMoveListener);
97
+ removeEventListener('mouseup', this._onMouseUpListener);
74
98
  }
75
99
  setDuration(duration) {
76
100
  this.duration = duration;
77
101
  }
78
102
  xToTime(x) {
79
- return ((x - this.offset) / (this.lineWidth)) * this.duration;
103
+ return ((x - this.offset.x) / (this.lineWidth)) * this.duration;
80
104
  }
81
105
  timeToX(time) {
82
- return (time / this.duration) * (this.lineWidth) + this.offset;
106
+ return (time / this.duration) * (this.lineWidth) + this.offset.x;
83
107
  }
84
108
  setCurrentTime(time) {
85
109
  this.currentX = this.timeToX(time);
@@ -124,16 +148,15 @@ class TimeBar {
124
148
  ctx.fillRect(0, 0, this.canvas.width, this.canvas.height);
125
149
  // Draw background timeline
126
150
  ctx.fillStyle = TimeBar.COLOR;
127
- ctx.fillRect(this.position.x, this.position.y, this.lineWidth, this.lineHeight);
151
+ ctx.fillRect(this.linePosition.x, this.linePosition.y, this.lineWidth, this.lineHeight);
128
152
  // Draw background trimed timeline
129
153
  ctx.fillStyle = TimeBar.ACTIVE_COLOR;
130
- ctx.fillRect(this.startX, this.position.y, this.endX - this.startX, this.lineHeight);
154
+ ctx.fillRect(this.startX, this.linePosition.y, this.endX - this.startX, this.lineHeight);
131
155
  ctx.restore();
132
156
  // Min-Max time markers
133
157
  this._drawTrimMarker('start', this.startX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9' });
134
158
  this._drawTrimMarker('end', this.endX, { color: null, fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9' });
135
- this._drawTimeMarker('current', this.currentX, { color: '#e5e5e5',
136
- fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9', width: this.markerWidth });
159
+ this._drawTimeMarker('current', this.currentX, { color: '#e5e5e5', fillColor: TimeBar.ACTIVE_COLOR || '#5f88c9', width: this.markerWidth });
137
160
  if (this.onDraw) {
138
161
  this.onDraw();
139
162
  }
@@ -165,9 +188,9 @@ class TimeBar {
165
188
  ctx.shadowBlur = 0;
166
189
  }
167
190
  _drawTimeMarker(name, x, options = {}) {
168
- let y = this.offset;
191
+ let y = this.offset.y;
169
192
  const w = options.width ? options.width : (this.dragging == name ? 6 : 4);
170
- const h = this.canvas.height - this.offset * 2;
193
+ this.canvas.height - this.offset.y * 2;
171
194
  let ctx = this.ctx;
172
195
  if (!ctx)
173
196
  return;
@@ -182,15 +205,14 @@ class TimeBar {
182
205
  ctx.fillStyle = ctx.strokeStyle = 'white';
183
206
  ctx.beginPath();
184
207
  ctx.moveTo(x, y);
185
- ctx.lineTo(x, y + h * 0.5);
208
+ ctx.lineTo(x, this.linePosition.y + this.lineHeight * 0.5);
186
209
  ctx.stroke();
187
210
  ctx.closePath();
188
211
  ctx.fillStyle = ctx.strokeStyle = options.fillColor || '#111'; // "#FFF";
189
- y -= this.offset + 8;
190
212
  // Current time ball grab
191
213
  ctx.fillStyle = options.fillColor || '#e5e5e5';
192
214
  ctx.beginPath();
193
- ctx.roundRect(x - w * 0.5, y + this.offset, w, w, 5);
215
+ ctx.roundRect(x - w * 0.5, y - w * 0.5, w, w, 5);
194
216
  ctx.fill();
195
217
  ctx.shadowBlur = 0;
196
218
  }
@@ -212,13 +234,11 @@ class TimeBar {
212
234
  const y = e.offsetY;
213
235
  // Check if some marker is clicked
214
236
  const threshold = this.markerWidth;
237
+ const startDist = Math.abs(this.startX - x);
238
+ const endDist = Math.abs(this.endX - x);
215
239
  // grab trim markers only from the bottom
216
- if (Math.abs(this.startX - x) < threshold && this.position.y < y) {
217
- this.dragging = 'start';
218
- canvas.style.cursor = 'grabbing';
219
- }
220
- else if (Math.abs(this.endX - x) < threshold && this.position.y < y) {
221
- this.dragging = 'end';
240
+ if ((startDist < threshold || endDist < threshold) && this.linePosition.y < y) {
241
+ this.dragging = (startDist < endDist || x < this.startX) ? 'start' : 'end';
222
242
  canvas.style.cursor = 'grabbing';
223
243
  }
224
244
  else {
@@ -235,9 +255,14 @@ class TimeBar {
235
255
  }
236
256
  this.onSetCurrentValue(this.currentX);
237
257
  }
258
+ this._mouseDownCanvasRect = canvas.getBoundingClientRect(); // cache this to avoid stalls during mousemove
259
+ window.addEventListener('mousemove', this._onMouseMoveListener);
260
+ window.addEventListener('mouseup', this._onMouseUpListener);
238
261
  this._draw();
239
262
  }
240
263
  onMouseUp(e) {
264
+ window.removeEventListener('mousemove', this._onMouseMoveListener);
265
+ window.removeEventListener('mouseup', this._onMouseUpListener);
241
266
  if (this.onMouse) {
242
267
  this.onMouse(e);
243
268
  }
@@ -260,17 +285,17 @@ class TimeBar {
260
285
  e.preventDefault();
261
286
  const canvas = this.canvas;
262
287
  // Process mouse
263
- const x = e.target == canvas ? e.offsetX : e.clientX - canvas.offsetLeft;
264
- e.target == canvas ? e.offsetY : e.clientY - canvas.offsetTop;
288
+ const x = e.target == canvas ? e.offsetX : (e.clientX - this._mouseDownCanvasRect.left);
289
+ e.target == canvas ? e.offsetY : (e.clientY - this._mouseDownCanvasRect.top);
265
290
  if (this.dragging) {
266
291
  switch (this.dragging) {
267
292
  case 'start':
268
- this.startX = Math.max(this.position.x, Math.min(this.endX, x));
293
+ this.startX = Math.max(this.linePosition.x, Math.min(this.endX, x));
269
294
  this.currentX = this.startX;
270
295
  this.onSetStartValue(this.startX);
271
296
  break;
272
297
  case 'end':
273
- this.endX = Math.max(this.startX, Math.min(this.position.x + this.lineWidth, x));
298
+ this.endX = Math.max(this.startX, Math.min(this.linePosition.x + this.lineWidth, x));
274
299
  this.currentX = this.endX;
275
300
  this.onSetEndValue(this.endX);
276
301
  break;
@@ -304,15 +329,18 @@ class TimeBar {
304
329
  resize(size) {
305
330
  this.canvas.width = Math.max(0, size[0]);
306
331
  this.canvas.height = Math.max(0, size[1]);
307
- let newWidth = size[0] - this.offset * 2;
332
+ this.markerHeight = (this.options.markerHeight ?? 0.5) * this.canvas.height;
333
+ let newWidth = size[0] - this.offset.x * 2;
308
334
  newWidth = newWidth < 0.00001 ? 0.00001 : newWidth; // actual width of the line = canvas.width - offsetleft - offsetRight
309
- const startRatio = (this.startX - this.offset) / this.lineWidth;
310
- const currentRatio = (this.currentX - this.offset) / this.lineWidth;
311
- const endRatio = (this.endX - this.offset) / this.lineWidth;
335
+ const startRatio = (this.startX - this.offset.x) / this.lineWidth;
336
+ const currentRatio = (this.currentX - this.offset.x) / this.lineWidth;
337
+ const endRatio = (this.endX - this.offset.x) / this.lineWidth;
312
338
  this.lineWidth = newWidth;
313
- this.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset;
314
- this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset;
315
- this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset;
339
+ this.linePosition.x = this.offset.x;
340
+ this.linePosition.y = this.canvas.height * 0.5 - this.lineHeight * 0.5;
341
+ this.startX = Math.min(Math.max(newWidth * startRatio, 0), newWidth) + this.offset.x;
342
+ this.currentX = Math.min(Math.max(newWidth * currentRatio, 0), newWidth) + this.offset.x;
343
+ this.endX = Math.min(Math.max(newWidth * endRatio, 0), newWidth) + this.offset.x;
316
344
  this._draw();
317
345
  }
318
346
  }
@@ -333,10 +361,7 @@ class VideoEditor {
333
361
  playing = false;
334
362
  videoReady = false;
335
363
  controls = true;
336
- startTimeString = '0:0';
337
- endTimeString = '0:0';
338
364
  speed = 1.0;
339
- currentTime = 0.0;
340
365
  startTime = 0.0;
341
366
  endTime = 0.0;
342
367
  requestId;
@@ -347,27 +372,29 @@ class VideoEditor {
347
372
  crop = false;
348
373
  dragOffsetX = 0.0;
349
374
  dragOffsetY = 0.0;
350
- currentTimeString = '';
351
- timebar;
375
+ timebar = null;
352
376
  mainArea;
353
377
  cropArea; // HTMLElement with normCoord attribute;
378
+ videoArea;
354
379
  controlsArea;
355
- controlsPanelLeft;
356
- controlsPanelRight;
357
- controlsCurrentPanel;
380
+ controlsComponents;
358
381
  onChangeCurrent;
359
382
  onChangeStart;
360
383
  onChangeEnd;
361
384
  onKeyUp;
362
385
  onSetTime;
363
386
  onVideoLoaded;
364
- onCropArea;
365
387
  onResize;
388
+ onCropArea;
366
389
  onChangeSpeed;
390
+ onChangeState;
391
+ onChangeLoop;
367
392
  _updateTime = true;
368
393
  _onCropMouseUp;
369
394
  _onCropMouseMove;
370
- resize;
395
+ resize = null;
396
+ resizeControls = null;
397
+ resizeVideo = null;
371
398
  constructor(area, options = {}) {
372
399
  this.options = options ?? {};
373
400
  this.speed = options.speed ?? this.speed;
@@ -379,8 +406,7 @@ class VideoEditor {
379
406
  controlsArea = options.controlsArea;
380
407
  }
381
408
  else {
382
- [videoArea, controlsArea] = area.split({ type: 'vertical', sizes: ['85%', null], minimizable: false,
383
- resize: false });
409
+ [videoArea, controlsArea] = area.split({ type: 'vertical', sizes: ['85%', null], minimizable: false, resize: false });
384
410
  }
385
411
  controlsArea.root.classList.add('lexconstrolsarea');
386
412
  this.cropArea = document.createElement('div');
@@ -401,7 +427,7 @@ class VideoEditor {
401
427
  }
402
428
  if (options.videoArea) {
403
429
  options.videoArea.root.classList.add('lexvideoeditor');
404
- options.videoArea.root.style.position = "relative";
430
+ options.videoArea.root.style.position = 'relative';
405
431
  options.videoArea.attach(this.cropArea);
406
432
  videoArea.attach(options.videoArea);
407
433
  }
@@ -410,126 +436,43 @@ class VideoEditor {
410
436
  videoArea.attach(this.cropArea);
411
437
  videoArea.root.classList.add('lexvideoeditor');
412
438
  }
413
- videoArea.root.style.position = "relative";
439
+ videoArea.root.style.position = 'relative';
440
+ this.videoArea = videoArea;
414
441
  this.controlsArea = controlsArea;
415
- // Create playing timeline area and attach panels
416
- let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null],
417
- minimizable: false, resize: false });
418
- bottomArea.setSize([bottomArea.size[0], 40]);
419
- let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null],
420
- minimizable: false, resize: false });
421
- let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null],
422
- minimizable: false, resize: false });
423
- topArea.root.classList.add('lexbar');
424
- bottomArea.root.classList.add('lexbar');
425
- this.controlsCurrentPanel = new LX.Panel({ className: 'lexcontrolspanel lextime' });
426
- this.controlsCurrentPanel.refresh = () => {
427
- this.controlsCurrentPanel.clear();
428
- this.controlsCurrentPanel.addLabel(this.currentTimeString, { float: 'center' });
429
- };
430
- topArea.root.classList.add('lexflexarea');
431
- topArea.attach(this.controlsCurrentPanel);
432
- this.controlsCurrentPanel.refresh();
433
- const style = getComputedStyle(bottomArea.root);
434
- let padding = Number(style.getPropertyValue('padding').replace('px', ''));
435
- this.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: padding });
436
- // Create controls panel (play/pause button and start time)
437
- this.controlsPanelLeft = new LX.Panel({ className: 'lexcontrolspanel' });
438
- this.controlsPanelLeft.refresh = () => {
439
- this.controlsPanelLeft.clear();
440
- this.controlsPanelLeft.sameLine();
441
- let playbtn = this.controlsPanelLeft.addButton('Play', '', (v) => {
442
- this.playing = v;
443
- if (this.playing) {
444
- if (this.video.currentTime + 0.000001 >= this.endTime) {
445
- this.video.currentTime = this.startTime;
446
- }
447
- this.video.play();
448
- }
449
- else {
450
- this.video.pause();
451
- }
452
- }, { width: '40px', icon: 'Play@solid', swap: 'Pause@solid', hideName: true,
453
- className: 'justify-center' });
454
- playbtn.setState(this.playing, true);
455
- this.controlsPanelLeft.addButton('', '', (v, e) => {
456
- const panel = new LX.Panel();
457
- panel.addRange('Speed', this.speed, (v) => {
458
- this.speed = v;
459
- this.video.playbackRate = v;
460
- if (this.onChangeSpeed) {
461
- this.onChangeSpeed(v);
462
- }
463
- }, { min: 0, max: 2.5, step: 0.01, hideName: true });
464
- new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
465
- }, { width: '40px', title: 'speed', icon: 'Timer@solid', className: 'justify-center' });
466
- this.controlsPanelLeft.addButton('', 'Loop', (v) => {
467
- this.loop = v;
468
- }, { width: '40px', title: 'loop', icon: ('Repeat@solid'), className: `justify-center`, selectable: true,
469
- selected: this.loop });
470
- this.controlsPanelLeft.addLabel(this.startTimeString, { width: '100px' });
471
- this.controlsPanelLeft.endLine();
472
- let availableWidth = leftArea.root.clientWidth - controlsLeft.root.clientWidth;
473
- this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
442
+ this.controlsComponents = {
443
+ timebar: null,
444
+ playBtn: null,
445
+ speedBtn: null,
446
+ loopBtn: null,
447
+ trimStartText: null,
448
+ trimEndText: null,
449
+ curTimeText: null,
450
+ resetCropBtn: null
474
451
  };
475
- this.controlsPanelLeft.refresh();
476
- controlsLeft.root.style.minWidth = 'fit-content';
477
- // controlsLeft.root.classList.add();
478
- controlsLeft.attach(this.controlsPanelLeft);
479
- // Create right controls panel (ens time)
480
- this.controlsPanelRight = new LX.Panel({ className: 'lexcontrolspanel' });
481
- this.controlsPanelRight.refresh = () => {
482
- this.controlsPanelRight.clear();
483
- this.controlsPanelRight.addLabel(this.endTimeString, { width: 100 });
484
- };
485
- this.controlsPanelRight.refresh();
486
- controlsRight.root.style.minWidth = 'fit-content';
487
- controlsRight.attach(this.controlsPanelRight);
488
- this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
489
- this.timebar.onChangeStart = this._setStartTime.bind(this);
490
- this.timebar.onChangeEnd = this._setEndTime.bind(this);
491
- this.resize = () => {
492
- bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
493
- let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
494
- - controlsRight.root.clientWidth;
495
- this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
452
+ this.createControls();
453
+ this.resizeVideo = () => {
496
454
  this.moveCropArea(this.cropArea.normCoords.x, this.cropArea.normCoords.y, true);
497
455
  this.resizeCropArea(this.cropArea.normCoords.w, this.cropArea.normCoords.h, true);
498
456
  if (this.onResize) {
499
457
  this.onResize([videoArea.root.clientWidth, videoArea.root.clientHeight]);
500
458
  }
501
459
  };
460
+ this.resize = () => {
461
+ this.resizeVideo();
462
+ this.resizeControls();
463
+ };
502
464
  area.onresize = this.resize.bind(this);
503
465
  window.addEventListener('resize', area.onresize);
504
466
  this.onKeyUp = (e) => {
505
467
  if (this.controls && e.key == ' ') {
506
468
  e.preventDefault();
507
469
  e.stopPropagation();
508
- this.playing = !this.playing;
509
- if (this.playing) {
510
- if (this.video.currentTime + 0.000001 >= this.endTime) {
511
- this.video.currentTime = this.startTime;
512
- }
513
- this.video.play();
514
- }
515
- else {
516
- this.video.pause();
517
- }
518
- this.controlsPanelLeft.refresh();
470
+ // do not skip callback
471
+ this.controlsComponents.playBtn?.setState(!this.playing, false);
519
472
  }
520
473
  };
521
474
  window.addEventListener('keyup', this.onKeyUp);
522
- const parent = controlsArea.parentElement ? controlsArea.parentElement : controlsArea.root.parentElement;
523
- // Add canvas event listeneres
524
- parent.addEventListener('mousedown', (e) => {
525
- // if( this.controls) {
526
- // this.timebar.onMouseDown(e);
527
- // }
528
- });
529
475
  this._onCropMouseUp = (event) => {
530
- // if(this.controls) {
531
- // this.timebar.onMouseUp(event);
532
- // }
533
476
  event.preventDefault();
534
477
  event.stopPropagation();
535
478
  if ((this.isDragging || this.isResizing) && this.onCropArea) {
@@ -541,9 +484,6 @@ class VideoEditor {
541
484
  document.removeEventListener('mousemove', this._onCropMouseMove); // self destroy. Added during mouseDown on cropArea and handles
542
485
  };
543
486
  this._onCropMouseMove = (event) => {
544
- // if(this.controls) {
545
- // this.timebar.onMouseMove(event);
546
- // }
547
487
  window.getSelection()?.removeAllRanges();
548
488
  event.preventDefault();
549
489
  event.stopPropagation();
@@ -604,6 +544,182 @@ class VideoEditor {
604
544
  this.onChangeStart = null;
605
545
  this.onChangeEnd = null;
606
546
  }
547
+ createControls(options = null) {
548
+ const controlsArea = this.controlsArea;
549
+ options = options ?? this.options;
550
+ // clear area. Signals are not cleared !!! (not a problem if there are no signals)
551
+ while (controlsArea.root.children.length) {
552
+ controlsArea.root.children[0].remove();
553
+ }
554
+ controlsArea.sections.length = 0;
555
+ // start trimming text
556
+ this.controlsComponents.trimStartText = new LX.TextInput(null, this.timeToString(this.startTime), null, { width: '100px',
557
+ title: 'Trimmed Start Time', disabled: true, inputClass: 'bg-none' });
558
+ this.controlsComponents.trimEndText = new LX.TextInput(null, this.timeToString(this.endTime), null, { width: '100px',
559
+ title: 'Trimmed End Time', disabled: true, inputClass: 'bg-none' });
560
+ this.controlsComponents.curTimeText = new LX.TextInput(null, this.video.currentTime, null, { title: 'Current Time', float: 'center',
561
+ disabled: true, inputClass: 'bg-none' });
562
+ // reset crop area
563
+ this.controlsComponents.resetCropBtn = new LX.Button('ResetCrop', null, (v) => {
564
+ this.moveCropArea(0, 0, true);
565
+ this.resizeCropArea(1, 1, true);
566
+ if (this.onCropArea) {
567
+ this.onCropArea(this.getCroppedArea());
568
+ }
569
+ }, { width: '40px', title: 'Reset Crop Area', icon: 'Crop@solid', hideName: true,
570
+ className: 'justify-center' + (this.crop ? '' : ' hidden') });
571
+ // play button
572
+ this.controlsComponents.playBtn = new LX.Button('Play', '', (v) => {
573
+ this.playing = v;
574
+ if (this.playing) {
575
+ if (this.video.currentTime + 0.000001 >= this.endTime) {
576
+ this.video.currentTime = this.startTime;
577
+ }
578
+ this.video.play();
579
+ }
580
+ else {
581
+ this.video.pause();
582
+ }
583
+ if (this.onChangeState) {
584
+ this.onChangeState(v);
585
+ }
586
+ }, { width: '40px', title: 'Play/Pause', icon: 'Play@solid', swap: 'Pause@solid', hideName: true, className: 'justify-center' });
587
+ this.controlsComponents.playBtn.setState(this.playing, true);
588
+ // speed button
589
+ this.controlsComponents.speedBtn = new LX.Button('Speed', '', (v, e) => {
590
+ const panel = new LX.Panel();
591
+ panel.addRange('Speed', this.speed, (v) => {
592
+ this.speed = v;
593
+ this.video.playbackRate = v;
594
+ if (this.onChangeSpeed) {
595
+ this.onChangeSpeed(v);
596
+ }
597
+ }, { min: 0, max: 2.5, step: 0.01, hideName: true });
598
+ new LX.Popover(e.target, [panel], { align: 'start', side: 'top', sideOffset: 12 });
599
+ }, { width: '40px', title: 'Speed', hideName: true, icon: 'Timer@solid', className: 'justify-center' });
600
+ // loop button
601
+ this.controlsComponents.loopBtn = new LX.Button('', 'Loop', (v) => {
602
+ this.loop = v;
603
+ if (this.onChangeLoop) {
604
+ this.onChangeLoop(v);
605
+ }
606
+ }, { width: '40px', hideName: true, title: 'Loop', icon: 'Repeat@solid', className: `justify-center`, selectable: true,
607
+ selected: this.loop });
608
+ let timeBarArea = null;
609
+ if (typeof (options.controlsLayout) == 'function') {
610
+ timeBarArea = options.controlsLayout;
611
+ }
612
+ else if (options.controlsLayout == 1) {
613
+ timeBarArea = this._createControlsLayout_1();
614
+ }
615
+ else {
616
+ timeBarArea = this._createControlsLayout_0();
617
+ }
618
+ if (this.timebar) {
619
+ this.timebar.unbind();
620
+ }
621
+ this.timebar = this.controlsComponents.timebar = new TimeBar(timeBarArea, TimeBar.TIMEBAR_TRIM, { offset: [12, null] });
622
+ this.timebar.onChangeCurrent = this._setCurrentTime.bind(this);
623
+ this.timebar.onChangeStart = this._setStartTime.bind(this);
624
+ this.timebar.onChangeEnd = this._setEndTime.bind(this);
625
+ let duration = 1;
626
+ if (this.video.duration !== Infinity && !isNaN(this.video.duration)) {
627
+ duration = this.video.duration;
628
+ }
629
+ this.timebar.setDuration(duration);
630
+ this.timebar.setEndTime(this.endTime);
631
+ this.timebar.setStartTime(this.startTime);
632
+ this.timebar.setCurrentTime(this.startTime);
633
+ this.resizeControls();
634
+ }
635
+ /**
636
+ * Creates the areas where components will be.
637
+ * Attaches all (desired) components of controlsComponents except the timebar
638
+ * @returns {Area} for the timebar
639
+ * Layout:
640
+ * |--------------------------timebar--------------------------|
641
+ * play speed loop resetCrop curTime trimStart / trimEnd
642
+ */
643
+ _createControlsLayout_1() {
644
+ const controlsArea = this.controlsArea;
645
+ // Create playing timeline area and attach panels
646
+ let [timeBarArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
647
+ bottomArea.root.classList.add('relative');
648
+ let separator = document.createElement('p');
649
+ separator.style.alignContent = 'center';
650
+ separator.innerText = '/';
651
+ let trimDiv = LX.makeContainer(['fit-content', '100%'], 'relative flex flex-row pb-2', null, bottomArea, { float: 'right' });
652
+ trimDiv.appendChild(this.controlsComponents.trimStartText.root);
653
+ trimDiv.appendChild(separator);
654
+ trimDiv.appendChild(this.controlsComponents.trimEndText.root);
655
+ this.controlsComponents.trimStartText.root.querySelector('input').classList.add('text-end');
656
+ this.controlsComponents.trimStartText.root.classList.add('top-0', 'bottom-0');
657
+ this.controlsComponents.trimEndText.root.classList.add('top-0', 'bottom-0');
658
+ // current time
659
+ let curTimeDiv = LX.makeContainer(['100%', '100%'], 'absolute top-0 left-0 flex flex-row justify-center items-center pb-2', null, bottomArea, {});
660
+ curTimeDiv.appendChild(this.controlsComponents.curTimeText.root);
661
+ // Buttons
662
+ const buttonsPanel = bottomArea.addPanel({ className: 'absolute top-0 left-0 flex flex-row pl-4 pr-4 pt-1 pb-2' });
663
+ buttonsPanel.root.classList.remove('pad-md');
664
+ buttonsPanel._attachComponent(this.controlsComponents.playBtn);
665
+ buttonsPanel._attachComponent(this.controlsComponents.speedBtn);
666
+ buttonsPanel._attachComponent(this.controlsComponents.loopBtn);
667
+ buttonsPanel._attachComponent(this.controlsComponents.resetCropBtn);
668
+ this.controlsComponents.playBtn.root.classList.add('pl-0');
669
+ this.controlsComponents.resetCropBtn.root.classList.add('pr-0');
670
+ // timebar
671
+ timeBarArea.root.classList.add('p-4', 'pb-0');
672
+ this.resizeControls = () => {
673
+ const style = getComputedStyle(timeBarArea.root);
674
+ let pleft = parseFloat(style.paddingLeft);
675
+ let pright = parseFloat(style.paddingRight);
676
+ let ptop = parseFloat(style.paddingTop);
677
+ let pbot = parseFloat(style.paddingBottom);
678
+ // assuming timeBarArea will not overflow
679
+ this.timebar.resize([timeBarArea.root.clientWidth - pleft - pright, timeBarArea.root.clientHeight - ptop - pbot]);
680
+ };
681
+ return timeBarArea;
682
+ }
683
+ /**
684
+ * Creates the areas where components will be.
685
+ * Attaches all (desired) components of controlsComponents except the timebar
686
+ * @returns {Area} for the timebar
687
+ * Layout:
688
+ * curTime
689
+ * play speed loop trimStart |---timebar---| trimend
690
+ */
691
+ _createControlsLayout_0() {
692
+ const controlsArea = this.controlsArea;
693
+ // Create playing timeline area and attach panels
694
+ let [topArea, bottomArea] = controlsArea.split({ type: 'vertical', sizes: ['50%', null], minimizable: false, resize: false });
695
+ bottomArea.setSize([bottomArea.size[0], 40]);
696
+ let [leftArea, controlsRight] = bottomArea.split({ type: 'horizontal', sizes: ['92%', null], minimizable: false, resize: false });
697
+ let [controlsLeft, timeBarArea] = leftArea.split({ type: 'horizontal', sizes: ['10%', null], minimizable: false, resize: false });
698
+ const controlsCurrentPanel = topArea.addPanel({ className: 'flex' });
699
+ controlsCurrentPanel._attachComponent(this.controlsComponents.curTimeText);
700
+ // Create controls panel (play/pause button and start time)
701
+ controlsLeft.root.classList.add('min-w-fit');
702
+ const controlsPanelLeft = controlsLeft.addPanel({ className: 'lexcontrolspanel p-0 pl-2' });
703
+ controlsPanelLeft.root.classList.remove('pad-md');
704
+ controlsPanelLeft.sameLine();
705
+ controlsPanelLeft._attachComponent(this.controlsComponents.playBtn);
706
+ controlsPanelLeft._attachComponent(this.controlsComponents.speedBtn);
707
+ controlsPanelLeft._attachComponent(this.controlsComponents.loopBtn);
708
+ controlsPanelLeft._attachComponent(this.controlsComponents.trimStartText);
709
+ controlsPanelLeft.endLine();
710
+ // Create right controls panel (end time)
711
+ controlsRight.root.classList.add('min-w-fit');
712
+ const controlsPanelRight = controlsRight.addPanel({ className: 'lexcontrolspanel p-0' });
713
+ controlsPanelRight.root.classList.remove('pad-md');
714
+ controlsPanelRight._attachComponent(this.controlsComponents.trimEndText);
715
+ this.resizeControls = () => {
716
+ bottomArea.setSize([this.controlsArea.root.clientWidth, 40]);
717
+ let availableWidth = this.controlsArea.root.clientWidth - controlsLeft.root.clientWidth
718
+ - controlsRight.root.clientWidth;
719
+ this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
720
+ };
721
+ return timeBarArea;
722
+ }
607
723
  setCropAreaHandles(flags) {
608
724
  // remove existing resizer handles
609
725
  const resizers = this.cropArea.getElementsByClassName('resize-handle');
@@ -732,18 +848,12 @@ class VideoEditor {
732
848
  this.video.currentTime = this.startTime;
733
849
  this.timebar.setCurrentTime(this.video.currentTime);
734
850
  };
735
- this.timebar.startX = this.timebar.position.x;
736
- this.timebar.endX = this.timebar.position.x + this.timebar.lineWidth;
737
851
  this.startTime = 0;
738
852
  this.endTime = this.video.duration;
739
853
  this.timebar.setDuration(this.endTime);
740
854
  this.timebar.setEndTime(this.video.duration);
741
855
  this.timebar.setStartTime(this.startTime);
742
856
  this.timebar.setCurrentTime(this.startTime);
743
- // this.timebar.setStartValue( this.timebar.startX);
744
- // this.timebar.currentX = this._timeToX( this.video.currentTime);
745
- // this.timebar.setCurrentValue( this.timebar.currentX);
746
- // this.timebar.update( this.timebar.currentX );
747
857
  // only have one update on flight
748
858
  if (!this.requestId) {
749
859
  this._update();
@@ -776,7 +886,7 @@ class VideoEditor {
776
886
  this.video.pause();
777
887
  if (!this.loop) {
778
888
  this.playing = false;
779
- this.controlsPanelLeft.refresh();
889
+ this.controlsComponents.playBtn?.setState(false, true); // skip callback
780
890
  }
781
891
  else {
782
892
  this.video.currentTime = this.startTime;
@@ -802,8 +912,7 @@ class VideoEditor {
802
912
  if (this.video.currentTime != t && this._updateTime) {
803
913
  this.video.currentTime = t;
804
914
  }
805
- this.currentTimeString = this.timeToString(t);
806
- this.controlsCurrentPanel.refresh();
915
+ this.controlsComponents.curTimeText?.set(this.timeToString(t));
807
916
  if (this.onSetTime) {
808
917
  this.onSetTime(t);
809
918
  }
@@ -813,8 +922,7 @@ class VideoEditor {
813
922
  }
814
923
  _setStartTime(t) {
815
924
  this.startTime = this.video.currentTime = t;
816
- this.startTimeString = this.timeToString(t);
817
- this.controlsPanelLeft.refresh();
925
+ this.controlsComponents.trimStartText?.set(this.timeToString(t));
818
926
  if (this.onSetTime) {
819
927
  this.onSetTime(t);
820
928
  }
@@ -824,8 +932,7 @@ class VideoEditor {
824
932
  }
825
933
  _setEndTime(t) {
826
934
  this.endTime = this.video.currentTime = t;
827
- this.endTimeString = this.timeToString(t);
828
- this.controlsPanelRight.refresh();
935
+ this.controlsComponents.trimEndText?.set(this.timeToString(t));
829
936
  if (this.onSetTime) {
830
937
  this.onSetTime(t);
831
938
  }
@@ -846,7 +953,9 @@ class VideoEditor {
846
953
  return this.cropArea.getBoundingClientRect();
847
954
  }
848
955
  showCropArea() {
956
+ this.crop = true;
849
957
  this.cropArea.classList.remove('hidden');
958
+ this.controlsComponents.resetCropBtn?.root.classList.remove('hidden');
850
959
  const nodes = this.cropArea.parentElement?.childNodes ?? [];
851
960
  const rect = this.cropArea.getBoundingClientRect();
852
961
  for (let i = 0; i < nodes.length; i++) {
@@ -859,7 +968,9 @@ class VideoEditor {
859
968
  }
860
969
  }
861
970
  hideCropArea() {
971
+ this.crop = false;
862
972
  this.cropArea.classList.add('hidden');
973
+ this.controlsComponents.resetCropBtn?.root.classList.add('hidden');
863
974
  const nodes = this.cropArea.parentElement?.childNodes ?? [];
864
975
  for (let i = 0; i < nodes.length; i++) {
865
976
  const node = nodes[i];
@@ -887,8 +998,11 @@ class VideoEditor {
887
998
  this.stopUpdates();
888
999
  this.video.pause();
889
1000
  this.playing = false;
890
- this.controlsPanelLeft.refresh();
1001
+ this.controlsComponents.playBtn?.setState(false, true); // skip callback
891
1002
  this.video.src = '';
1003
+ if (this.timebar) {
1004
+ this.timebar.unbind();
1005
+ }
892
1006
  window.removeEventListener('keyup', this.onKeyUp);
893
1007
  document.removeEventListener('mouseup', this._onCropMouseUp);
894
1008
  document.removeEventListener('mousemove', this._onCropMouseMove);