lexgui 0.5.6 → 0.5.8

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.
@@ -6,26 +6,6 @@ if(!LX) {
6
6
 
7
7
  LX.components.push( 'Timeline' );
8
8
 
9
- /**
10
- * @class Session
11
- * @description Store info about timeline session
12
- */
13
-
14
- class Session {
15
-
16
- constructor() {
17
-
18
- this.start_time = -0.01;
19
- this.left_margin = 0;
20
- // this.current_time = 0;
21
- // this.last_time = 0;
22
- // this.seconds_to_pixels = 50;
23
- this.scroll_y = 0;
24
- // this.offset_y = 0;
25
- // this.selection = null;
26
- }
27
- };
28
-
29
9
  /**
30
10
  * @class Timeline
31
11
  * @description Agnostic timeline, do not impose any timeline content. Renders to a canvas
@@ -40,8 +20,10 @@ class Timeline {
40
20
  constructor( name, options = {} ) {
41
21
 
42
22
  this.name = name ?? '';
43
- this.currentTime = 0;
44
23
  this.opacity = options.opacity || 1;
24
+ this.currentTime = 0;
25
+ this.visualTimeRange = [0,0]; // [start time, end time] - visible range of time. 0 <= time <= duration
26
+ this.visualOriginTime = 0; // time visible at pixel 0. -infinity < time < infinity
45
27
  this.topMargin = 40;
46
28
  this.clickDiscardTimeout = 200; // ms
47
29
  this.lastMouse = [];
@@ -59,30 +41,31 @@ class Timeline {
59
41
  this.movingKeys = false;
60
42
  this.timeBeforeMove = 0;
61
43
 
62
- this.onBeforeCreateTopBar = options.onBeforeCreateTopBar;
63
- this.onAfterCreateTopBar = options.onAfterCreateTopBar;
64
- this.onChangePlayMode = options.onChangePlayMode;
44
+ this.onCreateBeforeTopBar = options.onCreateBeforeTopBar;
45
+ this.onCreateAfterTopBar = options.onCreateAfterTopBar;
46
+ this.onCreateControlsButtons = options.onCreateControlsButtons;
47
+ this.onCreateSettingsButtons = options.onCreateSettingsButtons;
48
+ this.onChangeLoopMode = options.onChangeLoopMode;
65
49
  this.onShowConfiguration = options.onShowConfiguration;
66
50
  this.onBeforeDrawContent = options.onBeforeDrawContent;
67
51
 
68
52
  this.playing = false;
69
53
  this.loop = options.loop ?? true;
70
54
 
71
- this.session = new Session();
72
-
73
55
  this.canvas = options.canvas ?? document.createElement('canvas');
56
+ this.canvas.style.width = "100%";
57
+ this.canvas.style.height = "100%";
58
+
74
59
 
75
60
  this.duration = 1;
76
61
  this.speed = 1;
77
- this.position = [ 0, 0 ];
78
62
  this.size = [ options.width ?? 400, options.height ?? 100 ];
79
63
 
80
64
  this.currentScroll = 0; //in percentage
81
65
  this.currentScrollInPixels = 0; //in pixels
82
- this.scrollableHeight = this.size[1]; //true height of the timeline content
83
66
 
84
- this.secondsToPixels = Math.max( 0.00001, this.size[0]/1 );
85
- this.pixelsToSeconds = 1 / this.secondsToPixels;
67
+ this.pixelsPerSecond = Math.max( 0.00001, this.size[0]/1 );
68
+ this.secondsPerPixel = 1 / this.pixelsPerSecond;
86
69
  this.selectedItems = options.selectedItems ?? [];
87
70
  this.animationClip = options.animationClip ?? null;
88
71
  this.trackHeight = options.trackHeight ?? 25;
@@ -99,28 +82,34 @@ class Timeline {
99
82
 
100
83
  this.optimizeThreshold = 0.01;
101
84
 
102
- this.root = new LX.Area({className : 'lextimeline'});
103
-
104
85
  this.header_offset = 48;
86
+
87
+ // main area -- root
88
+ this.mainArea = new LX.Area({className : 'lextimeline'});
89
+ this.mainArea.split({ type: "vertical", sizes: [this.header_offset, "auto"], resize: false});
90
+
91
+ // header
92
+ this.header = new LX.Panel( { id: 'lextimelineheader' } );
93
+ this.mainArea.sections[0].attach( this.header );
94
+ this.updateHeader();
105
95
 
106
- let width = options.width ? options.width : null;
107
- let height = options.height ? options.height - this.header_offset : null;
108
-
109
- let area = new LX.Area( {id: "bottom-timeline-area", width: width || "calc(100% - 7px)", height: height || "100%"});
110
- area.split({ type: "horizontal", sizes: ["15%", "85%"] });
111
- area.splitBar.style.zIndex = 1; // for some reason this is needed here
112
- this.content_area = area;
113
- let [ left, right ] = area.sections;
96
+ // content area
97
+ const contentArea = this.mainArea.sections[1];
98
+ contentArea.root.id = "bottom-timeline-area";
99
+ contentArea.split({ type: "horizontal", sizes: ["15%", "85%"] });
100
+ let [ left, right ] = contentArea.sections;
114
101
 
115
- right.root.appendChild( this.canvas );
102
+ right.attach( this.canvas );
116
103
  this.canvasArea = right;
117
104
  this.canvasArea.root.classList.add("lextimelinearea");
118
- this.updateHeader();
119
- this.updateLeftPanel( left );
120
- this.root.root.appendChild( area.root );
105
+
106
+ this.leftPanel = left.addPanel( { className: 'lextimelinepanel', width: "100%", height: "100%" } );
107
+ this.trackTreesPanel = null;
108
+ this.trackTreesWidget = null;
109
+ this.updateLeftPanel();
121
110
 
122
111
  if(!options.canvas && this.name != '') {
123
- this.root.root.id = this.name;
112
+ this.mainArea.root.id = this.name;
124
113
  this.canvas.id = this.name + '-canvas';
125
114
  }
126
115
 
@@ -136,10 +125,10 @@ class Timeline {
136
125
  // Process keys events
137
126
  this.canvasArea.root.addEventListener("keydown", this.processKeys.bind(this));
138
127
 
139
- right.onresize = bounding => {
128
+ this.canvasArea.onresize = bounding => {
140
129
  if(!(bounding.width && bounding.height))
141
130
  return;
142
- this.resizeCanvas( [ bounding.width, bounding.height + this.header_offset ] );
131
+ this.resizeCanvas();
143
132
  }
144
133
  this.resize(this.size);
145
134
 
@@ -157,18 +146,9 @@ class Timeline {
157
146
  */
158
147
 
159
148
  updateHeader() {
149
+ this.header.clear();
160
150
 
161
- if( this.header )
162
- {
163
- this.header.clear();
164
- }
165
- else
166
- {
167
- this.header = new LX.Panel( { id: 'lextimelineheader', height: this.header_offset + "px" } );
168
- this.root.root.appendChild( this.header.root );
169
- }
170
-
171
- let header = this.header;
151
+ const header = this.header;
172
152
  header.sameLine();
173
153
 
174
154
  if( this.name )
@@ -180,40 +160,53 @@ class Timeline {
180
160
 
181
161
  header.queue( buttonContainer );
182
162
 
183
- header.addButton("playBtn", '<i class="fa-solid fa-'+ (this.playing ? 'pause' : 'play') +'"></i>', (value, event) => {
163
+ const playbtn = header.addButton("playBtn", '', (value, event) => {
184
164
  this.changeState();
185
- }, { buttonClass: "accept", title: "Play", hideName: true });
165
+ }, { buttonClass: "accept", title: "Play", hideName: true, icon: "fa-solid fa-play", swap: "fa-solid fa-pause" });
166
+ playbtn.root.setState(this.playing, true);
186
167
 
187
- header.addButton("toggleLoopBtn", '<i class="fa-solid fa-rotate"></i>', ( value, event ) => {
188
- this.loop = !this.loop;
189
- if( this.onChangePlayMode )
190
- {
191
- this.onChangePlayMode( this.loop );
168
+ header.addBlank("0.05em", "auto");
169
+
170
+ header.addButton("stopBtn", '', (value, event) => {
171
+ this.setState(false, true); // skip callback of set state
172
+ if ( this.onStateStop ){
173
+ this.onStateStop();
192
174
  }
193
- }, { selectable: true, selected: this.loop, title: 'Loop', hideName: true });
175
+ }, { buttonClass: "accept", title: "Stop", hideName: true, icon: "fa-solid fa-stop" });
176
+
177
+ header.addBlank("0.05em", "auto");
178
+
179
+ header.addButton("loopBtn", '', ( value, event ) => {
180
+ this.setLoopMode(!this.loop);
181
+ }, { selectable: true, selected: this.loop, title: 'Loop', hideName: true, icon: "fa-solid fa-rotate" });
194
182
 
195
- if( this.onBeforeCreateTopBar )
196
- {
197
- this.onBeforeCreateTopBar( header );
183
+ if( this.onCreateControlsButtons ){
184
+ this.onCreateControlsButtons( header );
198
185
  }
199
-
186
+
200
187
  header.clearQueue( buttonContainer );
201
-
202
188
  header.addContent( "header-buttons", buttonContainer );
203
189
 
190
+ // time number inputs - duration, speed, current time, etc
191
+
192
+ if( this.onCreateBeforeTopBar )
193
+ {
194
+ this.onCreateBeforeTopBar( header );
195
+ }
196
+
204
197
  header.addNumber("Current Time", this.currentTime, (value, event) => {
205
198
  this.setTime(value)
206
199
  }, {
207
- units: "s",
200
+ // units: "s", // commented until lexgui is refactored. There is a performance hit while using units
208
201
  signal: "@on_set_time_" + this.name,
209
202
  step: 0.01, min: 0, precision: 3,
210
203
  skipSlider: true
211
204
  });
212
205
 
213
206
  header.addNumber("Duration", + this.duration.toFixed(3), (value, event) => {
214
- this.setDuration(value, false)
207
+ this.setDuration(value, false, false);
215
208
  }, {
216
- units: "s",
209
+ // units: "s", // commented until lexgui is refactored. There is a performance hit while using units
217
210
  step: 0.01, min: 0,
218
211
  signal: "@on_set_duration_" + this.name
219
212
  });
@@ -225,19 +218,30 @@ class Timeline {
225
218
  signal: "@on_set_speed_" + this.name
226
219
  });
227
220
 
228
- if( this.onAfterCreateTopBar )
221
+ if( this.onCreateAfterTopBar )
229
222
  {
230
- this.onAfterCreateTopBar( header );
223
+ this.onCreateAfterTopBar( header );
224
+ }
225
+
226
+ // settings buttons - optimize, settings, etc
227
+
228
+ const buttonContainerEnd = LX.makeContainer( ["auto", "100%"], "flex flex-row" );
229
+ header.queue( buttonContainerEnd );
230
+
231
+ if( this.onCreateSettingsButtons ){
232
+ this.onCreateSettingsButtons( header );
233
+ header.addBlank("0.05em", "auto");
231
234
  }
232
235
 
233
236
  if( this.onShowOptimizeMenu )
234
237
  {
235
- header.addButton(null, '<i class="fa-solid fa-filter"></i>', (value, event) => {this.onShowOptimizeMenu(event)}, { title: "Optimize" });
238
+ header.addButton(null, "", (value, event) => {this.onShowOptimizeMenu(event)}, { title: "Optimize", icon:"fa-solid fa-filter" });
236
239
  }
240
+ header.addBlank("0.05em", "auto");
237
241
 
238
242
  if( this.onShowConfiguration )
239
243
  {
240
- header.addButton(null, '<i class="fa-solid fa-gear"></i>', (value, event) => {
244
+ header.addButton(null, "", (value, event) => {
241
245
  if(this.configurationDialog){
242
246
  this.configurationDialog.close();
243
247
  this.configurationDialog = null;
@@ -252,9 +256,12 @@ class Timeline {
252
256
  root.remove();
253
257
  }
254
258
  })
255
- }, { title: "Settings" })
259
+ }, { title: "Settings", icon: "fa-solid fa-gear" })
256
260
  }
257
261
 
262
+ header.clearQueue( buttonContainerEnd );
263
+ header.addContent( "header-buttons-end", buttonContainerEnd );
264
+
258
265
  header.endLine( "justify-around" );
259
266
  }
260
267
 
@@ -262,22 +269,14 @@ class Timeline {
262
269
  * @method updateLeftPanel
263
270
  *
264
271
  */
265
- updateLeftPanel( area ) {
272
+ updateLeftPanel( ) {
266
273
 
267
- let scrollTop = 0;
268
- if( this.leftPanel )
269
- {
270
- scrollTop = this.leftPanel.root.children[ 1 ].scrollTop;
271
- this.leftPanel.clear();
272
- }
273
- else
274
- {
275
- this.leftPanel = area.addPanel( { className: 'lextimelinepanel', width: "100%", height: "100%" } );
276
- }
274
+ const scrollTop = this.trackTreesPanel ? this.trackTreesPanel.root.scrollTop : 0;
275
+ this.leftPanel.clear();
277
276
 
278
- let panel = this.leftPanel;
277
+ const panel = this.leftPanel;
278
+
279
279
  panel.sameLine( 2 );
280
-
281
280
  let titleWidget = panel.addTitle( "Tracks" );
282
281
  let title = titleWidget.root;
283
282
 
@@ -287,7 +286,6 @@ class Timeline {
287
286
  this.addNewTrack();
288
287
  }, { hideName: true, title: "Add Track" });
289
288
  }
290
-
291
289
  panel.endLine();
292
290
 
293
291
  const styles = window.getComputedStyle( title );
@@ -295,10 +293,9 @@ class Timeline {
295
293
 
296
294
  let p = new LX.Panel({height: "calc(100% - " + titleHeight + "px)"});
297
295
 
296
+ let treeTracks = [];
298
297
  if( this.animationClip && this.selectedItems.length )
299
298
  {
300
- let items = { 'id': '', 'children': [] };
301
-
302
299
  const tracksPerItem = this.animationClip.tracksPerItem;
303
300
  for( let i = 0; i < this.selectedItems.length; i++ )
304
301
  {
@@ -347,66 +344,71 @@ class Timeline {
347
344
  // panel.addTitle(track.name + (track.type? '(' + track.type + ')' : ''));
348
345
  }
349
346
 
350
- items.children.push( t );
351
-
352
- let el = p.addTree(null, t, {filter: false, rename: false, draggable: false, onevent: (e) => {
353
- switch(e.type) {
354
- case LX.TreeEvent.NODE_SELECTED:
355
- if (e.node.parent){
356
- const tracksInItem = this.animationClip.tracksPerItem[e.node.parent.id];
357
- const type = e.node.id;
358
- for(let i = 0; i < tracksInItem.length; i++) {
359
- if(tracksInItem[i].type == type){
360
- this.selectTrack(tracksInItem[i].clipIdx);
361
- break;
362
- }
363
- }
347
+ treeTracks.push( t );
348
+ }
349
+
350
+ }
351
+ this.trackTreesWidget = p.addTree(null, treeTracks, {filter: false, rename: false, draggable: false, onevent: (e) => {
352
+ switch(e.type) {
353
+ case LX.TreeEvent.NODE_SELECTED:
354
+ if (e.node.parent){
355
+ const tracksInItem = this.animationClip.tracksPerItem[e.node.parent.id];
356
+ const type = e.node.id;
357
+ for(let i = 0; i < tracksInItem.length; i++) {
358
+ if(tracksInItem[i].type == type){
359
+ this.selectTrack(tracksInItem[i].clipIdx);
360
+ break;
364
361
  }
365
- break;
366
- case LX.TreeEvent.NODE_VISIBILITY:
367
- if ( e.node.parent )
368
- {
369
- const tracksInItem = this.animationClip.tracksPerItem[ e.node.parent.id ];
370
- const type = e.node.id;
371
- for(let i = 0; i < tracksInItem.length; i++) {
372
- if(tracksInItem[i].type == type)
373
- {
374
- this.changeTrackVisibility( tracksInItem[ i ].clipIdx, e.value );
375
- break;
376
- }
377
- }
378
- }
379
- break;
380
- case LX.TreeEvent.NODE_CARETCHANGED:
381
- const tracksInItem = this.animationClip.tracksPerItem[e.node.id];
382
- for( let i = 0; i < tracksInItem; ++i )
362
+ }
363
+ }
364
+ break;
365
+ case LX.TreeEvent.NODE_VISIBILITY:
366
+ if ( e.node.parent )
367
+ {
368
+ const tracksInItem = this.animationClip.tracksPerItem[ e.node.parent.id ];
369
+ const type = e.node.id;
370
+ for(let i = 0; i < tracksInItem.length; i++) {
371
+ if(tracksInItem[i].type == type)
383
372
  {
384
- this.changeTrackDisplay( tracksInItem[ i ].clipIdx, e.node.closed );
373
+ this.changeTrackVisibility( tracksInItem[ i ].clipIdx, e.value );
374
+ break;
385
375
  }
386
- break;
376
+ }
377
+ }
378
+ break;
379
+ case LX.TreeEvent.NODE_CARETCHANGED:
380
+ const tracksInItem = this.animationClip.tracksPerItem[e.node.id];
381
+ for( let i = 0; i < tracksInItem; ++i )
382
+ {
383
+ this.changeTrackDisplay( tracksInItem[ i ].clipIdx, e.node.closed );
387
384
  }
388
- }});
389
-
385
+ break;
390
386
  }
391
- }
387
+ }});
388
+ // setting a name in the addTree function adds an undesired node
389
+ this.trackTreesWidget.name = "tracksTrees";
390
+ p.widgets[this.trackTreesWidget.name] = this.trackTreesWidget;
392
391
 
393
- panel.attach( p.root )
394
- p.root.style.overflowY = "scroll";
392
+ this.trackTreesPanel = p;
393
+ panel.attach( p.root );
395
394
  p.root.addEventListener("scroll", e => {
396
- this.currentScroll = e.currentTarget.scrollTop/(e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
395
+ if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight){
396
+ this.currentScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
397
+ this.currentScrollInPixels = e.currentTarget.scrollTop;
398
+ }
399
+ else{
400
+ this.currentScroll = 0;
401
+ this.currentScrollInPixels = 0;
402
+ }
397
403
  });
398
- // for(let i = 0; i < this.animationClip.tracks.length; i++) {
399
- // let track = this.animationClip.tracks[i];
400
- // panel.addTitle(track.name + (track.type? '(' + track.type + ')' : ''));
401
- // }
402
- this.leftPanel.root.children[ 1 ].scrollTop = scrollTop;
403
404
 
404
- if( this.leftPanel.parent.root.classList.contains("hidden") || !this.root.root.parent )
405
- {
405
+ this.trackTreesPanel.scrollTop = scrollTop;
406
+
407
+ if( this.leftPanel.parent.root.classList.contains("hidden") || !this.mainArea.root.parent ){
406
408
  return;
407
409
  }
408
410
 
409
- this.resizeCanvas([ this.root.root.clientWidth - this.leftPanel.root.clientWidth - 8, this.size[1]]);
411
+ this.resizeCanvas();
410
412
  }
411
413
 
412
414
  /**
@@ -497,7 +499,6 @@ class Timeline {
497
499
  this.duration = this.animationClip.duration;
498
500
  this.speed = this.animationClip.speed ?? this.speed;
499
501
 
500
- //this.updateHeader();
501
502
  this.updateLeftPanel();
502
503
 
503
504
  return this.animationClip;
@@ -514,28 +515,30 @@ class Timeline {
514
515
 
515
516
  // background of timeinfo
516
517
  ctx.fillStyle = Timeline.BACKGROUND_COLOR;
517
- ctx.fillRect( this.session.left_margin, 0, this.canvas.width, h );
518
- ctx.strokeStyle = LX.Timeline.FONT_COLOR;
518
+ ctx.fillRect( 0, 0, this.canvas.width, h );
519
+ ctx.strokeStyle = Timeline.FONT_COLOR_PRIMARY;
519
520
 
520
521
  // set tick and sub tick times
521
522
  let tickTime = 4;
522
- if ( this.secondsToPixels > 900 ) { tickTime = 1; }
523
- else if ( this.secondsToPixels > 100 ) { tickTime = 2; }
524
- else if ( this.secondsToPixels > 50 ) { tickTime = 3; }
523
+ if ( this.pixelsPerSecond > 900 ) { tickTime = 1; }
524
+ else if ( this.pixelsPerSecond > 100 ) { tickTime = 2; }
525
+ else if ( this.pixelsPerSecond > 50 ) { tickTime = 3; }
525
526
 
526
527
  let subtickTime = this.timeSeparators[tickTime - 1];
527
528
  tickTime = this.timeSeparators[tickTime];
528
529
 
530
+ const startTime = this.visualTimeRange[0];
531
+ const endTime = this.visualTimeRange[1];
529
532
  // Transform times into pixel coords
530
- let tickX = this.timeToX( this.startTime + tickTime ) - this.timeToX( this.startTime );
533
+ let tickX = this.timeToX( startTime + tickTime ) - this.timeToX( startTime );
531
534
  let subtickX = subtickTime * tickX / tickTime;
532
535
 
533
- let startx = this.timeToX( Math.floor( this.startTime / tickTime) * tickTime ); // floor because might need to draw previous subticks
534
- let endx = this.timeToX( this.endTime ); // draw up to endTime
536
+ let startx = this.timeToX( Math.floor( startTime / tickTime) * tickTime ); // floor because might need to draw previous subticks
537
+ let endx = this.timeToX( endTime ); // draw up to endTime
535
538
 
536
539
  // Begin drawing
537
540
  ctx.beginPath();
538
- ctx.fillStyle = Timeline.FONT_COLOR;
541
+ ctx.fillStyle = Timeline.FONT_COLOR_PRIMARY;
539
542
  ctx.globalAlpha = this.opacity;
540
543
 
541
544
  for( let x = startx; x <= endx; x += tickX )
@@ -569,34 +572,35 @@ class Timeline {
569
572
  ctx.globalAlpha = this.opacity;
570
573
 
571
574
  // Content
572
- let margin = this.session.left_margin;
573
- let timeline_height = this.topMargin;
574
- let line_height = this.trackHeight;
575
+ const topMargin = this.topMargin;
576
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
577
+ const line_height = this.trackHeight;
575
578
 
576
579
  //fill track lines
577
580
  w = w || canvas.width;
578
- let max_tracks = Math.ceil( (h - timeline_height + this.currentScrollInPixels) / line_height );
581
+ let max_tracks = Math.ceil( (h - topMargin) / line_height ) + 1;
579
582
 
580
583
  ctx.save();
581
584
  ctx.fillStyle = Timeline.TRACK_COLOR_SECONDARY;
582
- ctx.globalAlpha = 0.3 * this.opacity;
583
- for(let i = 0; i <= max_tracks; i+=2)
585
+
586
+ const rectsOffset = this.currentScrollInPixels % line_height;
587
+ const blackOrWhite = 1 - Math.floor(this.currentScrollInPixels / line_height ) % 2;
588
+ for(let i = blackOrWhite; i <= max_tracks; i+=2)
584
589
  {
585
- ctx.fillRect(0, timeline_height + i * line_height - this.currentScrollInPixels, w, line_height );
590
+ ctx.fillRect(0, treeOffset - rectsOffset + i * line_height, w, line_height );
586
591
  }
587
- ctx.globalAlpha = this.opacity;
588
592
 
589
593
  //bg lines
590
594
  ctx.strokeStyle = Timeline.TRACK_COLOR_TERCIARY;
591
595
  ctx.beginPath();
592
596
 
593
597
  let pos = this.timeToX( 0 );
594
- if(pos < margin)
595
- pos = margin;
598
+ if(pos < 0)
599
+ pos = 0;
596
600
  ctx.lineWidth = 1;
597
- ctx.moveTo( pos + 0.5, timeline_height);
601
+ ctx.moveTo( pos + 0.5, topMargin);
598
602
  ctx.lineTo( pos + 0.5, canvas.height);
599
- ctx.moveTo( Math.round( this.timeToX( duration ) ) + 0.5, timeline_height);
603
+ ctx.moveTo( Math.round( this.timeToX( duration ) ) + 0.5, topMargin);
600
604
  ctx.lineTo( Math.round( this.timeToX( duration ) ) + 0.5, canvas.height);
601
605
  ctx.stroke();
602
606
 
@@ -605,36 +609,33 @@ class Timeline {
605
609
 
606
610
  /**
607
611
  * @method draw
608
- * @param {*} rect (optional)
609
612
  */
610
613
 
611
- draw( rect = null ) {
614
+ draw( ) {
612
615
 
613
616
  let ctx = this.canvas.getContext("2d");
614
617
  ctx.textBaseline = "bottom";
615
618
  ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
616
- if(!rect)
617
- rect = [0, 0, ctx.canvas.width, ctx.canvas.height ];
618
619
 
619
620
  // this.canvas = ctx.canvas;
620
- this.position[0] = rect[0];
621
- this.position[1] = rect[1];
622
- let w = rect[2];
623
- let h = rect[3];
624
- // this.updateHeader();
625
- this.currentScrollInPixels = this.scrollableHeight <= h ? 0 : (this.currentScroll * (this.scrollableHeight - h));
621
+ const w = ctx.canvas.width;
622
+ const h = ctx.canvas.height;
626
623
 
627
- //zoom
628
- this.startTime = this.session.start_time; //seconds
629
- if(this.startTime < 0)
630
- this.startTime = 0;
631
- // this.endTime = Math.ceil( this.startTime + (w - this.session.left_margin) * this.pixelsToSeconds );
632
- this.endTime = this.session.start_time + (w - this.session.left_margin) * this.pixelsToSeconds;
633
- if(this.endTime > this.duration)
634
- this.endTime = this.duration;
635
- if(this.startTime > this.endTime)
636
- this.endTime = this.startTime;
624
+ const scrollableHeight = this.trackTreesWidget.root.scrollHeight;
625
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
637
626
 
627
+ if ( this.trackTreesPanel.root.scrollHeight > 0 ){
628
+ const ul = this.trackTreesWidget.innerTree.domEl.children[0];
629
+ this.trackHeight = ul.children.length < 1 ? 25 : (ul.offsetHeight / ul.children.length);
630
+ }
631
+
632
+ //zoom
633
+ let startTime = this.visualOriginTime; //seconds
634
+ startTime = Math.min( this.duration, Math.max( 0, startTime ) );
635
+ let endTime = this.visualOriginTime + w * this.secondsPerPixel; //seconds
636
+ endTime = Math.max( startTime, Math.min( this.duration, endTime ) );
637
+ this.visualTimeRange[0] = startTime;
638
+ this.visualTimeRange[1] = endTime;
638
639
 
639
640
  this.tracksDrawn.length = 0;
640
641
 
@@ -650,40 +651,38 @@ class Timeline {
650
651
  }
651
652
 
652
653
  if(this.animationClip) {
653
-
654
- ctx.translate( this.position[0], this.position[1] + this.topMargin ); //20 is the top margin area
655
-
654
+ ctx.translate( 0, treeOffset );
656
655
  this.drawContent( ctx, this.timeStart, this.timeEnd, this );
657
-
658
- ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) ); //20 is the top margin area
656
+ ctx.translate( 0, -treeOffset );
659
657
  }
660
658
 
661
659
  //scrollbar
662
- if( h < this.scrollableHeight ){
660
+ if( (h-this.topMargin) < scrollableHeight ){
663
661
  ctx.fillStyle = "#222";
664
- ctx.fillRect( w - this.session.left_margin - 10, 0, 10, h );
662
+ ctx.fillRect( w - 10, 0, 10, h );
665
663
 
666
- ctx.fillStyle = this.grabbingScroll ? Timeline.FONT_COLOR : Timeline.TRACK_COLOR_SECONDARY;
664
+ ctx.fillStyle = this.grabbingScroll ? Timeline.FONT_COLOR_PRIMARY : Timeline.FONT_COLOR_QUATERNARY;
667
665
 
668
- let scrollBarHeight = Math.max( 10, (h-this.topMargin)* (h-this.topMargin)/ this.leftPanel.root.children[1].scrollHeight);
666
+ let scrollBarHeight = Math.max( 10, (h-this.topMargin)* (h-this.topMargin)/ this.trackTreesPanel.root.scrollHeight);
669
667
  let scrollLoc = this.currentScroll * ( h - this.topMargin - scrollBarHeight ) + this.topMargin;
670
668
  ctx.roundRect( w - 10, scrollLoc, 10, scrollBarHeight, 5, true );
671
669
  }
670
+
672
671
  this.drawTimeInfo(w);
673
672
 
674
673
  // Current time marker vertical line
675
674
  let posx = Math.round( this.timeToX( this.currentTime ) );
676
675
  let posy = this.topMargin * 0.4;
677
- if(posx >= this.session.left_margin)
676
+ if(posx >= 0)
678
677
  {
679
- ctx.strokeStyle = ctx.fillStyle = LX.getThemeColor("global-selected");
678
+ ctx.strokeStyle = ctx.fillStyle = Timeline.TIME_MARKER_COLOR;
680
679
  ctx.globalAlpha = this.opacity;
681
680
  ctx.beginPath();
682
681
  ctx.moveTo(posx, posy * 0.6); ctx.lineTo(posx, this.canvas.height);//line
683
682
  ctx.stroke();
684
683
  ctx.closePath();
685
684
  ctx.shadowBlur = 8;
686
- ctx.shadowColor = LX.getThemeColor("global-selected");
685
+ ctx.shadowColor = Timeline.TIME_MARKER_COLOR;
687
686
  ctx.shadowOffsetX = 1;
688
687
  ctx.shadowOffsetY = 1;
689
688
 
@@ -698,12 +697,12 @@ class Timeline {
698
697
  ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
699
698
  ctx.textAlign = "center";
700
699
  //ctx.textBaseline = "middle";
701
- ctx.fillStyle = Timeline.COLOR_INACTIVE;
700
+ ctx.fillStyle = Timeline.TIME_MARKER_COLOR_TEXT;
702
701
  ctx.fillText( (Math.floor(this.currentTime*10)*0.1).toFixed(1), posx, this.topMargin * 0.6 );
703
702
 
704
703
  // Selections
705
- ctx.strokeStyle = ctx.fillStyle = Timeline.FONT_COLOR;
706
- ctx.translate( this.position[0], this.position[1] + this.topMargin )
704
+ ctx.strokeStyle = ctx.fillStyle = Timeline.FONT_COLOR_PRIMARY;
705
+ ctx.translate( 0, this.topMargin );
707
706
  if(this.boxSelection) {
708
707
  ctx.globalAlpha = 0.15 * this.opacity;
709
708
  ctx.fillStyle = Timeline.BOX_SELECTION_COLOR;
@@ -712,7 +711,7 @@ class Timeline {
712
711
  ctx.stroke();
713
712
  ctx.globalAlpha = this.opacity;
714
713
  }
715
- ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) ); //20 is the top margin area
714
+ ctx.translate( 0, -this.topMargin );
716
715
 
717
716
  }
718
717
 
@@ -730,8 +729,8 @@ class Timeline {
730
729
  let markersPos = [];
731
730
  for (let i = 0; i < markers.length; ++i) {
732
731
  let marker = markers[i];
733
- if (marker.time < this.startTime - this.pixelsToSeconds * 100 ||
734
- marker.time > this.endTime)
732
+ if (marker.time < this.visualTimeRange[0] - this.secondsPerPixel * 100 ||
733
+ marker.time > this.visualTimeRange[1])
735
734
  continue;
736
735
  var x = this.timeToX(marker.time);
737
736
  markersPos.push(x);
@@ -769,14 +768,12 @@ class Timeline {
769
768
  * @param {Number} t
770
769
  */
771
770
 
772
- setDuration( t, updateHeader = true, skipCallback = false ) {
771
+ setDuration( t, skipCallback = false, updateHeader = true ) {
773
772
  let v = this.validateDuration(t);
774
- let decimals = t.toString().split('.')[1] ? t.toString().split('.')[1].length : 0;
775
- updateHeader = (updateHeader || +v.toFixed(decimals) != t);
776
773
  this.duration = this.animationClip.duration = v;
777
774
 
778
775
  if(updateHeader) {
779
- LX.emit( "@on_set_duration_" + this.name, +this.duration.toFixed(3)); // skipcallback = true
776
+ LX.emit( "@on_set_duration_" + this.name, +this.duration.toFixed(2)); // skipcallback = true
780
777
  }
781
778
 
782
779
  if( this.onSetDuration && !skipCallback )
@@ -816,30 +813,26 @@ class Timeline {
816
813
 
817
814
  // Converts distance in pixels to time
818
815
  xToTime( x ) {
819
- return (x - this.session.left_margin) / this.secondsToPixels + this.session.start_time;
816
+ return x * this.secondsPerPixel + this.visualOriginTime;
820
817
  }
821
818
 
822
819
  // Converts time to disance in pixels
823
820
  timeToX( t ) {
824
- return this.session.left_margin + (t - this.session.start_time) * this.secondsToPixels;
821
+ return (t - this.visualOriginTime) * this.pixelsPerSecond;
825
822
  }
826
823
 
827
824
  /**
828
825
  * @method setScale
829
- * @param {*} v
826
+ * @param {*} pixelsPerSecond >0. totalVisiblePixels / totalVisibleSeconds.
830
827
  */
831
828
 
832
- setScale( v ) {
833
-
834
- if(!this.session)
835
- return;
836
-
829
+ setScale( pixelsPerSecond ) {
837
830
  const xCurrentTime = this.timeToX(this.currentTime);
838
- this.secondsToPixels *= v;
839
- this.secondsToPixels = Math.max( 0.00001, this.secondsToPixels );
831
+ this.pixelsPerSecond = pixelsPerSecond;
832
+ this.pixelsPerSecond = Math.max( 0.00001, this.pixelsPerSecond );
840
833
 
841
- this.pixelsToSeconds = 1 / this.secondsToPixels;
842
- this.session.start_time += this.currentTime - this.xToTime(xCurrentTime);
834
+ this.secondsPerPixel = 1 / this.pixelsPerSecond;
835
+ this.visualOriginTime += this.currentTime - this.xToTime(xCurrentTime);
843
836
  }
844
837
 
845
838
  /**
@@ -862,12 +855,12 @@ class Timeline {
862
855
  let y = e.offsetY;
863
856
  e.deltax = x - this.lastMouse[0];
864
857
  e.deltay = y - this.lastMouse[1];
865
- let localX = e.offsetX - this.position[0];
866
- let localY = e.offsetY - this.position[1];
858
+ let localX = e.offsetX;
859
+ let localY = e.offsetY;
867
860
 
868
861
  let timeX = this.timeToX( this.currentTime );
869
- let isHoveringTimeBar = localY < this.topMargin && localX > this.session.left_margin &&
870
- localX > (timeX - 6) && localX < (timeX + 6);
862
+ let isHoveringTimeBar = localY < this.topMargin &&
863
+ localX > (timeX - 6) && localX < (timeX + 6);
871
864
 
872
865
  if( isHoveringTimeBar ) {
873
866
  this.canvas.style.cursor = "col-resize";
@@ -885,16 +878,16 @@ class Timeline {
885
878
  if( e.type == "wheel" ) {
886
879
  if(e.shiftKey)
887
880
  {
888
- // mouseTime = xToTime(localX)_prev = xToTime(localX)_after
889
- //(x - this.session.left_margin) / this.secondsToPixels_prev + this.session.start_time_prev = (x - this.session.left_margin) / this.secondsToPixels_after + this.session.start_time_after
890
- // start_time = xToTime(localX)_prev - (x - this.session.left_margin) / this.secondsToPixels_after
891
- let mouseTime = this.xToTime(localX);
892
- this.setScale( e.wheelDelta < 0 ? 0.95 : 1.05 );
893
- this.session.start_time = mouseTime - (localX - this.session.left_margin) / this.secondsToPixels;
881
+ if ( e.wheelDelta ){
882
+ let mouseTime = this.xToTime(localX);
883
+ this.setScale( this.pixelsPerSecond * (e.wheelDelta < 0 ? 0.95 : 1.05) );
884
+ this.visualOriginTime = mouseTime - localX * this.secondsPerPixel;
885
+ }
886
+
894
887
  }
895
- else if( h < this.scrollableHeight)
888
+ else if( (h-this.topMargin) < this.trackTreesWidget.root.scrollHeight)
896
889
  {
897
- this.leftPanel.root.children[1].scrollTop += e.deltaY; // wheel deltaY
890
+ this.trackTreesPanel.root.scrollTop += e.deltaY; // wheel deltaY
898
891
  }
899
892
 
900
893
  if ( this.onMouse ){
@@ -905,8 +898,8 @@ class Timeline {
905
898
 
906
899
  var time = this.xToTime(x, true);
907
900
 
908
- var is_inside = x >= this.position[0] && x <= (this.position[0] + this.size[0]) &&
909
- y >= this.position[1] && y <= (this.position[1] + this.size[1]);
901
+ var is_inside = x >= 0 && x <= this.size[0] &&
902
+ y >= 0 && y <= this.size[1];
910
903
 
911
904
  var track = null;
912
905
  for(var i = this.tracksDrawn.length - 1; i >= 0; --i)
@@ -971,7 +964,7 @@ class Timeline {
971
964
  this.grabbingTimeBar = true;
972
965
  this.setTime(time);
973
966
  }
974
- else if( h < this.scrollableHeight && x > w - 10 ) { // grabbing scroll bar
967
+ else if( (h-this.topMargin) < this.trackTreesWidget.root.scrollHeight && x > w - 10 ) { // grabbing scroll bar
975
968
  this.grabbing = true;
976
969
  this.grabbingScroll = true;
977
970
  }
@@ -999,22 +992,22 @@ class Timeline {
999
992
  }
1000
993
  else if(this.grabbingScroll)
1001
994
  {
1002
- let h = this.leftPanel.root.clientHeight;
1003
- let scrollBarHeight = Math.max( 10, (h-this.topMargin)* (h-this.topMargin)/this.leftPanel.root.children[1].scrollHeight);
1004
- let minScrollLoc = this.topMargin;
1005
- let maxScrollLoc = h - scrollBarHeight; // - sizeScrollBar
1006
-
1007
- this.currentScroll = Math.min( 1, Math.max(e.localY - minScrollLoc, 0 ) / (maxScrollLoc - minScrollLoc) );
1008
- this.leftPanel.root.children[1].scrollTop = this.currentScroll * (this.leftPanel.root.children[1].scrollHeight-this.leftPanel.root.children[1].clientHeight);
995
+ // will automatically call scroll event
996
+ if ( y < this.topMargin ){
997
+ this.trackTreesPanel.root.scrollTop = 0;
998
+ }
999
+ else{
1000
+ this.trackTreesPanel.root.scrollTop += this.trackTreesPanel.root.scrollHeight * e.deltay / (h-this.topMargin);
1001
+ }
1009
1002
  }
1010
1003
  else
1011
1004
  {
1012
1005
  // Move timeline in X (independent of current time)
1013
1006
  var old = this.xToTime( this.lastMouse[0] );
1014
1007
  var now = this.xToTime( e.offsetX );
1015
- this.session.start_time += (old - now);
1008
+ this.visualOriginTime += (old - now);
1016
1009
 
1017
- this.leftPanel.root.children[1].scrollTop -= e.deltay; // will automatically call scroll event
1010
+ this.trackTreesPanel.root.scrollTop -= e.deltay; // will automatically call scroll event
1018
1011
 
1019
1012
  }
1020
1013
  }
@@ -1077,15 +1070,43 @@ class Timeline {
1077
1070
 
1078
1071
  /**
1079
1072
  * @method changeState
1073
+ * @param {bool} skipCallback defaults false
1080
1074
  * @description change play/pause state
1081
- * ...
1082
1075
  **/
1083
- changeState() {
1084
- this.playing = !this.playing;
1085
- this.updateHeader();
1076
+ changeState(skipCallback = false) {
1077
+ this.setState(!this.playing, skipCallback);
1078
+ }
1079
+ /**
1080
+ * @method setState
1081
+ * @param {bool} state
1082
+ * @param {bool} skipCallback defaults false
1083
+ * @description change play/pause state
1084
+ **/
1085
+ setState(state, skipCallback = false) {
1086
+ this.playing = state;
1087
+
1088
+ this.header.widgets.playBtn.root.setState(this.playing, true);
1089
+
1090
+ if(this.onStateChange && !skipCallback) {
1091
+ this.onStateChange(this.playing);
1092
+ }
1093
+ }
1086
1094
 
1087
- if(this.onChangeState) {
1088
- this.onChangeState(this.playing);
1095
+ /**
1096
+ * @method setLoopMode
1097
+ * @param {bool} loopState
1098
+ * @param {bool} skipCallback defaults false
1099
+ * @description change loop mode of the timeline
1100
+ */
1101
+ setLoopMode(loopState, skipCallback = false){
1102
+ this.loop = loopState;
1103
+ if ( this.loop ){
1104
+ this.header.widgets.loopBtn.root.children[0].classList.add("selected");
1105
+ }else{
1106
+ this.header.widgets.loopBtn.root.children[0].classList.remove("selected")
1107
+ }
1108
+ if( this.onChangeLoopMode && !skipCallback ){
1109
+ this.onChangeLoopMode( this.loop );
1089
1110
  }
1090
1111
  }
1091
1112
 
@@ -1096,38 +1117,33 @@ class Timeline {
1096
1117
 
1097
1118
  drawTrackWithBoxes( ctx, y, trackHeight, title, track ) {
1098
1119
 
1120
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
1121
+ this.tracksDrawn.push([track, y + treeOffset, trackHeight]);
1122
+
1123
+ // Fill track background if it's selected
1124
+ ctx.globalAlpha = 0.2 * this.opacity;
1125
+ ctx.fillStyle = Timeline.TRACK_SELECTED_LIGHT;
1126
+ if(track.isSelected) {
1127
+ ctx.fillRect(0, y, ctx.canvas.width, trackHeight );
1128
+ }
1129
+
1130
+ const clips = track.clips;
1131
+ if(!clips) {
1132
+ return;
1133
+ }
1134
+
1099
1135
  const offset = (trackHeight - trackHeight * 0.6) * 0.5;
1100
- this.tracksDrawn.push([track, y + this.topMargin, trackHeight]);
1101
1136
 
1102
1137
  trackHeight *= 0.6;
1103
- this.canvas = this.canvas || ctx.canvas;
1104
1138
 
1105
1139
  let selectedClipArea = null;
1106
1140
 
1107
- if(track.enabled === false) {
1108
- ctx.globalAlpha = 0.4 * this.opacity;
1109
- }
1110
- else {
1111
- ctx.globalAlpha = 0.2 * this.opacity;
1112
- }
1113
-
1114
1141
  ctx.font = Math.floor( trackHeight * 0.8) + "px" + Timeline.FONT;
1115
1142
  ctx.textAlign = "left";
1116
1143
  ctx.textBaseline = "middle";
1117
- ctx.fillStyle = Timeline.TRACK_SELECTED_LIGHT;
1118
-
1119
- // Fill track background if it's selected
1120
- if(track.isSelected) {
1121
- ctx.fillRect(0, y + offset - 2, ctx.canvas.width, trackHeight + 4 );
1122
- }
1123
-
1124
- let clips = track.clips;
1125
- let trackAlpha = this.opacity;
1126
-
1127
- if(!clips) {
1128
- return;
1129
- }
1144
+ const trackAlpha = this.opacity;
1130
1145
 
1146
+
1131
1147
  for(var j = 0; j < clips.length; ++j)
1132
1148
  {
1133
1149
  selectedClipArea = null;
@@ -1142,20 +1158,20 @@ class Timeline {
1142
1158
 
1143
1159
  // Overwrite clip color state depending on its state
1144
1160
  ctx.globalAlpha = trackAlpha;
1145
- ctx.fillStyle = clip.clipColor || (track.hovered[j] ? Timeline.COLOR_HOVERED : (Timeline.COLOR));
1161
+ ctx.fillStyle = clip.clipColor || (track.hovered[j] ? Timeline.KEYFRAME_COLOR_HOVERED : (Timeline.KEYFRAME_COLOR));
1146
1162
  if(track.selected[j] && !clip.clipColor) {
1147
1163
  ctx.fillStyle = Timeline.TRACK_SELECTED;
1148
1164
  }
1149
1165
  if(!this.active || track.active == false) {
1150
- ctx.fillStyle = Timeline.COLOR_INACTIVE;
1166
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_INACTIVE;
1151
1167
  }
1152
1168
 
1153
1169
  // Draw clip background
1154
1170
  ctx.roundRect( x, y + offset, w, trackHeight , 5, true);
1155
1171
 
1156
1172
  // Compute timeline position of fade-in and fade-out clip times
1157
- let fadeinX = this.secondsToPixels * ((clip.fadein || 0) - clip.start);
1158
- let fadeoutX = this.secondsToPixels * (clip.start + clip.duration - (clip.fadeout || (clip.start + clip.duration)));
1173
+ let fadeinX = this.pixelsPerSecond * ((clip.fadein || 0) - clip.start);
1174
+ let fadeoutX = this.pixelsPerSecond * (clip.start + clip.duration - (clip.fadeout || (clip.start + clip.duration)));
1159
1175
 
1160
1176
  if(this.active && track.active) {
1161
1177
  // Transform fade-in and fade-out fill color to RGBA
@@ -1177,7 +1193,7 @@ class Timeline {
1177
1193
  }
1178
1194
  }
1179
1195
 
1180
- ctx.fillStyle = clip.color || Timeline.FONT_COLOR; // clip.color || Timeline.FONT_COLOR;
1196
+ ctx.fillStyle = clip.color || Timeline.FONT_COLOR_PRIMARY;
1181
1197
  //ctx.font = "12px" + Timeline.FONT;
1182
1198
 
1183
1199
  // Overwrite style and draw clip selection area if it's selected
@@ -1201,19 +1217,19 @@ class Timeline {
1201
1217
  }
1202
1218
 
1203
1219
  // Overwrite style with small font size if it's zoomed out
1204
- if( this.secondsToPixels < 200) {
1205
- ctx.font = this.secondsToPixels*0.06 +"px" + Timeline.FONT;
1220
+ if( this.pixelsPerSecond < 200) {
1221
+ ctx.font = this.pixelsPerSecond*0.06 +"px" + Timeline.FONT;
1206
1222
  }
1207
1223
 
1208
1224
  const text = clip.id.replaceAll("_", " ").replaceAll("-", " ");
1209
1225
  const textInfo = ctx.measureText( text );
1210
1226
 
1211
1227
  // Draw clip name if it's readable
1212
- if(this.secondsToPixels > 100) {
1228
+ if(this.pixelsPerSecond > 100) {
1213
1229
  ctx.fillText( text, x + (w - textInfo.width)*0.5, y + offset + trackHeight * 0.5);
1214
1230
  }
1215
1231
 
1216
- ctx.fillStyle = track.hovered[j] ? "white" : Timeline.FONT_COLOR;
1232
+ ctx.fillStyle = track.hovered[j] ? "white" : Timeline.FONT_COLOR_PRIMARY;
1217
1233
  // Draw resize bounding
1218
1234
  ctx.roundRect(x + w - 8 , y + offset , 8, trackHeight, {tl: 4, bl: 4, tr:4, br:4}, true);
1219
1235
  }
@@ -1224,7 +1240,7 @@ class Timeline {
1224
1240
  /**
1225
1241
  * @method selectTrack
1226
1242
  * @param {int} trackIdx
1227
- * // NOTE: to select a track from outside of the timeline, a this.leftPanelTrackTree.select(item) needs to be called.
1243
+ * // NOTE: to select a track from outside of the timeline, a this.trackTreesWidget.innerTree.select(item) needs to be called.
1228
1244
  */
1229
1245
  selectTrack( trackIdx ) {
1230
1246
 
@@ -1293,23 +1309,17 @@ class Timeline {
1293
1309
  * @method resize
1294
1310
  * @param {*} size
1295
1311
  */
1296
- resize( size = [this.root.parent.root.clientWidth, this.root.parent.root.clientHeight]) {
1312
+ resize( size = [this.mainArea.parent.root.clientWidth, this.mainArea.parent.root.clientHeight]) {
1297
1313
 
1298
- // this.root.root.style.width = size[0] + "px";
1299
- // this.root.root.style.height = size[1] + "px";
1300
-
1301
1314
  this.size = size;
1302
1315
  //this.content_area.setSize([size[0], size[1] - this.header_offset]);
1303
- this.content_area.root.style.height = "calc(100% - "+ this.header_offset + "px)";
1316
+ this.mainArea.sections[1].root.style.height = "calc(100% - "+ this.header_offset + "px)";
1304
1317
 
1305
1318
  let w = size[0] - this.leftPanel.root.clientWidth - 8;
1306
- this.resizeCanvas([w , size[1]]);
1319
+ this.resizeCanvas();
1307
1320
  }
1308
1321
 
1309
- resizeCanvas( size ) {
1310
- if( size[0] <= 0 && size[1] <=0 )
1311
- return;
1312
- size[1] -= this.header_offset;
1322
+ resizeCanvas( ) {
1313
1323
  this.canvas.width = this.canvasArea.root.clientWidth;
1314
1324
  this.canvas.height = this.canvasArea.root.clientHeight;
1315
1325
  }
@@ -1319,7 +1329,7 @@ class Timeline {
1319
1329
  * Hide timeline area
1320
1330
  */
1321
1331
  hide() {
1322
- this.root.hide();
1332
+ this.mainArea.hide();
1323
1333
  }
1324
1334
 
1325
1335
  /**
@@ -1328,9 +1338,9 @@ class Timeline {
1328
1338
  */
1329
1339
  show() {
1330
1340
 
1331
- this.root.show();
1332
- this.updateLeftPanel();
1341
+ this.mainArea.show();
1333
1342
  this.resize();
1343
+ this.updateLeftPanel();
1334
1344
  }
1335
1345
 
1336
1346
  /**
@@ -1343,8 +1353,15 @@ class Timeline {
1343
1353
  Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor("global-color-terciary");
1344
1354
  Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor("global-color-quaternary");
1345
1355
  Timeline.FONT = LX.getThemeColor("global-font");
1346
- Timeline.FONT_COLOR = LX.getThemeColor("global-text-primary");
1347
- }
1356
+ Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor("global-text-primary");
1357
+ Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor("global-text-quaternary");
1358
+
1359
+ Timeline.KEYFRAME_COLOR = LX.getThemeColor("lxTimeline-keyframe");
1360
+ Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor("lxTimeline-keyframe-selected");
1361
+ Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor("lxTimeline-keyframe-locked");
1362
+ Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor("lxTimeline-keyframe-edited");
1363
+ Timeline.KEYFRAME_COLOR_INACTIVE =LX.getThemeColor("lxTimeline-keyframe-inactive");
1364
+ }
1348
1365
  };
1349
1366
 
1350
1367
  Timeline.BACKGROUND_COLOR = LX.getThemeColor("global-blur-background");
@@ -1352,16 +1369,25 @@ Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor("global-color-primary");
1352
1369
  Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor("global-color-secondary");
1353
1370
  Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor("global-color-terciary");
1354
1371
  Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor("global-color-quaternary");
1355
- Timeline.TRACK_SELECTED = LX.getThemeColor("global-selected");
1356
- Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor("global-selected-light");
1372
+ Timeline.TRACK_SELECTED = LX.getThemeColor("global-color-accent");
1373
+ Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor("global-color-accent-light");
1357
1374
  Timeline.FONT = LX.getThemeColor("global-font");
1358
- Timeline.FONT_COLOR = LX.getThemeColor("global-text-primary");
1359
- Timeline.COLOR = LX.getThemeColor("global-selected-dark");
1360
- Timeline.COLOR_SELECTED = Timeline.COLOR_HOVERED = "rgba(250,250,20,1)";///"rgba(250,250,20,1)";
1361
- // Timeline.COLOR_HOVERED = LX.getThemeColor("global-selected");
1362
- Timeline.COLOR_INACTIVE = "rgba(250,250,250,0.7)";
1363
- Timeline.COLOR_LOCK = "rgba(255,125,125,0.7)";
1364
- Timeline.COLOR_EDITED = "rgba(20,230,20,0.7)"//"rgba(125,250,250, 1)";
1375
+ Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor("global-text-primary");
1376
+ Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor("global-text-quaternary");
1377
+ Timeline.TIME_MARKER_COLOR = LX.getThemeColor("global-color-accent");
1378
+ Timeline.TIME_MARKER_COLOR_TEXT = "#ffffff";
1379
+
1380
+ LX.setThemeColor("lxTimeline-keyframe", "light-dark(#2d69da,#2d69da)");
1381
+ LX.setThemeColor("lxTimeline-keyframe-selected", "light-dark(#f5c700,#fafa14)");
1382
+ LX.setThemeColor("lxTimeline-keyframe-hovered", "light-dark(#f5c700,#fafa14)");
1383
+ LX.setThemeColor("lxTimeline-keyframe-locked", "light-dark(#c62e2e,#ff7d7d)");
1384
+ LX.setThemeColor("lxTimeline-keyframe-edited", "light-dark(#00d000,#00d000)");
1385
+ LX.setThemeColor("lxTimeline-keyframe-inactive", "light-dark(#706b6b,#706b6b)");
1386
+ Timeline.KEYFRAME_COLOR = LX.getThemeColor("lxTimeline-keyframe");
1387
+ Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor("lxTimeline-keyframe-selected");
1388
+ Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor("lxTimeline-keyframe-locked");
1389
+ Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor("lxTimeline-keyframe-edited");
1390
+ Timeline.KEYFRAME_COLOR_INACTIVE =LX.getThemeColor("lxTimeline-keyframe-inactive");
1365
1391
  Timeline.BOX_SELECTION_COLOR = "#AAA";
1366
1392
  LX.Timeline = Timeline;
1367
1393
 
@@ -1394,7 +1420,7 @@ class KeyFramesTimeline extends Timeline {
1394
1420
  e.multipleSelection = true;
1395
1421
  // Manual multiple selection
1396
1422
  if(!discard && track) {
1397
- const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
1423
+ const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
1398
1424
  if ( keyFrameIdx > -1 ){
1399
1425
  track.selected[keyFrameIdx] ?
1400
1426
  this.unSelectKeyFrame(track, keyFrameIdx) :
@@ -1403,13 +1429,13 @@ class KeyFramesTimeline extends Timeline {
1403
1429
  }
1404
1430
  // Box selection
1405
1431
  else if(this.boxSelection) {
1406
- let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.pixelsToSeconds * 5);
1432
+ let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.secondsPerPixel * 5);
1407
1433
 
1408
1434
  for(let t of tracks) {
1409
1435
  let keyFrameIndices = this.getKeyFramesInRange(t,
1410
1436
  this.xToTime( this.boxSelectionStart[0] ),
1411
1437
  this.xToTime( this.boxSelectionEnd[0] ),
1412
- this.pixelsToSeconds * 5);
1438
+ this.secondsPerPixel * 5);
1413
1439
 
1414
1440
  if(keyFrameIndices) {
1415
1441
  for(let index = keyFrameIndices[0]; index <= keyFrameIndices[1]; ++index){
@@ -1428,7 +1454,7 @@ class KeyFramesTimeline extends Timeline {
1428
1454
  this.unSelectAllKeyFrames();
1429
1455
  }
1430
1456
  if (track){
1431
- const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
1457
+ const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
1432
1458
  if( keyFrameIndex > -1 ) {
1433
1459
  this.processCurrentKeyFrame( e, keyFrameIndex, track, null, e.multipleSelection ); // Settings this as multiple so time is not being set
1434
1460
  }
@@ -1555,7 +1581,7 @@ class KeyFramesTimeline extends Timeline {
1555
1581
  else if(track) {
1556
1582
 
1557
1583
  this.unHoverAll();
1558
- let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
1584
+ let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
1559
1585
  if(keyFrameIndex > -1 ) {
1560
1586
 
1561
1587
  const name = this.animationClip.tracksDictionary[track.fullname];
@@ -1638,43 +1664,44 @@ class KeyFramesTimeline extends Timeline {
1638
1664
 
1639
1665
  }
1640
1666
 
1641
- drawContent( ctx, timeStart, timeEnd ) {
1667
+ drawContent( ctx ) {
1642
1668
 
1643
1669
  if(!this.animationClip || !this.animationClip.tracksPerItem)
1644
1670
  return;
1645
1671
 
1646
1672
  ctx.save();
1647
- this.scrollableHeight = this.topMargin;
1648
1673
 
1649
- let offset = this.trackHeight;
1674
+ const trackHeight = this.trackHeight;
1650
1675
  const tracksPerItem = this.animationClip.tracksPerItem;
1676
+ const scrollY = - this.currentScrollInPixels;
1677
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
1678
+
1679
+ let offset = scrollY;
1680
+ ctx.translate(0, offset);
1681
+
1651
1682
  for(let t = 0; t < this.selectedItems.length; t++) {
1652
- let tracks = tracksPerItem[this.selectedItems[t]] ? tracksPerItem[this.selectedItems[t]] : [{name: this.selectedItems[t]}];
1683
+ let tracks = tracksPerItem[this.selectedItems[t]];
1653
1684
  if(!tracks) continue;
1654
1685
 
1655
- const height = this.trackHeight;
1656
- this.scrollableHeight += (tracks.length+1)*height;
1657
- let scroll_y = - this.currentScrollInPixels;
1686
+ offset += trackHeight;
1687
+ ctx.translate(0, trackHeight);
1688
+
1689
+ if ( this.trackTreesWidget.innerTree.data[t].closed ){
1690
+ continue;
1691
+ }
1658
1692
 
1659
- let offsetI = 0;
1660
1693
  for(let i = 0; i < tracks.length; i++) {
1661
1694
  let track = tracks[i];
1662
1695
  if(track.hide) {
1663
1696
  continue;
1664
1697
  }
1665
1698
 
1666
- ctx.save();
1667
-
1668
- let track_y = offsetI * height + offset + scroll_y;
1669
- ctx.translate(0, track_y);
1670
- this.drawTrackWithKeyframes(ctx, height, track);
1671
- this.tracksDrawn.push([track, track_y + this.topMargin, height]);
1672
-
1673
- ctx.restore();
1674
-
1675
- offsetI++;
1699
+ this.drawTrackWithKeyframes(ctx, trackHeight, track);
1700
+ this.tracksDrawn.push([track, offset + treeOffset, trackHeight]);
1701
+
1702
+ offset += trackHeight;
1703
+ ctx.translate(0, trackHeight);
1676
1704
  }
1677
- offset += offsetI * height + height;
1678
1705
  }
1679
1706
 
1680
1707
  ctx.restore();
@@ -1699,19 +1726,22 @@ class KeyFramesTimeline extends Timeline {
1699
1726
  ctx.fillRect(0, 0, ctx.canvas.width, trackHeight );
1700
1727
  }
1701
1728
 
1702
- ctx.fillStyle = Timeline.COLOR;
1729
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR;
1703
1730
  ctx.globalAlpha = this.opacity;
1704
1731
 
1705
- let keyframes = track.times;
1732
+ const keyframes = track.times;
1706
1733
 
1707
1734
  if(!keyframes) {
1708
1735
  return;
1709
1736
  }
1710
1737
 
1738
+ const startTime = this.visualTimeRange[0];
1739
+ const endTime = this.visualTimeRange[1];
1740
+
1711
1741
  for(let j = 0; j < keyframes.length; ++j)
1712
1742
  {
1713
1743
  let time = keyframes[j];
1714
- if( time < this.startTime || time > this.endTime ) {
1744
+ if( time < startTime || time > endTime ) {
1715
1745
  continue;
1716
1746
  }
1717
1747
 
@@ -1719,23 +1749,23 @@ class KeyFramesTimeline extends Timeline {
1719
1749
  let size = trackHeight * 0.3;
1720
1750
 
1721
1751
  if(!this.active || track.active == false) {
1722
- ctx.fillStyle = Timeline.COLOR_INACTIVE;
1752
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_INACTIVE;
1723
1753
  }
1724
1754
  else if(track.locked) {
1725
- ctx.fillStyle = Timeline.COLOR_LOCK;
1755
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_LOCK;
1726
1756
  }
1727
1757
  else if(track.hovered[j]) {
1728
1758
  size = trackHeight * 0.45;
1729
- ctx.fillStyle = Timeline.COLOR_HOVERED;
1759
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_HOVERED;
1730
1760
  }
1731
1761
  else if(track.selected[j]) {
1732
- ctx.fillStyle = Timeline.COLOR_SELECTED;
1762
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_SELECTED;
1733
1763
  }
1734
1764
  else if(track.edited[j]) {
1735
- ctx.fillStyle = Timeline.COLOR_EDITED;
1765
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_EDITED;
1736
1766
  }
1737
1767
  else {
1738
- ctx.fillStyle = Timeline.COLOR;
1768
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR;
1739
1769
  }
1740
1770
 
1741
1771
  ctx.save();
@@ -2446,7 +2476,13 @@ class KeyFramesTimeline extends Timeline {
2446
2476
 
2447
2477
  this.unSelectAllKeyFrames();
2448
2478
  this.unHoverAll();
2449
- this.selectedItems = itemsName;
2479
+
2480
+ this.selectedItems = [];
2481
+ for( let i = 0; i < itemsName.length; ++i ){
2482
+ if ( this.animationClip.tracksPerItem[itemsName[i]] ){
2483
+ this.selectedItems.push(itemsName[i]);
2484
+ }
2485
+ }
2450
2486
  this.updateLeftPanel();
2451
2487
  }
2452
2488
 
@@ -2663,7 +2699,7 @@ class KeyFramesTimeline extends Timeline {
2663
2699
  this.unSelectAllKeyFrames();
2664
2700
  }
2665
2701
 
2666
- keyFrameIndex = keyFrameIndex ?? this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
2702
+ keyFrameIndex = keyFrameIndex ?? this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
2667
2703
  if(keyFrameIndex < 0)
2668
2704
  return;
2669
2705
 
@@ -2759,23 +2795,17 @@ class ClipsTimeline extends Timeline {
2759
2795
  this.lastTrackClipsMove = 0; // vertical movement of clips, onMouseMove onMousedown
2760
2796
  }
2761
2797
 
2762
- updateLeftPanel(area) {
2798
+ updateLeftPanel() {
2763
2799
 
2764
- let scrollTop = 0;
2765
- if(this.leftPanel){
2766
- scrollTop = this.leftPanel.root.children[1].scrollTop;
2767
- this.leftPanel.clear();
2768
- }
2769
- else {
2770
- this.leftPanel = area.addPanel({className: 'lextimelinepanel', width: "100%", height: "100%"});
2771
- }
2800
+ const scrollTop = this.trackTreesPanel ? this.trackTreesPanel.root.scrollTop : 0;
2801
+ this.leftPanel.clear();
2772
2802
 
2773
- let panel = this.leftPanel;
2774
-
2775
- panel.sameLine(2);
2803
+ const panel = this.leftPanel;
2776
2804
 
2805
+ panel.sameLine(2);
2777
2806
  let titleWidget = panel.addTitle("Tracks");
2778
2807
  let title = titleWidget.root;
2808
+
2779
2809
  if(!this.disableNewTracks)
2780
2810
  {
2781
2811
  panel.addButton("addTrackBtn", '<i class = "fa-solid fa-plus"></i>', (value, event) => {
@@ -2783,8 +2813,10 @@ class ClipsTimeline extends Timeline {
2783
2813
  }, { hideName: true, title: "Add Track" });
2784
2814
  }
2785
2815
  panel.endLine();
2816
+
2786
2817
  const styles = window.getComputedStyle(title);
2787
2818
  const titleHeight = title.clientHeight + parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom']);
2819
+
2788
2820
  let p = new LX.Panel({height: "calc(100% - " + titleHeight + "px)"});
2789
2821
 
2790
2822
  let treeTracks = [];
@@ -2798,12 +2830,11 @@ class ClipsTimeline extends Timeline {
2798
2830
  'skipVisibility': this.skipVisibility,
2799
2831
  'visible': track.active,
2800
2832
  // 'selected' : track.isSelected
2801
- } );
2802
-
2833
+ } );
2803
2834
  }
2804
2835
 
2805
2836
  }
2806
- this.leftPanelTrackTree = p.addTree(null, treeTracks, {filter: false, rename: false, draggable: false, onevent: (e) => {
2837
+ this.trackTreesWidget = p.addTree(null, treeTracks, {filter: false, rename: false, draggable: false, onevent: (e) => {
2807
2838
  switch(e.type) {
2808
2839
  case LX.TreeEvent.NODE_SELECTED:
2809
2840
  this.selectTrack( parseInt( e.node.id.split("Track_")[1] ) );
@@ -2816,17 +2847,30 @@ class ClipsTimeline extends Timeline {
2816
2847
  break;
2817
2848
  }
2818
2849
  }});
2850
+ // setting a name in the addTree function adds an undesired node
2851
+ this.trackTreesWidget.name = "tracksTrees";
2852
+ p.widgets[this.trackTreesWidget.name] = this.trackTreesWidget;
2853
+
2854
+ this.trackTreesPanel = p;
2819
2855
  panel.attach(p.root)
2820
- p.root.style.overflowY = "scroll";
2821
2856
  p.root.addEventListener("scroll", (e) => {
2822
- this.currentScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
2823
- })
2857
+ if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight){
2858
+ this.currentScroll = e.currentTarget.scrollTop / (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
2859
+ this.currentScrollInPixels = e.currentTarget.scrollTop;
2860
+ }
2861
+ else{
2862
+ this.currentScroll = 0;
2863
+ this.currentScrollInPixels = 0;
2864
+ }
2865
+ });
2824
2866
 
2825
- this.leftPanel.root.children[1].scrollTop = scrollTop;
2867
+ this.trackTreesPanel.root.scrollTop = scrollTop;
2826
2868
 
2827
- if(this.leftPanel.parent.root.classList.contains("hidden") || !this.root.root.parent)
2869
+ if(this.leftPanel.parent.root.classList.contains("hidden") || !this.mainArea.root.parent){
2828
2870
  return;
2829
- this.resizeCanvas([ this.root.root.clientWidth - this.leftPanel.root.clientWidth - 8, this.size[1]]);
2871
+ }
2872
+
2873
+ this.resizeCanvas();
2830
2874
  }
2831
2875
 
2832
2876
  unSelectAllTracks() {
@@ -2856,7 +2900,7 @@ class ClipsTimeline extends Timeline {
2856
2900
  // Manual Multiple selection
2857
2901
  if(!discard) {
2858
2902
  if ( track ){
2859
- let clipIndex = this.getCurrentClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
2903
+ let clipIndex = this.getCurrentClip( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
2860
2904
  if ( clipIndex > -1 ){
2861
2905
  track.selected[clipIndex] ?
2862
2906
  this.unselectClip( track, clipIndex, null ) :
@@ -2867,13 +2911,13 @@ class ClipsTimeline extends Timeline {
2867
2911
  // Box selection
2868
2912
  else if (this.boxSelection){
2869
2913
 
2870
- let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.pixelsToSeconds * 5);
2914
+ let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.secondsPerPixel * 5);
2871
2915
 
2872
2916
  for(let t of tracks) {
2873
2917
  let clipsIndices = this.getClipsInRange(t,
2874
2918
  this.xToTime( this.boxSelectionStart[0] ),
2875
2919
  this.xToTime( this.boxSelectionEnd[0] ),
2876
- this.pixelsToSeconds * 5);
2920
+ this.secondsPerPixel * 5);
2877
2921
 
2878
2922
  if(clipsIndices) {
2879
2923
  for(let index of clipsIndices)
@@ -2917,7 +2961,7 @@ class ClipsTimeline extends Timeline {
2917
2961
  this.canvas.style.cursor = "grab";
2918
2962
  let curTrackIdx = -1;
2919
2963
 
2920
- this.lastTrackClipsMove = Math.floor( (e.localY - this.topMargin + this.leftPanel.root.children[1].scrollTop) / this.trackHeight );
2964
+ this.lastTrackClipsMove = Math.floor( (e.localY - this.topMargin + this.trackTreesPanel.root.scrollTop) / this.trackHeight );
2921
2965
 
2922
2966
  for(let i = 0; i < selectedClips.length; i++)
2923
2967
  {
@@ -3001,7 +3045,8 @@ class ClipsTimeline extends Timeline {
3001
3045
 
3002
3046
  this.movingKeys = true;
3003
3047
 
3004
- let newTrackClipsMove = Math.floor( (e.localY - this.topMargin + this.leftPanel.root.children[1].scrollTop) / this.trackHeight );
3048
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
3049
+ let newTrackClipsMove = Math.floor( (e.localY - treeOffset) / this.trackHeight );
3005
3050
 
3006
3051
  // move clips vertically
3007
3052
  if ( e.altKey ){
@@ -3306,23 +3351,19 @@ class ClipsTimeline extends Timeline {
3306
3351
 
3307
3352
  }
3308
3353
 
3309
- drawContent( ctx, timeStart, timeEnd ) {
3354
+ drawContent( ctx ) {
3310
3355
 
3311
- if(!this.animationClip)
3356
+ if(!this.animationClip || !this.animationClip.tracks)
3312
3357
  return;
3313
- let tracks = this.animationClip.tracks|| [{name: "NMF", clips: []}];
3314
- if(!tracks)
3315
- return;
3316
-
3317
- const height = this.trackHeight;
3318
-
3319
- this.scrollableHeight = (tracks.length)*height + this.topMargin;
3320
- let scroll_y = - this.currentScrollInPixels;
3358
+
3359
+ const tracks = this.animationClip.tracks;
3360
+ const trackHeight = this.trackHeight;
3361
+ const scrollY = - this.currentScrollInPixels;
3321
3362
 
3322
3363
  ctx.save();
3323
3364
  for(let i = 0; i < tracks.length; i++) {
3324
3365
  let track = tracks[i];
3325
- this.drawTrackWithBoxes(ctx, (i) * height + scroll_y, height, track.name || "", track);
3366
+ this.drawTrackWithBoxes(ctx, i * trackHeight + scrollY, trackHeight, track.name || "", track);
3326
3367
  }
3327
3368
 
3328
3369
  ctx.restore();
@@ -3841,7 +3882,7 @@ class ClipsTimeline extends Timeline {
3841
3882
 
3842
3883
  selectClip( track, clipIndex = null, localX = null, unselect = true, skipCallback = false ) {
3843
3884
 
3844
- clipIndex = clipIndex ?? this.getCurrentClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
3885
+ clipIndex = clipIndex ?? this.getCurrentClip( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
3845
3886
 
3846
3887
  if(unselect){
3847
3888
  this.unSelectAllClips();
@@ -3873,7 +3914,7 @@ class ClipsTimeline extends Timeline {
3873
3914
  }
3874
3915
 
3875
3916
  unselectClip( track, clipIndex, localX = null ){
3876
- clipIndex = clipIndex ?? this.getCurrentClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
3917
+ clipIndex = clipIndex ?? this.getCurrentClip( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
3877
3918
 
3878
3919
  if(clipIndex == -1)
3879
3920
  return -1;
@@ -3977,7 +4018,7 @@ class CurvesTimeline extends Timeline {
3977
4018
  e.multipleSelection = true;
3978
4019
  // Manual multiple selection
3979
4020
  if(!discard && track) {
3980
- const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
4021
+ const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
3981
4022
  if ( keyFrameIdx > -1 ){
3982
4023
  track.selected[keyFrameIdx] ?
3983
4024
  this.unSelectKeyFrame(track, keyFrameIdx) :
@@ -3986,13 +4027,13 @@ class CurvesTimeline extends Timeline {
3986
4027
  }
3987
4028
  // Box selection
3988
4029
  else if(this.boxSelection) {
3989
- let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.pixelsToSeconds * 5);
4030
+ let tracks = this.getTracksInRange(this.boxSelectionStart[1], this.boxSelectionEnd[1], this.secondsPerPixel * 5);
3990
4031
 
3991
4032
  for(let t of tracks) {
3992
4033
  let keyFrameIndices = this.getKeyFramesInRange(t,
3993
4034
  this.xToTime( this.boxSelectionStart[0] ),
3994
4035
  this.xToTime( this.boxSelectionEnd[0] ),
3995
- this.pixelsToSeconds * 5);
4036
+ this.secondsPerPixel * 5);
3996
4037
 
3997
4038
  if(keyFrameIndices) {
3998
4039
  for(let index = keyFrameIndices[0]; index <= keyFrameIndices[1]; ++index){
@@ -4011,7 +4052,7 @@ class CurvesTimeline extends Timeline {
4011
4052
  this.unSelectAllKeyFrames();
4012
4053
  }
4013
4054
  if (track){
4014
- const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
4055
+ const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
4015
4056
  if( keyFrameIndex > -1 ) {
4016
4057
  this.processCurrentKeyFrame( e, keyFrameIndex, track, null, e.multipleSelection ); // Settings this as multiple so time is not being set
4017
4058
  }
@@ -4161,7 +4202,7 @@ class CurvesTimeline extends Timeline {
4161
4202
  else if(track) {
4162
4203
 
4163
4204
  this.unHoverAll();
4164
- let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
4205
+ let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
4165
4206
  if(keyFrameIndex > -1 ) {
4166
4207
 
4167
4208
  const name = this.animationClip.tracksDictionary[track.fullname];
@@ -4245,42 +4286,44 @@ class CurvesTimeline extends Timeline {
4245
4286
 
4246
4287
  }
4247
4288
 
4248
- drawContent( ctx, timeStart, timeEnd ) {
4289
+ drawContent( ctx ) {
4249
4290
 
4250
4291
  if(!this.animationClip || !this.animationClip.tracksPerItem)
4251
4292
  return;
4252
4293
 
4253
4294
  ctx.save();
4254
- this.scrollableHeight = this.topMargin;
4255
4295
 
4256
- let offset = this.trackHeight;
4296
+ const trackHeight = this.trackHeight;
4297
+ const tracksPerItem = this.animationClip.tracksPerItem;
4298
+ const scrollY = - this.currentScrollInPixels;
4299
+ const treeOffset = this.trackTreesWidget.innerTree.domEl.offsetTop - this.canvas.offsetTop;
4300
+
4301
+ let offset = scrollY;
4302
+ ctx.translate(0, offset);
4303
+
4257
4304
  for(let t = 0; t < this.selectedItems.length; t++) {
4258
- let tracks = this.animationClip.tracksPerItem[this.selectedItems[t]] ? this.animationClip.tracksPerItem[this.selectedItems[t]] : [{name: this.selectedItems[t]}];
4305
+ let tracks = tracksPerItem[this.selectedItems[t]];
4259
4306
  if(!tracks) continue;
4260
4307
 
4261
- const height = this.trackHeight;
4262
- this.scrollableHeight += (tracks.length+1)*height;
4263
- let scroll_y = - this.currentScrollInPixels;
4308
+ offset += trackHeight;
4309
+ ctx.translate(0, trackHeight);
4264
4310
 
4265
- let offsetI = 0;
4311
+ if ( this.trackTreesWidget.innerTree.data[t].closed ){
4312
+ continue;
4313
+ }
4314
+
4266
4315
  for(let i = 0; i < tracks.length; i++) {
4267
4316
  let track = tracks[i];
4268
4317
  if(track.hide) {
4269
4318
  continue;
4270
4319
  }
4271
4320
 
4272
- ctx.save();
4273
-
4274
- let track_y = offsetI * height + offset + scroll_y;
4275
- ctx.translate(0, track_y);
4276
- this.drawTrackWithCurves(ctx, height, track);
4277
- this.tracksDrawn.push([track, track_y + this.topMargin, height]);
4278
-
4279
- ctx.restore();
4280
-
4281
- offsetI++;
4321
+ this.drawTrackWithCurves(ctx, trackHeight, track);
4322
+ this.tracksDrawn.push([track, offset + treeOffset, trackHeight]);
4323
+
4324
+ offset += trackHeight;
4325
+ ctx.translate(0, trackHeight);
4282
4326
  }
4283
- offset += offsetI * height + height;
4284
4327
  }
4285
4328
  ctx.restore();
4286
4329
 
@@ -4298,7 +4341,13 @@ class CurvesTimeline extends Timeline {
4298
4341
  }
4299
4342
 
4300
4343
  ctx.globalAlpha = this.opacity;
4301
-
4344
+
4345
+ const defaultPointSize = 5;
4346
+ const hoverPointSize = 7;
4347
+ const valueRange = this.range; //[min, max]
4348
+ const displayRange = trackHeight - defaultPointSize * 2;
4349
+ const startTime = this.visualTimeRange[0];
4350
+ const endTime = this.visualTimeRange[1];
4302
4351
  //draw lines
4303
4352
  ctx.strokeStyle = "white";
4304
4353
  ctx.beginPath();
@@ -4307,9 +4356,9 @@ class CurvesTimeline extends Timeline {
4307
4356
  let time = keyframes[j];
4308
4357
  let keyframePosX = this.timeToX( time );
4309
4358
  let value = values[j];
4310
- value = ((value - this.range[0]) / (this.range[1] - this.range[0])) * (-trackHeight) + trackHeight;
4359
+ value = ((value - valueRange[0]) / (valueRange[1] - valueRange[0])) * (-displayRange) + (trackHeight - defaultPointSize); // normalize and offset
4311
4360
 
4312
- if( time < this.startTime ){
4361
+ if( time < startTime ){
4313
4362
  ctx.moveTo( keyframePosX, value );
4314
4363
  continue;
4315
4364
  }
@@ -4317,40 +4366,40 @@ class CurvesTimeline extends Timeline {
4317
4366
  //convert to timeline track range
4318
4367
  ctx.lineTo( keyframePosX, value );
4319
4368
 
4320
- if ( time > this.endTime ){
4369
+ if ( time > endTime ){
4321
4370
  break; //end loop, but print line
4322
4371
  }
4323
4372
  }
4324
4373
  ctx.stroke();
4325
4374
 
4326
4375
  //draw points
4327
- ctx.fillStyle = Timeline.COLOR;
4376
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR;
4328
4377
  for(let j = 0; j < keyframes.length; ++j)
4329
4378
  {
4330
4379
  let time = keyframes[j];
4331
- if( time < this.startTime || time > this.endTime )
4380
+ if( time < startTime || time > endTime )
4332
4381
  continue;
4333
4382
 
4334
- let size = 5;
4383
+ let size = defaultPointSize;
4335
4384
  let keyframePosX = this.timeToX( time );
4336
4385
 
4337
4386
  if(!this.active || !track.active)
4338
- ctx.fillStyle = Timeline.COLOR_INACTIVE;
4387
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_INACTIVE;
4339
4388
  else if(track.locked)
4340
- ctx.fillStyle = Timeline.COLOR_LOCK;
4389
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_LOCK;
4341
4390
  else if(track.hovered[j]) {
4342
- size = 7;
4343
- ctx.fillStyle = Timeline.COLOR_HOVERED;
4391
+ size = hoverPointSize;
4392
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_HOVERED;
4344
4393
  }
4345
4394
  else if(track.selected[j])
4346
- ctx.fillStyle = Timeline.COLOR_SELECTED;
4395
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_SELECTED;
4347
4396
  else if(track.edited[j])
4348
- ctx.fillStyle = Timeline.COLOR_EDITED;
4397
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR_EDITED;
4349
4398
  else
4350
- ctx.fillStyle = Timeline.COLOR
4399
+ ctx.fillStyle = Timeline.KEYFRAME_COLOR
4351
4400
 
4352
4401
  let value = values[j];
4353
- value = ((value - this.range[0]) / (this.range[1] - this.range[0])) * ( -trackHeight) + trackHeight;
4402
+ value = ((value - this.range[0]) / (this.range[1] - this.range[0])) *(-displayRange) + (trackHeight - defaultPointSize); // normalize and offset
4354
4403
 
4355
4404
  ctx.beginPath();
4356
4405
  ctx.arc( keyframePosX, value, size, 0, Math.PI * 2);
@@ -5032,7 +5081,13 @@ class CurvesTimeline extends Timeline {
5032
5081
 
5033
5082
  this.unSelectAllKeyFrames();
5034
5083
  this.unHoverAll();
5035
- this.selectedItems = itemsName;
5084
+
5085
+ this.selectedItems = [];
5086
+ for( let i = 0; i < itemsName.length; ++i ){
5087
+ if ( this.animationClip.tracksPerItem[itemsName[i]] ){
5088
+ this.selectedItems.push(itemsName[i]);
5089
+ }
5090
+ }
5036
5091
  this.updateLeftPanel();
5037
5092
  }
5038
5093
 
@@ -5250,7 +5305,7 @@ class CurvesTimeline extends Timeline {
5250
5305
  this.unSelectAllKeyFrames();
5251
5306
  }
5252
5307
 
5253
- keyFrameIndex = keyFrameIndex ?? this.getCurrentKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
5308
+ keyFrameIndex = keyFrameIndex ?? this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * 5 );
5254
5309
  if (keyFrameIndex < 0)
5255
5310
  return;
5256
5311