lexgui 0.1.30 → 0.1.32

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.
@@ -44,12 +44,11 @@ class Timeline {
44
44
  this.framerate = 30;
45
45
  this.opacity = options.opacity || 1;
46
46
  this.sidebarWidth = 0// 200;
47
- this.topMargin = 47;
47
+ this.topMargin = 40;
48
48
  this.renderOutFrames = false;
49
49
  this.lastMouse = [];
50
50
  this.lastKeyFramesSelected = [];
51
51
  this.tracksDrawn = [];
52
- this.buttonsDrawn = [];
53
52
  this.trackState = [];
54
53
  this.clipboard = null;
55
54
  this.grabTime = 0;
@@ -58,12 +57,20 @@ class Timeline {
58
57
  this.tracksDictionary = {};
59
58
  this._times = [];
60
59
 
60
+ this.onBeforeCreateTopBar = options.onBeforeCreateTopBar;
61
+ this.onAfterCreateTopBar = options.onAfterCreateTopBar;
62
+ this.onChangePlayMode = options.onChangePlayMode;
63
+
64
+ this.playing = false;
65
+ this.loop = options.loop ?? true;
66
+
61
67
  this.session = new Session();
62
68
 
63
69
  this.canvas = options.canvas ?? document.createElement('canvas');
64
70
 
65
71
  this.duration = 1;
66
- this.position = [options.x ?? 0, options.y ?? 0];
72
+ this.speed = 1;
73
+ this.position = [0, 0];
67
74
  this.size = [ options.width ?? 400, options.height ?? 100];
68
75
 
69
76
  this.currentScroll = 0; //in percentage
@@ -79,6 +86,7 @@ class Timeline {
79
86
  this.active = true;
80
87
  this.skipVisibility = options.skipVisibility ?? false;
81
88
  this.skipLock = options.skipLock ?? false;
89
+ this.disableNewTracks = options.disableNewTracks ?? false;
82
90
 
83
91
  this.optimizeThreshold = 0.025;
84
92
 
@@ -117,14 +125,14 @@ class Timeline {
117
125
 
118
126
  this.canvas.tabIndex = 1;
119
127
  // Process keys events
120
- this.canvas.addEventListener("keydown", this.processKeys.bind(this));
128
+ this.canvasArea.root.addEventListener("keydown", this.processKeys.bind(this));
121
129
 
122
130
  right.onresize = bounding => {
123
131
  if(!(bounding.width && bounding.height))
124
132
  return;
125
133
  this.resizeCanvas( [ bounding.width, bounding.height + this.header_offset ] );
126
134
  }
127
-
135
+ this.resize(this.size);
128
136
  }
129
137
 
130
138
  /**
@@ -144,8 +152,26 @@ class Timeline {
144
152
  let header = this.header;
145
153
  LX.DEFAULT_NAME_WIDTH = "50%";
146
154
  header.sameLine();
147
- header.addTitle(this.name);
148
-
155
+
156
+ if(this.name) {
157
+ header.addTitle(this.name);
158
+ }
159
+
160
+ header.addButton('', '<i class="fa-solid fa-'+ (this.playing ? 'pause' : 'play') +'"></i>', (value, event) => {
161
+ this.changeState();
162
+ }, {width: "40px", buttonClass: "accept"});
163
+
164
+ header.addButton('', '<i class="fa-solid fa-rotate"></i>', (value, event) => {
165
+ this.loop = !this.loop;
166
+ if(this.onChangePlayMode) {
167
+ this.onChangePlayMode(this.loop);
168
+ }
169
+
170
+ }, {width: "40px", selectable: true, selected: this.loop});
171
+
172
+ if(this.onBeforeCreateTopBar)
173
+ this.onBeforeCreateTopBar(header);
174
+
149
175
  header.addNumber("Current Time", this.currentTime, (value, event) => {
150
176
  if(value > this.duration) {
151
177
  value = this.duration;
@@ -163,10 +189,11 @@ class Timeline {
163
189
  this.setDuration(value, false)}, {step: 0.01, min: 0, signal: "@on_set_duration"
164
190
  });
165
191
 
166
- for(let i = 0; i < this.buttonsDrawn.length; i++) {
167
- let button = this.buttonsDrawn[i];
168
- this.header.addButton( button.title || "", button.name, button.callback, button);
169
- }
192
+ header.addNumber("Speed", +this.speed.toFixed(3), (value, event) => {
193
+ this.setSpeed(value)}, {step: 0.01});
194
+
195
+ if(this.onAfterCreateTopBar)
196
+ this.onAfterCreateTopBar(header);
170
197
 
171
198
  if(this.onShowOptimizeMenu)
172
199
  header.addButton("", '<i class="fa-solid fa-filter"></i>', (value, event) => {this.onShowOptimizeMenu(event)}, {width: "40px"});
@@ -193,6 +220,7 @@ class Timeline {
193
220
  }
194
221
  })
195
222
  }, {width: "40px"})
223
+
196
224
  header.endLine();
197
225
  LX.DEFAULT_NAME_WIDTH = "30%";
198
226
  }
@@ -207,15 +235,25 @@ class Timeline {
207
235
  if(this.leftPanel)
208
236
  this.leftPanel.clear();
209
237
  else {
210
- this.leftPanel = area.addPanel({className: 'lextimelinepanel', width: "100%"});
238
+ this.leftPanel = area.addPanel({className: 'lextimelinepanel', width: "100%", height: "100%"});
211
239
  }
212
240
 
213
241
  let panel = this.leftPanel;
242
+ panel.sameLine(2);
214
243
  let title = panel.addTitle("Tracks");
244
+
245
+ if(!this.disableNewTracks)
246
+ {
247
+ panel.addButton('', '<i class = "fa-solid fa-plus"></i>', (value, event) => {
248
+ this.addNewTrack();
249
+ }, {width: "40px", height: "40px"});
250
+ }
251
+ panel.endLine();
252
+
215
253
  const styles = window.getComputedStyle(title);
216
254
  const titleHeight = title.clientHeight + parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom']);
255
+
217
256
  let p = new LX.Panel({height: "calc(100% - " + titleHeight + "px)"});
218
-
219
257
  if(this.animationClip && this.selectedItems) {
220
258
  let items = {'id': '', 'children': []};
221
259
 
@@ -230,10 +268,10 @@ class Timeline {
230
268
  let track = this.tracksPerItem[selected][j];
231
269
  let id = track.type ? track.type : track.name;
232
270
 
233
- t.children.push({'id': id, 'skipVisibility': this.skipVisibility, visible: track.active, 'children':[], actions : this.skipLock ? null : [{
271
+ t.children.push({'id': id, 'skipVisibility': this.skipVisibility, visible: track.active, selected: track.isSelected, 'children':[], actions : this.skipLock ? null : [{
234
272
  'name':'Lock edition',
235
- 'icon': 'fa-solid '+ (track.locked ? 'fa-lock' : 'fa-lock-open'),
236
- 'callback': (el, node) => {
273
+ 'icon': 'fa-solid '+ (track.locked ? 'fa-lock' : 'fa-lock-open'),
274
+ 'callback': (node, el) => {
237
275
  // TO DO (apply functionality)
238
276
  let value = el.classList.contains('fa-lock');
239
277
 
@@ -269,6 +307,7 @@ class Timeline {
269
307
  switch(e.type) {
270
308
  case LX.TreeEvent.NODE_SELECTED:
271
309
  this.selectTrack(e.node);
310
+ this.updateLeftPanel();
272
311
  break;
273
312
  case LX.TreeEvent.NODE_VISIBILITY:
274
313
  this.changeTrackVisibility(e.node, e.value);
@@ -296,16 +335,6 @@ class Timeline {
296
335
  this.resizeCanvas([ this.root.root.clientWidth - this.leftPanel.root.clientWidth - 8, this.size[1]]);
297
336
  }
298
337
 
299
- /**
300
- * @method addButtons
301
- * @param buttons: array
302
- */
303
-
304
- addButtons(buttons) {
305
- this.buttonsDrawn = buttons || this.buttonsDrawn;
306
- this.updateHeader();
307
- }
308
-
309
338
  /**
310
339
  * @method addNewTrack
311
340
  */
@@ -322,6 +351,7 @@ class Timeline {
322
351
  };
323
352
 
324
353
  this.animationClip.tracks.push(trackInfo);
354
+ this.updateLeftPanel();
325
355
  return trackInfo.idx;
326
356
  }
327
357
 
@@ -359,30 +389,37 @@ class Timeline {
359
389
  /**
360
390
  * @method setAnimationClip
361
391
  * @param {*} animation
392
+ * @param {boolean} needsToProcess
362
393
  * TODO
363
394
  */
364
395
 
365
- setAnimationClip( animation ) {
366
- this.animationClip = animation;
367
- this.duration = animation.duration;
368
- this.speed = animation.speed || 1;
396
+ setAnimationClip( animation, needsToProcess = true ) {
397
+ if(!animation || !animation.tracks || needsToProcess) {
398
+ this.processTracks(animation); // generate default animationclip or process the user's one
399
+ }
400
+ else{
401
+ this.animationClip = animation;
402
+ }
403
+
404
+ this.duration = this.animationClip.duration;
405
+ this.speed = this.animationClip.speed ?? this.speed;
369
406
  var w = Math.max(300, this.canvas.width);
370
407
  this.secondsToPixels = ( w - this.session.left_margin ) / this.duration;
371
- // if(this.secondsToPixels < 1)
372
- // this.secondsToPixels = 100;
373
- // this.session.start_time = -50 / this.secondsToPixels;
374
-
375
- if(this.animationClip && this.animationClip.tracks.length)
376
- this.processTracks(animation);
377
408
 
378
- this.updateHeader();
409
+ //this.updateHeader();
379
410
  this.updateLeftPanel();
411
+
412
+ return this.animationClip;
380
413
  }
381
414
 
382
415
  drawTimeInfo (w, h = this.topMargin) {
383
416
 
384
417
  let ctx = this.canvas.getContext("2d");
418
+ ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
419
+ ctx.textAlign = "center";
420
+
385
421
  let canvas = this.canvas;
422
+
386
423
  // Draw time markers
387
424
  let startx = Math.round( this.timeToX( this.startTime ) ) + 0.5;
388
425
  let endx = Math.round( this.timeToX( this.endTime ) ) + 0.5;
@@ -393,11 +430,11 @@ class Timeline {
393
430
  ctx.save();
394
431
 
395
432
  ctx.fillStyle = Timeline.BACKGROUND_COLOR;
396
- ctx.fillRect( this.session.left_margin,0, canvas.width, h );
433
+ ctx.fillRect( this.session.left_margin, 0, canvas.width, h );
434
+ ctx.strokeStyle = LX.Timeline.FONT_COLOR;
397
435
 
398
436
  if(this.secondsToPixels > 200 )
399
437
  {
400
- ctx.strokeStyle = LX.getThemeColor("global-selected-light");
401
438
  ctx.globalAlpha = 0.5 * (1.0 - LX.UTILS.clamp( 200 / this.secondsToPixels, 0, 1));
402
439
  ctx.beginPath();
403
440
  for( let time = this.startTime; time <= this.endTime; time += 1 / this.framerate )
@@ -405,18 +442,18 @@ class Timeline {
405
442
  let x = this.timeToX( time );
406
443
  if(x < this.session.left_margin)
407
444
  continue;
408
- ctx.moveTo(Math.round(x) + 0.5, h * 0.8);
409
- ctx.lineTo(Math.round(x) + 0.5, h *0.95);
445
+ ctx.moveTo(Math.round(x) + 0.5, h * 0.9);
446
+ ctx.lineTo(Math.round(x) + 0.5, h * 0.95);
410
447
  }
411
448
  ctx.stroke();
412
449
  ctx.globalAlpha = this.opacity;
413
450
  }
414
451
 
415
452
  ctx.globalAlpha = 0.5;
416
- ctx.strokeStyle = LX.getThemeColor("global-selected-light");
417
453
  ctx.beginPath();
418
454
  let times = this._times;
419
455
  this._times.length = 0;
456
+
420
457
  for( let time = this.startTime; time <= this.endTime; time += tick_time)
421
458
  {
422
459
  let x = this.timeToX( time );
@@ -425,31 +462,28 @@ class Timeline {
425
462
  continue;
426
463
 
427
464
  let is_tick = time % 5 == 0;
428
- if(is_tick || this.secondsToPixels > 70 ) {
429
-
465
+ if(is_tick || this.secondsToPixels > 70 ) {
430
466
  times.push([x,time]);
431
- ctx.moveTo(Math.round(x) + 0.5, h * 0.4 + (is_tick ? 0 : h * 0.3) );
432
- ctx.lineTo(Math.round(x) + 0.5, h *0.95 );
467
+ ctx.moveTo(Math.round(x) + 0.5, h * 0.4 + (is_tick ? h * 0.3 : h * 0.4) );
468
+ ctx.lineTo(Math.round(x) + 0.5, h * 0.95 );
469
+ ctx.stroke();
433
470
  }
434
-
435
471
  }
436
472
 
437
473
  let x = startx;
438
- if(x < this.session.left_margin)
439
- x = this.session.left_margin;
440
- ctx.moveTo( x, h - 0.5);
474
+ if(x < this.session.left_margin) {
475
+ x = this.session.left_margin;
476
+ }
477
+ //ctx.moveTo(x, h - 0.5);
441
478
  // ctx.lineTo( endx, h - 0.5);
442
- ctx.stroke();
443
479
  ctx.globalAlpha = this.opacity;
444
480
 
445
- //time seconds in text
446
- ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
447
- ctx.textAlign = "center";
481
+ // Time seconds in text
448
482
  ctx.fillStyle = Timeline.FONT_COLOR//"#888";
449
483
  for(var i = 0; i < times.length; ++i)
450
484
  {
451
485
  let time = times[i][1];
452
- ctx.fillText( time == (time|0) ? time : time.toFixed(1), times[i][0] +12, h*0.9);
486
+ ctx.fillText( time == (time|0) ? time : time.toFixed(1), times[i][0], h * 0.6);
453
487
  }
454
488
 
455
489
  ctx.restore();
@@ -510,6 +544,8 @@ class Timeline {
510
544
  draw( currentTime = this.currentTime, rect ) {
511
545
 
512
546
  let ctx = this.canvas.getContext("2d");
547
+ ctx.textBaseline = "bottom";
548
+ ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
513
549
  if(!rect)
514
550
  rect = [0, ctx.canvas.height - ctx.canvas.height , ctx.canvas.width, ctx.canvas.height ];
515
551
 
@@ -568,28 +604,38 @@ class Timeline {
568
604
  this.drawTimeInfo(w);
569
605
 
570
606
  // Current time marker vertical line
571
- let true_pos = Math.round( this.timeToX( this.currentTime ) ) + 0.5;
572
- let quant_current_time = Math.round( this.currentTime * this.framerate ) / this.framerate;
573
- let pos = Math.round( this.timeToX( quant_current_time ) ) + 0.5; //current_time is quantized
607
+ let truePos = Math.round( this.timeToX( this.currentTime ) ) + 0.5;
608
+ let quantCurrentTime = Math.round( this.currentTime * this.framerate ) / this.framerate;
609
+ let pos = Math.round( this.timeToX( quantCurrentTime ) ) + 0.5; //current_time is quantized
610
+
611
+ let posy = this.topMargin * 0.4;
574
612
  if(pos >= this.session.left_margin)
575
613
  {
576
- // ctx.strokeStyle = "#ABA";
577
- // ctx.beginPath();
578
- // ctx.globalAlpha = 0.3;
579
- // ctx.moveTo(pos, 0); ctx.lineTo( pos, h );
580
- // ctx.stroke();
581
-
582
- ctx.strokeStyle = ctx.fillStyle = LX.getThemeColor("global-selected-light");
614
+ ctx.strokeStyle = ctx.fillStyle = LX.getThemeColor("global-selected");
583
615
  ctx.globalAlpha = this.opacity;
584
616
  ctx.beginPath();
585
- ctx.moveTo(true_pos, 0); ctx.lineTo(true_pos, this.canvas.height);//line
617
+ ctx.moveTo(truePos, posy * 0.6); ctx.lineTo(truePos, this.canvas.height);//line
586
618
  ctx.stroke();
587
- ctx.beginPath();
588
- ctx.moveTo(true_pos - 4, 0); ctx.lineTo(true_pos + 4, 0); ctx.lineTo(true_pos + 4, 10); ctx.lineTo(true_pos + 2, 12);ctx.lineTo(true_pos - 2, 12); ctx.lineTo(true_pos - 4, 10); //triangle
589
619
  ctx.closePath();
620
+ ctx.shadowBlur = 8;
621
+ ctx.shadowColor = LX.getThemeColor("global-selected");
622
+ ctx.shadowOffsetX = 1;
623
+ ctx.shadowOffsetY = 1;
624
+
625
+ ctx.roundRect( truePos - 10, posy * 0.6, 20, posy, 5, true );
590
626
  ctx.fill();
627
+ ctx.shadowBlur = 0;
628
+ ctx.shadowOffsetX = 0;
629
+ ctx.shadowOffsetY = 0;
591
630
  }
592
-
631
+
632
+ // Current time seconds in text
633
+ ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
634
+ ctx.textAlign = "center";
635
+ //ctx.textBaseline = "middle";
636
+ ctx.fillStyle = Timeline.COLOR_UNACTIVE//"#888";
637
+ ctx.fillText( this.currentTime.toFixed(1), truePos, this.topMargin * 0.6 );
638
+
593
639
  // Selections
594
640
  ctx.strokeStyle = ctx.fillStyle = Timeline.FONT_COLOR;
595
641
  ctx.translate( this.position[0], this.position[1] + this.topMargin )
@@ -681,6 +727,21 @@ class Timeline {
681
727
  return t;
682
728
  }
683
729
 
730
+
731
+ /**
732
+ * @method setSpeed
733
+ * @param {Number} speed
734
+ */
735
+
736
+ setSpeed(speed) {
737
+ this.speed = speed;
738
+ LX.emit( "@on_set_speed", +speed.toFixed(3));
739
+
740
+
741
+ if( this.onSetSpeed )
742
+ this.onSetSpeed( speed );
743
+ }
744
+
684
745
  // Converts distance in pixels to time
685
746
  xToTime( x ) {
686
747
  return (x - this.session.left_margin) / this.secondsToPixels + this.session.start_time;
@@ -734,12 +795,12 @@ class Timeline {
734
795
 
735
796
  if(!this.canvas)
736
797
  return;
737
- // e.preventDefault();
738
- // e.stopPropagation();
798
+
739
799
  e.multipleSelection = false;
740
800
 
741
801
  let h = this.canvas.height;
742
802
  let w = this.canvas.width;
803
+
743
804
  // Process mouse
744
805
  let x = e.offsetX;
745
806
  let y = e.offsetY;
@@ -748,21 +809,22 @@ class Timeline {
748
809
  let localX = e.offsetX - this.position[0];
749
810
  let localY = e.offsetY - this.position[1];
750
811
 
751
- // if(!this.grabbing_timeline && !this.movingKeys)
752
- // this.canvas.style.cursor = "default";
753
-
754
-
755
812
  let timeX = this.timeToX( this.currentTime );
756
813
  let current_grabbing_timeline = localY < this.topMargin && localX > this.session.left_margin &&
757
814
  localX > (timeX - 6) && localX < (timeX + 6);
758
815
 
759
- if( current_grabbing_timeline )
816
+ if( current_grabbing_timeline ) {
760
817
  this.canvas.style.cursor = "col-resize";
818
+ }
761
819
  else if(this.movingKeys) {
762
820
  this.canvas.style.cursor = "grabbing";
763
821
  }
764
- else
822
+ else if(e.shiftKey) {
823
+ this.canvas.style.cursor = "crosshair";
824
+ }
825
+ else {
765
826
  this.canvas.style.cursor = "default";
827
+ }
766
828
 
767
829
  if( e.type == "wheel" ) {
768
830
  if(e.shiftKey)
@@ -770,15 +832,14 @@ class Timeline {
770
832
  this.setScale( e.wheelDelta < 0 ? 0.95 : 1.05 );
771
833
  }
772
834
  else if( h < this.scrollableHeight)
773
- {
774
- this.currentScroll = LX.UTILS.clamp( this.currentScroll + (e.wheelDelta < 0 ? 0.1 : -0.1), 0, 1);
775
- this.leftPanel.root.children[1].scrollTop = this.currentScroll* (this.scrollableHeight - h);
835
+ {
836
+ this.leftPanel.root.children[1].scrollTop += e.deltaY * 0.1 ;
776
837
  }
777
838
 
778
839
  return;
779
840
  }
780
841
 
781
- var time = this.xToTime(x, true);
842
+ var time = this.xToTime(x, true);
782
843
 
783
844
  var is_inside = x >= this.position[0] && x <= (this.position[0] + this.size[0]) &&
784
845
  y >= this.position[1] && y <= (this.position[1] + this.size[1]);
@@ -798,8 +859,6 @@ class Timeline {
798
859
  e.localX = localX;
799
860
  e.localY = localY;
800
861
 
801
-
802
-
803
862
  const innerSetTime = (t) => {
804
863
  LX.emit( "@on_current_time_" + this.constructor.name, t);
805
864
  if( this.onSetTime )
@@ -833,15 +892,13 @@ class Timeline {
833
892
  return;
834
893
  }
835
894
 
836
- if( e.button == 0 && this.onMouseUp )
895
+ if( e.button == 0 && this.onMouseUp ) {
837
896
  this.onMouseUp(e, time);
897
+ }
898
+ this.unSelectAllTracks();
899
+ this.updateLeftPanel();
838
900
  }
839
-
840
- if( !is_inside && !this.grabbing && !(e.metaKey || e.altKey ) )
841
- return true;
842
-
843
- if( this.onMouse && this.onMouse( e, time, this ) )
844
- return;
901
+
845
902
 
846
903
  if( e.type == "mousedown") {
847
904
 
@@ -863,7 +920,6 @@ class Timeline {
863
920
  this.grabTime = time - this.currentTime;
864
921
  if(!track || track && this.getCurrentContent(track, time, 0.001) == undefined) {
865
922
  this.grabbing_timeline = current_grabbing_timeline;
866
-
867
923
  }
868
924
 
869
925
  if(this.onMouseDown && this.active )
@@ -890,7 +946,7 @@ class Timeline {
890
946
  }
891
947
  else if(this.grabbingScroll )
892
948
  {
893
- this.currentScroll = LX.UTILS.clamp( this.currentScroll + (e.movementY / h), 0, 1);
949
+ this.leftPanel.root.children[1].scrollTop += e.movementY ;
894
950
  }
895
951
  else
896
952
  {
@@ -900,18 +956,28 @@ class Timeline {
900
956
  this.session.start_time += (old - now);
901
957
  }
902
958
  }
903
- if(this.onMouseMove)
959
+
960
+ if(this.onMouseMove) {
904
961
  this.onMouseMove(e, time);
962
+ }
905
963
  }
906
964
  else if (e.type == "dblclick" && this.onDblClick) {
907
965
  this.onDblClick(e);
908
966
  }
909
- else if (e.type == "contextmenu" && this.showContextMenu && this.active)
967
+ else if (e.type == "contextmenu" && this.showContextMenu && this.active) {
910
968
  this.showContextMenu(e);
969
+ }
911
970
 
912
971
  this.lastMouse[0] = x;
913
972
  this.lastMouse[1] = y;
914
973
 
974
+ if( !is_inside && !this.grabbing && !(e.metaKey || e.altKey ) ) {
975
+ return true;
976
+ }
977
+
978
+ if( this.onMouse && this.onMouse( e, time, this ) )
979
+ return;
980
+
915
981
  return true;
916
982
  }
917
983
 
@@ -934,9 +1000,32 @@ class Timeline {
934
1000
  if(e.ctrlKey)
935
1001
  this.pasteContent();
936
1002
  break;
1003
+ case ' ':
1004
+ e.preventDefault();
1005
+ e.stopImmediatePropagation();
1006
+ this.changeState();
1007
+ break;
1008
+
1009
+ case "Shift":
1010
+ this.canvas.style.cursor = "crosshair";
1011
+ break;
937
1012
  }
938
1013
  }
939
1014
  }
1015
+
1016
+ /**
1017
+ * @method changeState
1018
+ * @description change play/pause state
1019
+ * ...
1020
+ **/
1021
+ changeState() {
1022
+ this.playing = !this.playing;
1023
+ this.updateHeader();
1024
+
1025
+ if(this.onChangeState) {
1026
+ this.onChangeState(this.playing);
1027
+ }
1028
+ }
940
1029
 
941
1030
  /**
942
1031
  * @method drawTrackWithKeyframes
@@ -948,72 +1037,81 @@ class Timeline {
948
1037
 
949
1038
  drawTrackWithKeyframes( ctx, y, trackHeight, title, track, trackInfo ) {
950
1039
 
951
- if(trackInfo.enabled === false)
1040
+ if(trackInfo.enabled === false) {
952
1041
  ctx.globalAlpha = 0.4;
1042
+ }
953
1043
 
954
- ctx.font = Math.floor( trackHeight * 0.8) + "px Arial";
1044
+ ctx.font = Math.floor( trackHeight * 0.8) + "px" + Timeline.FONT;
955
1045
  ctx.textAlign = "left";
956
- ctx.fillStyle = "rgba(255,255,255,0.8)";
957
-
958
- // if(title != null)
959
- // {
960
- // // var info = ctx.measureText( title );
961
- // ctx.fillStyle = this.active ? "rgba(255,255,255,0.9)" : "rgba(250,250,250,0.7)";
962
- // ctx.fillText( title, 25, y + trackHeight * 0.75 );
963
- // }
1046
+
964
1047
  ctx.globalAlpha = 0.2;
965
- ctx.fillStyle = Timeline.TRACK_SELECTED//"#2c303570";
966
- if(trackInfo.isSelected)
1048
+
1049
+ if(trackInfo.isSelected) {
1050
+ ctx.fillStyle = Timeline.TRACK_SELECTED;//"#2c303570";
967
1051
  ctx.fillRect(0, y, ctx.canvas.width, trackHeight );
1052
+ }
1053
+ ctx.fillStyle = Timeline.COLOR;//"#2c303570";
968
1054
  ctx.globalAlpha = 1;
969
- ctx.fillStyle = "#5e9fdd"//"rgba(10,200,200,1)";
970
- var keyframes = track.times;
1055
+
1056
+ let keyframes = track.times;
971
1057
 
972
- if(keyframes) {
973
-
974
- this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
975
- for(var j = 0; j < keyframes.length; ++j)
976
- {
977
- let time = keyframes[j];
978
- let selected = trackInfo.selected[j];
979
- if( time < this.startTime || time > this.endTime )
980
- continue;
981
- var keyframePosX = this.timeToX( time );
982
- if( keyframePosX > this.sidebarWidth ){
983
- ctx.save();
1058
+ if(!keyframes) {
1059
+ return;
1060
+ }
1061
+
1062
+ this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
1063
+
1064
+ for(let j = 0; j < keyframes.length; ++j)
1065
+ {
1066
+ let time = keyframes[j];
1067
+ let selected = trackInfo.selected[j];
1068
+ if( time < this.startTime || time > this.endTime ) {
1069
+ continue;
1070
+ }
1071
+ let keyframePosX = this.timeToX( time );
1072
+ if( keyframePosX > this.sidebarWidth ){
1073
+ ctx.save();
984
1074
 
985
- let margin = -1;
986
- let size = trackHeight * 0.3;
987
- if(trackInfo.edited[j])
988
- ctx.fillStyle = Timeline.COLOR_EDITED;
989
- if(selected) {
990
- ctx.fillStyle = Timeline.COLOR_SELECTED;
991
- size = trackHeight * 0.35;
992
- margin = 0;
993
- }
994
- if(trackInfo.hovered[j]) {
995
- size = trackHeight * 0.35;
996
- ctx.fillStyle = Timeline.COLOR_HOVERED;
997
- margin = 0;
998
- }
999
- if(trackInfo.locked)
1000
- ctx.fillStyle = Timeline.COLOR_LOCK;
1075
+ let margin = -1;
1076
+ let size = trackHeight * 0.3;
1077
+
1078
+ if(trackInfo.edited[j]) {
1079
+ ctx.fillStyle = Timeline.COLOR_EDITED;
1080
+ }
1081
+
1082
+ if(selected) {
1083
+ ctx.fillStyle = Timeline.COLOR_SELECTED;
1084
+ ctx.shadowBlur = 8;
1085
+ size = trackHeight * 0.35;
1086
+ margin = 0;
1087
+ }
1001
1088
 
1002
- if(!this.active || trackInfo.active == false)
1003
- ctx.fillStyle = Timeline.COLOR_UNACTIVE;
1004
-
1005
- ctx.translate(keyframePosX, y + this.trackHeight * 0.75 + margin);
1006
- ctx.rotate(45 * Math.PI / 180);
1007
- ctx.fillRect( -size, -size, size, size);
1008
- if(selected) {
1009
- ctx.globalAlpha = 0.3;
1010
- ctx.fillRect( -size*1.5, -size*1.5, size*2, size*2);
1011
- }
1012
-
1013
- ctx.restore();
1089
+ if(trackInfo.hovered[j]) {
1090
+ size = trackHeight * 0.35;
1091
+ ctx.fillStyle = Timeline.COLOR_HOVERED;
1092
+ ctx.shadowBlur = 8;
1093
+ margin = 0;
1094
+ }
1095
+ if(trackInfo.locked) {
1096
+ ctx.fillStyle = Timeline.COLOR_LOCK;
1097
+ }
1098
+
1099
+ if(!this.active || trackInfo.active == false) {
1100
+ ctx.fillStyle = Timeline.COLOR_UNACTIVE;
1014
1101
  }
1102
+
1103
+ ctx.translate(keyframePosX, y + this.trackHeight * 0.75 + margin);
1104
+ ctx.rotate(45 * Math.PI / 180);
1105
+ ctx.fillRect( -size, -size, size, size);
1106
+ if(selected) {
1107
+ ctx.globalAlpha = 0.3;
1108
+ ctx.fillRect( -size*1.1, -size*1.1, size*1.2, size*1.2);
1109
+ }
1110
+ ctx.shadowBlur = 0;
1111
+ ctx.restore();
1015
1112
  }
1016
1113
  }
1114
+
1017
1115
 
1018
1116
  ctx.globalAlpha = this.opacity;
1019
1117
  }
@@ -1027,99 +1125,129 @@ class Timeline {
1027
1125
 
1028
1126
  drawTrackWithBoxes( ctx, y, trackHeight, title, track ) {
1029
1127
 
1030
- let offset = (trackHeight - trackHeight *0.6)*0.5;
1031
- this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
1128
+ const offset = (trackHeight - trackHeight * 0.6) * 0.5;
1129
+ this.tracksDrawn.push([track, y + this.topMargin, trackHeight]);
1130
+
1032
1131
  trackHeight *= 0.6;
1132
+ this.canvas = this.canvas || ctx.canvas;
1133
+
1033
1134
  let selectedClipArea = null;
1034
1135
 
1035
- if(track.enabled === false)
1136
+ if(track.enabled === false) {
1036
1137
  ctx.globalAlpha = 0.4;
1037
- this.canvas = this.canvas || ctx.canvas;
1038
- ctx.font = Math.floor( trackHeight * 0.8) + "px Arial";
1039
- ctx.textAlign = "left";
1040
- ctx.fillStyle = "rgba(255,255,255,0.8)";
1138
+ }
1139
+ else {
1140
+ ctx.globalAlpha = 0.2;
1141
+ }
1041
1142
 
1042
- // if(title != null)
1043
- // {
1044
- // // var info = ctx.measureText( title );
1045
- // ctx.fillStyle = "rgba(255,255,255,0.9)";
1046
- // ctx.fillText( title, 25, y + trackHeight * 0.8 );
1047
- // }
1143
+ ctx.font = Math.floor( trackHeight * 0.8) + "px" + Timeline.FONT;
1144
+ ctx.textAlign = "left";
1145
+ ctx.textBaseline = "middle";
1146
+ ctx.fillStyle = Timeline.TRACK_SELECTED_LIGHT//"#2c303570";
1147
+
1148
+ // Fill track background if it's selected
1149
+ if(track.isSelected) {
1150
+ ctx.fillRect(0, y + offset - 2, ctx.canvas.width, trackHeight + 4 );
1151
+ }
1048
1152
 
1049
- ctx.fillStyle = "rgba(10,200,200,1)";
1050
1153
  var clips = track.clips;
1051
1154
  let trackAlpha = 1;
1052
1155
 
1053
- if(clips) {
1054
-
1055
- for(var j = 0; j < clips.length; ++j)
1056
- {
1057
- selectedClipArea = null;
1058
- let clip = clips[j];
1059
- //let selected = track.selected[j];
1060
- var x = Math.floor( this.timeToX(clip.start) ) + 0.5;
1061
- var x2 = Math.floor( this.timeToX( clip.start + clip.duration ) ) + 0.5;
1062
- var w = x2-x;
1063
-
1064
- if( x2 < 0 || x > this.canvas.width )
1065
- continue;
1156
+ if(!clips) {
1157
+ return;
1158
+ }
1066
1159
 
1067
- //background rect
1068
- ctx.globalAlpha = trackAlpha;
1069
- ctx.fillStyle = clip.clipColor || "#5e9fdd"//#333";
1070
- //ctx.fillRect(x,y,w,trackHeight);
1071
- ctx.roundRect( x, y + offset, w, trackHeight , 5, true);
1072
-
1073
- let fadeinX = this.secondsToPixels * ((clip.fadein || 0) - clip.start);
1074
- let fadeoutX = this.secondsToPixels * (clip.start + clip.duration - (clip.fadeout || (clip.start + clip.duration)));
1075
-
1076
- let color = LX.UTILS.HexToRgb(ctx.fillStyle);
1077
- color = color.map(x => x*=0.8);
1078
- ctx.fillStyle = 'rgba(' + color.join(',') + ', 0.8)';
1079
-
1080
- if(fadeinX>0)
1160
+ for(var j = 0; j < clips.length; ++j)
1161
+ {
1162
+ selectedClipArea = null;
1163
+ let clip = clips[j];
1164
+ //let selected = track.selected[j];
1165
+ var x = Math.floor( this.timeToX(clip.start) ) + 0.5;
1166
+ var x2 = Math.floor( this.timeToX( clip.start + clip.duration ) ) + 0.5;
1167
+ var w = x2-x;
1168
+
1169
+ if( x2 < 0 || x > this.canvas.width )
1170
+ continue;
1171
+
1172
+ // Overwrite clip color state depending on its state
1173
+ ctx.globalAlpha = trackAlpha;
1174
+ ctx.fillStyle = clip.clipColor || (track.hovered[j] ? Timeline.COLOR_HOVERED : (Timeline.COLOR));
1175
+ if(track.selected[j] && !clip.clipColor) {
1176
+ ctx.fillStyle = Timeline.TRACK_SELECTED;
1177
+ }
1178
+ if(!this.active || track.active == false) {
1179
+ ctx.fillStyle = Timeline.COLOR_UNACTIVE;
1180
+ }
1181
+
1182
+ // Draw clip background
1183
+ ctx.roundRect( x, y + offset, w, trackHeight , 5, true);
1184
+
1185
+ // Compute timeline position of fade-in and fade-out clip times
1186
+ let fadeinX = this.secondsToPixels * ((clip.fadein || 0) - clip.start);
1187
+ let fadeoutX = this.secondsToPixels * (clip.start + clip.duration - (clip.fadeout || (clip.start + clip.duration)));
1188
+
1189
+ if(this.active && track.active) {
1190
+ // Transform fade-in and fade-out fill color to RGBA
1191
+ if(ctx.fillStyle[0] == "#") {
1192
+ let color = LX.UTILS.HexToRgb(ctx.fillStyle);
1193
+ color = color.map(x => x*=0.8);
1194
+ ctx.fillStyle = 'rgba(' + color.join(',') + ', 0.8)';
1195
+ }
1196
+ else {
1197
+ ctx.globalAlpha = 0.8;
1198
+ }
1199
+
1200
+ // Draw fade-in and fade-out
1201
+ if(fadeinX >= 0) {
1081
1202
  ctx.roundRect(x, y + offset, fadeinX, trackHeight, {tl: 5, bl: 5, tr:0, br:0}, true);
1082
- if(fadeoutX)
1203
+ }
1204
+ if(fadeoutX) {
1083
1205
  ctx.roundRect( x + w - fadeoutX, y + offset, fadeoutX, trackHeight, {tl: 0, bl: 0, tr:5, br:5}, true);
1084
-
1085
- //draw clip outline
1086
- if(clip.hidden)
1087
- ctx.globalAlpha = trackAlpha * 0.5;
1088
-
1089
- ctx.globalAlpha = trackAlpha;
1090
- if(this.selectedClip == clip || track.selected[j])
1091
- selectedClipArea = [x, y + offset, x2-x, trackHeight];
1092
-
1093
- ctx.fillStyle = "black"; //Timeline.FONT_COLOR; // clip.color || Timeline.FONT_COLOR;
1094
- ctx.font = "12px" + Timeline.FONT;
1095
- //render clip selection area
1096
- if(selectedClipArea)
1097
- {
1098
- ctx.strokeStyle = track.clips[j].clipColor;
1099
- ctx.fillStyle = "white"; //(Timeline.FONT_COLOR || track.clips[j].clipColor);
1100
- ctx.globalAlpha = 0.6;
1101
- ctx.lineWidth = 2.5;
1102
- ctx.roundRect(selectedClipArea[0]-1,selectedClipArea[1]-1,selectedClipArea[2]+2,selectedClipArea[3]+2, 5, false, true);
1103
- ctx.strokeStyle = "#888";
1104
- ctx.lineWidth = 0.5;
1105
- ctx.globalAlpha = this.opacity;
1106
- ctx.font = "bold 13px " + Timeline.FONT;
1107
1206
  }
1207
+ }
1208
+
1209
+ ctx.fillStyle = clip.color || Timeline.FONT_COLOR; // clip.color || Timeline.FONT_COLOR;
1210
+ //ctx.font = "12px" + Timeline.FONT;
1108
1211
 
1109
- let text = clip.id.replaceAll("_", " ").replaceAll("-", " ");
1110
-
1111
- if( this.secondsToPixels < 200)
1112
- ctx.font = this.secondsToPixels*0.06 +"px" + Timeline.FONT;
1212
+ // Overwrite style and draw clip selection area if it's selected
1213
+ ctx.globalAlpha = clip.hidden ? trackAlpha * 0.5 : trackAlpha;
1113
1214
 
1114
- let textInfo = ctx.measureText( text );
1115
- if(this.secondsToPixels > 100)
1116
- ctx.fillText( text, x + (w - textInfo.width)*0.5, y + offset + trackHeight/2 + textInfo.fontBoundingBoxDescent );
1215
+ if(this.selectedClip == clip || track.selected[j] || track.hovered[j]) {
1216
+ ctx.strokeStyle = ctx.shadowColor = track.clips[j].clipColor || Timeline.TRACK_SELECTED;
1217
+ ctx.shadowBlur = 10;
1218
+ ctx.shadowOffsetX = 1.5;
1219
+ ctx.shadowOffsetY = 1.5;
1220
+
1221
+ selectedClipArea = [x - 1, y + offset -1, x2 - x + 2, trackHeight + 2];
1222
+ ctx.roundRect(selectedClipArea[0], selectedClipArea[1], selectedClipArea[2], selectedClipArea[3], 5, false, true);
1223
+
1224
+ ctx.shadowBlur = 0;
1225
+ ctx.shadowOffsetX = 0;
1226
+ ctx.shadowOffsetY = 0;
1117
1227
 
1118
- ctx.font = "12px" + Timeline.FONT;
1228
+ ctx.font = "bold" + Math.floor( trackHeight) + "px " + Timeline.FONT;
1229
+ ctx.fillStyle = "white";
1119
1230
  }
1231
+
1232
+ // Overwrite style with small font size if it's zoomed out
1233
+ if( this.secondsToPixels < 200) {
1234
+ ctx.font = this.secondsToPixels*0.06 +"px" + Timeline.FONT;
1235
+ }
1236
+
1237
+ const text = clip.id.replaceAll("_", " ").replaceAll("-", " ");
1238
+ const textInfo = ctx.measureText( text );
1239
+
1240
+ // Draw clip name if it's readable
1241
+ if(this.secondsToPixels > 100) {
1242
+ ctx.fillText( text, x + (w - textInfo.width)*0.5, y + offset + trackHeight * 0.5);
1243
+ }
1244
+
1245
+ ctx.fillStyle = track.hovered[j] ? "white" : Timeline.FONT_COLOR;
1246
+ // Draw resize bounding
1247
+ ctx.roundRect(x + w - 8 , y + offset , 8, trackHeight, {tl: 4, bl: 4, tr:4, br:4}, true);
1120
1248
  }
1121
1249
 
1122
- //ctx.restore();
1250
+ ctx.font = "12px" + Timeline.FONT;
1123
1251
  }
1124
1252
 
1125
1253
  /**
@@ -1228,18 +1356,15 @@ class Timeline {
1228
1356
  */
1229
1357
  resize( size = [this.root.parent.root.clientWidth, this.root.parent.root.clientHeight]) {
1230
1358
 
1231
- this.root.root.style.width = size[0] + "px";
1232
- this.root.root.style.height = size[1] + "px";
1359
+ // this.root.root.style.width = size[0] + "px";
1360
+ // this.root.root.style.height = size[1] + "px";
1233
1361
 
1234
1362
  this.size = size;
1235
- this.content_area.setSize([size[0], size[1] - this.header_offset]);
1236
-
1237
- let w = size[0] - this.leftPanel.root.clientWidth - 8;
1238
- this.resizeCanvas([w , size[1]]);
1239
-
1240
- // this.session.start_time = 0;
1241
-
1363
+ //this.content_area.setSize([size[0], size[1] - this.header_offset]);
1364
+ this.content_area.root.style.height = "calc(100% - "+ this.header_offset + "px)";
1242
1365
 
1366
+ let w = size[0] - this.leftPanel.root.clientWidth - 8;
1367
+ this.resizeCanvas([w , size[1]]);
1243
1368
  }
1244
1369
 
1245
1370
  resizeCanvas( size ) {
@@ -1252,15 +1377,11 @@ class Timeline {
1252
1377
  this.pixelsToSeconds = 1 / this.secondsToPixels;
1253
1378
  }
1254
1379
  size[1] -= this.header_offset;
1255
- this.canvasArea.setSize(size);
1256
- this.canvas.width = size[0];
1257
- this.canvas.height = size[1];
1258
-
1259
- // var centerx = this.canvas.width * 0.5;
1260
- // var x = this.xToTime( centerx );
1261
- // this.session.start_time += x - this.xToTime( centerx );
1380
+ //this.canvasArea.setSize(size);
1381
+ //this.canvasArea.root.style.height = "calc(100% - "+ this.header_offset + "px)";
1382
+ this.canvas.width = this.canvasArea.root.clientWidth;
1383
+ this.canvas.height = this.canvasArea.root.clientHeight;
1262
1384
  this.draw(this.currentTime);
1263
-
1264
1385
  }
1265
1386
 
1266
1387
  /**
@@ -1278,19 +1399,20 @@ class Timeline {
1278
1399
  show() {
1279
1400
 
1280
1401
  this.root.show();
1281
- this.resize();
1282
-
1402
+ this.updateLeftPanel();
1403
+ this.resize();
1283
1404
  }
1284
1405
  };
1285
1406
 
1286
1407
  Timeline.BACKGROUND_COLOR = LX.getThemeColor("global-color-primary");
1287
- Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor("global-color-secondary");
1408
+ Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor("global-blur-background");
1288
1409
  Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor("global-color-terciary");
1289
1410
  Timeline.TRACK_SELECTED = LX.getThemeColor("global-selected");
1411
+ Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor("global-selected-light");
1290
1412
  Timeline.FONT = LX.getThemeColor("global-font");
1291
1413
  Timeline.FONT_COLOR = LX.getThemeColor("global-text");
1292
- Timeline.COLOR = "#5e9fdd";
1293
- Timeline.COLOR_HOVERED = "rgba(250,250,250,0.7)";
1414
+ Timeline.COLOR = LX.getThemeColor("global-selected-dark");//"#5e9fdd";
1415
+ Timeline.COLOR_HOVERED = LX.getThemeColor("global-selected");
1294
1416
  Timeline.COLOR_SELECTED = "rgba(250,250,20,1)"///"rgba(250,250,20,1)";
1295
1417
  Timeline.COLOR_UNACTIVE = "rgba(250,250,250,0.7)";
1296
1418
  Timeline.COLOR_LOCK = "rgba(255,125,125,0.7)";
@@ -1319,14 +1441,8 @@ class KeyFramesTimeline extends Timeline {
1319
1441
  this.autoKeyEnabled = false;
1320
1442
 
1321
1443
 
1322
- if(this.animationClip && this.animationClip.tracks.length)
1444
+ if(this.animationClip && this.animationClip.tracks.length) {
1323
1445
  this.processTracks(this.animationClip);
1324
-
1325
- // Add button data
1326
- let offset = 25;
1327
- if(this.active)
1328
- {
1329
-
1330
1446
  }
1331
1447
  }
1332
1448
 
@@ -1363,28 +1479,17 @@ class KeyFramesTimeline extends Timeline {
1363
1479
  }
1364
1480
 
1365
1481
  }else {
1366
- let boundingBox = this.canvas.getBoundingClientRect()
1367
- if(e.y < boundingBox.top || e.y > boundingBox.bottom)
1482
+ let boundingBox = this.canvas.getBoundingClientRect();
1483
+ if(e.y < boundingBox.top || e.y > boundingBox.bottom) {
1368
1484
  return;
1485
+ }
1486
+
1369
1487
  // Check exact track keyframe
1370
1488
  if(!discard && track) {
1371
- this.processCurrentKeyFrame( e, null, track, localX );
1372
-
1489
+ this.processCurrentKeyFrame( e, null, track, localX );
1373
1490
  }
1374
1491
  else {
1375
- this.unSelectAllKeyFrames();
1376
- let x = e.offsetX;
1377
- let y = e.offsetY - this.topMargin;
1378
- for( const b of this.buttonsDrawn ) {
1379
- b.pressed = false;
1380
- const bActive = x >= b[2] && x <= (b[2] + b[4]) && y >= b[3] && y <= (b[3] + b[5]);
1381
- if(bActive) {
1382
- const callback = b[6];
1383
- if(callback) callback(e);
1384
- else this[ b[1] ] = !this[ b[1] ];
1385
- break;
1386
- }
1387
- }
1492
+ this.unSelectAllKeyFrames();
1388
1493
  }
1389
1494
  }
1390
1495
 
@@ -1423,15 +1528,6 @@ class KeyFramesTimeline extends Timeline {
1423
1528
  }
1424
1529
 
1425
1530
  this.timeBeforeMove = track.times[ keyFrameIndex ];
1426
-
1427
-
1428
- }
1429
- } else if(!track) {
1430
- let x = e.offsetX;
1431
- let y = e.offsetY - this.topMargin;
1432
- for( const b of this.buttonsDrawn ) {
1433
- const bActive = x >= b[2] && x <= (b[2] + b[4]) && y >= b[3] && y <= (b[3] + b[5]);
1434
- b.pressed = bActive;
1435
1531
  }
1436
1532
  }
1437
1533
  }
@@ -1638,7 +1734,7 @@ class KeyFramesTimeline extends Timeline {
1638
1734
  track.selected[newIdx] = true;
1639
1735
 
1640
1736
  }
1641
- LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
1737
+ //LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
1642
1738
  // Update time
1643
1739
  if(this.onSetTime)
1644
1740
  this.onSetTime(this.currentTime);
@@ -1652,48 +1748,84 @@ class KeyFramesTimeline extends Timeline {
1652
1748
  this.tracksPerItem = {};
1653
1749
  this.tracksDictionary = {};
1654
1750
  this.animationClip = {
1655
- name: animation.name,
1656
- duration: animation.duration,
1657
- speed: animation.speed ?? 1,
1751
+ name: (animation && animation.name) ? animation.name : "animationClip",
1752
+ duration: animation ? animation.duration : 0,
1753
+ speed: (animation && animation.speed ) ? animation.speed : this.speed,
1658
1754
  tracks: []
1659
1755
  };
1756
+ if (animation && animation.tracks) {
1757
+ for( let i = 0; i < animation.tracks.length; ++i ) {
1758
+
1759
+ let track = animation.tracks[i];
1760
+
1761
+ const [name, type] = this.getTrackName(track.name);
1762
+
1763
+ let trackInfo = {
1764
+ fullname: track.name,
1765
+ name: name, type: type,
1766
+ dim: track.values.length/track.times.length,
1767
+ selected: [], edited: [], hovered: [], active: true,
1768
+ times: track.times,
1769
+ values: track.values
1770
+ };
1771
+
1772
+ if(!this.tracksPerItem[name]) {
1773
+ this.tracksPerItem[name] = [trackInfo];
1774
+ }else {
1775
+ this.tracksPerItem[name].push( trackInfo );
1776
+ }
1777
+
1778
+
1779
+ const trackIndex = this.tracksPerItem[name].length - 1;
1780
+ this.tracksPerItem[name][trackIndex].idx = trackIndex;
1781
+ this.tracksPerItem[name][trackIndex].clipIdx = i;
1782
+
1783
+ // Save index also in original track
1784
+ track.idx = trackIndex;
1785
+ this.tracksDictionary[track.name] = name;
1786
+
1787
+ this.animationClip.tracks.push(trackInfo);
1788
+ }
1789
+ }
1790
+ this.resize();
1791
+ }
1660
1792
 
1661
- for( let i = 0; i < animation.tracks.length; ++i ) {
1662
-
1663
- let track = animation.tracks[i];
1793
+ updateTrack(trackIdx, track) {
1794
+ if(!this.animationClip)
1795
+ return;
1796
+ this.animationClip.tracks[trackIdx].values = track.values;
1797
+ this.animationClip.tracks[trackIdx].times = track.times;
1798
+ this.processTrack(trackIdx);
1664
1799
 
1665
- const [name, type] = this.getTrackName(track.name);
1800
+ }
1666
1801
 
1667
- let trackInfo = {
1668
- fullname: track.name,
1669
- name: name, type: type,
1670
- dim: track.values.length/track.times.length,
1671
- selected: [], edited: [], hovered: [], active: true,
1672
- times: track.times,
1673
- values: track.values
1674
- };
1675
-
1676
- if(!this.tracksPerItem[name]) {
1677
- this.tracksPerItem[name] = [trackInfo];
1678
- }else {
1679
- this.tracksPerItem[name].push( trackInfo );
1680
- }
1681
-
1802
+ processTrack(trackIdx) {
1803
+ if(!this.animationClip)
1804
+ return;
1682
1805
 
1683
- const trackIndex = this.tracksPerItem[name].length - 1;
1684
- this.tracksPerItem[name][trackIndex].idx = trackIndex;
1685
- this.tracksPerItem[name][trackIndex].clipIdx = i;
1806
+ let track = this.animationClip.tracks[trackIdx];
1686
1807
 
1687
- // Save index also in original track
1688
- track.idx = trackIndex;
1689
- this.tracksDictionary[track.name] = name;
1808
+ const [name, type] = this.getTrackName(track.fullname || track.name);
1690
1809
 
1691
- this.animationClip.tracks.push(trackInfo);
1810
+ let trackInfo = {
1811
+ fullname: track.name,
1812
+ name: name, type: type,
1813
+ dim: track.values.length/track.times.length,
1814
+ selected: [], edited: [], hovered: [], active: true,
1815
+ times: track.times,
1816
+ values: track.values
1817
+ };
1818
+
1819
+ for(let i = 0; i < this.tracksPerItem[name].length; i++) {
1820
+ if(this.tracksPerItem[name][i].fullname == trackInfo.fullname) {
1821
+ trackInfo.idx = this.tracksPerItem[name][i].idx;
1822
+ trackInfo.clipIdx = this.tracksPerItem[name][i].clipIdx;
1823
+ this.tracksPerItem[name][i] = trackInfo;
1824
+ return;
1825
+ }
1692
1826
  }
1693
- this.resize();
1694
1827
  }
1695
1828
 
1696
-
1697
1829
  optimizeTrack(trackIdx) {
1698
1830
  const track = this.animationClip.tracks[trackIdx];
1699
1831
  if(track.optimize) {
@@ -2396,35 +2528,148 @@ class ClipsTimeline extends Timeline {
2396
2528
  this.lastClipsSelected = [];
2397
2529
  }
2398
2530
 
2399
- resizeCanvas( size ) {
2400
- if( size[0] <= 0 && size[1] <=0 )
2401
- return;
2531
+ // resizeCanvas( size ) {
2532
+ // if( size[0] <= 0 && size[1] <=0 )
2533
+ // return;
2402
2534
 
2403
- size[1] -= this.header_offset;
2535
+ // size[1] -= this.header_offset;
2404
2536
 
2405
- if(Math.abs(this.canvas.width - size[0]) > 1) {
2537
+ // if(Math.abs(this.canvas.width - size[0]) > 1) {
2406
2538
 
2407
- var w = Math.max(300, size[0] );
2408
- this.secondsToPixels = ( w- this.session.left_margin ) / this.duration;
2409
- this.pixelsToSeconds = 1 / this.secondsToPixels;
2539
+ // var w = Math.max(300, size[0] );
2540
+ // this.secondsToPixels = ( w- this.session.left_margin ) / this.duration;
2541
+ // this.pixelsToSeconds = 1 / this.secondsToPixels;
2542
+ // }
2543
+
2544
+ // this.canvasArea.setSize(size);
2545
+ // this.canvas.width = size[0];
2546
+ // this.canvas.height = size[1];
2547
+ // var w = Math.max(300, this.canvas.width);
2548
+
2549
+ // this.draw(this.currentTime);
2550
+ // }
2551
+
2552
+ updateLeftPanel(area) {
2553
+
2554
+ if(this.leftPanel)
2555
+ this.leftPanel.clear();
2556
+ else {
2557
+ this.leftPanel = area.addPanel({className: 'lextimelinepanel', width: "100%", height: "100%"});
2410
2558
  }
2411
2559
 
2412
- this.canvasArea.setSize(size);
2413
- this.canvas.width = size[0];
2414
- this.canvas.height = size[1];
2415
- var w = Math.max(300, this.canvas.width);
2416
- // this.secondsToPixels = ( w - this.session.left_margin ) / this.duration;
2417
- // this.pixelsToSeconds = 1 / this.secondsToPixels;
2560
+ let panel = this.leftPanel;
2418
2561
 
2419
- let timeline_height = this.topMargin;
2420
- let line_height = this.trackHeight;
2421
- let max_tracks = Math.ceil( (size[1] - timeline_height) / line_height );
2422
- while(this.animationClip.tracks.length < max_tracks - 1) {
2423
- this.addNewTrack();
2562
+ panel.sameLine(2);
2563
+ let title = panel.addTitle("Tracks");
2564
+ if(!this.disableNewTracks)
2565
+ {
2566
+ panel.addButton('', '<i class = "fa-solid fa-plus"></i>', (value, event) => {
2567
+ this.addNewTrack();
2568
+ }, {width: "40px", height: "40px"});
2569
+ }
2570
+ panel.endLine();
2571
+ const styles = window.getComputedStyle(title);
2572
+ const titleHeight = title.clientHeight + parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom']);
2573
+ let p = new LX.Panel({height: "calc(100% - " + titleHeight + "px)"});
2574
+
2575
+ if(this.animationClip) {
2576
+
2577
+ for(let i = 0; i < this.animationClip.tracks.length; i++ ) {
2578
+ let track = this.animationClip.tracks[i];
2579
+ let t = {
2580
+ 'id': track.name ?? "Track_" + track.idx.toString(),
2581
+ 'name': track.name,
2582
+ 'skipVisibility': this.skipVisibility,
2583
+ 'visible': track.active,
2584
+ 'selected' : track.isSelected
2585
+ }
2586
+
2587
+ let tree = p.addTree(null, t, {filter: false, rename: false, draggable: false, onevent: (e) => {
2588
+ switch(e.type) {
2589
+ case LX.TreeEvent.NODE_SELECTED:
2590
+ this.selectTrack(e.node);
2591
+ break;
2592
+ case LX.TreeEvent.NODE_VISIBILITY:
2593
+ this.changeTrackVisibility(e.node, e.value);
2594
+ break;
2595
+ case LX.TreeEvent.NODE_CARETCHANGED:
2596
+ this.changeTrackDisplay(e.node, e.node.closed);
2597
+ break;
2598
+ }
2599
+ }});
2600
+ }
2601
+ }
2602
+ panel.attach(p.root)
2603
+ p.root.style.overflowY = "scroll";
2604
+ p.root.addEventListener("scroll", (e) => {
2605
+ this.currentScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
2606
+ })
2607
+ // for(let i = 0; i < this.animationClip.tracks.length; i++) {
2608
+ // let track = this.animationClip.tracks[i];
2609
+ // panel.addTitle(track.name + (track.type? '(' + track.type + ')' : ''));
2610
+ // }
2611
+ if(this.leftPanel.parent.root.classList.contains("hidden") || !this.root.root.parent)
2612
+ return;
2613
+ this.resizeCanvas([ this.root.root.clientWidth - this.leftPanel.root.clientWidth - 8, this.size[1]]);
2614
+ }
2615
+
2616
+
2617
+ /**
2618
+ * @method changeTrackVisibility
2619
+ * @param {id, parent, children, visible} trackInfo
2620
+ */
2621
+
2622
+ changeTrackVisibility(trackInfo, visible) {
2623
+ let [name, type] = trackInfo.id.split(" (");
2624
+ if(type)
2625
+ type = type.replaceAll(")", "").replaceAll(" ", "");
2626
+ else {
2627
+ type = name;
2628
+ name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
2424
2629
  }
2630
+ let id = name.split("Track_")[1];
2631
+ trackInfo = {name, type};
2632
+ let track = this.animationClip.tracks[id];
2633
+
2634
+ track.active = visible;
2635
+ trackInfo = track;
2425
2636
 
2426
- this.draw(this.currentTime);
2427
-
2637
+ this.draw();
2638
+ if(this.onChangeTrackVisibility)
2639
+ this.onChangeTrackVisibility(trackInfo, visible);
2640
+ }
2641
+
2642
+ /**
2643
+ * @method selectTrack
2644
+ * @param {id, parent, children, visible} trackInfo
2645
+ */
2646
+
2647
+ selectTrack( trackInfo) {
2648
+ this.unSelectAllTracks();
2649
+
2650
+ let [name, type] = trackInfo.id.split(" (");
2651
+
2652
+ if(type)
2653
+ type = type.replaceAll(")", "").replaceAll(" ", "");
2654
+ else {
2655
+ type = name;
2656
+ name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
2657
+ }
2658
+ let id = name.split("Track_")[1];
2659
+ let track = this.animationClip.tracks[id];
2660
+ track.isSelected = true;
2661
+
2662
+ trackInfo = track;
2663
+ this.updateLeftPanel();
2664
+ if(this.onSelectTrack)
2665
+ this.onSelectTrack(trackInfo);
2666
+ }
2667
+
2668
+ unSelectAllTracks() {
2669
+
2670
+ for(let t = 0; t < this.animationClip.tracks.length; t++) {
2671
+ this.animationClip.tracks[t].isSelected = false;
2672
+ }
2428
2673
  }
2429
2674
 
2430
2675
  onMouseUp( e ) {
@@ -2478,7 +2723,7 @@ class ClipsTimeline extends Timeline {
2478
2723
  this.boxSelectionStart = null;
2479
2724
  this.boxSelectionEnd = null;
2480
2725
 
2481
- }
2726
+ }
2482
2727
 
2483
2728
  onMouseDown( e, time ) {
2484
2729
 
@@ -2496,26 +2741,26 @@ class ClipsTimeline extends Timeline {
2496
2741
 
2497
2742
  let x = e.offsetX;
2498
2743
  let selectedClips = [];
2499
- if(this.lastClipsSelected.length){
2744
+ if(this.lastClipsSelected.length > 1) {
2500
2745
  selectedClips = this.lastClipsSelected;
2501
2746
  }
2502
- else{
2747
+ else {
2503
2748
  let clipIndex = this.getCurrentClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
2504
2749
  if(clipIndex != undefined)
2505
2750
  {
2506
2751
  this.lastClipsSelected = selectedClips = [[track.idx, clipIndex]];
2507
- }
2508
-
2752
+ }
2509
2753
  }
2754
+
2510
2755
  this.canvas.style.cursor = "grab";
2511
- for(let i = 0; i< selectedClips.length; i++)
2756
+ this.timelineClickedClips = [];
2757
+ for(let i = 0; i < selectedClips.length; i++)
2512
2758
  {
2513
2759
  this.movingKeys = false
2514
2760
  let [trackIndex, clipIndex] = selectedClips[i];
2515
2761
  var clip = this.animationClip.tracks[trackIndex].clips[clipIndex];
2516
2762
 
2517
- if(!this.timelineClickedClips)
2518
- this.timelineClickedClips = [];
2763
+ //if(!this.timelineClickedClips)
2519
2764
  if(this.timelineClickedClips.indexOf(clip) < 0) {
2520
2765
  this.timelineClickedClips.push(clip);
2521
2766
 
@@ -2524,7 +2769,7 @@ class ClipsTimeline extends Timeline {
2524
2769
  this.timelineClickedClipsTime.push(this.xToTime( localX ));
2525
2770
  }
2526
2771
 
2527
-
2772
+
2528
2773
  var endingX = this.timeToX( clip.start + clip.duration );
2529
2774
  var distToStart = Math.abs( this.timeToX( clip.start ) - x );
2530
2775
  var distToEnd = Math.abs( this.timeToX( clip.start + clip.duration ) - e.offsetX );
@@ -2535,7 +2780,7 @@ class ClipsTimeline extends Timeline {
2535
2780
  //this.addUndoStep( "clip_modified", clip );
2536
2781
  if( (e.ctrlKey && distToStart < 5) || (clip.fadein && Math.abs( this.timeToX( clip.start + clip.fadein ) - e.offsetX ) < 5) )
2537
2782
  this.dragClipMode = "fadein";
2538
- else if(e.ctrlKey && Math.abs( endingX - x ) < 5 ) {
2783
+ else if(Math.abs( endingX - x ) < 5 ) {
2539
2784
  this.dragClipMode = "duration";
2540
2785
  this.canvas.style.cursor = "column-resize";
2541
2786
  }
@@ -2564,6 +2809,14 @@ class ClipsTimeline extends Timeline {
2564
2809
  if(this.onSelectClip)
2565
2810
  this.onSelectClip(null);
2566
2811
  }
2812
+ else if (track && (this.dragClipMode == "duration" || this.dragClipMode == "fadein" || this.dragClipMode == "fadeout" )) {
2813
+ let clips = this.getClipsInRange(track, time, time, 0.1);
2814
+ if(!clips) {
2815
+ return;
2816
+ }
2817
+ this.lastClipsSelected = [[track.idx, clips[0]]];
2818
+ this.timelineClickedClips = [track.clips[clips[0]]];
2819
+ }
2567
2820
  }
2568
2821
 
2569
2822
  onMouseMove( e, time ) {
@@ -2574,6 +2827,11 @@ class ClipsTimeline extends Timeline {
2574
2827
  this.onSetTime( t );
2575
2828
  }
2576
2829
 
2830
+ const removeHover = () => {
2831
+ if(this.lastHovered)
2832
+ this.animationClip.tracks[ this.lastHovered[0] ].hovered[ this.lastHovered[1] ] = undefined;
2833
+ };
2834
+
2577
2835
  if(e.shiftKey) {
2578
2836
  if(this.boxSelection) {
2579
2837
  this.boxSelectionEnd = [localX,localY - this.topMargin];
@@ -2594,8 +2852,9 @@ class ClipsTimeline extends Timeline {
2594
2852
 
2595
2853
  let trackIdx = this.lastClipsSelected[i][0];
2596
2854
  let clipIdx = this.lastClipsSelected[i][1];
2597
- var clip = this.timelineClickedClips[i] ;
2598
- var diff = clip.start + delta < 0 ? - clip.start : delta;//this.currentTime - this.timelineClickedClipsTime[i];//delta;
2855
+ let clip = this.timelineClickedClips[i];
2856
+ let diff = clip.start + delta < 0 ? - clip.start : delta;//this.currentTime - this.timelineClickedClipsTime[i];//delta;
2857
+
2599
2858
  if( this.dragClipMode == "move" ) {
2600
2859
  let clipsInRange = this.getClipsInRange(this.animationClip.tracks[trackIdx], clip.start+diff, clip.start + clip.duration + diff, 0.01)
2601
2860
  if(clipsInRange && clipsInRange[0] != clipIdx)
@@ -2605,6 +2864,7 @@ class ClipsTimeline extends Timeline {
2605
2864
  clip.fadein += diff;
2606
2865
  if(clip.fadeout != undefined)
2607
2866
  clip.fadeout += diff;
2867
+
2608
2868
  this.canvas.style.cursor = "grabbing";
2609
2869
 
2610
2870
  if( this.timelineClickedClips.length == 1 && e.track && e.movementY != 0) {
@@ -2623,41 +2883,94 @@ class ClipsTimeline extends Timeline {
2623
2883
  }
2624
2884
  // }
2625
2885
  }
2626
- if(this.onContentMoved) {
2627
- this.onContentMoved(clip, diff);
2628
- }
2886
+ // if(this.onContentMoved) {
2887
+ // this.onContentMoved(clip, diff);
2888
+ // }
2629
2889
  }
2630
- else if( this.dragClipMode == "fadein" )
2890
+ else if( this.dragClipMode == "fadein" ) {
2631
2891
  clip.fadein = Math.min(Math.max((clip.fadein || 0) + delta, clip.start), clip.start+clip.duration);
2632
- else if( this.dragClipMode == "fadeout" )
2892
+ diff = 0;
2893
+ }
2894
+ else if( this.dragClipMode == "fadeout" ) {
2633
2895
  clip.fadeout = Math.max(Math.min((clip.fadeout || clip.start+clip.duration) + delta, clip.start+clip.duration), clip.start);
2896
+ diff = 0;
2897
+ }
2634
2898
  else if( this.dragClipMode == "duration" ) {
2635
2899
  clip.duration += delta;
2636
- clip.fadeout += delta;
2637
- if(this.onContentMoved) {
2638
- this.onContentMoved(clip, 0);
2639
- }
2900
+ // if(delta < 0) {
2901
+ // clip.fadein = Math.min(Math.max((clip.fadein || 0) + delta, clip.start), clip.start+clip.duration);
2902
+ // }
2903
+ clip.fadeout = Math.max(Math.min((clip.fadeout || clip.start+clip.duration) + delta, clip.start+clip.duration), clip.start);
2904
+ diff = 0;
2905
+ // if(this.onContentMoved) {
2906
+ // this.onContentMoved(clip, 0);
2907
+ // }
2640
2908
  }
2641
2909
 
2642
2910
  if(this.duration < clip.start + clip.duration )
2643
2911
  {
2644
2912
  this.setDuration(clip.start + clip.duration);
2645
2913
  }
2914
+ if(this.onContentMoved) {
2915
+ this.onContentMoved(clip, diff);
2916
+ }
2646
2917
  }
2918
+
2647
2919
  return true;
2648
2920
  }
2649
2921
  else{
2650
2922
  innerSetTime( this.currentTime );
2651
2923
  }
2652
2924
  }
2653
- else if(e.track && e.ctrlKey) {
2654
- for(let i = 0; i < e.track.clips.length; i++) {
2655
- let clip = e.track.clips[i];
2656
- const x = this.timeToX(clip.start+clip.duration);
2657
- if(Math.abs(e.localX - x) < 5)
2925
+ // else if(e.track && e.ctrlKey) {
2926
+ // for(let i = 0; i < e.track.clips.length; i++) {
2927
+ // let clip = e.track.clips[i];
2928
+ // const x = this.timeToX(clip.start+clip.duration);
2929
+ // if(Math.abs(e.localX - x) < 5)
2930
+ // this.canvas.style.cursor = "col-resize";
2931
+ // }
2932
+ // }
2933
+ else if(e.track) {
2934
+
2935
+ let clips = this.getClipsInRange(e.track, time, time, 0.1)
2936
+ if(!e.track.locked && clips != undefined) {
2937
+
2938
+ removeHover();
2939
+ this.lastHovered = [e.track.idx, clips[0]];
2940
+ e.track.hovered[clips[0]] = true;
2941
+
2942
+ let clip = e.track.clips[clips[0]];
2943
+ if(!clip) {
2944
+ return;
2945
+ }
2946
+
2947
+ const durationX = this.timeToX(clip.start + clip.duration);
2948
+ const fadeinX = this.timeToX(clip.fadein);
2949
+ const fadeoutX = this.timeToX(clip.fadeout);
2950
+ if(Math.abs(e.localX - durationX) < 8) {
2658
2951
  this.canvas.style.cursor = "col-resize";
2952
+ this.dragClipMode = "duration";
2953
+ }
2954
+ else if(Math.abs(e.localX - fadeinX) < 8) {
2955
+ this.canvas.style.cursor = "e-resize";
2956
+ this.dragClipMode = "fadein";
2957
+ }
2958
+ else if(Math.abs(e.localX - fadeoutX) < 8) {
2959
+ this.canvas.style.cursor = "e-resize";
2960
+ this.dragClipMode = "fadeout";
2961
+ }
2962
+ else {
2963
+ this.dragClipMode = "";
2964
+ }
2965
+ }
2966
+ else {
2967
+ removeHover();
2659
2968
  }
2660
2969
  }
2970
+ else {
2971
+ removeHover();
2972
+ }
2973
+
2661
2974
  }
2662
2975
 
2663
2976
  onDblClick( e ) {
@@ -2749,30 +3062,35 @@ class ClipsTimeline extends Timeline {
2749
3062
  this.tracksPerItem = {};
2750
3063
  this.tracksDictionary = {};
2751
3064
  this.animationClip = {
2752
- name: animation.name,
2753
- duration: animation.duration,
2754
- speed: animation.speed ?? 1,
3065
+ name: (animation && animation.name) ? animation.name : "animationClip",
3066
+ duration: animation ? animation.duration : 0,
3067
+ speed: (animation && animation.speed ) ? animation.speed : this.speed,
2755
3068
  tracks: []
2756
3069
  };
2757
3070
 
2758
- for( let i = 0; i < animation.tracks.length; ++i ) {
2759
-
2760
- let track = animation.tracks[i];
2761
-
2762
- const name = track.name;
2763
- const type = track.type;
2764
-
2765
- let trackInfo = {
2766
- fullname: track.name,
2767
- clips: track.clips,
2768
- name: name, type: type,
2769
- selected: [], edited: [], hovered: [], active: true,
2770
- times: track.times,
2771
- };
2772
-
2773
- this.tracksDictionary[track.name] = name;
2774
-
2775
- this.animationClip.tracks.push(trackInfo);
3071
+ if (animation && animation.tracks){
3072
+ for( let i = 0; i < animation.tracks.length; ++i ) {
3073
+
3074
+ let track = animation.tracks[i];
3075
+
3076
+ const name = track.name;
3077
+ const type = track.type;
3078
+
3079
+ let trackInfo = {
3080
+ fullname: track.name,
3081
+ clips: track.clips,
3082
+ name: name, type: type,
3083
+ selected: [], edited: [], hovered: [], active: true,
3084
+ times: track.times,
3085
+ };
3086
+
3087
+ this.tracksDictionary[track.name] = name;
3088
+
3089
+ this.animationClip.tracks.push(trackInfo);
3090
+ }
3091
+ }
3092
+ else {
3093
+ this.addNewTrack();
2776
3094
  }
2777
3095
  }
2778
3096
 
@@ -2845,14 +3163,21 @@ class ClipsTimeline extends Timeline {
2845
3163
  let newStart = this.currentTime + offsetTime + clip.start;
2846
3164
  if(clip.fadein != undefined)
2847
3165
  clip.fadein += (newStart - clip.start);
3166
+ else
3167
+ clip.fadein = 0;
3168
+
2848
3169
  if(clip.fadeout != undefined)
2849
3170
  clip.fadeout += (newStart - clip.start);
3171
+ else
3172
+ clip.fadeout = clip.duration;
3173
+
2850
3174
  clip.start = newStart;
2851
3175
 
2852
3176
  // Time slot with other clip?
2853
3177
  let clipInCurrentSlot = null;
2854
- if(!this.animationClip)
3178
+ if(!this.animationClip || !this.animationClip.tracks || !this.animationClip.tracks.length) {
2855
3179
  this.addNewTrack();
3180
+ }
2856
3181
 
2857
3182
  for(let i = 0; i < this.animationClip.tracks.length; i++) {
2858
3183
  clipInCurrentSlot = this.animationClip.tracks[i].clips.find( t => {
@@ -3008,7 +3333,7 @@ class ClipsTimeline extends Timeline {
3008
3333
  */
3009
3334
  addClips( clips, offsetTime = 0, callback = null ) {
3010
3335
 
3011
- if(!this.animationClip)
3336
+ if(!this.animationClip || !this.animationClip.tracks || !this.animationClip.tracks.length)
3012
3337
  this.addNewTrack();
3013
3338
 
3014
3339
  //Search track where to place each new clip
@@ -3037,7 +3362,7 @@ class ClipsTimeline extends Timeline {
3037
3362
  if(!this.animationClip.tracks[i+1]) {
3038
3363
  this.addNewTrack();
3039
3364
 
3040
- trackIdxs[c] = {trackIdx: i+1, stat: newStart, end: newStart + clip.duration};
3365
+ trackIdxs[c] = {trackIdx: i+1, start: newStart, end: newStart + clip.duration};
3041
3366
  }
3042
3367
  else {
3043
3368
 
@@ -3288,15 +3613,16 @@ class ClipsTimeline extends Timeline {
3288
3613
  addNewTrack() {
3289
3614
 
3290
3615
  if(!this.animationClip)
3291
- this.animationClip = {tracks:[]};
3616
+ this.animationClip = {duration:0, tracks:[]};
3292
3617
 
3293
3618
  let trackInfo = {
3294
3619
  idx: this.animationClip.tracks.length,
3295
3620
  clips: [],
3296
- selected: [], edited: [], hovered: []
3621
+ selected: [], edited: [], hovered: [], active: true
3297
3622
  };
3298
3623
 
3299
3624
  this.animationClip.tracks.push(trackInfo);
3625
+ this.updateLeftPanel();
3300
3626
  return trackInfo.idx;
3301
3627
  }
3302
3628
 
@@ -3456,15 +3782,32 @@ class ClipsTimeline extends Timeline {
3456
3782
 
3457
3783
  let indices = [];
3458
3784
 
3785
+ // for(let i = 0; i < track.clips.length; ++i) {
3786
+ // let t = track.clips[i];
3787
+ // if((t.start + t.duration <= (maxTime + threshold) || t.start <= (maxTime + threshold)) &&
3788
+ // (t.start + t.duration >= (minTime - threshold) || t.start >= (minTime - threshold)) )
3789
+ // {
3790
+ // indices.push(i);
3791
+ // }
3792
+ // }
3793
+
3459
3794
  for(let i = 0; i < track.clips.length; ++i) {
3460
3795
  let t = track.clips[i];
3461
- if((t.start + t.duration <= (maxTime + threshold) || t.start <= (maxTime + threshold)) &&
3462
- (t.start + t.duration >= (minTime - threshold) || t.start >= (minTime - threshold)) )
3796
+ if( (minTime - threshold >= t.start) &&
3797
+ (minTime - threshold <= t.start + t.duration) ||
3798
+ (minTime + threshold >= t.start) &&
3799
+ (minTime + threshold <= t.start + t.duration) ||
3800
+ (maxTime - threshold >= t.start) &&
3801
+ (maxTime - threshold <= t.start + t.duration) ||
3802
+ (maxTime + threshold >= t.start) &&
3803
+ (maxTime + threshold <= t.start + t.duration) ||
3804
+ (minTime - threshold <= t.start || minTime + threshold <= t.start) &&
3805
+ (maxTime - threshold >= t.start + t.duration || maxTime + threshold >= t.start + t.duration)
3806
+ )
3463
3807
  {
3464
3808
  indices.push(i);
3465
3809
  }
3466
3810
  }
3467
-
3468
3811
  return indices;
3469
3812
  }
3470
3813
 
@@ -3558,22 +3901,7 @@ class CurvesTimeline extends Timeline {
3558
3901
  if(!discard && track) {
3559
3902
  this.processCurrentKeyFrame( e, null, track, localX );
3560
3903
 
3561
- }
3562
- else {
3563
- let x = e.offsetX;
3564
- let y = e.offsetY - this.topMargin;
3565
- for( const b of this.buttonsDrawn ) {
3566
- b.pressed = false;
3567
- const bActive = x >= b[2] && x <= (b[2] + b[4]) && y >= b[3] && y <= (b[3] + b[5]);
3568
- if(bActive) {
3569
- const callback = b[6];
3570
- if(callback) callback(e);
3571
- else this[ b[1] ] = !this[ b[1] ];
3572
- break;
3573
- }
3574
- }
3575
- }
3576
-
3904
+ }
3577
3905
  }
3578
3906
 
3579
3907
  this.boxSelection = false;
@@ -3589,7 +3917,6 @@ class CurvesTimeline extends Timeline {
3589
3917
  let track = e.track;
3590
3918
 
3591
3919
  if(e.shiftKey) {
3592
-
3593
3920
  this.boxSelection = true;
3594
3921
  this.boxSelectionStart = [localX, localY - this.topMargin];
3595
3922
  e.multipleSelection = true;
@@ -3617,13 +3944,7 @@ class CurvesTimeline extends Timeline {
3617
3944
  }
3618
3945
  }
3619
3946
  else if(!track) {
3620
- this.unSelectAllKeyFrames()
3621
- let x = e.offsetX;
3622
- let y = e.offsetY - this.topMargin;
3623
- for( const b of this.buttonsDrawn ) {
3624
- const bActive = x >= b[2] && x <= (b[2] + b[4]) && y >= b[3] && y <= (b[3] + b[5]);
3625
- b.pressed = bActive;
3626
- }
3947
+ this.unSelectAllKeyFrames()
3627
3948
  }
3628
3949
  }
3629
3950
 
@@ -3824,11 +4145,12 @@ class CurvesTimeline extends Timeline {
3824
4145
  let values = track.values;
3825
4146
 
3826
4147
  if(keyframes) {
3827
-
3828
- ctx.fillStyle = "#2c303570";
4148
+ ctx.globalAlpha = 0.2;
4149
+ ctx.fillStyle = Timeline.TRACK_SELECTED_LIGHT//"#2c303570";
3829
4150
  if(trackInfo.isSelected)
3830
4151
  ctx.fillRect(0, y - 3, ctx.canvas.width, trackHeight );
3831
4152
 
4153
+ ctx.globalAlpha = 1;
3832
4154
  this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
3833
4155
 
3834
4156
  //draw lines
@@ -3957,43 +4279,47 @@ class CurvesTimeline extends Timeline {
3957
4279
  this.tracksPerItem = {};
3958
4280
  this.tracksDictionary = {};
3959
4281
  this.animationClip = {
3960
- name: animation.name,
3961
- duration: animation.duration,
3962
- speed: animation.speed ?? 1,
4282
+ name: (animation && animation.name) ? animation.name : "animationClip",
4283
+ duration: animation ? animation.duration : 0,
4284
+ speed: (animation && animation.speed ) ? animation.speed : this.speed,
3963
4285
  tracks: []
3964
4286
  };
3965
- for( let i = 0; i < animation.tracks.length; ++i ) {
3966
-
3967
- let track = animation.tracks[i];
3968
4287
 
3969
- const [name, type] = this.getTrackName(track.name);
4288
+ if (animation && animation.tracks) {
3970
4289
 
3971
- let trackInfo = {
3972
- fullname: track.name,
3973
- name: name, type: type,
3974
- dim: track.values.length/track.times.length,
3975
- selected: [], edited: [], hovered: [], active: true,
3976
- values: track.values,
3977
- times: track.times
4290
+ for( let i = 0; i < animation.tracks.length; ++i ) {
4291
+
4292
+ let track = animation.tracks[i];
4293
+
4294
+ const [name, type] = this.getTrackName(track.name);
4295
+
4296
+ let trackInfo = {
4297
+ fullname: track.name,
4298
+ name: name, type: type,
4299
+ dim: track.values.length/track.times.length,
4300
+ selected: [], edited: [], hovered: [], active: true,
4301
+ values: track.values,
4302
+ times: track.times
4303
+
4304
+ };
4305
+
4306
+ if(!this.tracksPerItem[name]) {
4307
+ this.tracksPerItem[name] = [trackInfo];
4308
+ }else {
4309
+ this.tracksPerItem[name].push( trackInfo );
4310
+ }
4311
+
4312
+
4313
+ const trackIndex = this.tracksPerItem[name].length - 1;
4314
+ this.tracksPerItem[name][trackIndex].idx = trackIndex;
4315
+ this.tracksPerItem[name][trackIndex].clipIdx = i;
4316
+
4317
+ // Save index also in original track
4318
+ trackInfo.idx = trackIndex;
4319
+ this.tracksDictionary[track.name] = name;
4320
+ this.animationClip.tracks.push(trackInfo);
3978
4321
 
3979
- };
3980
-
3981
- if(!this.tracksPerItem[name]) {
3982
- this.tracksPerItem[name] = [trackInfo];
3983
- }else {
3984
- this.tracksPerItem[name].push( trackInfo );
3985
4322
  }
3986
-
3987
-
3988
- const trackIndex = this.tracksPerItem[name].length - 1;
3989
- this.tracksPerItem[name][trackIndex].idx = trackIndex;
3990
- this.tracksPerItem[name][trackIndex].clipIdx = i;
3991
-
3992
- // Save index also in original track
3993
- trackInfo.idx = trackIndex;
3994
- this.tracksDictionary[track.name] = name;
3995
- this.animationClip.tracks.push(trackInfo);
3996
-
3997
4323
  }
3998
4324
  }
3999
4325