lexgui 0.1.32 → 0.1.33
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/components/timeline.js +2059 -1443
- package/build/components/videoeditor.js +191 -100
- package/build/lexgui.css +27 -5
- package/build/lexgui.js +65 -30
- package/build/lexgui.module.js +62 -5
- package/changelog.md +7 -0
- package/examples/timeline.html +39 -24
- package/examples/video_editor.html +30 -13
- package/examples/video_editor2.html +118 -0
- package/package.json +1 -1
|
@@ -16,17 +16,13 @@ class Session {
|
|
|
16
16
|
constructor() {
|
|
17
17
|
|
|
18
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;
|
|
19
|
+
this.left_margin = 0;
|
|
23
20
|
this.scroll_y = 0;
|
|
24
|
-
// this.offset_y = 0;
|
|
25
|
-
// this.selection = null;
|
|
26
21
|
}
|
|
27
22
|
};
|
|
28
23
|
|
|
29
24
|
/**
|
|
25
|
+
* Abstract class
|
|
30
26
|
* @class Timeline
|
|
31
27
|
* @description Agnostic timeline, do not impose any timeline content. Renders to a canvas
|
|
32
28
|
*/
|
|
@@ -43,9 +39,7 @@ class Timeline {
|
|
|
43
39
|
this.currentTime = 0;
|
|
44
40
|
this.framerate = 30;
|
|
45
41
|
this.opacity = options.opacity || 1;
|
|
46
|
-
this.sidebarWidth = 0// 200;
|
|
47
42
|
this.topMargin = 40;
|
|
48
|
-
this.renderOutFrames = false;
|
|
49
43
|
this.lastMouse = [];
|
|
50
44
|
this.lastKeyFramesSelected = [];
|
|
51
45
|
this.tracksDrawn = [];
|
|
@@ -55,40 +49,46 @@ class Timeline {
|
|
|
55
49
|
this.timeBeforeMove = 0;
|
|
56
50
|
this.tracksPerItem = {};
|
|
57
51
|
this.tracksDictionary = {};
|
|
58
|
-
this._times = [];
|
|
59
|
-
|
|
60
|
-
this.onBeforeCreateTopBar = options.onBeforeCreateTopBar;
|
|
61
|
-
this.onAfterCreateTopBar = options.onAfterCreateTopBar;
|
|
62
|
-
this.onChangePlayMode = options.onChangePlayMode;
|
|
63
52
|
|
|
64
53
|
this.playing = false;
|
|
65
|
-
this.loop = options.loop ?? true;
|
|
66
|
-
|
|
67
|
-
this.session = new Session();
|
|
68
|
-
|
|
69
|
-
this.canvas = options.canvas ?? document.createElement('canvas');
|
|
70
54
|
|
|
71
55
|
this.duration = 1;
|
|
72
56
|
this.speed = 1;
|
|
73
57
|
this.position = [0, 0];
|
|
74
58
|
this.size = [ options.width ?? 400, options.height ?? 100];
|
|
75
59
|
|
|
60
|
+
this.active = true;
|
|
61
|
+
this.loop = options.loop ?? true;
|
|
62
|
+
this.optimizeThreshold = 0.025;
|
|
63
|
+
|
|
64
|
+
this.session = new Session();
|
|
65
|
+
|
|
66
|
+
this.canvas = options.canvas ?? document.createElement('canvas');
|
|
67
|
+
|
|
76
68
|
this.currentScroll = 0; //in percentage
|
|
77
69
|
this.currentScrollInPixels = 0; //in pixels
|
|
78
70
|
this.scrollableHeight = this.size[1]; //true height of the timeline content
|
|
79
|
-
|
|
71
|
+
|
|
80
72
|
this.secondsToPixels = this.canvas.width/this.duration;
|
|
81
73
|
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
82
74
|
this.selectedItems = options.selectedItems ?? null;
|
|
83
|
-
this.animationClip = options.animationClip ?? null;
|
|
84
75
|
this.trackHeight = options.trackHeight ?? 25;
|
|
85
76
|
|
|
86
|
-
this.active = true;
|
|
87
77
|
this.skipVisibility = options.skipVisibility ?? false;
|
|
88
78
|
this.skipLock = options.skipLock ?? false;
|
|
89
79
|
this.disableNewTracks = options.disableNewTracks ?? false;
|
|
90
|
-
|
|
91
|
-
this.
|
|
80
|
+
|
|
81
|
+
this.onBeforeCreateTopBar = options.onBeforeCreateTopBar;
|
|
82
|
+
this.onAfterCreateTopBar = options.onAfterCreateTopBar;
|
|
83
|
+
this.onChangePlayMode = options.onChangePlayMode;
|
|
84
|
+
this.onChangeState = options.onChangeState;
|
|
85
|
+
this.onSetDuration = options.onSetDuration;
|
|
86
|
+
this.onSetSpeed = options.onSetSpeed;
|
|
87
|
+
this.onSelectTrack = options.onSelectTrack;
|
|
88
|
+
this.onChangeTrackVisibility = options.onChangeTrackVisibility;
|
|
89
|
+
this.onChangeTrackDisplay = options.onChangeTrackDisplay;
|
|
90
|
+
this.onSetTime = options.onSetTime;
|
|
91
|
+
this.onLockTrack = options.onLockTrack;
|
|
92
92
|
|
|
93
93
|
this.root = new LX.Area({className : 'lextimeline'});
|
|
94
94
|
|
|
@@ -133,129 +133,487 @@ class Timeline {
|
|
|
133
133
|
this.resizeCanvas( [ bounding.width, bounding.height + this.header_offset ] );
|
|
134
134
|
}
|
|
135
135
|
this.resize(this.size);
|
|
136
|
+
|
|
137
|
+
if(options.animationClip) {
|
|
138
|
+
this.setAnimationClip(options.animationClip);
|
|
139
|
+
}
|
|
136
140
|
}
|
|
137
141
|
|
|
138
142
|
/**
|
|
139
|
-
* @method
|
|
140
|
-
* @param {*}
|
|
143
|
+
* @method setAnimationClip
|
|
144
|
+
* @param {*} animation
|
|
145
|
+
* @param {boolean} needsToProcess
|
|
146
|
+
* @param {obj} processOptions
|
|
147
|
+
* [KeyFrameTimeline] - each track should contain an attribute "dim" to indicate the value dimension (e.g. vector3 -> dim=3). Otherwise dimensions will be infered from track's values and times. Default is 1
|
|
141
148
|
*/
|
|
149
|
+
setAnimationClip( animation, needsToProcess = true ) {
|
|
150
|
+
if(!animation || !animation.tracks || needsToProcess) {
|
|
151
|
+
this.processTracks(animation); // generate default animationclip or process the user's one
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
this.animationClip = animation;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
this.duration = this.animationClip.duration;
|
|
158
|
+
this.speed = this.animationClip.speed ?? this.speed;
|
|
159
|
+
const w = Math.max(300, this.canvas.width);
|
|
160
|
+
this.secondsToPixels = ( w - this.session.left_margin ) / this.duration;
|
|
161
|
+
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
142
162
|
|
|
143
|
-
|
|
163
|
+
this.updateLeftPanel();
|
|
164
|
+
this.draw(this.currentTime);
|
|
165
|
+
return this.animationClip;
|
|
166
|
+
}
|
|
144
167
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
168
|
+
/**
|
|
169
|
+
* @method validateDuration
|
|
170
|
+
* @param {Number} t
|
|
171
|
+
* @returns minimum available duration
|
|
172
|
+
*/
|
|
173
|
+
validateDuration(t) {
|
|
174
|
+
return t;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* @method setDuration
|
|
179
|
+
* @param {Number} t
|
|
180
|
+
*/
|
|
181
|
+
|
|
182
|
+
setDuration( t, updateHeader = true ) {
|
|
183
|
+
let v = this.validateDuration(t);
|
|
184
|
+
const decimals = t.toString().split('.')[1] ? t.toString().split('.')[1].length : 0;
|
|
185
|
+
updateHeader = (updateHeader || +v.toFixed(decimals) != t);
|
|
186
|
+
this.duration = this.animationClip.duration = v;
|
|
187
|
+
|
|
188
|
+
if(updateHeader) {
|
|
189
|
+
LX.emit( "@on_set_duration", +v.toFixed(3));
|
|
150
190
|
}
|
|
151
191
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
192
|
+
if( this.onSetDuration ) {
|
|
193
|
+
this.onSetDuration( v );
|
|
194
|
+
}
|
|
195
|
+
}
|
|
155
196
|
|
|
156
|
-
|
|
157
|
-
|
|
197
|
+
/**
|
|
198
|
+
* @method setSpeed
|
|
199
|
+
* @param {Number} speed
|
|
200
|
+
*/
|
|
201
|
+
setSpeed(speed) {
|
|
202
|
+
this.speed = speed;
|
|
203
|
+
LX.emit( "@on_set_speed", +speed.toFixed(3));
|
|
204
|
+
|
|
205
|
+
if( this.onSetSpeed ) {
|
|
206
|
+
this.onSetSpeed( speed );
|
|
158
207
|
}
|
|
208
|
+
}
|
|
159
209
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
210
|
+
/**
|
|
211
|
+
* @method setScale
|
|
212
|
+
* @param {Number} v
|
|
213
|
+
*/
|
|
214
|
+
setScale( v ) {
|
|
163
215
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
if(this.onChangePlayMode) {
|
|
167
|
-
this.onChangePlayMode(this.loop);
|
|
168
|
-
}
|
|
216
|
+
if(!this.session)
|
|
217
|
+
return;
|
|
169
218
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
if(this.onBeforeCreateTopBar)
|
|
173
|
-
this.onBeforeCreateTopBar(header);
|
|
219
|
+
if( this.secondsToPixels * v < 100)
|
|
220
|
+
return;
|
|
174
221
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
this.currentTime = value;
|
|
182
|
-
if(this.onSetTime)
|
|
183
|
-
this.onSetTime(this.currentTime);
|
|
184
|
-
this.draw();
|
|
185
|
-
|
|
186
|
-
}, {signal: "@on_current_time_" + this.constructor.name, step: 0.01, min: 0, precision: 3, skipSlider: true});
|
|
222
|
+
const xCurrentTime = this.timeToX(this.currentTime);
|
|
223
|
+
this.secondsToPixels *= v;
|
|
224
|
+
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
225
|
+
this.session.start_time += this.currentTime - this.xToTime(xCurrentTime);
|
|
226
|
+
this.draw();
|
|
227
|
+
}
|
|
187
228
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
229
|
+
/**
|
|
230
|
+
* @method setFramerate
|
|
231
|
+
* @param {Number} v
|
|
232
|
+
*/
|
|
233
|
+
setFramerate( v ) {
|
|
234
|
+
this.framerate = v;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/** DEPRACATED??
|
|
238
|
+
* @method getCurrentFrame
|
|
239
|
+
* @param {Number} framerate
|
|
240
|
+
*/
|
|
241
|
+
getCurrentFrame( framerate = this.framerate ) {
|
|
242
|
+
return Math.floor(this.currentTime * framerate);
|
|
243
|
+
}
|
|
191
244
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
245
|
+
/**
|
|
246
|
+
* @method getContent
|
|
247
|
+
* @param {Track} track
|
|
248
|
+
* @param {Number} time
|
|
249
|
+
* @param {Number} threshold
|
|
250
|
+
*/
|
|
251
|
+
getContent(track, time, threshold) {
|
|
197
252
|
|
|
198
|
-
if(this.
|
|
199
|
-
|
|
253
|
+
if(this.getKeyFrame) {
|
|
254
|
+
return this.getKeyFrame(track, time, threshold);
|
|
255
|
+
}
|
|
200
256
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
d.addNumber("Framerate", this.framerate, (v) => {
|
|
206
|
-
this.framerate = v;
|
|
207
|
-
}, {min: 0, disabled: false});
|
|
208
|
-
d.addNumber("Num items", Object.keys(this.tracksPerItem).length, null, {disabled: true});
|
|
209
|
-
d.addNumber("Num tracks", this.animationClip ? this.animationClip.tracks.length : 0, null, {disabled: true});
|
|
210
|
-
if(this.onShowOptimizeMenu)
|
|
211
|
-
d.addNumber("Optimize Threshold", this.optimizeThreshold, v => {
|
|
212
|
-
this.optimizeThreshold = v;
|
|
213
|
-
}, {min: 0, max: 0.25, step: 0.001, precision: 4});
|
|
214
|
-
|
|
215
|
-
}, {
|
|
216
|
-
onclose: (root) => {
|
|
217
|
-
|
|
218
|
-
root.remove();
|
|
219
|
-
this.dialog = null;
|
|
220
|
-
}
|
|
221
|
-
})
|
|
222
|
-
}, {width: "40px"})
|
|
257
|
+
if(this.getClip) {
|
|
258
|
+
return this.getClip(track, time, threshold);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
223
261
|
|
|
224
|
-
|
|
225
|
-
|
|
262
|
+
/**
|
|
263
|
+
* @method getTracksInRange
|
|
264
|
+
* @param {Number} minY
|
|
265
|
+
* @param {Number} maxY
|
|
266
|
+
* @param {Number} threshold
|
|
267
|
+
*/
|
|
268
|
+
getTracksInRange( minY, maxY, threshold ) {
|
|
269
|
+
|
|
270
|
+
let tracks = [];
|
|
271
|
+
|
|
272
|
+
// Manage negative selection
|
|
273
|
+
if(minY > maxY) {
|
|
274
|
+
let aux = minY;
|
|
275
|
+
minY = maxY;
|
|
276
|
+
maxY = aux;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
for(let i = this.tracksDrawn.length - 1; i >= 0; --i) {
|
|
280
|
+
let t = this.tracksDrawn[i];
|
|
281
|
+
let pos = t[1] - this.topMargin, size = t[2];
|
|
282
|
+
if( pos + threshold >= minY && (pos + size - threshold) <= maxY ) {
|
|
283
|
+
tracks.push( t[0] );
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
return tracks;
|
|
226
288
|
}
|
|
227
289
|
|
|
228
290
|
/**
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
291
|
+
* @method addNewTrack
|
|
292
|
+
* @description Add empty new track
|
|
293
|
+
*/
|
|
294
|
+
addNewTrack() {
|
|
233
295
|
|
|
296
|
+
if(!this.animationClip)
|
|
297
|
+
this.animationClip = {tracks:[]};
|
|
234
298
|
|
|
235
|
-
|
|
236
|
-
this.
|
|
299
|
+
let trackInfo = {
|
|
300
|
+
idx: this.animationClip.tracks.length,
|
|
301
|
+
values: [], times: [],
|
|
302
|
+
selected: [], edited: [], hovered: []
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
this.animationClip.tracks.push(trackInfo);
|
|
306
|
+
this.updateLeftPanel();
|
|
307
|
+
return trackInfo.idx;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* @method selectTrack
|
|
312
|
+
* @param {Object} trackInfo = {id, parent, children, visible}
|
|
313
|
+
*/
|
|
314
|
+
selectTrack(trackInfo) {
|
|
315
|
+
this.unSelectAllTracks();
|
|
316
|
+
|
|
317
|
+
let [name, type] = trackInfo.id.split(" (");
|
|
318
|
+
|
|
319
|
+
if(type)
|
|
320
|
+
type = type.replaceAll(")", "").replaceAll(" ", "");
|
|
237
321
|
else {
|
|
238
|
-
|
|
322
|
+
type = name;
|
|
323
|
+
name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
|
|
239
324
|
}
|
|
325
|
+
let tracks = this.tracksPerItem[name];
|
|
240
326
|
|
|
241
|
-
let
|
|
242
|
-
|
|
243
|
-
|
|
327
|
+
for(let i = 0; i < tracks.length; i++) {
|
|
328
|
+
if(tracks[i].type != type && tracks.length > 1)
|
|
329
|
+
continue;
|
|
330
|
+
this.tracksPerItem[name][i].isSelected = true;
|
|
331
|
+
trackInfo = this.tracksPerItem[name][i];
|
|
332
|
+
}
|
|
244
333
|
|
|
245
|
-
if(
|
|
246
|
-
|
|
247
|
-
panel.addButton('', '<i class = "fa-solid fa-plus"></i>', (value, event) => {
|
|
248
|
-
this.addNewTrack();
|
|
249
|
-
}, {width: "40px", height: "40px"});
|
|
334
|
+
if(this.onSelectTrack) {
|
|
335
|
+
this.onSelectTrack(trackInfo);
|
|
250
336
|
}
|
|
251
|
-
|
|
337
|
+
}
|
|
252
338
|
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
let
|
|
339
|
+
/**
|
|
340
|
+
* @method unSelectAllTracks
|
|
341
|
+
*/
|
|
342
|
+
unSelectAllTracks() {
|
|
343
|
+
for(let i = 0; i < this.selectedItems.length; i++) {
|
|
344
|
+
let item = this.selectedItems[i];
|
|
345
|
+
let tracks = this.tracksPerItem[item];
|
|
346
|
+
for(let t = 0; t < tracks.length; t++) {
|
|
347
|
+
tracks[t].isSelected = false;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* @method changeTrackVisibility
|
|
354
|
+
* @param {Object} trackInfo = {id, parent, children}
|
|
355
|
+
* @param {Boolean} visible
|
|
356
|
+
*/
|
|
357
|
+
changeTrackVisibility(trackInfo, visible) {
|
|
358
|
+
let [name, type] = trackInfo.id.split(" (");
|
|
359
|
+
if(type)
|
|
360
|
+
type = type.replaceAll(")", "").replaceAll(" ", "");
|
|
361
|
+
else {
|
|
362
|
+
type = name;
|
|
363
|
+
name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
|
|
364
|
+
}
|
|
365
|
+
trackInfo = {name, type};
|
|
366
|
+
let tracks = this.tracksPerItem[name];
|
|
367
|
+
|
|
368
|
+
for(let i = 0; i < tracks.length; i++) {
|
|
369
|
+
if(tracks[i].type != type && tracks.length > 1)
|
|
370
|
+
continue;
|
|
371
|
+
this.tracksPerItem[name][i].active = visible;
|
|
372
|
+
trackInfo = this.tracksPerItem[name][i];
|
|
373
|
+
}
|
|
374
|
+
this.draw();
|
|
375
|
+
if(this.onChangeTrackVisibility) {
|
|
376
|
+
this.onChangeTrackVisibility(trackInfo, visible);
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
/**
|
|
381
|
+
* @method changeTrackDisplay
|
|
382
|
+
* @param {Object} trackInfo = {id, parent, children}
|
|
383
|
+
* @param {Boolean} hide
|
|
384
|
+
*/
|
|
385
|
+
changeTrackDisplay(trackInfo, hide) {
|
|
386
|
+
|
|
387
|
+
for(let idx = 0; idx < trackInfo.children.length; idx++) {
|
|
388
|
+
let [name, type] = trackInfo.children[idx].id.split(" (");
|
|
389
|
+
if(type)
|
|
390
|
+
type = type.replaceAll(")", "").replaceAll(" ", "");
|
|
391
|
+
else {
|
|
392
|
+
type = name;
|
|
393
|
+
name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
|
|
394
|
+
}
|
|
395
|
+
//trackInfo = {name, type};
|
|
396
|
+
let tracks = this.tracksPerItem[name];
|
|
397
|
+
|
|
398
|
+
for(let i = 0; i < tracks.length; i++) {
|
|
399
|
+
if(tracks[i].type != type && tracks.length > 1)
|
|
400
|
+
continue;
|
|
401
|
+
this.tracksPerItem[name][i].hide = hide;
|
|
402
|
+
// trackInfo = this.tracksPerItem[name][i];
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
this.draw();
|
|
407
|
+
|
|
408
|
+
if(this.onChangeTrackDisplay) {
|
|
409
|
+
this.onChangeTrackDisplay(trackInfo, hide)
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @method clearState
|
|
415
|
+
*/
|
|
416
|
+
clearState() {
|
|
417
|
+
this.trackState = [];
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* @method show
|
|
422
|
+
* @description Show timeline area if it is hidden
|
|
423
|
+
*/
|
|
424
|
+
show() {
|
|
425
|
+
|
|
426
|
+
this.root.show();
|
|
427
|
+
this.updateLeftPanel();
|
|
428
|
+
this.resize();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* @method hide
|
|
433
|
+
* @description Hide timeline area
|
|
434
|
+
*/
|
|
435
|
+
hide() {
|
|
436
|
+
this.root.hide();
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* @method resize
|
|
441
|
+
* @param {Array} size = [width, height]
|
|
442
|
+
*/
|
|
443
|
+
resize( size = [this.root.parent.root.clientWidth, this.root.parent.root.clientHeight]) {
|
|
444
|
+
|
|
445
|
+
// this.root.root.style.width = size[0] + "px";
|
|
446
|
+
// this.root.root.style.height = size[1] + "px";
|
|
447
|
+
|
|
448
|
+
this.size = size;
|
|
449
|
+
//this.content_area.setSize([size[0], size[1] - this.header_offset]);
|
|
450
|
+
this.content_area.root.style.height = "calc(100% - "+ this.header_offset + "px)";
|
|
451
|
+
|
|
452
|
+
let w = size[0] - this.leftPanel.root.clientWidth - 8;
|
|
453
|
+
this.resizeCanvas([w , size[1]]);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* @method resizeCanvas
|
|
458
|
+
* @param {*Array} size = [width, height]
|
|
459
|
+
*/
|
|
460
|
+
resizeCanvas( size ) {
|
|
461
|
+
if( size[0] <= 0 && size[1] <=0 )
|
|
462
|
+
return;
|
|
463
|
+
if(Math.abs(this.canvas.width - size[0]) > 1) {
|
|
464
|
+
|
|
465
|
+
var w = Math.max(300, size[0] );
|
|
466
|
+
this.secondsToPixels = ( w- this.session.left_margin ) / this.duration;
|
|
467
|
+
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
468
|
+
}
|
|
469
|
+
size[1] -= this.header_offset;
|
|
470
|
+
//this.canvasArea.setSize(size);
|
|
471
|
+
//this.canvasArea.root.style.height = "calc(100% - "+ this.header_offset + "px)";
|
|
472
|
+
this.canvas.width = this.canvasArea.root.clientWidth;
|
|
473
|
+
this.canvas.height = this.canvasArea.root.clientHeight;
|
|
474
|
+
this.draw(this.currentTime);
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* @method xToTime
|
|
479
|
+
* @description Converts distance in pixels to time
|
|
480
|
+
* @param {Number} x canvas position in pixels
|
|
481
|
+
*/
|
|
482
|
+
xToTime( x ) {
|
|
483
|
+
return (x - this.session.left_margin) / this.secondsToPixels + this.session.start_time;
|
|
484
|
+
}
|
|
485
|
+
/**
|
|
486
|
+
* @method xToTime
|
|
487
|
+
* @description Converts time to disance in pixels
|
|
488
|
+
* @param {Number} t time
|
|
489
|
+
*/
|
|
490
|
+
timeToX( t ) {
|
|
491
|
+
return this.session.left_margin + (t - this.session.start_time) * this.secondsToPixels;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* @method updateHeader
|
|
496
|
+
* @param {*}
|
|
497
|
+
*/
|
|
498
|
+
updateHeader() {
|
|
499
|
+
|
|
500
|
+
if(this.header)
|
|
501
|
+
this.header.clear();
|
|
502
|
+
else {
|
|
503
|
+
this.header = new LX.Panel({id:'lextimelineheader', height: this.header_offset+"px"});
|
|
504
|
+
this.root.root.appendChild(this.header.root);
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
let header = this.header;
|
|
508
|
+
LX.DEFAULT_NAME_WIDTH = "50%";
|
|
509
|
+
header.sameLine();
|
|
510
|
+
|
|
511
|
+
if(this.name) {
|
|
512
|
+
header.addTitle(this.name);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
header.addButton('', '<i class="fa-solid fa-'+ (this.playing ? 'pause' : 'play') +'"></i>', (value, event) => {
|
|
516
|
+
this.changeState();
|
|
517
|
+
}, {width: "40px", buttonClass: "accept"});
|
|
518
|
+
|
|
519
|
+
header.addButton('', '<i class="fa-solid fa-rotate"></i>', (value, event) => {
|
|
520
|
+
this.loop = !this.loop;
|
|
521
|
+
if(this.onChangePlayMode) {
|
|
522
|
+
this.onChangePlayMode(this.loop);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
}, {width: "40px", selectable: true, selected: this.loop});
|
|
526
|
+
|
|
527
|
+
if(this.onBeforeCreateTopBar)
|
|
528
|
+
this.onBeforeCreateTopBar(header);
|
|
529
|
+
|
|
530
|
+
header.addNumber("Current Time", this.currentTime, (value, event) => {
|
|
531
|
+
if(value > this.duration) {
|
|
532
|
+
value = this.duration;
|
|
533
|
+
if(event.constructor != CustomEvent) {
|
|
534
|
+
LX.emit( "@on_current_time_" + this.constructor.name, value);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
this.currentTime = value;
|
|
539
|
+
if(this.onSetTime)
|
|
540
|
+
this.onSetTime(this.currentTime);
|
|
541
|
+
this.draw();
|
|
542
|
+
|
|
543
|
+
}, {signal: "@on_current_time_" + this.constructor.name, step: 0.01, min: 0, precision: 3, skipSlider: true});
|
|
544
|
+
|
|
545
|
+
header.addNumber("Duration", +this.duration.toFixed(3), (value, event) => {
|
|
546
|
+
this.setDuration(value, false)}, {step: 0.01, min: 0, signal: "@on_set_duration"
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
header.addNumber("Speed", +this.speed.toFixed(3), (value, event) => {
|
|
550
|
+
this.setSpeed(value)}, {step: 0.01});
|
|
551
|
+
|
|
552
|
+
if(this.onAfterCreateTopBar)
|
|
553
|
+
this.onAfterCreateTopBar(header);
|
|
554
|
+
|
|
555
|
+
if(this.onShowOptimizeMenu)
|
|
556
|
+
header.addButton("", '<i class="fa-solid fa-filter"></i>', (value, event) => {this.onShowOptimizeMenu(event)}, {width: "40px"});
|
|
557
|
+
|
|
558
|
+
header.addButton("", '<i class="fa-solid fa-gear"></i>', (value, event) => {
|
|
559
|
+
if(this.dialog)
|
|
560
|
+
return;
|
|
561
|
+
this.dialog = new LX.Dialog("Configuration", d => {
|
|
562
|
+
d.addNumber("Framerate", this.framerate, (v) => {
|
|
563
|
+
this.framerate = v;
|
|
564
|
+
}, {min: 0, disabled: false});
|
|
565
|
+
d.addNumber("Num items", Object.keys(this.tracksPerItem).length, null, {disabled: true});
|
|
566
|
+
d.addNumber("Num tracks", this.animationClip ? this.animationClip.tracks.length : 0, null, {disabled: true});
|
|
567
|
+
if(this.onShowOptimizeMenu)
|
|
568
|
+
d.addNumber("Optimize Threshold", this.optimizeThreshold, v => {
|
|
569
|
+
this.optimizeThreshold = v;
|
|
570
|
+
}, {min: 0, max: 0.25, step: 0.001, precision: 4});
|
|
571
|
+
|
|
572
|
+
}, {
|
|
573
|
+
onclose: (root) => {
|
|
574
|
+
|
|
575
|
+
root.remove();
|
|
576
|
+
this.dialog = null;
|
|
577
|
+
}
|
|
578
|
+
})
|
|
579
|
+
}, {width: "40px"})
|
|
580
|
+
|
|
581
|
+
header.endLine();
|
|
582
|
+
LX.DEFAULT_NAME_WIDTH = "30%";
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
/**
|
|
586
|
+
* @method updateLeftPanel Creates/updates the information of the drawn tracks in the left panel
|
|
587
|
+
* @param {Area} area LX.Area where to add the left panel
|
|
588
|
+
*/
|
|
589
|
+
updateLeftPanel(area) {
|
|
590
|
+
|
|
591
|
+
if(this.leftPanel)
|
|
592
|
+
this.leftPanel.clear();
|
|
593
|
+
else {
|
|
594
|
+
this.leftPanel = area.addPanel({className: 'lextimelinepanel', width: "100%", height: "100%"});
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
let panel = this.leftPanel;
|
|
598
|
+
panel.sameLine(2);
|
|
599
|
+
const title = panel.addTitle("Tracks");
|
|
600
|
+
|
|
601
|
+
// If new tracks can be added manually, a button is added to the panel
|
|
602
|
+
if(!this.disableNewTracks)
|
|
603
|
+
{
|
|
604
|
+
panel.addButton('', '<i class = "fa-solid fa-plus"></i>', (value, event) => {
|
|
605
|
+
this.addNewTrack();
|
|
606
|
+
}, {width: "40px", height: "40px"});
|
|
607
|
+
}
|
|
608
|
+
panel.endLine();
|
|
609
|
+
|
|
610
|
+
const styles = window.getComputedStyle(title);
|
|
611
|
+
const titleHeight = title.clientHeight + parseFloat(styles['marginTop']) + parseFloat(styles['marginBottom']);
|
|
612
|
+
|
|
613
|
+
// Create a tree with the tracks for the selected item
|
|
614
|
+
let p = new LX.Panel({height: "calc(100% - " + titleHeight + "px)"});
|
|
615
|
+
if(this.animationClip && this.selectedItems) {
|
|
616
|
+
let items = {'id': '', 'children': []};
|
|
259
617
|
|
|
260
618
|
for(let i = 0; i < this.selectedItems.length; i++ ) {
|
|
261
619
|
let selected = this.selectedItems[i];
|
|
@@ -294,13 +652,13 @@ class Timeline {
|
|
|
294
652
|
track = tracks[i];
|
|
295
653
|
}
|
|
296
654
|
}
|
|
297
|
-
if(this.onLockTrack)
|
|
655
|
+
if(this.onLockTrack) {
|
|
298
656
|
this.onLockTrack(el, track, node)
|
|
657
|
+
}
|
|
299
658
|
this.draw();
|
|
300
659
|
}
|
|
301
660
|
|
|
302
661
|
}]})
|
|
303
|
-
// panel.addTitle(track.name + (track.type? '(' + track.type + ')' : ''));
|
|
304
662
|
}
|
|
305
663
|
items.children.push(t);
|
|
306
664
|
let el = p.addTree(null, t, {filter: false, rename: false, draggable: false, onevent: (e) => {
|
|
@@ -322,126 +680,95 @@ class Timeline {
|
|
|
322
680
|
}
|
|
323
681
|
panel.attach(p.root)
|
|
324
682
|
p.root.style.overflowY = "scroll";
|
|
325
|
-
p.root.addEventListener("scroll", (e) => {
|
|
326
|
-
|
|
683
|
+
p.root.addEventListener("scroll", (e) => {
|
|
327
684
|
this.currentScroll = e.currentTarget.scrollTop/(e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
|
|
328
|
-
|
|
685
|
+
});
|
|
329
686
|
// for(let i = 0; i < this.animationClip.tracks.length; i++) {
|
|
330
687
|
// let track = this.animationClip.tracks[i];
|
|
331
688
|
// panel.addTitle(track.name + (track.type? '(' + track.type + ')' : ''));
|
|
332
689
|
// }
|
|
333
|
-
if(this.leftPanel.parent.root.classList.contains("hidden") || !this.root.root.parent)
|
|
690
|
+
if(this.leftPanel.parent.root.classList.contains("hidden") || !this.root.root.parent) {
|
|
334
691
|
return;
|
|
692
|
+
}
|
|
335
693
|
this.resizeCanvas([ this.root.root.clientWidth - this.leftPanel.root.clientWidth - 8, this.size[1]]);
|
|
336
694
|
}
|
|
337
695
|
|
|
338
696
|
/**
|
|
339
|
-
* @
|
|
697
|
+
* @deprecated
|
|
698
|
+
* @method drawMarkers
|
|
699
|
+
* @param {*} ctx
|
|
700
|
+
* @param {*} markers
|
|
701
|
+
* TODO
|
|
340
702
|
*/
|
|
341
703
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
if(!this.animationClip)
|
|
345
|
-
this.animationClip = {tracks:[]};
|
|
346
|
-
|
|
347
|
-
let trackInfo = {
|
|
348
|
-
idx: this.animationClip.tracks.length,
|
|
349
|
-
values: [], times: [],
|
|
350
|
-
selected: [], edited: [], hovered: []
|
|
351
|
-
};
|
|
352
|
-
|
|
353
|
-
this.animationClip.tracks.push(trackInfo);
|
|
354
|
-
this.updateLeftPanel();
|
|
355
|
-
return trackInfo.idx;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
getTracksInRange( minY, maxY, threshold ) {
|
|
359
|
-
|
|
360
|
-
let tracks = [];
|
|
704
|
+
drawMarkers( ctx, markers ) {
|
|
361
705
|
|
|
362
|
-
//
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
706
|
+
//render markers
|
|
707
|
+
ctx.fillStyle = "white";
|
|
708
|
+
ctx.textAlign = "left";
|
|
709
|
+
let markersPos = [];
|
|
710
|
+
for (let i = 0; i < markers.length; ++i) {
|
|
711
|
+
let marker = markers[i];
|
|
712
|
+
if (marker.time < this.startTime - this.pixelsToSeconds * 100 ||
|
|
713
|
+
marker.time > this.endTime)
|
|
714
|
+
continue;
|
|
715
|
+
var x = this.timeToX(marker.time);
|
|
716
|
+
markersPos.push(x);
|
|
717
|
+
ctx.save();
|
|
718
|
+
ctx.translate(x, 0);
|
|
719
|
+
ctx.rotate(Math.PI * -0.25);
|
|
720
|
+
ctx.fillText(marker.title, 20, 4);
|
|
721
|
+
ctx.restore();
|
|
367
722
|
}
|
|
368
723
|
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
724
|
+
if (markersPos.length) {
|
|
725
|
+
ctx.beginPath();
|
|
726
|
+
for (var i = 0; i < markersPos.length; ++i) {
|
|
727
|
+
ctx.moveTo(markersPos[i] - 5, 0);
|
|
728
|
+
ctx.lineTo(markersPos[i], -5);
|
|
729
|
+
ctx.lineTo(markersPos[i] + 5, 0);
|
|
730
|
+
ctx.lineTo(markersPos[i], 5);
|
|
731
|
+
ctx.lineTo(markersPos[i] - 5, 0);
|
|
374
732
|
}
|
|
733
|
+
ctx.fill();
|
|
375
734
|
}
|
|
376
|
-
|
|
377
|
-
return tracks;
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
getCurrentContent(track, time, threshold) {
|
|
381
|
-
|
|
382
|
-
if(this.getCurrentKeyFrame)
|
|
383
|
-
return this.getCurrentKeyFrame(track, time, threshold);
|
|
384
|
-
|
|
385
|
-
if(this.getCurrentClip)
|
|
386
|
-
return this.getCurrentClip(track, time, threshold);
|
|
387
735
|
}
|
|
388
736
|
|
|
389
737
|
/**
|
|
390
|
-
* @method
|
|
391
|
-
* @
|
|
392
|
-
* @param {boolean} needsToProcess
|
|
393
|
-
* TODO
|
|
738
|
+
* @method drawTimeInfo
|
|
739
|
+
* @description Draw top bar time information (number and lines)
|
|
394
740
|
*/
|
|
395
|
-
|
|
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;
|
|
406
|
-
var w = Math.max(300, this.canvas.width);
|
|
407
|
-
this.secondsToPixels = ( w - this.session.left_margin ) / this.duration;
|
|
408
|
-
|
|
409
|
-
//this.updateHeader();
|
|
410
|
-
this.updateLeftPanel();
|
|
411
|
-
|
|
412
|
-
return this.animationClip;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
drawTimeInfo (w, h = this.topMargin) {
|
|
741
|
+
drawTimeInfo() {
|
|
416
742
|
|
|
417
743
|
let ctx = this.canvas.getContext("2d");
|
|
418
744
|
ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
|
|
419
745
|
ctx.textAlign = "center";
|
|
420
746
|
|
|
421
|
-
|
|
422
|
-
|
|
747
|
+
const canvas = this.canvas;
|
|
748
|
+
const h = this.topMargin;
|
|
749
|
+
|
|
423
750
|
// Draw time markers
|
|
424
|
-
|
|
425
|
-
let endx = Math.round( this.timeToX( this.endTime ) ) + 0.5;
|
|
751
|
+
const startX = Math.round( this.timeToX( this.startTime ) ) + 0.5;
|
|
426
752
|
let tick_time = this.secondsToPixels > 400 ? 0.1 : 0.5;
|
|
427
|
-
if(this.secondsToPixels < 100 )
|
|
753
|
+
if(this.secondsToPixels < 100 ) {
|
|
428
754
|
tick_time = 1;
|
|
755
|
+
}
|
|
429
756
|
|
|
430
757
|
ctx.save();
|
|
431
|
-
|
|
432
758
|
ctx.fillStyle = Timeline.BACKGROUND_COLOR;
|
|
433
759
|
ctx.fillRect( this.session.left_margin, 0, canvas.width, h );
|
|
434
760
|
ctx.strokeStyle = LX.Timeline.FONT_COLOR;
|
|
435
761
|
|
|
436
|
-
if
|
|
437
|
-
{
|
|
762
|
+
// Paint in-between time info if there's enough zoom in
|
|
763
|
+
if(this.secondsToPixels > 200 ) {
|
|
438
764
|
ctx.globalAlpha = 0.5 * (1.0 - LX.UTILS.clamp( 200 / this.secondsToPixels, 0, 1));
|
|
439
765
|
ctx.beginPath();
|
|
440
766
|
for( let time = this.startTime; time <= this.endTime; time += 1 / this.framerate )
|
|
441
767
|
{
|
|
442
|
-
|
|
443
|
-
if(x < this.session.left_margin)
|
|
768
|
+
const x = this.timeToX( time );
|
|
769
|
+
if(x < this.session.left_margin) {
|
|
444
770
|
continue;
|
|
771
|
+
}
|
|
445
772
|
ctx.moveTo(Math.round(x) + 0.5, h * 0.9);
|
|
446
773
|
ctx.lineTo(Math.round(x) + 0.5, h * 0.95);
|
|
447
774
|
}
|
|
@@ -451,17 +778,16 @@ class Timeline {
|
|
|
451
778
|
|
|
452
779
|
ctx.globalAlpha = 0.5;
|
|
453
780
|
ctx.beginPath();
|
|
454
|
-
let times =
|
|
455
|
-
this._times.length = 0;
|
|
781
|
+
let times = [];
|
|
456
782
|
|
|
457
|
-
for( let time = this.startTime; time <= this.endTime; time += tick_time)
|
|
458
|
-
|
|
459
|
-
let x = this.timeToX( time );
|
|
783
|
+
for( let time = this.startTime; time <= this.endTime; time += tick_time) {
|
|
784
|
+
const x = this.timeToX( time );
|
|
460
785
|
|
|
461
|
-
if(x < this.session.left_margin)
|
|
786
|
+
if(x < this.session.left_margin) {
|
|
462
787
|
continue;
|
|
788
|
+
}
|
|
463
789
|
|
|
464
|
-
|
|
790
|
+
const is_tick = time % 5 == 0;
|
|
465
791
|
if(is_tick || this.secondsToPixels > 70 ) {
|
|
466
792
|
times.push([x,time]);
|
|
467
793
|
ctx.moveTo(Math.round(x) + 0.5, h * 0.4 + (is_tick ? h * 0.3 : h * 0.4) );
|
|
@@ -470,40 +796,43 @@ class Timeline {
|
|
|
470
796
|
}
|
|
471
797
|
}
|
|
472
798
|
|
|
473
|
-
let x =
|
|
799
|
+
let x = startX;
|
|
474
800
|
if(x < this.session.left_margin) {
|
|
475
801
|
x = this.session.left_margin;
|
|
476
802
|
}
|
|
477
|
-
|
|
478
|
-
// ctx.lineTo( endx, h - 0.5);
|
|
803
|
+
|
|
479
804
|
ctx.globalAlpha = this.opacity;
|
|
480
805
|
|
|
481
806
|
// Time seconds in text
|
|
482
807
|
ctx.fillStyle = Timeline.FONT_COLOR//"#888";
|
|
483
|
-
for(var i = 0; i < times.length; ++i)
|
|
484
|
-
{
|
|
808
|
+
for(var i = 0; i < times.length; ++i) {
|
|
485
809
|
let time = times[i][1];
|
|
486
810
|
ctx.fillText( time == (time|0) ? time : time.toFixed(1), times[i][0], h * 0.6);
|
|
487
811
|
}
|
|
488
|
-
|
|
489
812
|
ctx.restore();
|
|
490
813
|
}
|
|
491
814
|
|
|
815
|
+
/**
|
|
816
|
+
* @method drawTracksBackground
|
|
817
|
+
* @description Draw empty tracks
|
|
818
|
+
* @param {Number} w
|
|
819
|
+
* @param {Number} h
|
|
820
|
+
*/
|
|
492
821
|
drawTracksBackground(w, h) {
|
|
493
822
|
|
|
494
|
-
|
|
823
|
+
const canvas = this.canvas;
|
|
495
824
|
let ctx = canvas.getContext("2d");
|
|
496
|
-
|
|
825
|
+
const duration = this.duration;
|
|
497
826
|
ctx.globalAlpha = this.opacity;
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
827
|
+
|
|
828
|
+
const margin = this.session.left_margin;
|
|
829
|
+
const timeline_height = this.topMargin;
|
|
830
|
+
const line_height = this.trackHeight;
|
|
502
831
|
|
|
503
|
-
//fill track lines
|
|
504
832
|
w = w || canvas.width;
|
|
505
|
-
|
|
506
|
-
|
|
833
|
+
const max_tracks = Math.ceil( (h - timeline_height + this.currentScrollInPixels) / line_height );
|
|
834
|
+
|
|
835
|
+
// Fill tracks background
|
|
507
836
|
ctx.save();
|
|
508
837
|
ctx.fillStyle = Timeline.BACKGROUND_COLOR;
|
|
509
838
|
for(let i = 0; i <= max_tracks; ++i)
|
|
@@ -512,19 +841,19 @@ class Timeline {
|
|
|
512
841
|
ctx.fillRect(0, timeline_height + i * line_height - this.currentScrollInPixels, w, line_height );
|
|
513
842
|
}
|
|
514
843
|
|
|
515
|
-
//
|
|
844
|
+
// Fill background with opacity
|
|
516
845
|
ctx.globalAlpha = 0.7;
|
|
517
846
|
ctx.fillStyle = Timeline.BACKGROUND_COLOR;
|
|
518
847
|
ctx.fillRect( margin, 0, canvas.width - margin, canvas.height);
|
|
519
848
|
ctx.globalAlpha = this.opacity;
|
|
520
849
|
|
|
521
|
-
//
|
|
850
|
+
// Draw start and end lines
|
|
522
851
|
ctx.strokeStyle = "#444";
|
|
523
|
-
ctx.beginPath();
|
|
524
|
-
|
|
852
|
+
ctx.beginPath();
|
|
525
853
|
let pos = this.timeToX( 0 );
|
|
526
|
-
if(pos < margin)
|
|
854
|
+
if(pos < margin) {
|
|
527
855
|
pos = margin;
|
|
856
|
+
}
|
|
528
857
|
ctx.moveTo( pos + 0.5, timeline_height);
|
|
529
858
|
ctx.lineTo( pos + 0.5, canvas.height);
|
|
530
859
|
ctx.moveTo( Math.round( this.timeToX( duration ) ) + 0.5, timeline_height);
|
|
@@ -536,42 +865,40 @@ class Timeline {
|
|
|
536
865
|
|
|
537
866
|
/**
|
|
538
867
|
* @method draw
|
|
539
|
-
* @param {
|
|
540
|
-
* @param {
|
|
541
|
-
* TODO
|
|
868
|
+
* @param {Number} currentTime
|
|
869
|
+
* @param {Array} rect [x, y, width, height]
|
|
542
870
|
*/
|
|
543
|
-
|
|
544
871
|
draw( currentTime = this.currentTime, rect ) {
|
|
545
872
|
|
|
546
873
|
let ctx = this.canvas.getContext("2d");
|
|
547
874
|
ctx.textBaseline = "bottom";
|
|
548
875
|
ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
|
|
549
|
-
if(!rect)
|
|
876
|
+
if(!rect) {
|
|
550
877
|
rect = [0, ctx.canvas.height - ctx.canvas.height , ctx.canvas.width, ctx.canvas.height ];
|
|
878
|
+
}
|
|
551
879
|
|
|
552
|
-
// this.canvas = ctx.canvas;
|
|
553
880
|
this.position[0] = rect[0];
|
|
554
881
|
this.position[1] = rect[1];
|
|
555
882
|
let w = rect[2];
|
|
556
883
|
let h = rect[3];
|
|
557
884
|
this.currentTime = currentTime;
|
|
558
|
-
// this.updateHeader();
|
|
559
885
|
this.currentScrollInPixels = this.scrollableHeight <= h ? 0 : (this.currentScroll * (this.scrollableHeight - h));
|
|
560
886
|
|
|
561
|
-
//zoom
|
|
887
|
+
// Manage zoom
|
|
562
888
|
if(this.duration > 0)
|
|
563
889
|
{
|
|
564
890
|
this.startTime = -50 / this.secondsToPixels;
|
|
565
|
-
// this.startTime = Math.floor( this.session.start_time ); //seconds
|
|
566
891
|
this.startTime = this.session.start_time ; //seconds
|
|
567
|
-
if(this.startTime < 0)
|
|
892
|
+
if(this.startTime < 0) {
|
|
568
893
|
this.startTime = 0;
|
|
569
|
-
|
|
894
|
+
}
|
|
570
895
|
this.endTime = this.startTime + (w - this.session.left_margin) * this.pixelsToSeconds ;
|
|
571
|
-
if(this.endTime > this.duration)
|
|
896
|
+
if(this.endTime > this.duration) {
|
|
572
897
|
this.endTime = this.duration;
|
|
573
|
-
|
|
898
|
+
}
|
|
899
|
+
if(this.startTime > this.endTime) { //avoids weird bug
|
|
574
900
|
this.endTime = this.startTime + 1;
|
|
901
|
+
}
|
|
575
902
|
}
|
|
576
903
|
|
|
577
904
|
this.tracksDrawn.length = 0;
|
|
@@ -582,47 +909,48 @@ class Timeline {
|
|
|
582
909
|
ctx.clearRect(0,0, this.canvas.width, this.canvas.height );
|
|
583
910
|
|
|
584
911
|
this.drawTracksBackground(w, h);
|
|
585
|
-
|
|
586
|
-
|
|
912
|
+
|
|
913
|
+
// Draw content
|
|
914
|
+
if(this.drawContent && this.animationClip) {
|
|
587
915
|
|
|
588
|
-
ctx.translate( this.position[0], this.position[1] + this.topMargin );
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) ); //20 is the top margin area
|
|
916
|
+
ctx.translate( this.position[0], this.position[1] + this.topMargin );
|
|
917
|
+
this.drawContent( ctx, this.timeStart, this.timeEnd, this );
|
|
918
|
+
ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) );
|
|
593
919
|
}
|
|
594
920
|
|
|
595
|
-
//scrollbar
|
|
596
|
-
if( h < this.scrollableHeight )
|
|
597
|
-
{
|
|
921
|
+
// Draw scrollbar
|
|
922
|
+
if( h < this.scrollableHeight ) {
|
|
598
923
|
ctx.fillStyle = "#222";
|
|
599
924
|
ctx.fillRect( w - this.session.left_margin - 10, 0, 10, h );
|
|
600
925
|
var scrollh = (h - this.topMargin)*h/ this.scrollableHeight;
|
|
601
926
|
ctx.fillStyle = this.grabbingScroll ? Timeline.FONT_COLOR : Timeline.TRACK_COLOR_SECONDARY;
|
|
602
927
|
ctx.roundRect( w - 8, this.currentScroll * (h - scrollh) + this.topMargin, 4, scrollh - this.topMargin, 5, true );
|
|
603
928
|
}
|
|
604
|
-
this.drawTimeInfo(w);
|
|
605
929
|
|
|
606
|
-
//
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
930
|
+
// Draw top bar with time information
|
|
931
|
+
this.drawTimeInfo();
|
|
932
|
+
|
|
933
|
+
// Draw current time marker (vertical line)
|
|
934
|
+
const quantCurrentTime = Math.round( this.currentTime * this.framerate ) / this.framerate;
|
|
935
|
+
const posX = Math.round( this.timeToX( quantCurrentTime ) ) + 0.5; //current_time is quantized
|
|
936
|
+
const posY = this.topMargin * 0.4;
|
|
937
|
+
const truePos = Math.round( this.timeToX( this.currentTime ) ) + 0.5;
|
|
938
|
+
|
|
939
|
+
if(posX >= this.session.left_margin) { // Draw it only if its inside the timeline tracks area
|
|
940
|
+
// Draw vertical line
|
|
614
941
|
ctx.strokeStyle = ctx.fillStyle = LX.getThemeColor("global-selected");
|
|
615
942
|
ctx.globalAlpha = this.opacity;
|
|
616
943
|
ctx.beginPath();
|
|
617
|
-
ctx.moveTo(truePos,
|
|
944
|
+
ctx.moveTo(truePos, posY * 0.6); ctx.lineTo(truePos, this.canvas.height); //line
|
|
618
945
|
ctx.stroke();
|
|
619
946
|
ctx.closePath();
|
|
947
|
+
|
|
948
|
+
// Draw top decoration
|
|
620
949
|
ctx.shadowBlur = 8;
|
|
621
950
|
ctx.shadowColor = LX.getThemeColor("global-selected");
|
|
622
951
|
ctx.shadowOffsetX = 1;
|
|
623
952
|
ctx.shadowOffsetY = 1;
|
|
624
|
-
|
|
625
|
-
ctx.roundRect( truePos - 10, posy * 0.6, 20, posy, 5, true );
|
|
953
|
+
ctx.roundRect( truePos - 10, posY * 0.6, 20, posY, 5, true );
|
|
626
954
|
ctx.fill();
|
|
627
955
|
ctx.shadowBlur = 0;
|
|
628
956
|
ctx.shadowOffsetX = 0;
|
|
@@ -632,11 +960,10 @@ class Timeline {
|
|
|
632
960
|
// Current time seconds in text
|
|
633
961
|
ctx.font = "11px " + Timeline.FONT;//"11px Calibri";
|
|
634
962
|
ctx.textAlign = "center";
|
|
635
|
-
//ctx.textBaseline = "middle";
|
|
636
963
|
ctx.fillStyle = Timeline.COLOR_UNACTIVE//"#888";
|
|
637
964
|
ctx.fillText( this.currentTime.toFixed(1), truePos, this.topMargin * 0.6 );
|
|
638
965
|
|
|
639
|
-
//
|
|
966
|
+
// Draw box selection
|
|
640
967
|
ctx.strokeStyle = ctx.fillStyle = Timeline.FONT_COLOR;
|
|
641
968
|
ctx.translate( this.position[0], this.position[1] + this.topMargin )
|
|
642
969
|
if(this.boxSelection && this.boxSelectionStart && this.boxSelectionEnd) {
|
|
@@ -647,237 +974,98 @@ class Timeline {
|
|
|
647
974
|
ctx.stroke();
|
|
648
975
|
ctx.globalAlpha = this.opacity;
|
|
649
976
|
}
|
|
650
|
-
ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) );
|
|
651
|
-
|
|
977
|
+
ctx.translate( -this.position[0], -(this.position[1] + this.topMargin) );
|
|
652
978
|
}
|
|
653
979
|
|
|
654
980
|
/**
|
|
655
|
-
* @method
|
|
656
|
-
* @param {
|
|
657
|
-
* @param {*} markers
|
|
658
|
-
* TODO
|
|
981
|
+
* @method processMouse
|
|
982
|
+
* @param {Event} e
|
|
659
983
|
*/
|
|
984
|
+
processMouse( e ) {
|
|
660
985
|
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
//render markers
|
|
664
|
-
ctx.fillStyle = "white";
|
|
665
|
-
ctx.textAlign = "left";
|
|
666
|
-
let markersPos = [];
|
|
667
|
-
for (let i = 0; i < markers.length; ++i) {
|
|
668
|
-
let marker = markers[i];
|
|
669
|
-
if (marker.time < this.startTime - this.pixelsToSeconds * 100 ||
|
|
670
|
-
marker.time > this.endTime)
|
|
671
|
-
continue;
|
|
672
|
-
var x = this.timeToX(marker.time);
|
|
673
|
-
markersPos.push(x);
|
|
674
|
-
ctx.save();
|
|
675
|
-
ctx.translate(x, 0);
|
|
676
|
-
ctx.rotate(Math.PI * -0.25);
|
|
677
|
-
ctx.fillText(marker.title, 20, 4);
|
|
678
|
-
ctx.restore();
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (markersPos.length) {
|
|
682
|
-
ctx.beginPath();
|
|
683
|
-
for (var i = 0; i < markersPos.length; ++i) {
|
|
684
|
-
ctx.moveTo(markersPos[i] - 5, 0);
|
|
685
|
-
ctx.lineTo(markersPos[i], -5);
|
|
686
|
-
ctx.lineTo(markersPos[i] + 5, 0);
|
|
687
|
-
ctx.lineTo(markersPos[i], 5);
|
|
688
|
-
ctx.lineTo(markersPos[i] - 5, 0);
|
|
689
|
-
}
|
|
690
|
-
ctx.fill();
|
|
986
|
+
if(!this.canvas) {
|
|
987
|
+
return;
|
|
691
988
|
}
|
|
692
|
-
|
|
989
|
+
|
|
990
|
+
e.multipleSelection = false;
|
|
693
991
|
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
*/
|
|
992
|
+
const w = this.canvas.width;
|
|
993
|
+
const h = this.canvas.height;
|
|
697
994
|
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
}
|
|
995
|
+
const x = e.offsetX;
|
|
996
|
+
const y = e.offsetY;
|
|
701
997
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
998
|
+
e.deltax = x - this.lastMouse[0];
|
|
999
|
+
e.deltay = y - this.lastMouse[1];
|
|
1000
|
+
|
|
1001
|
+
const localX = e.offsetX - this.position[0];
|
|
1002
|
+
const localY = e.offsetY - this.position[1];
|
|
706
1003
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
let decimals = t.toString().split('.')[1] ? t.toString().split('.')[1].length : 0;
|
|
710
|
-
updateHeader = (updateHeader || +v.toFixed(decimals) != t);
|
|
711
|
-
this.duration = this.animationClip.duration = v;
|
|
1004
|
+
const currentTimeX = this.timeToX( this.currentTime );
|
|
1005
|
+
const current_grabbing_timeline = localY < this.topMargin && localX > this.session.left_margin && localX > (currentTimeX - 6) && localX < (currentTimeX + 6);
|
|
712
1006
|
|
|
713
|
-
|
|
714
|
-
|
|
1007
|
+
// Set cursor style depending of the mouse action
|
|
1008
|
+
if( current_grabbing_timeline ) {
|
|
1009
|
+
this.canvas.style.cursor = "col-resize";
|
|
1010
|
+
}
|
|
1011
|
+
else if(this.movingKeys) {
|
|
1012
|
+
this.canvas.style.cursor = "grabbing";
|
|
1013
|
+
}
|
|
1014
|
+
else if(e.shiftKey) {
|
|
1015
|
+
this.canvas.style.cursor = "crosshair";
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
this.canvas.style.cursor = "default";
|
|
715
1019
|
}
|
|
716
1020
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
return t;
|
|
728
|
-
}
|
|
729
|
-
|
|
1021
|
+
// Zoom in/out or scroll content
|
|
1022
|
+
if( e.type == "wheel" ) {
|
|
1023
|
+
if(e.shiftKey) {
|
|
1024
|
+
this.setScale( e.wheelDelta < 0 ? 0.95 : 1.05 );
|
|
1025
|
+
}
|
|
1026
|
+
else if( h < this.scrollableHeight) {
|
|
1027
|
+
this.leftPanel.root.children[1].scrollTop += e.deltaY * 0.1 ;
|
|
1028
|
+
}
|
|
1029
|
+
return;
|
|
1030
|
+
}
|
|
730
1031
|
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
*/
|
|
1032
|
+
// Check if the mouse is inside the timeline
|
|
1033
|
+
const is_inside = x >= this.position[0] && x <= (this.position[0] + this.size[0]) &&
|
|
1034
|
+
y >= this.position[1] && y <= (this.position[1] + this.size[1]);
|
|
735
1035
|
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
1036
|
+
if(!is_inside) {
|
|
1037
|
+
return true;
|
|
1038
|
+
}
|
|
1039
|
+
// Check if the mouse is inside some track
|
|
1040
|
+
let track = null;
|
|
1041
|
+
for(let i = this.tracksDrawn.length - 1; i >= 0; --i)
|
|
1042
|
+
{
|
|
1043
|
+
const t = this.tracksDrawn[i];
|
|
1044
|
+
if( t[1] >= this.topMargin && localY >= t[1] && localY < (t[1] + t[2]) )
|
|
1045
|
+
{
|
|
1046
|
+
track = t[0];
|
|
1047
|
+
break;
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
740
1050
|
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
1051
|
+
e.track = track;
|
|
1052
|
+
e.localX = localX;
|
|
1053
|
+
e.localY = localY;
|
|
744
1054
|
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
1055
|
+
const time = this.xToTime(x, true);
|
|
1056
|
+
if( e.type == "mouseup" )
|
|
1057
|
+
{
|
|
1058
|
+
if(!this.active) {
|
|
1059
|
+
this.grabbing_timeline = false;
|
|
1060
|
+
this.grabbing = false;
|
|
1061
|
+
this.grabbingScroll = false;
|
|
1062
|
+
this.movingKeys = false;
|
|
1063
|
+
this.timeBeforeMove = null;
|
|
1064
|
+
return;
|
|
1065
|
+
}
|
|
749
1066
|
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
return this.session.left_margin + (t - this.session.start_time) * this.secondsToPixels;
|
|
753
|
-
}
|
|
754
|
-
|
|
755
|
-
getCurrentFrame( framerate ) {
|
|
756
|
-
return Math.floor(this.currentTime * framerate);
|
|
757
|
-
}
|
|
758
|
-
|
|
759
|
-
/**
|
|
760
|
-
* @method setScale
|
|
761
|
-
* @param {*} v
|
|
762
|
-
* TODO
|
|
763
|
-
*/
|
|
764
|
-
|
|
765
|
-
setScale( v ) {
|
|
766
|
-
|
|
767
|
-
if(!this.session)
|
|
768
|
-
return;
|
|
769
|
-
|
|
770
|
-
if( this.secondsToPixels * v < 100)
|
|
771
|
-
return;
|
|
772
|
-
|
|
773
|
-
const xCurrentTime = this.timeToX(this.currentTime);
|
|
774
|
-
this.secondsToPixels *= v;
|
|
775
|
-
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
776
|
-
this.session.start_time += this.currentTime - this.xToTime(xCurrentTime);
|
|
777
|
-
this.draw();
|
|
778
|
-
}
|
|
779
|
-
|
|
780
|
-
/**
|
|
781
|
-
* @method setFramerate
|
|
782
|
-
* @param {*} v
|
|
783
|
-
*/
|
|
784
|
-
|
|
785
|
-
setFramerate( v ) {
|
|
786
|
-
this.framerate = v;
|
|
787
|
-
}
|
|
788
|
-
|
|
789
|
-
/**
|
|
790
|
-
* @method processMouse
|
|
791
|
-
* @param {*} e
|
|
792
|
-
*/
|
|
793
|
-
|
|
794
|
-
processMouse( e ) {
|
|
795
|
-
|
|
796
|
-
if(!this.canvas)
|
|
797
|
-
return;
|
|
798
|
-
|
|
799
|
-
e.multipleSelection = false;
|
|
800
|
-
|
|
801
|
-
let h = this.canvas.height;
|
|
802
|
-
let w = this.canvas.width;
|
|
803
|
-
|
|
804
|
-
// Process mouse
|
|
805
|
-
let x = e.offsetX;
|
|
806
|
-
let y = e.offsetY;
|
|
807
|
-
e.deltax = x - this.lastMouse[0];
|
|
808
|
-
e.deltay = y - this.lastMouse[1];
|
|
809
|
-
let localX = e.offsetX - this.position[0];
|
|
810
|
-
let localY = e.offsetY - this.position[1];
|
|
811
|
-
|
|
812
|
-
let timeX = this.timeToX( this.currentTime );
|
|
813
|
-
let current_grabbing_timeline = localY < this.topMargin && localX > this.session.left_margin &&
|
|
814
|
-
localX > (timeX - 6) && localX < (timeX + 6);
|
|
815
|
-
|
|
816
|
-
if( current_grabbing_timeline ) {
|
|
817
|
-
this.canvas.style.cursor = "col-resize";
|
|
818
|
-
}
|
|
819
|
-
else if(this.movingKeys) {
|
|
820
|
-
this.canvas.style.cursor = "grabbing";
|
|
821
|
-
}
|
|
822
|
-
else if(e.shiftKey) {
|
|
823
|
-
this.canvas.style.cursor = "crosshair";
|
|
824
|
-
}
|
|
825
|
-
else {
|
|
826
|
-
this.canvas.style.cursor = "default";
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if( e.type == "wheel" ) {
|
|
830
|
-
if(e.shiftKey)
|
|
831
|
-
{
|
|
832
|
-
this.setScale( e.wheelDelta < 0 ? 0.95 : 1.05 );
|
|
833
|
-
}
|
|
834
|
-
else if( h < this.scrollableHeight)
|
|
835
|
-
{
|
|
836
|
-
this.leftPanel.root.children[1].scrollTop += e.deltaY * 0.1 ;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
return;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
var time = this.xToTime(x, true);
|
|
843
|
-
|
|
844
|
-
var is_inside = x >= this.position[0] && x <= (this.position[0] + this.size[0]) &&
|
|
845
|
-
y >= this.position[1] && y <= (this.position[1] + this.size[1]);
|
|
846
|
-
|
|
847
|
-
var track = null;
|
|
848
|
-
for(var i = this.tracksDrawn.length - 1; i >= 0; --i)
|
|
849
|
-
{
|
|
850
|
-
var t = this.tracksDrawn[i];
|
|
851
|
-
if( t[1] >= this.topMargin && localY >= t[1] && localY < (t[1] + t[2]) )
|
|
852
|
-
{
|
|
853
|
-
track = t[0];
|
|
854
|
-
break;
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
|
|
858
|
-
e.track = track;
|
|
859
|
-
e.localX = localX;
|
|
860
|
-
e.localY = localY;
|
|
861
|
-
|
|
862
|
-
const innerSetTime = (t) => {
|
|
863
|
-
LX.emit( "@on_current_time_" + this.constructor.name, t);
|
|
864
|
-
if( this.onSetTime )
|
|
865
|
-
this.onSetTime( t );
|
|
866
|
-
}
|
|
867
|
-
|
|
868
|
-
if( e.type == "mouseup" )
|
|
869
|
-
{
|
|
870
|
-
if(!this.active) {
|
|
871
|
-
this.grabbing_timeline = false;
|
|
872
|
-
this.grabbing = false;
|
|
873
|
-
this.grabbingScroll = false;
|
|
874
|
-
this.movingKeys = false;
|
|
875
|
-
this.timeBeforeMove = null;
|
|
876
|
-
return;
|
|
877
|
-
}
|
|
878
|
-
// this.canvas.style.cursor = "default";
|
|
879
|
-
const discard = this.movingKeys || (LX.UTILS.getTime() - this.clickTime) > 420; // ms
|
|
880
|
-
this.movingKeys ? innerSetTime( this.currentTime ) : 0;
|
|
1067
|
+
const discard = this.movingKeys || (LX.UTILS.getTime() - this.clickTime) > 420; // ms
|
|
1068
|
+
this.movingKeys ? LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime) : 0;
|
|
881
1069
|
|
|
882
1070
|
this.grabbing_timeline = false;
|
|
883
1071
|
this.grabbing = false;
|
|
@@ -888,7 +1076,7 @@ class Timeline {
|
|
|
888
1076
|
|
|
889
1077
|
if(e.localY <= this.topMargin && !e.shiftKey) {
|
|
890
1078
|
this.currentTime = Math.max(0, time);
|
|
891
|
-
|
|
1079
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime)
|
|
892
1080
|
return;
|
|
893
1081
|
}
|
|
894
1082
|
|
|
@@ -899,7 +1087,6 @@ class Timeline {
|
|
|
899
1087
|
this.updateLeftPanel();
|
|
900
1088
|
}
|
|
901
1089
|
|
|
902
|
-
|
|
903
1090
|
if( e.type == "mousedown") {
|
|
904
1091
|
|
|
905
1092
|
this.clickTime = LX.UTILS.getTime();
|
|
@@ -918,10 +1105,9 @@ class Timeline {
|
|
|
918
1105
|
|
|
919
1106
|
this.grabbing = true;
|
|
920
1107
|
this.grabTime = time - this.currentTime;
|
|
921
|
-
if(!track || track && this.
|
|
1108
|
+
if(!track || track && this.getContent(track, time, 0.001) == undefined) {
|
|
922
1109
|
this.grabbing_timeline = current_grabbing_timeline;
|
|
923
1110
|
}
|
|
924
|
-
|
|
925
1111
|
if(this.onMouseDown && this.active )
|
|
926
1112
|
this.onMouseDown(e, time);
|
|
927
1113
|
}
|
|
@@ -1027,95 +1213,6 @@ class Timeline {
|
|
|
1027
1213
|
}
|
|
1028
1214
|
}
|
|
1029
1215
|
|
|
1030
|
-
/**
|
|
1031
|
-
* @method drawTrackWithKeyframes
|
|
1032
|
-
* @param {*} ctx
|
|
1033
|
-
* ...
|
|
1034
|
-
* @description helper function, you can call it from onDrawContent to render all the keyframes
|
|
1035
|
-
* TODO
|
|
1036
|
-
*/
|
|
1037
|
-
|
|
1038
|
-
drawTrackWithKeyframes( ctx, y, trackHeight, title, track, trackInfo ) {
|
|
1039
|
-
|
|
1040
|
-
if(trackInfo.enabled === false) {
|
|
1041
|
-
ctx.globalAlpha = 0.4;
|
|
1042
|
-
}
|
|
1043
|
-
|
|
1044
|
-
ctx.font = Math.floor( trackHeight * 0.8) + "px" + Timeline.FONT;
|
|
1045
|
-
ctx.textAlign = "left";
|
|
1046
|
-
|
|
1047
|
-
ctx.globalAlpha = 0.2;
|
|
1048
|
-
|
|
1049
|
-
if(trackInfo.isSelected) {
|
|
1050
|
-
ctx.fillStyle = Timeline.TRACK_SELECTED;//"#2c303570";
|
|
1051
|
-
ctx.fillRect(0, y, ctx.canvas.width, trackHeight );
|
|
1052
|
-
}
|
|
1053
|
-
ctx.fillStyle = Timeline.COLOR;//"#2c303570";
|
|
1054
|
-
ctx.globalAlpha = 1;
|
|
1055
|
-
|
|
1056
|
-
let keyframes = track.times;
|
|
1057
|
-
|
|
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();
|
|
1074
|
-
|
|
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
|
-
}
|
|
1088
|
-
|
|
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;
|
|
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();
|
|
1112
|
-
}
|
|
1113
|
-
}
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
ctx.globalAlpha = this.opacity;
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
1216
|
/**
|
|
1120
1217
|
* @method drawTrackWithBoxes
|
|
1121
1218
|
* @param {*} ctx
|
|
@@ -1249,208 +1346,395 @@ class Timeline {
|
|
|
1249
1346
|
|
|
1250
1347
|
ctx.font = "12px" + Timeline.FONT;
|
|
1251
1348
|
}
|
|
1349
|
+
};
|
|
1350
|
+
|
|
1351
|
+
Timeline.BACKGROUND_COLOR = LX.getThemeColor("global-color-primary");
|
|
1352
|
+
Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor("global-blur-background");
|
|
1353
|
+
Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor("global-color-terciary");
|
|
1354
|
+
Timeline.TRACK_SELECTED = LX.getThemeColor("global-selected");
|
|
1355
|
+
Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor("global-selected-light");
|
|
1356
|
+
Timeline.FONT = LX.getThemeColor("global-font");
|
|
1357
|
+
Timeline.FONT_COLOR = LX.getThemeColor("global-text");
|
|
1358
|
+
Timeline.COLOR = LX.getThemeColor("global-selected-dark");//"#5e9fdd";
|
|
1359
|
+
Timeline.COLOR_SELECTED = Timeline.COLOR_HOVERED = "rgba(250,250,20,1)";///"rgba(250,250,20,1)";
|
|
1360
|
+
// Timeline.COLOR_HOVERED = LX.getThemeColor("global-selected");
|
|
1361
|
+
Timeline.COLOR_UNACTIVE = "rgba(250,250,250,0.7)";
|
|
1362
|
+
Timeline.COLOR_LOCK = "rgba(255,125,125,0.7)";
|
|
1363
|
+
Timeline.COLOR_EDITED = "white"//"rgba(125,250,250, 1)";
|
|
1364
|
+
|
|
1365
|
+
LX.Timeline = Timeline;
|
|
1366
|
+
|
|
1367
|
+
/**
|
|
1368
|
+
* @class KeyFramesTimeline
|
|
1369
|
+
*/
|
|
1370
|
+
|
|
1371
|
+
class KeyFramesTimeline extends Timeline {
|
|
1252
1372
|
|
|
1253
1373
|
/**
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1374
|
+
* @param {string} name
|
|
1375
|
+
* @param {object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
|
|
1376
|
+
*/
|
|
1377
|
+
constructor(name, options = {}) {
|
|
1257
1378
|
|
|
1258
|
-
|
|
1259
|
-
this.unSelectAllTracks();
|
|
1260
|
-
|
|
1261
|
-
let [name, type] = trackInfo.id.split(" (");
|
|
1262
|
-
|
|
1263
|
-
if(type)
|
|
1264
|
-
type = type.replaceAll(")", "").replaceAll(" ", "");
|
|
1265
|
-
else {
|
|
1266
|
-
type = name;
|
|
1267
|
-
name = trackInfo.parent ? trackInfo.parent.id : trackInfo.id;
|
|
1268
|
-
}
|
|
1269
|
-
let tracks = this.tracksPerItem[name];
|
|
1379
|
+
super(name, options);
|
|
1270
1380
|
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
trackInfo = this.tracksPerItem[name][i];
|
|
1276
|
-
}
|
|
1381
|
+
this.onShowOptimizeMenu = options.onShowOptimizeMenu;
|
|
1382
|
+
this.onGetOptimizeThreshold = options.onGetOptimizeThreshold;
|
|
1383
|
+
|
|
1384
|
+
this.tracksPerItem = {};
|
|
1277
1385
|
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1386
|
+
// this.selectedItems = selectedItems;
|
|
1387
|
+
this.snappedKeyFrameIndex = -1;
|
|
1388
|
+
this.autoKeyEnabled = false;
|
|
1281
1389
|
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
let item = this.selectedItems[i];
|
|
1285
|
-
let tracks = this.tracksPerItem[item];
|
|
1286
|
-
for(let t = 0; t < tracks.length; t++) {
|
|
1287
|
-
tracks[t].isSelected = false;
|
|
1288
|
-
}
|
|
1390
|
+
if(this.animationClip && this.animationClip.tracks.length) {
|
|
1391
|
+
this.processTracks(this.animationClip);
|
|
1289
1392
|
}
|
|
1290
1393
|
}
|
|
1291
1394
|
|
|
1292
1395
|
/**
|
|
1293
|
-
* @method
|
|
1294
|
-
* @
|
|
1396
|
+
* @method processTrackS
|
|
1397
|
+
* @description Creates a map for each item -> tracks
|
|
1398
|
+
* @param {Object} animation
|
|
1295
1399
|
*/
|
|
1400
|
+
processTracks(animation) {
|
|
1296
1401
|
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
}
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1402
|
+
this.tracksPerItem = {};
|
|
1403
|
+
this.tracksDictionary = {};
|
|
1404
|
+
this.animationClip = {
|
|
1405
|
+
name: (animation && animation.name) ? animation.name : "animationClip",
|
|
1406
|
+
duration: animation ? animation.duration : 0,
|
|
1407
|
+
speed: (animation && animation.speed ) ? animation.speed : this.speed,
|
|
1408
|
+
tracks: []
|
|
1409
|
+
};
|
|
1410
|
+
if (animation && animation.tracks) {
|
|
1411
|
+
for( let i = 0; i < animation.tracks.length; ++i ) {
|
|
1412
|
+
|
|
1413
|
+
let track = animation.tracks[i];
|
|
1414
|
+
|
|
1415
|
+
const [name, type] = this.processTrackName(track.name);
|
|
1416
|
+
|
|
1417
|
+
let valueDim = track.dim;
|
|
1418
|
+
if ( !valueDim || valueDim < 0 ){
|
|
1419
|
+
if ( track.times.length && track.values.length ){ valueDim = track.values.length/track.times.length; }
|
|
1420
|
+
else{ valueDim = 1; }
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
let leftOver = track.values.length % valueDim; // just in case values has an incorrect length
|
|
1424
|
+
let amounEntries = Math.min( track.times.length, track.values.length - leftOver );
|
|
1425
|
+
let times = track.times.slice(0, amounEntries);
|
|
1426
|
+
let values = track.values.slice(0, amounEntries * valueDim);
|
|
1427
|
+
let boolArray = (new Array(amounEntries)).fill(false);
|
|
1428
|
+
|
|
1429
|
+
// TO DO: Check if it can be replaced for processTrack function
|
|
1430
|
+
let trackInfo = {
|
|
1431
|
+
fullname: track.name,
|
|
1432
|
+
name: name, type: type,
|
|
1433
|
+
active: true,
|
|
1434
|
+
dim: valueDim,
|
|
1435
|
+
selected: boolArray.slice(), edited: boolArray.slice(), hovered: boolArray.slice(),
|
|
1436
|
+
times: times,
|
|
1437
|
+
values: values
|
|
1438
|
+
};
|
|
1439
|
+
|
|
1440
|
+
if(!this.tracksPerItem[name]) {
|
|
1441
|
+
this.tracksPerItem[name] = [trackInfo];
|
|
1442
|
+
}else {
|
|
1443
|
+
this.tracksPerItem[name].push( trackInfo );
|
|
1444
|
+
}
|
|
1445
|
+
|
|
1446
|
+
const trackIndex = this.tracksPerItem[name].length - 1;
|
|
1447
|
+
trackInfo.idx = trackIndex; // index of track in "name"
|
|
1448
|
+
trackInfo.clipIdx = i; // index of track in the entire animation
|
|
1449
|
+
|
|
1450
|
+
// Save index also in original track
|
|
1451
|
+
track.idx = trackIndex;
|
|
1452
|
+
this.tracksDictionary[track.name] = name; // map original track name with shortened one
|
|
1453
|
+
|
|
1454
|
+
this.animationClip.tracks.push(trackInfo);
|
|
1455
|
+
}
|
|
1313
1456
|
}
|
|
1314
|
-
this.
|
|
1315
|
-
if(this.onChangeTrackVisibility)
|
|
1316
|
-
this.onChangeTrackVisibility(trackInfo, visible);
|
|
1457
|
+
this.resize();
|
|
1317
1458
|
}
|
|
1318
1459
|
|
|
1319
1460
|
/**
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1461
|
+
* @method processTrackName
|
|
1462
|
+
* @description Process track name to get the its name and its type separately
|
|
1463
|
+
* @param {String} trackName
|
|
1464
|
+
*/
|
|
1465
|
+
processTrackName( trackName ) {
|
|
1323
1466
|
|
|
1324
|
-
|
|
1467
|
+
let name, type;
|
|
1325
1468
|
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1469
|
+
// Support other versions
|
|
1470
|
+
if(trackName.includes("[")) {
|
|
1471
|
+
const nameIndex = trackName.indexOf('['),
|
|
1472
|
+
trackNameInfo = trackName.substr(nameIndex+1).split("].");
|
|
1473
|
+
name = trackNameInfo[0];
|
|
1474
|
+
type = trackNameInfo[1];
|
|
1475
|
+
}
|
|
1476
|
+
else {
|
|
1477
|
+
const trackNameInfo = trackName.split(".");
|
|
1478
|
+
name = trackNameInfo[0];
|
|
1479
|
+
type = trackNameInfo[1];
|
|
1480
|
+
}
|
|
1481
|
+
return [name, type];
|
|
1482
|
+
}
|
|
1336
1483
|
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1484
|
+
processTrack(trackIdx) {
|
|
1485
|
+
if(!this.animationClip) {
|
|
1486
|
+
return;
|
|
1487
|
+
}
|
|
1488
|
+
|
|
1489
|
+
const track = this.animationClip.tracks[trackIdx];
|
|
1490
|
+
const [name, type] = this.processTrackName(track.fullname || track.name);
|
|
1491
|
+
|
|
1492
|
+
let trackInfo = {
|
|
1493
|
+
fullname: track.name,
|
|
1494
|
+
name: name, type: type,
|
|
1495
|
+
dim: track.values.length/track.times.length,
|
|
1496
|
+
selected: [], edited: [], hovered: [], active: true,
|
|
1497
|
+
times: track.times,
|
|
1498
|
+
values: track.values
|
|
1499
|
+
};
|
|
1500
|
+
|
|
1501
|
+
for(let i = 0; i < this.tracksPerItem[name].length; i++) {
|
|
1502
|
+
if(this.tracksPerItem[name][i].fullname == trackInfo.fullname) {
|
|
1503
|
+
trackInfo.idx = this.tracksPerItem[name][i].idx;
|
|
1504
|
+
trackInfo.clipIdx = this.tracksPerItem[name][i].clipIdx;
|
|
1505
|
+
this.tracksPerItem[name][i] = trackInfo;
|
|
1506
|
+
return;
|
|
1342
1507
|
}
|
|
1343
1508
|
}
|
|
1509
|
+
}
|
|
1510
|
+
|
|
1511
|
+
onUpdateTracks ( keyType ) {
|
|
1512
|
+
|
|
1513
|
+
if(this.selectedItems == null || this.lastKeyFramesSelected.length || !this.autoKeyEnabled)
|
|
1514
|
+
return;
|
|
1515
|
+
|
|
1516
|
+
for(let i = 0; i < this.selectedItems.length; i++) {
|
|
1517
|
+
let tracks = this.tracksPerItem[this.selectedItems[i]];
|
|
1518
|
+
if(!tracks) continue;
|
|
1519
|
+
|
|
1520
|
+
// Get current track
|
|
1521
|
+
const selectedTrackIdx = tracks.findIndex( t => t.type === keyType );
|
|
1522
|
+
if(selectedTrackIdx < 0)
|
|
1523
|
+
return;
|
|
1524
|
+
let track = tracks[ selectedTrackIdx ];
|
|
1525
|
+
|
|
1526
|
+
// Add new keyframe
|
|
1527
|
+
const newIdx = this.addKeyFrame( track );
|
|
1528
|
+
if(newIdx === null)
|
|
1529
|
+
continue;
|
|
1530
|
+
|
|
1531
|
+
// Select it
|
|
1532
|
+
this.lastKeyFramesSelected.push( [track.name, track.idx, newIdx] );
|
|
1533
|
+
track.selected[newIdx] = true;
|
|
1534
|
+
|
|
1535
|
+
}
|
|
1344
1536
|
|
|
1345
|
-
this.
|
|
1537
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
|
|
1538
|
+
return true; // Handled
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
|
|
1542
|
+
|
|
1543
|
+
updateTrack(trackIdx, track) {
|
|
1544
|
+
if(!this.animationClip)
|
|
1545
|
+
return;
|
|
1546
|
+
this.animationClip.tracks[trackIdx].values = track.values;
|
|
1547
|
+
this.animationClip.tracks[trackIdx].times = track.times;
|
|
1548
|
+
this.processTrack(trackIdx);
|
|
1346
1549
|
|
|
1347
|
-
|
|
1348
|
-
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
optimizeTrack(trackIdx) {
|
|
1553
|
+
const track = this.animationClip.tracks[trackIdx];
|
|
1554
|
+
if(track.optimize) {
|
|
1555
|
+
track.optimize( this.optimizeThreshold );
|
|
1556
|
+
if(this.onOptimizeTracks) {
|
|
1557
|
+
this.onOptimizeTracks(trackIdx);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
optimizeTracks() {
|
|
1563
|
+
|
|
1564
|
+
if(!this.animationClip) {
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
for( let i = 0; i < this.animationClip.tracks.length; ++i ) {
|
|
1569
|
+
const track = this.animationClip.tracks[i];
|
|
1570
|
+
if(track.optimize) {
|
|
1571
|
+
|
|
1572
|
+
track.optimize( this.optimizeThreshold );
|
|
1573
|
+
if(this.onOptimizeTracks) {
|
|
1574
|
+
this.onOptimizeTracks(i);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
}
|
|
1349
1578
|
}
|
|
1350
1579
|
|
|
1351
1580
|
/**
|
|
1352
|
-
* @method
|
|
1353
|
-
* @param {
|
|
1354
|
-
*
|
|
1355
|
-
*
|
|
1581
|
+
* @method setSelectedItems
|
|
1582
|
+
* @param {Array} itemsName
|
|
1356
1583
|
*/
|
|
1357
|
-
|
|
1584
|
+
setSelectedItems( itemsName ) {
|
|
1358
1585
|
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
this.size = size;
|
|
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)";
|
|
1586
|
+
if(itemsName.constructor !== Array)
|
|
1587
|
+
throw("Item name has to be an array!");
|
|
1365
1588
|
|
|
1366
|
-
|
|
1367
|
-
this.
|
|
1589
|
+
this.selectedItems = itemsName;
|
|
1590
|
+
this.unSelectAllKeyFrames();
|
|
1591
|
+
this.updateLeftPanel();
|
|
1592
|
+
this.resize();
|
|
1368
1593
|
}
|
|
1369
1594
|
|
|
1370
|
-
|
|
1371
|
-
|
|
1595
|
+
/**
|
|
1596
|
+
* @method getNumTracks
|
|
1597
|
+
* @description Get how many tracks are related to the passed item
|
|
1598
|
+
* @param {Object} item
|
|
1599
|
+
*/
|
|
1600
|
+
getNumTracks( item ) {
|
|
1601
|
+
if(!item || !this.tracksPerItem) {
|
|
1372
1602
|
return;
|
|
1373
|
-
if(Math.abs(this.canvas.width - size[0]) > 1) {
|
|
1374
|
-
|
|
1375
|
-
var w = Math.max(300, size[0] );
|
|
1376
|
-
this.secondsToPixels = ( w- this.session.left_margin ) / this.duration;
|
|
1377
|
-
this.pixelsToSeconds = 1 / this.secondsToPixels;
|
|
1378
1603
|
}
|
|
1379
|
-
|
|
1380
|
-
|
|
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;
|
|
1384
|
-
this.draw(this.currentTime);
|
|
1604
|
+
const tracks = this.tracksPerItem[item.name];
|
|
1605
|
+
return tracks ? tracks.length : null;
|
|
1385
1606
|
}
|
|
1386
1607
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1608
|
+
/**
|
|
1609
|
+
* @method getTrack
|
|
1610
|
+
* @description Get track given the item's name and track's id
|
|
1611
|
+
* @param {Object} trackInfo {name, track idx}
|
|
1612
|
+
* @returns {Object} track
|
|
1613
|
+
*/
|
|
1614
|
+
getTrack( trackInfo ) {
|
|
1615
|
+
const [name, trackIndex] = trackInfo;
|
|
1616
|
+
return this.tracksPerItem[name][trackIndex];
|
|
1393
1617
|
}
|
|
1394
1618
|
|
|
1395
1619
|
/**
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1620
|
+
* @method getKeyFrame
|
|
1621
|
+
* @description Get the keyframe in the given time. If there isn't any in that timem it returns null
|
|
1622
|
+
* @param {Object} track {times, values, ...}
|
|
1623
|
+
* @param {Number} time current time
|
|
1624
|
+
* @param {Number} threshold
|
|
1625
|
+
* @returns {Number or Null} keyframe index
|
|
1626
|
+
*/
|
|
1627
|
+
getKeyFrame( track, time, threshold ) {
|
|
1628
|
+
|
|
1629
|
+
if(!track || !track.times.length)
|
|
1630
|
+
return;
|
|
1631
|
+
|
|
1632
|
+
// Avoid iterating through all timestamps
|
|
1633
|
+
if((time + threshold) < track.times[0])
|
|
1634
|
+
return;
|
|
1635
|
+
|
|
1636
|
+
for(let i = 0; i < track.times.length; ++i) {
|
|
1637
|
+
let t = track.times[i];
|
|
1638
|
+
if(t >= (time - threshold) &&
|
|
1639
|
+
t <= (time + threshold)) {
|
|
1640
|
+
return i;
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
return;
|
|
1404
1644
|
}
|
|
1405
|
-
};
|
|
1406
1645
|
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
Timeline.COLOR_SELECTED = "rgba(250,250,20,1)"///"rgba(250,250,20,1)";
|
|
1417
|
-
Timeline.COLOR_UNACTIVE = "rgba(250,250,250,0.7)";
|
|
1418
|
-
Timeline.COLOR_LOCK = "rgba(255,125,125,0.7)";
|
|
1419
|
-
Timeline.COLOR_EDITED = "white"//"rgba(125,250,250, 1)";
|
|
1646
|
+
/**
|
|
1647
|
+
* @method getNearestKeyFrame
|
|
1648
|
+
* @description Get the nearest keyframe in the given time. It always returns a keyframe index
|
|
1649
|
+
* @param {Object} track {times, values, ...}
|
|
1650
|
+
* @param {Number} time current time
|
|
1651
|
+
* @param {Number} threshold
|
|
1652
|
+
* @returns {Number} keyframe index
|
|
1653
|
+
*/
|
|
1654
|
+
getNearestKeyFrame( track, time ) {
|
|
1420
1655
|
|
|
1421
|
-
|
|
1656
|
+
if(!track || !track.times.length) {
|
|
1657
|
+
return;
|
|
1658
|
+
}
|
|
1422
1659
|
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1660
|
+
return track.times.reduce((a, b) => {
|
|
1661
|
+
return Math.abs(b - time) < Math.abs(a - time) ? b : a;
|
|
1662
|
+
});
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
/**
|
|
1666
|
+
* @method getKeyFramesInRange
|
|
1667
|
+
* @description Get the keyframes inside a time range
|
|
1668
|
+
* @param {Object} track {times, values, ...}
|
|
1669
|
+
* @param {Number} minTime minimum time in the range
|
|
1670
|
+
* @param {Number} maxTime maximum time in the range
|
|
1671
|
+
* @param {Number} threshold
|
|
1672
|
+
* @returns {Number} keyframe index
|
|
1673
|
+
*/
|
|
1674
|
+
getKeyFramesInRange( track, minTime, maxTime, threshold ) {
|
|
1675
|
+
|
|
1676
|
+
if(!track || !track.times.length)
|
|
1677
|
+
return;
|
|
1678
|
+
|
|
1679
|
+
// Manage negative selection
|
|
1680
|
+
if(minTime > maxTime) {
|
|
1681
|
+
let aux = minTime;
|
|
1682
|
+
minTime = maxTime;
|
|
1683
|
+
maxTime = aux;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Avoid iterating through all timestamps
|
|
1687
|
+
if((maxTime + threshold) < track.times[0])
|
|
1688
|
+
return;
|
|
1689
|
+
|
|
1690
|
+
let indices = [];
|
|
1426
1691
|
|
|
1427
|
-
|
|
1692
|
+
for(let i = 0; i < track.times.length; ++i) {
|
|
1693
|
+
let t = track.times[i];
|
|
1694
|
+
if(t >= (minTime - threshold) &&
|
|
1695
|
+
t <= (maxTime + threshold)) {
|
|
1696
|
+
indices.push(i);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1428
1699
|
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
|
|
1700
|
+
return indices;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
/**
|
|
1704
|
+
* @method getNumKeyFramesSelected
|
|
1705
|
+
* @description Get the number of keyframes that are selected
|
|
1706
|
+
* @returns {Number} number of selected keyframes
|
|
1432
1707
|
*/
|
|
1433
|
-
|
|
1708
|
+
getNumKeyFramesSelected() {
|
|
1709
|
+
return this.lastKeyFramesSelected.length;
|
|
1710
|
+
}
|
|
1711
|
+
|
|
1712
|
+
/**
|
|
1713
|
+
* @override
|
|
1714
|
+
* @method addNewTrack
|
|
1715
|
+
* @description Add empty new track
|
|
1716
|
+
*/
|
|
1717
|
+
addNewTrack() {
|
|
1434
1718
|
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
this.tracksPerItem = {};
|
|
1438
|
-
|
|
1439
|
-
// this.selectedItems = selectedItems;
|
|
1440
|
-
this.snappedKeyFrameIndex = -1;
|
|
1441
|
-
this.autoKeyEnabled = false;
|
|
1719
|
+
if(!this.animationClip)
|
|
1720
|
+
this.animationClip = {tracks:[]};
|
|
1442
1721
|
|
|
1722
|
+
let trackInfo = {
|
|
1723
|
+
idx: this.animationClip.tracks.length,
|
|
1724
|
+
values: [], times: [],
|
|
1725
|
+
selected: [], edited: [], hovered: []
|
|
1726
|
+
};
|
|
1443
1727
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1728
|
+
this.animationClip.tracks.push(trackInfo);
|
|
1729
|
+
this.updateLeftPanel();
|
|
1730
|
+
return trackInfo.idx;
|
|
1447
1731
|
}
|
|
1448
1732
|
|
|
1449
|
-
onMouseUp( e
|
|
1733
|
+
onMouseUp( e ) {
|
|
1450
1734
|
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1735
|
+
const track = e.track;
|
|
1736
|
+
const localX = e.localX;
|
|
1737
|
+
const discard = e.discard;
|
|
1454
1738
|
|
|
1455
1739
|
if(e.shiftKey) {
|
|
1456
1740
|
e.multipleSelection = true;
|
|
@@ -1459,7 +1743,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1459
1743
|
this.processCurrentKeyFrame( e, null, track, localX, true );
|
|
1460
1744
|
}
|
|
1461
1745
|
// Box selection
|
|
1462
|
-
else if(this.
|
|
1746
|
+
else if(this.boxSelectionEnd) {
|
|
1463
1747
|
|
|
1464
1748
|
this.unSelectAllKeyFrames();
|
|
1465
1749
|
|
|
@@ -1478,7 +1762,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1478
1762
|
}
|
|
1479
1763
|
}
|
|
1480
1764
|
|
|
1481
|
-
}
|
|
1765
|
+
}
|
|
1766
|
+
else {
|
|
1482
1767
|
let boundingBox = this.canvas.getBoundingClientRect();
|
|
1483
1768
|
if(e.y < boundingBox.top || e.y > boundingBox.bottom) {
|
|
1484
1769
|
return;
|
|
@@ -1499,10 +1784,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1499
1784
|
this.boxSelectionEnd = null;
|
|
1500
1785
|
}
|
|
1501
1786
|
|
|
1502
|
-
onMouseDown( e
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1787
|
+
onMouseDown( e ) {
|
|
1788
|
+
const localX = e.localX;
|
|
1789
|
+
const localY = e.localY;
|
|
1790
|
+
const track = e.track;
|
|
1506
1791
|
|
|
1507
1792
|
if(e.shiftKey) {
|
|
1508
1793
|
|
|
@@ -1511,9 +1796,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1511
1796
|
e.multipleSelection = true;
|
|
1512
1797
|
}
|
|
1513
1798
|
else if(track && !track.locked) {
|
|
1514
|
-
const keyFrameIndex = this.
|
|
1799
|
+
const keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
1515
1800
|
if( keyFrameIndex != undefined ) {
|
|
1516
|
-
this.processCurrentKeyFrame( e, keyFrameIndex, track, null, e.multipleSelection ); // Settings this as multiple so time is not being set
|
|
1801
|
+
this.processCurrentKeyFrame( e, keyFrameIndex, track, null, this.lastKeyFramesSelected.length > 1 || e.multipleSelection ); // Settings this as multiple so time is not being set
|
|
1517
1802
|
if(e.ctrlKey ) {
|
|
1518
1803
|
|
|
1519
1804
|
this.movingKeys = true;
|
|
@@ -1530,82 +1815,104 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1530
1815
|
this.timeBeforeMove = track.times[ keyFrameIndex ];
|
|
1531
1816
|
}
|
|
1532
1817
|
}
|
|
1818
|
+
else if(!track) {
|
|
1819
|
+
this.unSelectAllKeyFrames()
|
|
1820
|
+
}
|
|
1533
1821
|
}
|
|
1534
1822
|
|
|
1535
|
-
onMouseMove( e
|
|
1823
|
+
onMouseMove( e ) {
|
|
1536
1824
|
|
|
1537
|
-
|
|
1538
|
-
let localY = e.localY;
|
|
1825
|
+
const localX = e.localX;
|
|
1539
1826
|
let track = e.track;
|
|
1540
1827
|
|
|
1541
|
-
const innerSetTime = (t) => {
|
|
1542
|
-
LX.emit( "@on_current_time_" + this.constructor.name, t);
|
|
1543
|
-
if( this.onSetTime )
|
|
1544
|
-
this.onSetTime( t );
|
|
1545
|
-
}
|
|
1546
1828
|
// Manage keyframe movement
|
|
1547
1829
|
if(this.movingKeys) {
|
|
1548
1830
|
|
|
1549
|
-
this.clearState();
|
|
1831
|
+
//this.clearState();
|
|
1550
1832
|
const newTime = this.xToTime( localX );
|
|
1551
1833
|
|
|
1834
|
+
// Update times of the selected frames
|
|
1552
1835
|
for(let [name, idx, keyIndex, keyTime] of this.lastKeyFramesSelected) {
|
|
1553
1836
|
track = this.tracksPerItem[name][idx];
|
|
1554
1837
|
if(track && track.locked)
|
|
1555
|
-
|
|
1838
|
+
continue;
|
|
1556
1839
|
|
|
1557
1840
|
this.canvas.style.cursor = "grabbing";
|
|
1558
1841
|
|
|
1559
1842
|
const delta = this.timeBeforeMove - keyTime;
|
|
1560
1843
|
this.animationClip.tracks[ track.clipIdx ].times[ keyIndex ] = Math.min( this.animationClip.duration, Math.max(0, newTime - delta) );
|
|
1561
1844
|
}
|
|
1845
|
+
|
|
1846
|
+
// Reorder track keyframes if necessary
|
|
1847
|
+
for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
|
|
1848
|
+
let s = this.lastKeyFramesSelected[i]; // pointer
|
|
1849
|
+
const name = s[0], localTrackIdx = s[1], keyTime = s[3];
|
|
1850
|
+
track = this.tracksPerItem[name][localTrackIdx];
|
|
1851
|
+
|
|
1852
|
+
if(track && track.locked) {
|
|
1853
|
+
continue;
|
|
1854
|
+
}
|
|
1562
1855
|
|
|
1856
|
+
let times = track.times;
|
|
1857
|
+
let k = s[2];
|
|
1858
|
+
for( ; k > 0; --k ){
|
|
1859
|
+
if ( times[k-1] < times[k] ){
|
|
1860
|
+
break;
|
|
1861
|
+
}
|
|
1862
|
+
this.swapKeyFrames(track, k-1, k);
|
|
1863
|
+
}
|
|
1864
|
+
for( ; k < track.times.length-1; ++k ){
|
|
1865
|
+
if ( times[k+1] > times[k] ){
|
|
1866
|
+
break;
|
|
1867
|
+
}
|
|
1868
|
+
this.swapKeyFrames(track, k+1, k);
|
|
1869
|
+
}
|
|
1870
|
+
s[2] = k; // "s" is a pointer. Modify selected keyFrame index
|
|
1871
|
+
}
|
|
1563
1872
|
return;
|
|
1564
1873
|
}
|
|
1565
1874
|
|
|
1566
|
-
const removeHover = () => {
|
|
1567
|
-
if(this.lastHovered)
|
|
1568
|
-
this.tracksPerItem[ this.lastHovered[0] ][ this.lastHovered[1] ].hovered[ this.lastHovered[2] ] = undefined;
|
|
1569
|
-
};
|
|
1570
|
-
|
|
1571
1875
|
if( this.grabbing && e.button != 2) {
|
|
1572
|
-
|
|
1573
1876
|
// fix this
|
|
1574
|
-
if(e.
|
|
1575
|
-
|
|
1877
|
+
if(e.ctrlKey && track) {
|
|
1576
1878
|
let keyFrameIndex = this.getNearestKeyFrame( track, this.currentTime);
|
|
1577
|
-
|
|
1879
|
+
|
|
1578
1880
|
if(keyFrameIndex != this.snappedKeyFrameIndex){
|
|
1579
1881
|
this.snappedKeyFrameIndex = keyFrameIndex;
|
|
1580
1882
|
this.currentTime = track.times[ keyFrameIndex ];
|
|
1581
|
-
|
|
1883
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
1582
1884
|
}
|
|
1583
1885
|
}
|
|
1584
|
-
else{
|
|
1585
|
-
|
|
1886
|
+
else {
|
|
1887
|
+
|
|
1888
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
1889
|
+
|
|
1586
1890
|
}
|
|
1587
|
-
|
|
1588
1891
|
}
|
|
1892
|
+
|
|
1893
|
+
|
|
1894
|
+
// Check if a keyframe is hovered
|
|
1589
1895
|
else if(track) {
|
|
1590
1896
|
|
|
1591
|
-
|
|
1897
|
+
const keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
1592
1898
|
if(keyFrameIndex != undefined) {
|
|
1593
1899
|
|
|
1594
1900
|
const name = this.tracksDictionary[track.fullname];
|
|
1595
1901
|
let t = this.tracksPerItem[ name ][track.idx];
|
|
1596
1902
|
if(t && t.locked)
|
|
1597
1903
|
return;
|
|
1598
|
-
|
|
1904
|
+
this.unHoverAll();
|
|
1599
1905
|
|
|
1600
1906
|
this.lastHovered = [name, track.idx, keyFrameIndex];
|
|
1601
1907
|
t.hovered[keyFrameIndex] = true;
|
|
1602
1908
|
|
|
1603
|
-
}
|
|
1604
|
-
|
|
1909
|
+
}
|
|
1910
|
+
else {
|
|
1911
|
+
this.unHoverAll();
|
|
1605
1912
|
}
|
|
1606
1913
|
}
|
|
1607
1914
|
else {
|
|
1608
|
-
|
|
1915
|
+
this.unHoverAll();
|
|
1609
1916
|
}
|
|
1610
1917
|
}
|
|
1611
1918
|
|
|
@@ -1615,7 +1922,6 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1615
1922
|
e.stopPropagation();
|
|
1616
1923
|
|
|
1617
1924
|
let actions = [];
|
|
1618
|
-
//let track = this.NMFtimeline.clip.tracks[0];
|
|
1619
1925
|
if(this.lastKeyFramesSelected && this.lastKeyFramesSelected.length) {
|
|
1620
1926
|
if(this.lastKeyFramesSelected.length == 1 && this.clipboard && this.clipboard.value)
|
|
1621
1927
|
{
|
|
@@ -1632,8 +1938,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1632
1938
|
{
|
|
1633
1939
|
title: "Copy",// + " <i class='bi bi-clipboard-fill float-right'></i>",
|
|
1634
1940
|
callback: () => {
|
|
1635
|
-
this.copyContent();
|
|
1636
|
-
|
|
1941
|
+
this.copyContent(); // copy value and keyframes selected
|
|
1637
1942
|
}
|
|
1638
1943
|
}
|
|
1639
1944
|
)
|
|
@@ -1641,7 +1946,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1641
1946
|
{
|
|
1642
1947
|
title: "Delete",// + " <i class='bi bi-trash float-right'></i>",
|
|
1643
1948
|
callback: () => {
|
|
1644
|
-
this.
|
|
1949
|
+
this.deleteContent({}); // TODO multipleSelection
|
|
1645
1950
|
}
|
|
1646
1951
|
}
|
|
1647
1952
|
)
|
|
@@ -1677,30 +1982,34 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1677
1982
|
|
|
1678
1983
|
}
|
|
1679
1984
|
|
|
1680
|
-
|
|
1985
|
+
drawContent( ctx ) {
|
|
1681
1986
|
|
|
1682
|
-
if(this.selectedItems == null || !this.tracksPerItem)
|
|
1987
|
+
if(this.selectedItems == null || !this.tracksPerItem) {
|
|
1683
1988
|
return;
|
|
1989
|
+
}
|
|
1684
1990
|
|
|
1685
1991
|
ctx.save();
|
|
1686
1992
|
this.scrollableHeight = this.topMargin;
|
|
1687
|
-
|
|
1993
|
+
const height = this.trackHeight;
|
|
1688
1994
|
let offset = this.trackHeight;
|
|
1995
|
+
let scroll_y = - this.currentScrollInPixels;
|
|
1996
|
+
this.tracksDrawn = [];
|
|
1997
|
+
|
|
1689
1998
|
for(let t = 0; t < this.selectedItems.length; t++) {
|
|
1690
1999
|
let tracks = this.tracksPerItem[this.selectedItems[t]] ? this.tracksPerItem[this.selectedItems[t]] : [{name: this.selectedItems[t]}];
|
|
1691
|
-
if(!tracks)
|
|
2000
|
+
if(!tracks) {
|
|
2001
|
+
continue;
|
|
2002
|
+
}
|
|
1692
2003
|
|
|
1693
|
-
|
|
1694
|
-
this.scrollableHeight += (tracks.length+1)*height;
|
|
1695
|
-
let scroll_y = - this.currentScrollInPixels;
|
|
2004
|
+
this.scrollableHeight += (tracks.length + 1) * height;
|
|
1696
2005
|
|
|
1697
2006
|
let offsetI = 0;
|
|
1698
2007
|
for(let i = 0; i < tracks.length; i++) {
|
|
1699
|
-
|
|
2008
|
+
const track = tracks[i];
|
|
1700
2009
|
if(track.hide) {
|
|
1701
2010
|
continue;
|
|
1702
2011
|
}
|
|
1703
|
-
this
|
|
2012
|
+
this.#drawTrackWithKeyframes(ctx, offsetI * height + offset + scroll_y, height, this.animationClip.tracks[track.clipIdx], track);
|
|
1704
2013
|
offsetI++;
|
|
1705
2014
|
}
|
|
1706
2015
|
offset += offsetI * height + height;
|
|
@@ -1709,158 +2018,91 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1709
2018
|
ctx.restore();
|
|
1710
2019
|
};
|
|
1711
2020
|
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
if(!tracks) continue;
|
|
1720
|
-
|
|
1721
|
-
// Get current track
|
|
1722
|
-
const selectedTrackIdx = tracks.findIndex( t => t.type === keyType );
|
|
1723
|
-
if(selectedTrackIdx < 0)
|
|
1724
|
-
return;
|
|
1725
|
-
let track = tracks[ selectedTrackIdx ];
|
|
1726
|
-
|
|
1727
|
-
// Add new keyframe
|
|
1728
|
-
const newIdx = this.addKeyFrame( track );
|
|
1729
|
-
if(newIdx === null)
|
|
1730
|
-
continue;
|
|
1731
|
-
|
|
1732
|
-
// Select it
|
|
1733
|
-
this.lastKeyFramesSelected.push( [track.name, track.idx, newIdx] );
|
|
1734
|
-
track.selected[newIdx] = true;
|
|
2021
|
+
/**
|
|
2022
|
+
* @method drawTrackWithKeyframes
|
|
2023
|
+
* @param {*} ctx
|
|
2024
|
+
* ...
|
|
2025
|
+
* @description helper function, you can call it from drawContent to render all the keyframes
|
|
2026
|
+
* TODO
|
|
2027
|
+
*/
|
|
1735
2028
|
|
|
2029
|
+
#drawTrackWithKeyframes( ctx, y, trackHeight, track, trackInfo ) {
|
|
2030
|
+
|
|
2031
|
+
if(trackInfo.enabled === false) {
|
|
2032
|
+
ctx.globalAlpha = 0.4;
|
|
1736
2033
|
}
|
|
1737
|
-
//LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
|
|
1738
|
-
// Update time
|
|
1739
|
-
if(this.onSetTime)
|
|
1740
|
-
this.onSetTime(this.currentTime);
|
|
1741
|
-
|
|
1742
|
-
return true; // Handled
|
|
1743
|
-
}
|
|
1744
|
-
|
|
1745
|
-
// Creates a map for each item -> tracks
|
|
1746
|
-
processTracks(animation) {
|
|
1747
2034
|
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
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
|
-
}
|
|
2035
|
+
ctx.font = Math.floor( trackHeight * 0.8) + "px" + Timeline.FONT;
|
|
2036
|
+
ctx.textAlign = "left";
|
|
2037
|
+
|
|
2038
|
+
ctx.globalAlpha = 0.2;
|
|
2039
|
+
|
|
2040
|
+
if(trackInfo.isSelected) {
|
|
2041
|
+
ctx.fillStyle = Timeline.TRACK_SELECTED;//"#2c303570";
|
|
2042
|
+
ctx.fillRect(0, y, ctx.canvas.width, trackHeight );
|
|
1789
2043
|
}
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
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);
|
|
1799
|
-
|
|
1800
|
-
}
|
|
2044
|
+
ctx.fillStyle = Timeline.COLOR;//"#2c303570";
|
|
2045
|
+
ctx.globalAlpha = 1;
|
|
2046
|
+
|
|
2047
|
+
let keyframes = track.times;
|
|
1801
2048
|
|
|
1802
|
-
|
|
1803
|
-
if(!this.animationClip)
|
|
2049
|
+
if(!keyframes) {
|
|
1804
2050
|
return;
|
|
1805
|
-
|
|
1806
|
-
let track = this.animationClip.tracks[trackIdx];
|
|
1807
|
-
|
|
1808
|
-
const [name, type] = this.getTrackName(track.fullname || track.name);
|
|
1809
|
-
|
|
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
|
-
};
|
|
2051
|
+
}
|
|
1818
2052
|
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1823
|
-
|
|
1824
|
-
|
|
2053
|
+
this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
|
|
2054
|
+
|
|
2055
|
+
for(let j = 0; j < keyframes.length; ++j)
|
|
2056
|
+
{
|
|
2057
|
+
let time = keyframes[j];
|
|
2058
|
+
let selected = trackInfo.selected[j];
|
|
2059
|
+
if( time < this.startTime || time > this.endTime ) {
|
|
2060
|
+
continue;
|
|
1825
2061
|
}
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
optimizeTrack(trackIdx) {
|
|
1830
|
-
const track = this.animationClip.tracks[trackIdx];
|
|
1831
|
-
if(track.optimize) {
|
|
1832
|
-
|
|
1833
|
-
track.optimize( this.optimizeThreshold );
|
|
1834
|
-
if(this.onOptimizeTracks)
|
|
1835
|
-
this.onOptimizeTracks(trackIdx);
|
|
1836
|
-
}
|
|
1837
|
-
}
|
|
1838
|
-
|
|
1839
|
-
optimizeTracks() {
|
|
2062
|
+
const keyframePosX = this.timeToX( time );
|
|
2063
|
+
ctx.save();
|
|
1840
2064
|
|
|
1841
|
-
|
|
1842
|
-
|
|
2065
|
+
let margin = -1;
|
|
2066
|
+
let size = trackHeight * 0.3;
|
|
2067
|
+
|
|
2068
|
+
if(trackInfo.edited[j]) {
|
|
2069
|
+
ctx.fillStyle = Timeline.COLOR_EDITED;
|
|
2070
|
+
}
|
|
2071
|
+
|
|
2072
|
+
if(selected) {
|
|
2073
|
+
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
2074
|
+
ctx.shadowBlur = 8;
|
|
2075
|
+
size = trackHeight * 0.35;
|
|
2076
|
+
margin = 0;
|
|
2077
|
+
}
|
|
1843
2078
|
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
2079
|
+
if(trackInfo.hovered[j]) {
|
|
2080
|
+
size = trackHeight * 0.35;
|
|
2081
|
+
ctx.fillStyle = Timeline.COLOR_HOVERED;
|
|
2082
|
+
ctx.shadowBlur = 8;
|
|
2083
|
+
margin = 0;
|
|
2084
|
+
}
|
|
2085
|
+
if(trackInfo.locked) {
|
|
2086
|
+
ctx.fillStyle = Timeline.COLOR_LOCK;
|
|
2087
|
+
}
|
|
1847
2088
|
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
this.onOptimizeTracks(i);
|
|
2089
|
+
if(!this.active || trackInfo.active == false) {
|
|
2090
|
+
ctx.fillStyle = Timeline.COLOR_UNACTIVE;
|
|
1851
2091
|
}
|
|
2092
|
+
|
|
2093
|
+
ctx.translate(keyframePosX, y + this.trackHeight * 0.75 + margin);
|
|
2094
|
+
ctx.rotate(45 * Math.PI / 180);
|
|
2095
|
+
ctx.fillRect( -size, -size, size, size);
|
|
2096
|
+
if(selected) {
|
|
2097
|
+
ctx.globalAlpha = 0.3;
|
|
2098
|
+
ctx.fillRect( -size*1.1, -size*1.1, size*1.2, size*1.2);
|
|
2099
|
+
}
|
|
2100
|
+
ctx.shadowBlur = 0;
|
|
2101
|
+
ctx.restore();
|
|
1852
2102
|
}
|
|
2103
|
+
ctx.globalAlpha = this.opacity;
|
|
1853
2104
|
}
|
|
1854
2105
|
|
|
1855
|
-
|
|
1856
|
-
getNumTracks( item ) {
|
|
1857
|
-
if(!item || !this.tracksPerItem)
|
|
1858
|
-
return;
|
|
1859
|
-
const tracks = this.tracksPerItem[item.name];
|
|
1860
|
-
return tracks ? tracks.length : null;
|
|
1861
|
-
}
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
2106
|
onShowOptimizeMenu( e ) {
|
|
1865
2107
|
|
|
1866
2108
|
if(this.selectedItems == null)
|
|
@@ -1889,28 +2131,19 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1889
2131
|
});
|
|
1890
2132
|
}
|
|
1891
2133
|
|
|
1892
|
-
onPreProcessTrack( track ) {
|
|
1893
|
-
const name = this.tracksDictionary[track.fullname];
|
|
1894
|
-
let trackInfo = this.tracksPerItem[name][track.idx];
|
|
1895
|
-
trackInfo.selected = [];
|
|
1896
|
-
trackInfo.edited = [];
|
|
1897
|
-
trackInfo.hovered = [];
|
|
1898
|
-
}
|
|
1899
|
-
|
|
1900
2134
|
isKeyFrameSelected( track, index ) {
|
|
1901
2135
|
return track.selected[ index ];
|
|
1902
2136
|
}
|
|
1903
2137
|
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
const trackInfo = this.
|
|
1909
|
-
|
|
2138
|
+
/**
|
|
2139
|
+
* @param {Number} trackIdx index of track in the animation (not local index)
|
|
2140
|
+
*/
|
|
2141
|
+
saveState( trackIdx ) {
|
|
2142
|
+
const trackInfo = this.animationClip.tracks[trackIdx];
|
|
1910
2143
|
this.trackState.push({
|
|
1911
|
-
idx:
|
|
1912
|
-
t:
|
|
1913
|
-
v:
|
|
2144
|
+
idx: trackIdx,
|
|
2145
|
+
t: trackInfo.times.slice(),
|
|
2146
|
+
v: trackInfo.values.slice(),
|
|
1914
2147
|
editedTracks: [].concat(trackInfo.edited)
|
|
1915
2148
|
});
|
|
1916
2149
|
}
|
|
@@ -1925,7 +2158,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1925
2158
|
this.animationClip.tracks[state.idx].values = state.v;
|
|
1926
2159
|
|
|
1927
2160
|
const localIdx = this.animationClip.tracks[state.idx].idx;
|
|
1928
|
-
const name = this.
|
|
2161
|
+
const name = this.processTrackName(this.animationClip.tracks[state.idx].name)[0];
|
|
1929
2162
|
this.tracksPerItem[name][localIdx].edited = state.editedTracks;
|
|
1930
2163
|
|
|
1931
2164
|
// Update animation action interpolation info
|
|
@@ -1933,6 +2166,43 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1933
2166
|
this.onUpdateTrack( state.idx );
|
|
1934
2167
|
}
|
|
1935
2168
|
|
|
2169
|
+
/**
|
|
2170
|
+
*
|
|
2171
|
+
* @param {*} track
|
|
2172
|
+
* @param {Number} srcIdx keyFrame index
|
|
2173
|
+
* @param {Number} trgIdx keyFrame index
|
|
2174
|
+
*/
|
|
2175
|
+
swapKeyFrames(track, srcIdx, trgIdx){
|
|
2176
|
+
let times = track.times;
|
|
2177
|
+
let values = track.values;
|
|
2178
|
+
|
|
2179
|
+
let tmp = times[srcIdx];
|
|
2180
|
+
times[srcIdx] = times[trgIdx];
|
|
2181
|
+
times[trgIdx] = tmp;
|
|
2182
|
+
|
|
2183
|
+
tmp = track.hovered[srcIdx];
|
|
2184
|
+
track.hovered[srcIdx] = track.hovered[trgIdx];
|
|
2185
|
+
track.hovered[trgIdx] = tmp;
|
|
2186
|
+
|
|
2187
|
+
tmp = track.edited[srcIdx];
|
|
2188
|
+
track.edited[srcIdx] = track.edited[trgIdx];
|
|
2189
|
+
track.edited[trgIdx] = tmp;
|
|
2190
|
+
|
|
2191
|
+
tmp = track.selected[srcIdx];
|
|
2192
|
+
track.selected[srcIdx] = track.selected[trgIdx];
|
|
2193
|
+
track.selected[trgIdx] = tmp;
|
|
2194
|
+
|
|
2195
|
+
let src = srcIdx * track.dim;
|
|
2196
|
+
let end = src + track.dim;
|
|
2197
|
+
let trg = trgIdx * track.dim;
|
|
2198
|
+
for( ; src < end; ++src ){
|
|
2199
|
+
tmp = values[ src ];
|
|
2200
|
+
values[ src ] = values[ trg ];
|
|
2201
|
+
values[ trg ] = tmp;
|
|
2202
|
+
++trg;
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
|
|
1936
2206
|
selectKeyFrame( track, selectionInfo, index ) {
|
|
1937
2207
|
|
|
1938
2208
|
if(index == undefined || !track)
|
|
@@ -1945,39 +2215,43 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1945
2215
|
return;
|
|
1946
2216
|
track.selected[index] = true;
|
|
1947
2217
|
this.currentTime = this.animationClip.tracks[track.clipIdx].times[ index ];
|
|
1948
|
-
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
|
|
1949
|
-
if( this.onSetTime )
|
|
1950
|
-
this.onSetTime( this.currentTime);
|
|
2218
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
|
|
1951
2219
|
}
|
|
1952
2220
|
|
|
1953
2221
|
copyContent() {
|
|
2222
|
+
if (!this.lastKeyFramesSelected.length){
|
|
2223
|
+
return;
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
// sort keyframes selected by track
|
|
1954
2227
|
let toCopy = {};
|
|
1955
2228
|
for(let i = 0; i < this.lastKeyFramesSelected.length; i++){
|
|
1956
|
-
let [id,
|
|
1957
|
-
|
|
1958
|
-
|
|
2229
|
+
let [id, localTrackIdx, keyIdx] = this.lastKeyFramesSelected[i];
|
|
2230
|
+
let track = this.tracksPerItem[id][localTrackIdx];
|
|
2231
|
+
let trackIdx = track.clipIdx;
|
|
2232
|
+
|
|
2233
|
+
if(toCopy[trackIdx]) {
|
|
2234
|
+
toCopy[trackIdx].idxs.push(keyIdx);
|
|
1959
2235
|
} else {
|
|
1960
|
-
toCopy[
|
|
1961
|
-
|
|
1962
|
-
}
|
|
2236
|
+
toCopy[trackIdx] = {track: track, idxs : [keyIdx]};
|
|
2237
|
+
}
|
|
1963
2238
|
if(i == 0) {
|
|
1964
|
-
this.copyKeyFrameValue(
|
|
2239
|
+
this.copyKeyFrameValue(track, keyIdx);
|
|
1965
2240
|
}
|
|
1966
2241
|
}
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2242
|
+
|
|
2243
|
+
// for each track selected, copy its values
|
|
2244
|
+
for(let trackIdx in toCopy) {
|
|
2245
|
+
this.copyKeyFrames(toCopy[trackIdx].track, toCopy[trackIdx].idxs);
|
|
1970
2246
|
}
|
|
1971
2247
|
}
|
|
1972
2248
|
|
|
2249
|
+
// copies the current value of the keyframe. This value can be pasted across any track (as long as they are of the same type)
|
|
1973
2250
|
copyKeyFrameValue( track, index ) {
|
|
1974
2251
|
|
|
1975
2252
|
// 1 element clipboard by now
|
|
1976
|
-
|
|
1977
|
-
let values = [];
|
|
1978
2253
|
let start = index * track.dim;
|
|
1979
|
-
|
|
1980
|
-
values.push( this.animationClip.tracks[ track.clipIdx ].values[i] );
|
|
2254
|
+
let values = this.animationClip.tracks[ track.clipIdx ].values.slice(start, start + track.dim);
|
|
1981
2255
|
|
|
1982
2256
|
if(!this.clipboard)
|
|
1983
2257
|
this.clipboard = {};
|
|
@@ -1988,45 +2262,49 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1988
2262
|
};
|
|
1989
2263
|
}
|
|
1990
2264
|
|
|
2265
|
+
// each track will have its own entry of copied keyframes. When pasting, only the apropiate track's keyframes are pasted
|
|
1991
2266
|
copyKeyFrames( track, indices ) {
|
|
1992
2267
|
|
|
1993
|
-
let
|
|
2268
|
+
let trackIdx = track.clipIdx;
|
|
1994
2269
|
if(!this.clipboard)
|
|
1995
2270
|
this.clipboard = {};
|
|
1996
2271
|
if(!this.clipboard.keyframes) {
|
|
1997
2272
|
this.clipboard.keyframes = {};
|
|
1998
2273
|
}
|
|
1999
|
-
|
|
2000
|
-
this.clipboard.keyframes[
|
|
2274
|
+
|
|
2275
|
+
this.clipboard.keyframes[trackIdx] = { track: track, values:{}, times:{} };
|
|
2276
|
+
|
|
2001
2277
|
// 1 element clipboard by now
|
|
2002
|
-
for(let
|
|
2003
|
-
let keyIdx = indices[
|
|
2004
|
-
let values = [];
|
|
2278
|
+
for(let i = 0; i < indices.length; i++ ){
|
|
2279
|
+
let keyIdx = indices[i];
|
|
2005
2280
|
let start = keyIdx * track.dim;
|
|
2006
|
-
|
|
2007
|
-
|
|
2008
|
-
|
|
2009
|
-
this.clipboard.keyframes[clipIdx].values[indices[idx]] = values;
|
|
2281
|
+
let keyValues = track.values.slice(start, start + track.dim); // copy values into a new array
|
|
2282
|
+
this.clipboard.keyframes[trackIdx].values[keyIdx] = keyValues; // save to clipboard
|
|
2283
|
+
this.clipboard.keyframes[trackIdx].times[keyIdx] = track.times[keyIdx]; // save to clipboard
|
|
2010
2284
|
};
|
|
2011
2285
|
}
|
|
2012
2286
|
|
|
2013
2287
|
pasteContent() {
|
|
2014
|
-
if(!this.clipboard)
|
|
2288
|
+
if(!this.clipboard) {
|
|
2015
2289
|
return;
|
|
2290
|
+
}
|
|
2016
2291
|
|
|
2292
|
+
// copy the value into the only selected keyframe
|
|
2017
2293
|
if(this.clipboard.value && this.lastKeyFramesSelected.length == 1) {
|
|
2018
2294
|
|
|
2019
|
-
let [id,
|
|
2020
|
-
this.pasteKeyFrameValue({}, this.tracksPerItem[id][
|
|
2295
|
+
let [id, localTrackIdx, keyIdx] = this.lastKeyFramesSelected[0];
|
|
2296
|
+
this.pasteKeyFrameValue({}, this.tracksPerItem[id][localTrackIdx], keyIdx);
|
|
2021
2297
|
}
|
|
2298
|
+
|
|
2299
|
+
// create new keyframes from the ones copied
|
|
2022
2300
|
if(this.clipboard.keyframes) {
|
|
2023
2301
|
let currentTime = this.currentTime;
|
|
2024
|
-
for(let
|
|
2025
|
-
|
|
2026
|
-
this.pasteKeyFrames({multipleSelection: this.clipboard.keyframes.length}, clipIdx, indices);
|
|
2302
|
+
for(let trackIdx in this.clipboard.keyframes) {
|
|
2303
|
+
this.pasteKeyFrames({multipleSelection: this.clipboard.keyframes.length}, trackIdx);
|
|
2027
2304
|
this.currentTime = currentTime;
|
|
2028
2305
|
}
|
|
2029
2306
|
}
|
|
2307
|
+
this.clipboard = {};
|
|
2030
2308
|
}
|
|
2031
2309
|
|
|
2032
2310
|
canPasteKeyFrame () {
|
|
@@ -2047,9 +2325,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2047
2325
|
this.animationClip.tracks[ track.clipIdx ].values[i] = clipboardInfo.values[j];
|
|
2048
2326
|
++j;
|
|
2049
2327
|
}
|
|
2050
|
-
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
2051
|
-
if(this.onSetTime)
|
|
2052
|
-
this.onSetTime(this.currentTime);
|
|
2328
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
2053
2329
|
|
|
2054
2330
|
track.edited[ index ] = true;
|
|
2055
2331
|
}
|
|
@@ -2061,53 +2337,91 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2061
2337
|
// Copy to current key
|
|
2062
2338
|
this.#paste( track, index );
|
|
2063
2339
|
|
|
2064
|
-
if(!e.multipleSelection)
|
|
2065
|
-
|
|
2340
|
+
if(!e || !e.multipleSelection){
|
|
2341
|
+
return;
|
|
2342
|
+
}
|
|
2066
2343
|
|
|
2067
2344
|
// Don't want anything after this
|
|
2068
2345
|
this.clearState();
|
|
2069
2346
|
|
|
2070
2347
|
// Copy to every selected key
|
|
2071
|
-
for(let [name,
|
|
2072
|
-
this.#paste( this.tracksPerItem[name][
|
|
2348
|
+
for(let [name, localTrackIdx, keyIndex] of this.lastKeyFramesSelected) {
|
|
2349
|
+
this.#paste( this.tracksPerItem[name][localTrackIdx], keyIndex );
|
|
2073
2350
|
}
|
|
2074
2351
|
}
|
|
2075
2352
|
|
|
2076
|
-
pasteKeyFrames( e,
|
|
2353
|
+
pasteKeyFrames( e, trackIdx ) {
|
|
2077
2354
|
|
|
2078
|
-
this.
|
|
2355
|
+
if ( !this.clipboard.keyframes[trackIdx] ){
|
|
2356
|
+
return;
|
|
2357
|
+
}
|
|
2358
|
+
this.saveState(trackIdx);
|
|
2359
|
+
|
|
2360
|
+
let clipboardInfo = this.clipboard.keyframes[trackIdx];
|
|
2361
|
+
let indices = Object.keys(clipboardInfo.values);
|
|
2362
|
+
indices.sort(); // just in case
|
|
2079
2363
|
|
|
2080
2364
|
// Copy to current key
|
|
2081
2365
|
for(let i = 0; i < indices.length; i++) {
|
|
2082
|
-
let value =
|
|
2366
|
+
let value = clipboardInfo.values[indices[i]];
|
|
2083
2367
|
if(typeof value == 'number')
|
|
2084
2368
|
value = [value];
|
|
2085
2369
|
if(i > 0) {
|
|
2086
|
-
let delta =
|
|
2370
|
+
let delta = clipboardInfo.times[indices[i]] - clipboardInfo.times[indices[i-1]];
|
|
2087
2371
|
this.currentTime += delta;
|
|
2088
|
-
|
|
2089
2372
|
}
|
|
2090
|
-
this.addKeyFrame(
|
|
2373
|
+
this.addKeyFrame( clipboardInfo.track, value);
|
|
2091
2374
|
}
|
|
2092
2375
|
|
|
2093
|
-
if(!e.multipleSelection)
|
|
2094
|
-
return;
|
|
2376
|
+
// if(!e.multipleSelection)
|
|
2377
|
+
// return;
|
|
2095
2378
|
|
|
2096
|
-
// Don't want anything after this
|
|
2097
|
-
this.clearState();
|
|
2379
|
+
// // Don't want anything after this
|
|
2380
|
+
// this.clearState();
|
|
2098
2381
|
|
|
2099
|
-
// Copy to every selected key
|
|
2100
|
-
for(let [name, idx, keyIndex] of this.lastKeyFramesSelected) {
|
|
2101
|
-
|
|
2102
|
-
}
|
|
2382
|
+
// // Copy to every selected key
|
|
2383
|
+
// for(let [name, idx, keyIndex] of this.lastKeyFramesSelected) {
|
|
2384
|
+
// this.#paste( this.tracksPerItem[name][idx], keyIndex );
|
|
2385
|
+
// }
|
|
2103
2386
|
}
|
|
2104
2387
|
|
|
2388
|
+
/**
|
|
2389
|
+
* @method updateSelectedKeyframe
|
|
2390
|
+
* @description Updates the value of the current selected keyframe
|
|
2391
|
+
* @param {Number or Array} value is a number or an array depending of the dimensions of the value
|
|
2392
|
+
*/
|
|
2393
|
+
updateSelectedKeyframe(value) {
|
|
2394
|
+
if(value.constructor != Array) {
|
|
2395
|
+
value = [value];
|
|
2396
|
+
}
|
|
2397
|
+
if(!this.animationClip || !this.lastKeyFramesSelected.length || this.lastKeyFramesSelected.length > 1) {
|
|
2398
|
+
return;
|
|
2399
|
+
}
|
|
2400
|
+
const trackIdx = this.lastKeyFramesSelected[0][1];
|
|
2401
|
+
const keyframe = this.lastKeyFramesSelected[0][2];
|
|
2402
|
+
const dim = this.animationClip.tracks[trackIdx].dim;
|
|
2403
|
+
|
|
2404
|
+
if(dim != value.length) {
|
|
2405
|
+
return;
|
|
2406
|
+
}
|
|
2407
|
+
|
|
2408
|
+
for(let i = 0; i < dim; i++) {
|
|
2409
|
+
this.animationClip.tracks[trackIdx].values[keyframe*dim + i] = value[i];
|
|
2410
|
+
}
|
|
2411
|
+
|
|
2412
|
+
this.processTrack(trackIdx);
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2105
2415
|
addKeyFrame( track, value = undefined, time = this.currentTime ) {
|
|
2106
2416
|
|
|
2417
|
+
if(!track) {
|
|
2418
|
+
return;
|
|
2419
|
+
}
|
|
2420
|
+
|
|
2107
2421
|
// Update animationClip information
|
|
2108
2422
|
let clipIdx = track.clipIdx;
|
|
2109
2423
|
|
|
2110
|
-
let [name, keyType] = this.
|
|
2424
|
+
let [name, keyType] = this.processTrackName(track.name)
|
|
2111
2425
|
let tracks = this.tracksPerItem[name];
|
|
2112
2426
|
if(!tracks) return;
|
|
2113
2427
|
|
|
@@ -2177,79 +2491,73 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2177
2491
|
}
|
|
2178
2492
|
|
|
2179
2493
|
// Reset this key's properties
|
|
2180
|
-
track.hovered[newIdx] =
|
|
2181
|
-
track.selected[newIdx] =
|
|
2494
|
+
track.hovered[newIdx] = false;
|
|
2495
|
+
track.selected[newIdx] = false;
|
|
2182
2496
|
track.edited[newIdx] = true;
|
|
2183
2497
|
|
|
2184
|
-
|
|
2185
2498
|
// Update animation action interpolation info
|
|
2186
2499
|
if(this.onUpdateTrack)
|
|
2187
2500
|
this.onUpdateTrack( clipIdx );
|
|
2188
2501
|
|
|
2189
2502
|
LX.emit( "@on_current_time_" + this.constructor.name, time);
|
|
2190
|
-
|
|
2191
|
-
this.onSetTime(time);
|
|
2192
|
-
this.draw();
|
|
2503
|
+
|
|
2193
2504
|
return newIdx;
|
|
2194
2505
|
}
|
|
2195
2506
|
|
|
2196
2507
|
deleteContent() {
|
|
2197
|
-
|
|
2198
2508
|
this.deleteKeyFrame({ multipleSelection: this.lastKeyFramesSelected.length > 1});
|
|
2199
2509
|
}
|
|
2200
2510
|
|
|
2201
|
-
/**
|
|
2202
|
-
* @
|
|
2203
|
-
|
|
2511
|
+
/**
|
|
2512
|
+
* @method #delete
|
|
2513
|
+
* @description Delete a keyframe given the track and the its index
|
|
2514
|
+
* @param {Number} trackIdx track that keyframe belongs to
|
|
2515
|
+
* @param {Number} index index of the keyframe on the track
|
|
2516
|
+
* @returns
|
|
2204
2517
|
*/
|
|
2205
|
-
#delete(
|
|
2518
|
+
#delete( trackIdx, index ) {
|
|
2519
|
+
|
|
2520
|
+
const track = this.animationClip.tracks[trackIdx];
|
|
2206
2521
|
|
|
2207
|
-
// Don't remove by now the first key
|
|
2208
|
-
if(index
|
|
2209
|
-
console.warn("Operation not supported! [
|
|
2210
|
-
return
|
|
2522
|
+
// Don't remove by now the first key (and avoid impossible indices)
|
|
2523
|
+
if(index < 1 || index >= track.times.length ) {
|
|
2524
|
+
console.warn("Operation not supported! " + (index==0 ?"[removing first keyframe track]":"[removing invalid keyframe " + i + " from " + track.times.length + "]"));
|
|
2525
|
+
return false;
|
|
2211
2526
|
}
|
|
2212
2527
|
|
|
2213
|
-
//
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
// console.warn("Operation not supported! [remove last keyframe track]");
|
|
2219
|
-
// return;
|
|
2220
|
-
// }
|
|
2221
|
-
|
|
2222
|
-
// Reset this key's properties
|
|
2223
|
-
track.hovered[index] = undefined;
|
|
2224
|
-
track.selected[index] = undefined;
|
|
2225
|
-
track.edited[index] = undefined;
|
|
2226
|
-
|
|
2227
|
-
// Delete time key
|
|
2228
|
-
this.animationClip.tracks[clipIdx].times = this.animationClip.tracks[clipIdx].times.filter( (v, i) => i != index);
|
|
2528
|
+
// Delete time key (TypedArrays do not have splice )
|
|
2529
|
+
track.times = track.times.filter( (v, i) => i != index);
|
|
2530
|
+
track.edited = track.edited.filter( (v, i) => i != index);
|
|
2531
|
+
track.selected = track.selected.filter( (v, i) => i != index);
|
|
2532
|
+
track.hovered = track.hovered.filter( (v, i) => i != index);
|
|
2229
2533
|
|
|
2230
2534
|
// Delete values
|
|
2231
2535
|
const indexDim = track.dim * index;
|
|
2232
|
-
const slice1 =
|
|
2233
|
-
const slice2 =
|
|
2536
|
+
const slice1 = track.values.slice(0, indexDim);
|
|
2537
|
+
const slice2 = track.values.slice(indexDim + track.dim);
|
|
2234
2538
|
|
|
2235
|
-
|
|
2539
|
+
track.values = LX.UTILS.concatTypedArray([slice1, slice2], Float32Array);
|
|
2236
2540
|
|
|
2237
|
-
// Move the other's key properties
|
|
2238
|
-
for(let i = index; i <
|
|
2239
|
-
|
|
2240
|
-
|
|
2541
|
+
// // Move the other's key properties
|
|
2542
|
+
// for(let i = index; i < track.times.length; ++i) {
|
|
2543
|
+
// track.edited[i] = track.edited[i + 1];
|
|
2544
|
+
// track.hovered[i] = track.hovered[i + 1];
|
|
2545
|
+
// track.selected[i] = track.selected[i + 1];
|
|
2546
|
+
// }
|
|
2241
2547
|
|
|
2242
2548
|
// Update animation action interpolation info
|
|
2243
2549
|
if(this.onDeleteKeyFrame)
|
|
2244
|
-
this.onDeleteKeyFrame(
|
|
2550
|
+
this.onDeleteKeyFrame( trackIdx, index );
|
|
2245
2551
|
|
|
2246
|
-
return
|
|
2552
|
+
return true;
|
|
2247
2553
|
}
|
|
2248
2554
|
|
|
2249
|
-
/**
|
|
2250
|
-
* @
|
|
2251
|
-
* @
|
|
2252
|
-
* @
|
|
2555
|
+
/**
|
|
2556
|
+
* @method deleteKeyFrame
|
|
2557
|
+
* @description Delete one or more keyframes given the triggered event
|
|
2558
|
+
* @param {Event} e: event
|
|
2559
|
+
* @param {Object} track:
|
|
2560
|
+
* @param {Number} index: index of the keyframe on the track
|
|
2253
2561
|
*/
|
|
2254
2562
|
deleteKeyFrame(e, track, index) {
|
|
2255
2563
|
|
|
@@ -2263,14 +2571,13 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2263
2571
|
|
|
2264
2572
|
if(!pts) continue;
|
|
2265
2573
|
|
|
2266
|
-
pts = pts.sort( (a,b) =>
|
|
2267
|
-
|
|
2268
|
-
let deletedIndices = 0;
|
|
2574
|
+
pts = pts.sort( (a,b) => b[2] - a[2] ); // sort by keyframe index (descending)
|
|
2269
2575
|
|
|
2270
|
-
// Delete every selected key
|
|
2271
|
-
for(let [name,
|
|
2272
|
-
this.
|
|
2273
|
-
|
|
2576
|
+
// Delete every selected key starting with the last one in the track
|
|
2577
|
+
for(let [name, localIdx, keyIndex] of pts) {
|
|
2578
|
+
let track = this.tracksPerItem[name][localIdx];
|
|
2579
|
+
this.saveState(track.clipIdx);
|
|
2580
|
+
this.#delete(track.clipIdx, keyIndex);
|
|
2274
2581
|
}
|
|
2275
2582
|
}
|
|
2276
2583
|
}
|
|
@@ -2278,25 +2585,20 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2278
2585
|
|
|
2279
2586
|
// Key pressed
|
|
2280
2587
|
if(!track && this.lastKeyFramesSelected.length > 0) {
|
|
2281
|
-
const [itemName,
|
|
2282
|
-
track = this.tracksPerItem[itemName][
|
|
2588
|
+
const [itemName, localTrackIndex, keyIndex] = this.lastKeyFramesSelected[0];
|
|
2589
|
+
track = this.tracksPerItem[itemName][localTrackIndex];
|
|
2283
2590
|
index = keyIndex;
|
|
2284
2591
|
}
|
|
2285
2592
|
|
|
2286
2593
|
if ( track ){
|
|
2287
2594
|
this.saveState(track.clipIdx);
|
|
2288
|
-
this.#delete(
|
|
2595
|
+
this.#delete(track.clipIdx, index);
|
|
2289
2596
|
}
|
|
2290
2597
|
}
|
|
2291
2598
|
|
|
2292
2599
|
this.unSelectAllKeyFrames();
|
|
2293
2600
|
}
|
|
2294
2601
|
|
|
2295
|
-
getNumKeyFramesSelected() {
|
|
2296
|
-
return this.lastKeyFramesSelected.length;
|
|
2297
|
-
}
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
2602
|
unSelect() {
|
|
2301
2603
|
|
|
2302
2604
|
if(!this.unSelectAllKeyFrames()) {
|
|
@@ -2306,98 +2608,15 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2306
2608
|
}
|
|
2307
2609
|
}
|
|
2308
2610
|
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
if(itemsName.constructor !== Array)
|
|
2312
|
-
throw("Item name has to be an array!");
|
|
2313
|
-
|
|
2314
|
-
this.selectedItems = itemsName;
|
|
2315
|
-
this.unSelectAllKeyFrames();
|
|
2316
|
-
this.updateLeftPanel();
|
|
2317
|
-
this.resize();
|
|
2318
|
-
}
|
|
2319
|
-
|
|
2320
|
-
getTrack( trackInfo ) {
|
|
2321
|
-
const [name, trackIndex] = trackInfo;
|
|
2322
|
-
return this.tracksPerItem[ name ][trackIndex];
|
|
2323
|
-
}
|
|
2324
|
-
|
|
2325
|
-
getTrackName( uglyName ) {
|
|
2326
|
-
|
|
2327
|
-
let name, type;
|
|
2328
|
-
|
|
2329
|
-
// Support other versions
|
|
2330
|
-
if(uglyName.includes("[")) {
|
|
2331
|
-
const nameIndex = uglyName.indexOf('['),
|
|
2332
|
-
trackNameInfo = uglyName.substr(nameIndex+1).split("].");
|
|
2333
|
-
name = trackNameInfo[0];
|
|
2334
|
-
type = trackNameInfo[1];
|
|
2335
|
-
}else {
|
|
2336
|
-
const trackNameInfo = uglyName.split(".");
|
|
2337
|
-
name = trackNameInfo[0];
|
|
2338
|
-
type = trackNameInfo[1];
|
|
2339
|
-
}
|
|
2340
|
-
|
|
2341
|
-
return [name, type];
|
|
2342
|
-
}
|
|
2343
|
-
|
|
2344
|
-
getCurrentKeyFrame( track, time, threshold ) {
|
|
2345
|
-
|
|
2346
|
-
if(!track || !track.times.length)
|
|
2347
|
-
return;
|
|
2348
|
-
|
|
2349
|
-
// Avoid iterating through all timestamps
|
|
2350
|
-
if((time + threshold) < track.times[0])
|
|
2351
|
-
return;
|
|
2352
|
-
|
|
2353
|
-
for(let i = 0; i < track.times.length; ++i) {
|
|
2354
|
-
let t = track.times[i];
|
|
2355
|
-
if(t >= (time - threshold) &&
|
|
2356
|
-
t <= (time + threshold)) {
|
|
2357
|
-
return i;
|
|
2358
|
-
}
|
|
2359
|
-
}
|
|
2360
|
-
|
|
2361
|
-
return;
|
|
2362
|
-
}
|
|
2363
|
-
|
|
2364
|
-
getKeyFramesInRange( track, minTime, maxTime, threshold ) {
|
|
2365
|
-
|
|
2366
|
-
if(!track || !track.times.length)
|
|
2367
|
-
return;
|
|
2368
|
-
|
|
2369
|
-
// Manage negative selection
|
|
2370
|
-
if(minTime > maxTime) {
|
|
2371
|
-
let aux = minTime;
|
|
2372
|
-
minTime = maxTime;
|
|
2373
|
-
maxTime = aux;
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
// Avoid iterating through all timestamps
|
|
2377
|
-
if((maxTime + threshold) < track.times[0])
|
|
2378
|
-
return;
|
|
2379
|
-
|
|
2380
|
-
let indices = [];
|
|
2611
|
+
|
|
2381
2612
|
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
t <= (maxTime + threshold)) {
|
|
2386
|
-
indices.push(i);
|
|
2387
|
-
}
|
|
2613
|
+
unHoverAll(){
|
|
2614
|
+
if(this.lastHovered) {
|
|
2615
|
+
this.tracksPerItem[ this.lastHovered[0] ][ this.lastHovered[1] ].hovered[ this.lastHovered[2] ] = false;
|
|
2388
2616
|
}
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
getNearestKeyFrame( track, time ) {
|
|
2394
|
-
|
|
2395
|
-
if(!track || !track.times.length)
|
|
2396
|
-
return;
|
|
2397
|
-
|
|
2398
|
-
return track.times.reduce((a, b) => {
|
|
2399
|
-
return Math.abs(b - time) < Math.abs(a - time) ? b : a;
|
|
2400
|
-
});
|
|
2617
|
+
let h = this.lastHovered;
|
|
2618
|
+
this.lastHovered = null;
|
|
2619
|
+
return h;
|
|
2401
2620
|
}
|
|
2402
2621
|
|
|
2403
2622
|
unSelectAllKeyFrames() {
|
|
@@ -2418,59 +2637,36 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2418
2637
|
return;
|
|
2419
2638
|
|
|
2420
2639
|
e.multipleSelection = multiple;
|
|
2421
|
-
keyFrameIndex = keyFrameIndex ?? this.
|
|
2640
|
+
keyFrameIndex = keyFrameIndex ?? this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
2422
2641
|
|
|
2423
2642
|
if(!multiple && e.button != 2) {
|
|
2424
2643
|
this.unSelectAllKeyFrames();
|
|
2425
2644
|
}
|
|
2426
|
-
|
|
2645
|
+
|
|
2646
|
+
if(keyFrameIndex == undefined)
|
|
2647
|
+
return;
|
|
2648
|
+
|
|
2427
2649
|
const name = this.tracksDictionary[track.fullname];
|
|
2428
2650
|
let t = this.tracksPerItem[ name ][track.idx];
|
|
2429
2651
|
let currentSelection = [name, track.idx, keyFrameIndex];
|
|
2430
|
-
|
|
2431
|
-
return;
|
|
2432
|
-
|
|
2652
|
+
|
|
2433
2653
|
if(!multiple)
|
|
2434
2654
|
this.selectKeyFrame(t, currentSelection, keyFrameIndex);
|
|
2435
2655
|
else
|
|
2436
2656
|
this.lastKeyFramesSelected.push( currentSelection );
|
|
2437
|
-
if( this.onSelectKeyFrame && this.onSelectKeyFrame(e, currentSelection, keyFrameIndex)) {
|
|
2438
|
-
// Event handled
|
|
2439
|
-
return;
|
|
2440
|
-
}
|
|
2441
|
-
|
|
2442
|
-
if(keyFrameIndex == undefined)
|
|
2443
|
-
return;
|
|
2444
|
-
|
|
2445
|
-
// Select if not handled
|
|
2446
|
-
|
|
2447
|
-
t.selected[keyFrameIndex] = true;
|
|
2448
|
-
|
|
2449
|
-
if( !multiple ) {
|
|
2450
|
-
LX.emit( "@on_current_time_" + this.constructor.name, track.times[ keyFrameIndex ]);
|
|
2451
|
-
|
|
2452
|
-
if(this.onSetTime )
|
|
2453
|
-
this.onSetTime( track.times[ keyFrameIndex ] );
|
|
2454
|
-
}
|
|
2455
|
-
}
|
|
2456
|
-
|
|
2457
|
-
/**
|
|
2458
|
-
* @method addNewTrack
|
|
2459
|
-
*/
|
|
2460
|
-
|
|
2461
|
-
addNewTrack() {
|
|
2462
|
-
|
|
2463
|
-
if(!this.animationClip)
|
|
2464
|
-
this.animationClip = {tracks:[]};
|
|
2465
2657
|
|
|
2466
|
-
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
};
|
|
2658
|
+
|
|
2659
|
+
if( !multiple ) {
|
|
2660
|
+
LX.emit( "@on_current_time_" + this.constructor.name, track.times[ keyFrameIndex ]);
|
|
2661
|
+
}
|
|
2471
2662
|
|
|
2472
|
-
|
|
2473
|
-
|
|
2663
|
+
// Select if not handled
|
|
2664
|
+
t.selected[keyFrameIndex] = true;
|
|
2665
|
+
|
|
2666
|
+
if( this.onSelectKeyFrame && this.onSelectKeyFrame(e, currentSelection, keyFrameIndex)) {
|
|
2667
|
+
// Event handled
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2474
2670
|
}
|
|
2475
2671
|
|
|
2476
2672
|
/**
|
|
@@ -2490,7 +2686,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2490
2686
|
for(let i = count - 1; i >= 0; i--)
|
|
2491
2687
|
{
|
|
2492
2688
|
this.saveState(track.clipIdx);
|
|
2493
|
-
this.#delete(track, i );
|
|
2689
|
+
this.#delete(track.clipIdx, i );
|
|
2494
2690
|
}
|
|
2495
2691
|
if(defaultValue != undefined) {
|
|
2496
2692
|
if(typeof(defaultValue) == 'number') {
|
|
@@ -2509,6 +2705,289 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2509
2705
|
|
|
2510
2706
|
LX.KeyFramesTimeline = KeyFramesTimeline;
|
|
2511
2707
|
|
|
2708
|
+
/**
|
|
2709
|
+
* @class CurvesKeyFramesTimeline
|
|
2710
|
+
*/
|
|
2711
|
+
|
|
2712
|
+
class CurvesKeyFramesTimeline extends KeyFramesTimeline {
|
|
2713
|
+
|
|
2714
|
+
/**
|
|
2715
|
+
* @param {string} name
|
|
2716
|
+
* @param {object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight, range}
|
|
2717
|
+
*/
|
|
2718
|
+
constructor(name, options = {}) {
|
|
2719
|
+
|
|
2720
|
+
super(name, options);
|
|
2721
|
+
this.range = options.range || [0, 1];
|
|
2722
|
+
}
|
|
2723
|
+
|
|
2724
|
+
drawContent( ctx ) {
|
|
2725
|
+
|
|
2726
|
+
if(this.selectedItems == null || !this.tracksPerItem)
|
|
2727
|
+
return;
|
|
2728
|
+
|
|
2729
|
+
ctx.save();
|
|
2730
|
+
this.scrollableHeight = this.topMargin;
|
|
2731
|
+
|
|
2732
|
+
let offset = this.trackHeight;
|
|
2733
|
+
for(let t = 0; t < this.selectedItems.length; t++) {
|
|
2734
|
+
let tracks = this.tracksPerItem[this.selectedItems[t]] ? this.tracksPerItem[this.selectedItems[t]] : [{name: this.selectedItems[t]}];
|
|
2735
|
+
if(!tracks) continue;
|
|
2736
|
+
|
|
2737
|
+
const height = this.trackHeight;
|
|
2738
|
+
this.scrollableHeight += (tracks.length+1)*height;
|
|
2739
|
+
let scroll_y = - this.currentScrollInPixels;
|
|
2740
|
+
|
|
2741
|
+
let offsetI = 0;
|
|
2742
|
+
for(let i = 0; i < tracks.length; i++) {
|
|
2743
|
+
let track = tracks[i];
|
|
2744
|
+
if(track.hide) {
|
|
2745
|
+
continue;
|
|
2746
|
+
}
|
|
2747
|
+
|
|
2748
|
+
this.#drawTrackWithCurves(ctx, offsetI * height + offset + scroll_y, height, this.animationClip.tracks[track.clipIdx], track);
|
|
2749
|
+
offsetI++;
|
|
2750
|
+
}
|
|
2751
|
+
offset += offsetI * height + height;
|
|
2752
|
+
}
|
|
2753
|
+
ctx.restore();
|
|
2754
|
+
|
|
2755
|
+
};
|
|
2756
|
+
|
|
2757
|
+
/**
|
|
2758
|
+
* @method drawTrackWithCurves
|
|
2759
|
+
* @param {*} ctx
|
|
2760
|
+
* @description helper function, you can call it from drawContent to render all the keyframes
|
|
2761
|
+
* TODO
|
|
2762
|
+
*/
|
|
2763
|
+
#drawTrackWithCurves (ctx, y, trackHeight, track, trackInfo) {
|
|
2764
|
+
const keyframes = track.times;
|
|
2765
|
+
let values = track.values;
|
|
2766
|
+
|
|
2767
|
+
if(keyframes) {
|
|
2768
|
+
ctx.globalAlpha = 0.2;
|
|
2769
|
+
ctx.fillStyle = Timeline.TRACK_SELECTED_LIGHT//"#2c303570";
|
|
2770
|
+
if(trackInfo.isSelected) {
|
|
2771
|
+
ctx.fillRect(0, y - 3, ctx.canvas.width, trackHeight );
|
|
2772
|
+
}
|
|
2773
|
+
|
|
2774
|
+
ctx.globalAlpha = 1;
|
|
2775
|
+
this.tracksDrawn.push([track,y+this.topMargin,trackHeight]);
|
|
2776
|
+
|
|
2777
|
+
//draw lines
|
|
2778
|
+
ctx.strokeStyle = "white";
|
|
2779
|
+
ctx.beginPath();
|
|
2780
|
+
for(var j = 0; j < keyframes.length; ++j)
|
|
2781
|
+
{
|
|
2782
|
+
let time = keyframes[j];
|
|
2783
|
+
let value = values[j];
|
|
2784
|
+
|
|
2785
|
+
//convert to timeline track range
|
|
2786
|
+
value = (((value - this.range[0]) * ( -this.trackHeight) ) / (this.range[1] - this.range[0])) + this.trackHeight;
|
|
2787
|
+
|
|
2788
|
+
if( time < this.startTime || time > this.endTime )
|
|
2789
|
+
continue;
|
|
2790
|
+
let keyframePosX = this.timeToX( time );
|
|
2791
|
+
|
|
2792
|
+
ctx.save();
|
|
2793
|
+
ctx.translate(keyframePosX, y );
|
|
2794
|
+
ctx.lineTo( 0, value );
|
|
2795
|
+
ctx.restore()
|
|
2796
|
+
}
|
|
2797
|
+
ctx.stroke();
|
|
2798
|
+
ctx.closePath();
|
|
2799
|
+
ctx.fillStyle = Timeline.COLOR;
|
|
2800
|
+
//draw points
|
|
2801
|
+
for(let j = 0; j < keyframes.length; ++j)
|
|
2802
|
+
{
|
|
2803
|
+
let time = keyframes[j];
|
|
2804
|
+
let selected = trackInfo.selected[j];
|
|
2805
|
+
let margin = 0;
|
|
2806
|
+
let size = 5;
|
|
2807
|
+
if( time < this.startTime || time > this.endTime )
|
|
2808
|
+
continue;
|
|
2809
|
+
const keyframePosX = this.timeToX( time );
|
|
2810
|
+
ctx.save();
|
|
2811
|
+
|
|
2812
|
+
|
|
2813
|
+
if(trackInfo.edited[j]) {
|
|
2814
|
+
ctx.fillStyle = Timeline.COLOR_EDITED;
|
|
2815
|
+
}
|
|
2816
|
+
if(selected) {
|
|
2817
|
+
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
2818
|
+
//size = 7;
|
|
2819
|
+
margin = -2;
|
|
2820
|
+
}
|
|
2821
|
+
if(trackInfo.hovered[j]) {
|
|
2822
|
+
//size = 7;
|
|
2823
|
+
ctx.fillStyle = Timeline.COLOR_HOVERED;
|
|
2824
|
+
margin = -2;
|
|
2825
|
+
}
|
|
2826
|
+
if(trackInfo.locked)
|
|
2827
|
+
ctx.fillStyle = Timeline.COLOR_LOCK;
|
|
2828
|
+
|
|
2829
|
+
if(!this.active || trackInfo.active == false)
|
|
2830
|
+
ctx.fillStyle = Timeline.COLOR_UNACTIVE;
|
|
2831
|
+
|
|
2832
|
+
ctx.translate(keyframePosX, y);
|
|
2833
|
+
|
|
2834
|
+
let value = values[j];
|
|
2835
|
+
value = (((value - this.range[0]) * ( -this.trackHeight) ) / (this.range[1] - this.range[0])) + this.trackHeight;
|
|
2836
|
+
|
|
2837
|
+
ctx.beginPath();
|
|
2838
|
+
ctx.arc( 0, value, size, 0, Math.PI * 2);
|
|
2839
|
+
ctx.fill();
|
|
2840
|
+
ctx.closePath();
|
|
2841
|
+
|
|
2842
|
+
if(trackInfo.selected[j]) {
|
|
2843
|
+
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
2844
|
+
ctx.beginPath();
|
|
2845
|
+
ctx.arc( 0, value, size - margin, 0, Math.PI * 2);
|
|
2846
|
+
ctx.fill();
|
|
2847
|
+
ctx.closePath();
|
|
2848
|
+
}
|
|
2849
|
+
ctx.restore();
|
|
2850
|
+
}
|
|
2851
|
+
}
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
onMouseDown( e, ) {
|
|
2855
|
+
|
|
2856
|
+
const localX = e.localX;
|
|
2857
|
+
const localY = e.localY;
|
|
2858
|
+
let track = e.track;
|
|
2859
|
+
|
|
2860
|
+
if(e.shiftKey) {
|
|
2861
|
+
this.boxSelection = true;
|
|
2862
|
+
this.boxSelectionStart = [localX, localY - this.topMargin];
|
|
2863
|
+
e.multipleSelection = true;
|
|
2864
|
+
|
|
2865
|
+
}
|
|
2866
|
+
else if(track && !track.locked) {
|
|
2867
|
+
|
|
2868
|
+
const keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
2869
|
+
if( keyFrameIndex != undefined ) {
|
|
2870
|
+
this.processCurrentKeyFrame( e, keyFrameIndex, track, null, his.lastKeyFramesSelected.length > 1 || e.multipleSelection ); // Settings this as multiple so time is not being set
|
|
2871
|
+
if(e.ctrlKey || e.altKey) {
|
|
2872
|
+
this.movingKeys = true;
|
|
2873
|
+
this.canvas.style.cursor = "grab";
|
|
2874
|
+
|
|
2875
|
+
}
|
|
2876
|
+
// Set pre-move state
|
|
2877
|
+
for(let selectedKey of this.lastKeyFramesSelected) {
|
|
2878
|
+
const [name, idx, keyIndex] = selectedKey;
|
|
2879
|
+
const trackInfo = this.tracksPerItem[name][idx];
|
|
2880
|
+
selectedKey[3] = this.animationClip.tracks[ trackInfo.clipIdx ].times[ keyIndex ];
|
|
2881
|
+
}
|
|
2882
|
+
|
|
2883
|
+
this.timeBeforeMove = track.times[ keyFrameIndex ];
|
|
2884
|
+
this.valueBeforeMove = localY;
|
|
2885
|
+
}
|
|
2886
|
+
}
|
|
2887
|
+
else if(!track) {
|
|
2888
|
+
this.unSelectAllKeyFrames()
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
onMouseMove( e ) {
|
|
2893
|
+
|
|
2894
|
+
const localX = e.localX;
|
|
2895
|
+
let localY = e.localY;
|
|
2896
|
+
let track = e.track;
|
|
2897
|
+
|
|
2898
|
+
// Manage keyframe movement
|
|
2899
|
+
if(this.movingKeys) {
|
|
2900
|
+
this.clearState();
|
|
2901
|
+
const newTime = this.xToTime( localX );
|
|
2902
|
+
|
|
2903
|
+
for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
|
|
2904
|
+
let s = this.lastKeyFramesSelected[i]; // pointer
|
|
2905
|
+
const name = s[0], idx = s[1], keyIndex = s[2], keyTime = s[3];
|
|
2906
|
+
track = this.tracksPerItem[name][idx];
|
|
2907
|
+
if(track && track.locked)
|
|
2908
|
+
return;
|
|
2909
|
+
|
|
2910
|
+
this.canvas.style.cursor = "grabbing";
|
|
2911
|
+
|
|
2912
|
+
if(e.ctrlKey) {
|
|
2913
|
+
const delta = this.timeBeforeMove - keyTime;
|
|
2914
|
+
this.animationClip.tracks[ track.clipIdx ].times[ keyIndex ] = Math.min( this.animationClip.duration, Math.max(0, newTime - delta) );
|
|
2915
|
+
|
|
2916
|
+
const times = track.times;
|
|
2917
|
+
let k = s[2];
|
|
2918
|
+
for( ; k > 0; --k ){
|
|
2919
|
+
if ( times[k-1] < times[k] ){
|
|
2920
|
+
break;
|
|
2921
|
+
}
|
|
2922
|
+
this.swapKeyFrames(track, k-1, k);
|
|
2923
|
+
}
|
|
2924
|
+
for( ; k < track.times.length-1; ++k ){
|
|
2925
|
+
if ( times[k+1] > times[k] ){
|
|
2926
|
+
break;
|
|
2927
|
+
}
|
|
2928
|
+
this.swapKeyFrames(track, k+1, k);
|
|
2929
|
+
}
|
|
2930
|
+
s[2] = k; // "s" is a pointer. Modify selected keyFrame index
|
|
2931
|
+
}
|
|
2932
|
+
|
|
2933
|
+
if(e.altKey) {
|
|
2934
|
+
const trackRange = [this.tracksDrawn[track.idx][1], this.tracksDrawn[track.idx][1] + this.trackHeight];
|
|
2935
|
+
localY = Math.min( trackRange[1], Math.max(trackRange[0], localY) );
|
|
2936
|
+
|
|
2937
|
+
//convert to range track values
|
|
2938
|
+
const value = (((localY - trackRange[1]) * (this.range[1] - this.range[0])) / (trackRange[0] - trackRange[1])) + this.range[0];
|
|
2939
|
+
track.edited[keyIndex] = true;
|
|
2940
|
+
this.animationClip.tracks[ track.clipIdx ].values[ keyIndex ] = value;
|
|
2941
|
+
LX.emit( "@on_change_" + this.tracksDrawn[track.idx][0].type, value );
|
|
2942
|
+
}
|
|
2943
|
+
}
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2946
|
+
|
|
2947
|
+
if( this.grabbing && e.button != 2) {
|
|
2948
|
+
|
|
2949
|
+
// fix this
|
|
2950
|
+
if(e.shiftKey && track) {
|
|
2951
|
+
|
|
2952
|
+
const keyFrameIndex = this.getNearestKeyFrame( track, this.currentTime);
|
|
2953
|
+
|
|
2954
|
+
if(keyFrameIndex != this.snappedKeyFrameIndex){
|
|
2955
|
+
this.snappedKeyFrameIndex = keyFrameIndex;
|
|
2956
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
2957
|
+
}
|
|
2958
|
+
}
|
|
2959
|
+
else {
|
|
2960
|
+
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
2961
|
+
}
|
|
2962
|
+
|
|
2963
|
+
}
|
|
2964
|
+
else if(track) {
|
|
2965
|
+
|
|
2966
|
+
const keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
2967
|
+
if(keyFrameIndex != undefined) {
|
|
2968
|
+
|
|
2969
|
+
const name = this.tracksDictionary[track.fullname];
|
|
2970
|
+
let t = this.tracksPerItem[ name ][track.idx];
|
|
2971
|
+
if(t && t.locked)
|
|
2972
|
+
return;
|
|
2973
|
+
this.unHoverAll();
|
|
2974
|
+
|
|
2975
|
+
this.lastHovered = [name, track.idx, keyFrameIndex];
|
|
2976
|
+
t.hovered[keyFrameIndex] = true;
|
|
2977
|
+
|
|
2978
|
+
}
|
|
2979
|
+
else {
|
|
2980
|
+
this.unHoverAll();
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
else {
|
|
2984
|
+
this.unHoverAll();
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
}
|
|
2988
|
+
|
|
2989
|
+
LX.CurvesKeyFramesTimeline = CurvesKeyFramesTimeline;
|
|
2990
|
+
|
|
2512
2991
|
/**
|
|
2513
2992
|
* @class ClipsTimeline
|
|
2514
2993
|
*/
|
|
@@ -2672,6 +3151,15 @@ class ClipsTimeline extends Timeline {
|
|
|
2672
3151
|
}
|
|
2673
3152
|
}
|
|
2674
3153
|
|
|
3154
|
+
unHoverAll(){
|
|
3155
|
+
if(this.lastHovered){
|
|
3156
|
+
this.animationClip.tracks[ this.lastHovered[0] ].hovered[ this.lastHovered[1] ] = false;
|
|
3157
|
+
}
|
|
3158
|
+
let h = this.lastHovered;
|
|
3159
|
+
this.lastHovered = null;
|
|
3160
|
+
return h;
|
|
3161
|
+
}
|
|
3162
|
+
|
|
2675
3163
|
onMouseUp( e ) {
|
|
2676
3164
|
|
|
2677
3165
|
let track = e.track;
|
|
@@ -2722,8 +3210,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2722
3210
|
this.boxSelection = false;
|
|
2723
3211
|
this.boxSelectionStart = null;
|
|
2724
3212
|
this.boxSelectionEnd = null;
|
|
2725
|
-
|
|
2726
|
-
}
|
|
3213
|
+
}
|
|
2727
3214
|
|
|
2728
3215
|
onMouseDown( e, time ) {
|
|
2729
3216
|
|
|
@@ -2745,7 +3232,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2745
3232
|
selectedClips = this.lastClipsSelected;
|
|
2746
3233
|
}
|
|
2747
3234
|
else {
|
|
2748
|
-
let clipIndex = this.
|
|
3235
|
+
let clipIndex = this.getClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
2749
3236
|
if(clipIndex != undefined)
|
|
2750
3237
|
{
|
|
2751
3238
|
this.lastClipsSelected = selectedClips = [[track.idx, clipIndex]];
|
|
@@ -2791,7 +3278,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2791
3278
|
}
|
|
2792
3279
|
|
|
2793
3280
|
}
|
|
2794
|
-
else if(!track || track && this.
|
|
3281
|
+
else if(!track || track && this.getContent(track, time, 0.001) == undefined) {
|
|
2795
3282
|
|
|
2796
3283
|
if( this.timelineClickedClips )
|
|
2797
3284
|
{
|
|
@@ -2823,15 +3310,10 @@ class ClipsTimeline extends Timeline {
|
|
|
2823
3310
|
|
|
2824
3311
|
const innerSetTime = (t) => {
|
|
2825
3312
|
LX.emit( "@on_current_time_" + this.constructor.name, t);
|
|
2826
|
-
if( this.onSetTime )
|
|
2827
|
-
|
|
3313
|
+
// if( this.onSetTime )
|
|
3314
|
+
// this.onSetTime( t );
|
|
2828
3315
|
}
|
|
2829
3316
|
|
|
2830
|
-
const removeHover = () => {
|
|
2831
|
-
if(this.lastHovered)
|
|
2832
|
-
this.animationClip.tracks[ this.lastHovered[0] ].hovered[ this.lastHovered[1] ] = undefined;
|
|
2833
|
-
};
|
|
2834
|
-
|
|
2835
3317
|
if(e.shiftKey) {
|
|
2836
3318
|
if(this.boxSelection) {
|
|
2837
3319
|
this.boxSelectionEnd = [localX,localY - this.topMargin];
|
|
@@ -2935,7 +3417,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2935
3417
|
let clips = this.getClipsInRange(e.track, time, time, 0.1)
|
|
2936
3418
|
if(!e.track.locked && clips != undefined) {
|
|
2937
3419
|
|
|
2938
|
-
|
|
3420
|
+
this.unHoverAll();
|
|
2939
3421
|
this.lastHovered = [e.track.idx, clips[0]];
|
|
2940
3422
|
e.track.hovered[clips[0]] = true;
|
|
2941
3423
|
|
|
@@ -2964,11 +3446,11 @@ class ClipsTimeline extends Timeline {
|
|
|
2964
3446
|
}
|
|
2965
3447
|
}
|
|
2966
3448
|
else {
|
|
2967
|
-
|
|
3449
|
+
this.unHoverAll();
|
|
2968
3450
|
}
|
|
2969
3451
|
}
|
|
2970
3452
|
else {
|
|
2971
|
-
|
|
3453
|
+
this.unHoverAll();
|
|
2972
3454
|
}
|
|
2973
3455
|
|
|
2974
3456
|
}
|
|
@@ -2978,7 +3460,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2978
3460
|
let track = e.track;
|
|
2979
3461
|
let localX = e.localX;
|
|
2980
3462
|
|
|
2981
|
-
let clipIndex = this.
|
|
3463
|
+
let clipIndex = this.getClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
2982
3464
|
if(clipIndex != undefined) {
|
|
2983
3465
|
this.lastClipsSelected = [[track.idx, clipIndex]];
|
|
2984
3466
|
|
|
@@ -3033,7 +3515,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3033
3515
|
|
|
3034
3516
|
}
|
|
3035
3517
|
|
|
3036
|
-
|
|
3518
|
+
drawContent( ctx, timeStart, timeEnd ) {
|
|
3037
3519
|
|
|
3038
3520
|
if(!this.animationClip)
|
|
3039
3521
|
return;
|
|
@@ -3059,7 +3541,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3059
3541
|
// Creates a map for each item -> tracks
|
|
3060
3542
|
processTracks(animation) {
|
|
3061
3543
|
|
|
3062
|
-
this.tracksPerItem = {};
|
|
3544
|
+
this.tracksPerItem = {}; // maps ( the name of item and local track index ) to ( animation tracks ). Holds pointers to tracks of animationClip.tracks
|
|
3063
3545
|
this.tracksDictionary = {};
|
|
3064
3546
|
this.animationClip = {
|
|
3065
3547
|
name: (animation && animation.name) ? animation.name : "animationClip",
|
|
@@ -3230,9 +3712,9 @@ class ClipsTimeline extends Timeline {
|
|
|
3230
3712
|
}
|
|
3231
3713
|
|
|
3232
3714
|
// Reset this clip's properties
|
|
3233
|
-
track.hovered[newIdx] =
|
|
3715
|
+
track.hovered[newIdx] = false;
|
|
3234
3716
|
track.selected[newIdx] = true;
|
|
3235
|
-
track.edited[newIdx] =
|
|
3717
|
+
track.edited[newIdx] = false;
|
|
3236
3718
|
|
|
3237
3719
|
this.lastClipsSelected.push( [track.idx, newIdx] );
|
|
3238
3720
|
|
|
@@ -3248,8 +3730,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3248
3730
|
this.onUpdateTrack( trackIdx );
|
|
3249
3731
|
|
|
3250
3732
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
3251
|
-
if(this.onSetTime)
|
|
3252
|
-
|
|
3733
|
+
// if(this.onSetTime)
|
|
3734
|
+
// this.onSetTime(this.currentTime);
|
|
3253
3735
|
|
|
3254
3736
|
if(this.onSelectClip)
|
|
3255
3737
|
this.onSelectClip(clip);
|
|
@@ -3301,9 +3783,9 @@ class ClipsTimeline extends Timeline {
|
|
|
3301
3783
|
}
|
|
3302
3784
|
|
|
3303
3785
|
// Reset this clip's properties
|
|
3304
|
-
track.hovered[newIdx] =
|
|
3305
|
-
track.selected[newIdx] =
|
|
3306
|
-
track.edited[newIdx] =
|
|
3786
|
+
track.hovered[newIdx] = false;
|
|
3787
|
+
track.selected[newIdx] = false;
|
|
3788
|
+
track.edited[newIdx] = false;
|
|
3307
3789
|
|
|
3308
3790
|
|
|
3309
3791
|
let end = clip.start + clip.duration;
|
|
@@ -3318,8 +3800,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3318
3800
|
this.onUpdateTrack( trackIdx );
|
|
3319
3801
|
|
|
3320
3802
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
3321
|
-
if(this.onSetTime)
|
|
3322
|
-
|
|
3803
|
+
// if(this.onSetTime)
|
|
3804
|
+
// this.onSetTime(this.currentTime);
|
|
3323
3805
|
|
|
3324
3806
|
if(callback)
|
|
3325
3807
|
callback();
|
|
@@ -3479,14 +3961,23 @@ class ClipsTimeline extends Timeline {
|
|
|
3479
3961
|
this.setDuration(end);
|
|
3480
3962
|
}
|
|
3481
3963
|
|
|
3482
|
-
// Update animation action interpolation info
|
|
3483
|
-
if(this.onUpdateTrack)
|
|
3484
|
-
this.onUpdateTrack( trackIdx );
|
|
3485
3964
|
}
|
|
3486
3965
|
|
|
3966
|
+
// Update animation action interpolation info
|
|
3967
|
+
if(this.onUpdateTrack && Object.keys(trackIdxs).length){
|
|
3968
|
+
let tracksChanged = [];
|
|
3969
|
+
for( let c in trackIdxs ) {
|
|
3970
|
+
if ( tracksChanged.includes(trackIdxs[c].trackIdx) ) {
|
|
3971
|
+
continue;
|
|
3972
|
+
}
|
|
3973
|
+
tracksChanged.push(trackIdxs[c].trackIdx);
|
|
3974
|
+
}
|
|
3975
|
+
this.onUpdateTrack( tracksChanged );
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3487
3978
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
3488
|
-
if(this.onSetTime)
|
|
3489
|
-
|
|
3979
|
+
// if(this.onSetTime)
|
|
3980
|
+
// this.onSetTime(this.currentTime);
|
|
3490
3981
|
|
|
3491
3982
|
if(callback)
|
|
3492
3983
|
callback();
|
|
@@ -3558,9 +4049,9 @@ class ClipsTimeline extends Timeline {
|
|
|
3558
4049
|
{
|
|
3559
4050
|
clips = [...clips.slice(0, clipIdx), ...clips.slice(clipIdx + 1, clips.length)];
|
|
3560
4051
|
this.animationClip.tracks[trackIdx].clips = clips;
|
|
3561
|
-
this.animationClip.tracks[trackIdx].hovered[clipIdx] =
|
|
3562
|
-
this.animationClip.tracks[trackIdx].selected[clipIdx] =
|
|
3563
|
-
this.animationClip.tracks[trackIdx].edited[clipIdx] =
|
|
4052
|
+
this.animationClip.tracks[trackIdx].hovered[clipIdx] = false;
|
|
4053
|
+
this.animationClip.tracks[trackIdx].selected[clipIdx] = false;
|
|
4054
|
+
this.animationClip.tracks[trackIdx].edited[clipIdx] = false;
|
|
3564
4055
|
|
|
3565
4056
|
if(clips.length)
|
|
3566
4057
|
{
|
|
@@ -3581,7 +4072,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3581
4072
|
this.lastClipsSelected = [...this.lastClipsSelected.slice(0, selectedIdx), ...this.lastClipsSelected.slice(selectedIdx + 1, this.lastClipsSelected.length)];
|
|
3582
4073
|
}
|
|
3583
4074
|
}
|
|
3584
|
-
|
|
4075
|
+
return true;
|
|
3585
4076
|
}
|
|
3586
4077
|
|
|
3587
4078
|
copyContent() {
|
|
@@ -3687,7 +4178,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3687
4178
|
this.onUpdateTrack( state.t.idx );
|
|
3688
4179
|
}
|
|
3689
4180
|
|
|
3690
|
-
|
|
4181
|
+
getClip( track, time, threshold ) {
|
|
3691
4182
|
|
|
3692
4183
|
if(!track || !track.clips.length)
|
|
3693
4184
|
return;
|
|
@@ -3737,7 +4228,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3737
4228
|
processCurrentClip( e, clipIndex, track, localX, multiple ) {
|
|
3738
4229
|
|
|
3739
4230
|
e.multipleSelection = multiple;
|
|
3740
|
-
clipIndex = clipIndex ?? this.
|
|
4231
|
+
clipIndex = clipIndex ?? this.getClip( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
3741
4232
|
|
|
3742
4233
|
if(!multiple && e.button != 2) {
|
|
3743
4234
|
this.unSelectAllClips();
|
|
@@ -3850,14 +4341,7 @@ class CurvesTimeline extends Timeline {
|
|
|
3850
4341
|
this.range = options.range || [0, 1];
|
|
3851
4342
|
|
|
3852
4343
|
if(this.animationClip && this.animationClip.tracks.length)
|
|
3853
|
-
this.processTracks(
|
|
3854
|
-
|
|
3855
|
-
// Add button data
|
|
3856
|
-
let offset = 25;
|
|
3857
|
-
if(this.active)
|
|
3858
|
-
{
|
|
3859
|
-
|
|
3860
|
-
}
|
|
4344
|
+
this.processTracks(this.animationClip);
|
|
3861
4345
|
}
|
|
3862
4346
|
|
|
3863
4347
|
onMouseUp( e, time) {
|
|
@@ -3894,9 +4378,10 @@ class CurvesTimeline extends Timeline {
|
|
|
3894
4378
|
|
|
3895
4379
|
}else {
|
|
3896
4380
|
|
|
3897
|
-
let boundingBox = this.canvas.getBoundingClientRect()
|
|
3898
|
-
if(e.y < boundingBox.top || e.y > boundingBox.bottom)
|
|
4381
|
+
let boundingBox = this.canvas.getBoundingClientRect();
|
|
4382
|
+
if(e.y < boundingBox.top || e.y > boundingBox.bottom) {
|
|
3899
4383
|
return;
|
|
4384
|
+
}
|
|
3900
4385
|
// Check exact track keyframe
|
|
3901
4386
|
if(!discard && track) {
|
|
3902
4387
|
this.processCurrentKeyFrame( e, null, track, localX );
|
|
@@ -3924,7 +4409,7 @@ class CurvesTimeline extends Timeline {
|
|
|
3924
4409
|
}
|
|
3925
4410
|
else if(track && !track.locked) {
|
|
3926
4411
|
|
|
3927
|
-
const keyFrameIndex = this.
|
|
4412
|
+
const keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
3928
4413
|
if( keyFrameIndex != undefined ) {
|
|
3929
4414
|
this.processCurrentKeyFrame( e, keyFrameIndex, track, null, e.multipleSelection ); // Settings this as multiple so time is not being set
|
|
3930
4415
|
if(e.ctrlKey || e.altKey) {
|
|
@@ -3956,15 +4441,17 @@ class CurvesTimeline extends Timeline {
|
|
|
3956
4441
|
|
|
3957
4442
|
const innerSetTime = (t) => {
|
|
3958
4443
|
LX.emit( "@on_current_time_" + this.constructor.name, t);
|
|
3959
|
-
if( this.onSetTime )
|
|
3960
|
-
|
|
4444
|
+
// if( this.onSetTime )
|
|
4445
|
+
// this.onSetTime( t );
|
|
3961
4446
|
}
|
|
3962
4447
|
// Manage keyframe movement
|
|
3963
4448
|
if(this.movingKeys) {
|
|
3964
4449
|
this.clearState();
|
|
3965
4450
|
const newTime = this.xToTime( localX );
|
|
3966
4451
|
|
|
3967
|
-
for(let
|
|
4452
|
+
for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
|
|
4453
|
+
let s = this.lastKeyFramesSelected[i]; // pointer
|
|
4454
|
+
let name = s[0], idx = s[1], keyIndex = s[2], keyTime = s[3];
|
|
3968
4455
|
track = this.tracksPerItem[name][idx];
|
|
3969
4456
|
if(track && track.locked)
|
|
3970
4457
|
return;
|
|
@@ -3974,6 +4461,22 @@ class CurvesTimeline extends Timeline {
|
|
|
3974
4461
|
if(e.ctrlKey) {
|
|
3975
4462
|
const delta = this.timeBeforeMove - keyTime;
|
|
3976
4463
|
this.animationClip.tracks[ track.clipIdx ].times[ keyIndex ] = Math.min( this.animationClip.duration, Math.max(0, newTime - delta) );
|
|
4464
|
+
|
|
4465
|
+
let times = track.times;
|
|
4466
|
+
let k = s[2];
|
|
4467
|
+
for( ; k > 0; --k ){
|
|
4468
|
+
if ( times[k-1] < times[k] ){
|
|
4469
|
+
break;
|
|
4470
|
+
}
|
|
4471
|
+
this.swapKeyFrames(track, k-1, k);
|
|
4472
|
+
}
|
|
4473
|
+
for( ; k < track.times.length-1; ++k ){
|
|
4474
|
+
if ( times[k+1] > times[k] ){
|
|
4475
|
+
break;
|
|
4476
|
+
}
|
|
4477
|
+
this.swapKeyFrames(track, k+1, k);
|
|
4478
|
+
}
|
|
4479
|
+
s[2] = k; // "s" is a pointer. Modify selected keyFrame index
|
|
3977
4480
|
}
|
|
3978
4481
|
|
|
3979
4482
|
if(e.altKey) {
|
|
@@ -3992,17 +4495,8 @@ class CurvesTimeline extends Timeline {
|
|
|
3992
4495
|
|
|
3993
4496
|
}
|
|
3994
4497
|
|
|
3995
|
-
const removeHover = () => {
|
|
3996
|
-
if(this.lastHovered)
|
|
3997
|
-
this.tracksPerItem[ this.lastHovered[0] ][ this.lastHovered[1] ].hovered[ this.lastHovered[2] ] = undefined;
|
|
3998
|
-
};
|
|
3999
|
-
|
|
4000
4498
|
if( this.grabbing && e.button != 2) {
|
|
4001
4499
|
|
|
4002
|
-
var curr = time - this.currentTime;
|
|
4003
|
-
var delta = curr - this.grabTime;
|
|
4004
|
-
this.grabTime = curr;
|
|
4005
|
-
|
|
4006
4500
|
// fix this
|
|
4007
4501
|
if(e.shiftKey && track) {
|
|
4008
4502
|
|
|
@@ -4020,24 +4514,24 @@ class CurvesTimeline extends Timeline {
|
|
|
4020
4514
|
}
|
|
4021
4515
|
else if(track) {
|
|
4022
4516
|
|
|
4023
|
-
let keyFrameIndex = this.
|
|
4517
|
+
let keyFrameIndex = this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
4024
4518
|
if(keyFrameIndex != undefined) {
|
|
4025
4519
|
|
|
4026
4520
|
const name = this.tracksDictionary[track.fullname];
|
|
4027
4521
|
let t = this.tracksPerItem[ name ][track.idx];
|
|
4028
|
-
removeHover();
|
|
4029
4522
|
if(t && t.locked)
|
|
4030
4523
|
return;
|
|
4031
|
-
|
|
4524
|
+
this.unHoverAll();
|
|
4525
|
+
|
|
4032
4526
|
this.lastHovered = [name, track.idx, keyFrameIndex];
|
|
4033
4527
|
t.hovered[keyFrameIndex] = true;
|
|
4034
4528
|
|
|
4035
4529
|
}else {
|
|
4036
|
-
|
|
4530
|
+
this.unHoverAll();
|
|
4037
4531
|
}
|
|
4038
4532
|
}
|
|
4039
4533
|
else {
|
|
4040
|
-
|
|
4534
|
+
this.unHoverAll();
|
|
4041
4535
|
}
|
|
4042
4536
|
}
|
|
4043
4537
|
|
|
@@ -4108,20 +4602,21 @@ class CurvesTimeline extends Timeline {
|
|
|
4108
4602
|
|
|
4109
4603
|
}
|
|
4110
4604
|
|
|
4111
|
-
|
|
4605
|
+
drawContent( ctx, timeStart, timeEnd ) {
|
|
4112
4606
|
|
|
4113
4607
|
if(this.selectedItems == null || !this.tracksPerItem)
|
|
4114
4608
|
return;
|
|
4609
|
+
|
|
4115
4610
|
ctx.save();
|
|
4116
|
-
// this.canvasArea.root.innerHtml = "";
|
|
4117
|
-
let offset = this.trackHeight;
|
|
4118
4611
|
this.scrollableHeight = this.topMargin;
|
|
4119
|
-
|
|
4612
|
+
|
|
4613
|
+
let offset = this.trackHeight;
|
|
4614
|
+
for(let t = 0; t < this.selectedItems.length; t++) {
|
|
4120
4615
|
let tracks = this.tracksPerItem[this.selectedItems[t]] ? this.tracksPerItem[this.selectedItems[t]] : [{name: this.selectedItems[t]}];
|
|
4121
4616
|
if(!tracks) continue;
|
|
4122
4617
|
|
|
4123
4618
|
const height = this.trackHeight;
|
|
4124
|
-
this.scrollableHeight += (tracks.length+1)*height
|
|
4619
|
+
this.scrollableHeight += (tracks.length+1)*height;
|
|
4125
4620
|
let scroll_y = - this.currentScrollInPixels;
|
|
4126
4621
|
|
|
4127
4622
|
let offsetI = 0;
|
|
@@ -4171,21 +4666,14 @@ class CurvesTimeline extends Timeline {
|
|
|
4171
4666
|
|
|
4172
4667
|
ctx.save();
|
|
4173
4668
|
ctx.translate(keyframePosX, y );
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
ctx.moveTo( 0, value );
|
|
4177
|
-
}
|
|
4178
|
-
else {
|
|
4179
|
-
ctx.lineTo( 0, value );
|
|
4180
|
-
}
|
|
4181
|
-
ctx.restore()
|
|
4182
|
-
|
|
4669
|
+
ctx.lineTo( 0, value );
|
|
4670
|
+
ctx.restore()
|
|
4183
4671
|
}
|
|
4184
4672
|
ctx.stroke();
|
|
4185
4673
|
ctx.closePath();
|
|
4186
4674
|
ctx.fillStyle = Timeline.COLOR;
|
|
4187
4675
|
//draw points
|
|
4188
|
-
for(
|
|
4676
|
+
for(let j = 0; j < keyframes.length; ++j)
|
|
4189
4677
|
{
|
|
4190
4678
|
let time = keyframes[j];
|
|
4191
4679
|
let selected = trackInfo.selected[j];
|
|
@@ -4193,48 +4681,46 @@ class CurvesTimeline extends Timeline {
|
|
|
4193
4681
|
let size = 5;
|
|
4194
4682
|
if( time < this.startTime || time > this.endTime )
|
|
4195
4683
|
continue;
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
ctx.save();
|
|
4684
|
+
const keyframePosX = this.timeToX( time );
|
|
4685
|
+
ctx.save();
|
|
4199
4686
|
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4687
|
+
|
|
4688
|
+
if(trackInfo.edited[j])
|
|
4689
|
+
ctx.fillStyle = Timeline.COLOR_EDITED;
|
|
4690
|
+
if(selected) {
|
|
4691
|
+
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
4692
|
+
//size = 7;
|
|
4693
|
+
margin = -2;
|
|
4694
|
+
}
|
|
4695
|
+
if(trackInfo.hovered[j]) {
|
|
4696
|
+
//size = 7;
|
|
4697
|
+
ctx.fillStyle = Timeline.COLOR_HOVERED;
|
|
4698
|
+
margin = -2;
|
|
4699
|
+
}
|
|
4700
|
+
if(trackInfo.locked)
|
|
4701
|
+
ctx.fillStyle = Timeline.COLOR_LOCK;
|
|
4215
4702
|
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
ctx.translate(keyframePosX, y);
|
|
4703
|
+
if(!this.active || trackInfo.active == false)
|
|
4704
|
+
ctx.fillStyle = Timeline.COLOR_UNACTIVE;
|
|
4220
4705
|
|
|
4221
|
-
|
|
4222
|
-
|
|
4706
|
+
ctx.translate(keyframePosX, y);
|
|
4707
|
+
|
|
4708
|
+
let value = values[j];
|
|
4709
|
+
value = (((value - this.range[0]) * ( -this.trackHeight) ) / (this.range[1] - this.range[0])) + this.trackHeight;
|
|
4223
4710
|
|
|
4711
|
+
ctx.beginPath();
|
|
4712
|
+
ctx.arc( 0, value, size, 0, Math.PI * 2);
|
|
4713
|
+
ctx.fill();
|
|
4714
|
+
ctx.closePath();
|
|
4715
|
+
|
|
4716
|
+
if(trackInfo.selected[j]) {
|
|
4717
|
+
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
4224
4718
|
ctx.beginPath();
|
|
4225
|
-
ctx.arc( 0, value, size, 0, Math.PI * 2);
|
|
4719
|
+
ctx.arc( 0, value, size - margin, 0, Math.PI * 2);
|
|
4226
4720
|
ctx.fill();
|
|
4227
4721
|
ctx.closePath();
|
|
4228
|
-
|
|
4229
|
-
if(trackInfo.selected[j]) {
|
|
4230
|
-
ctx.fillStyle = Timeline.COLOR_SELECTED;
|
|
4231
|
-
ctx.beginPath();
|
|
4232
|
-
ctx.arc( 0, value, size - margin, 0, Math.PI * 2);
|
|
4233
|
-
ctx.fill();
|
|
4234
|
-
ctx.closePath();
|
|
4235
|
-
}
|
|
4236
|
-
ctx.restore();
|
|
4237
4722
|
}
|
|
4723
|
+
ctx.restore();
|
|
4238
4724
|
}
|
|
4239
4725
|
}
|
|
4240
4726
|
}
|
|
@@ -4267,8 +4753,8 @@ class CurvesTimeline extends Timeline {
|
|
|
4267
4753
|
|
|
4268
4754
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
4269
4755
|
// Update time
|
|
4270
|
-
if(this.onSetTime)
|
|
4271
|
-
|
|
4756
|
+
// if(this.onSetTime)
|
|
4757
|
+
// this.onSetTime(this.currentTime);
|
|
4272
4758
|
|
|
4273
4759
|
return true; // Handled
|
|
4274
4760
|
}
|
|
@@ -4286,21 +4772,32 @@ class CurvesTimeline extends Timeline {
|
|
|
4286
4772
|
};
|
|
4287
4773
|
|
|
4288
4774
|
if (animation && animation.tracks) {
|
|
4289
|
-
|
|
4290
4775
|
for( let i = 0; i < animation.tracks.length; ++i ) {
|
|
4291
4776
|
|
|
4292
4777
|
let track = animation.tracks[i];
|
|
4293
4778
|
|
|
4294
|
-
const [name, type] = this.
|
|
4295
|
-
|
|
4779
|
+
const [name, type] = this.processTrackName(track.name);
|
|
4780
|
+
|
|
4781
|
+
let valueDim = track.dim;
|
|
4782
|
+
if ( !valueDim || valueDim < 0 ){
|
|
4783
|
+
if ( track.times.length && track.values.length ){ valueDim = track.values.length/track.times.length; }
|
|
4784
|
+
else{ valueDim = 1; }
|
|
4785
|
+
}
|
|
4786
|
+
|
|
4787
|
+
let leftOver = track.values.length % valueDim; // just in case values has an incorrect length
|
|
4788
|
+
let amounEntries = Math.min( track.times.length, track.values.length - leftOver );
|
|
4789
|
+
let times = track.times.slice(0, amounEntries);
|
|
4790
|
+
let values = track.values.slice(0, amounEntries * valueDim);
|
|
4791
|
+
let boolArray = (new Array(amounEntries)).fill(false);
|
|
4792
|
+
|
|
4296
4793
|
let trackInfo = {
|
|
4297
4794
|
fullname: track.name,
|
|
4298
4795
|
name: name, type: type,
|
|
4299
|
-
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
times:
|
|
4303
|
-
|
|
4796
|
+
active: true,
|
|
4797
|
+
dim: valueDim,
|
|
4798
|
+
selected: boolArray.slice(), edited: boolArray.slice(), hovered: boolArray.slice(),
|
|
4799
|
+
times: times,
|
|
4800
|
+
values: values
|
|
4304
4801
|
};
|
|
4305
4802
|
|
|
4306
4803
|
if(!this.tracksPerItem[name]) {
|
|
@@ -4311,11 +4808,11 @@ class CurvesTimeline extends Timeline {
|
|
|
4311
4808
|
|
|
4312
4809
|
|
|
4313
4810
|
const trackIndex = this.tracksPerItem[name].length - 1;
|
|
4314
|
-
|
|
4315
|
-
|
|
4811
|
+
trackInfo.idx = trackIndex; // index of track in "name"
|
|
4812
|
+
trackInfo.clipIdx = i; // index of track in the entire animation
|
|
4316
4813
|
|
|
4317
4814
|
// Save index also in original track
|
|
4318
|
-
|
|
4815
|
+
track.idx = trackIndex;
|
|
4319
4816
|
this.tracksDictionary[track.name] = name;
|
|
4320
4817
|
this.animationClip.tracks.push(trackInfo);
|
|
4321
4818
|
|
|
@@ -4360,28 +4857,19 @@ class CurvesTimeline extends Timeline {
|
|
|
4360
4857
|
});
|
|
4361
4858
|
}
|
|
4362
4859
|
|
|
4363
|
-
onPreProcessTrack( track, idx ) {
|
|
4364
|
-
const name = this.tracksDictionary[track.name];
|
|
4365
|
-
let trackInfo = this.tracksPerItem[track.name][idx];
|
|
4366
|
-
trackInfo.selected = [];
|
|
4367
|
-
trackInfo.edited = [];
|
|
4368
|
-
trackInfo.hovered = [];
|
|
4369
|
-
}
|
|
4370
|
-
|
|
4371
4860
|
isKeyFrameSelected( track, index ) {
|
|
4372
4861
|
return track.selected[ index ];
|
|
4373
4862
|
}
|
|
4374
4863
|
|
|
4375
|
-
|
|
4376
|
-
|
|
4377
|
-
|
|
4378
|
-
|
|
4379
|
-
const trackInfo = this.
|
|
4380
|
-
|
|
4864
|
+
/**
|
|
4865
|
+
* @param {Number} trackIdx index of track in the animation (not local index)
|
|
4866
|
+
*/
|
|
4867
|
+
saveState( trackIdx ) {
|
|
4868
|
+
const trackInfo = this.animationClip.tracks[trackIdx];
|
|
4381
4869
|
this.trackState.push({
|
|
4382
|
-
idx:
|
|
4383
|
-
t:
|
|
4384
|
-
v:
|
|
4870
|
+
idx: trackIdx,
|
|
4871
|
+
t: trackInfo.times.slice(),
|
|
4872
|
+
v: trackInfo.values.slice(),
|
|
4385
4873
|
editedTracks: [].concat(trackInfo.edited)
|
|
4386
4874
|
});
|
|
4387
4875
|
}
|
|
@@ -4396,7 +4884,7 @@ class CurvesTimeline extends Timeline {
|
|
|
4396
4884
|
this.animationClip.tracks[state.idx].values = state.v;
|
|
4397
4885
|
|
|
4398
4886
|
const localIdx = this.animationClip.tracks[state.idx].idx;
|
|
4399
|
-
const name = this.
|
|
4887
|
+
const name = this.processTrackName(this.animationClip.tracks[state.idx].name)[0];
|
|
4400
4888
|
this.tracksPerItem[name][localIdx].edited = state.editedTracks;
|
|
4401
4889
|
|
|
4402
4890
|
// Update animation action interpolation info
|
|
@@ -4404,6 +4892,43 @@ class CurvesTimeline extends Timeline {
|
|
|
4404
4892
|
this.onUpdateTrack( state.idx );
|
|
4405
4893
|
}
|
|
4406
4894
|
|
|
4895
|
+
/**
|
|
4896
|
+
*
|
|
4897
|
+
* @param {*} track
|
|
4898
|
+
* @param {Number} srcIdx keyFrame index
|
|
4899
|
+
* @param {Number} trgIdx keyFrame index
|
|
4900
|
+
*/
|
|
4901
|
+
swapKeyFrames(track, srcIdx, trgIdx){
|
|
4902
|
+
let times = track.times;
|
|
4903
|
+
let values = track.values;
|
|
4904
|
+
|
|
4905
|
+
let tmp = times[srcIdx];
|
|
4906
|
+
times[srcIdx] = times[trgIdx];
|
|
4907
|
+
times[trgIdx] = tmp;
|
|
4908
|
+
|
|
4909
|
+
tmp = track.hovered[srcIdx];
|
|
4910
|
+
track.hovered[srcIdx] = track.hovered[trgIdx];
|
|
4911
|
+
track.hovered[trgIdx] = tmp;
|
|
4912
|
+
|
|
4913
|
+
tmp = track.edited[srcIdx];
|
|
4914
|
+
track.edited[srcIdx] = track.edited[trgIdx];
|
|
4915
|
+
track.edited[trgIdx] = tmp;
|
|
4916
|
+
|
|
4917
|
+
tmp = track.selected[srcIdx];
|
|
4918
|
+
track.selected[srcIdx] = track.selected[trgIdx];
|
|
4919
|
+
track.selected[trgIdx] = tmp;
|
|
4920
|
+
|
|
4921
|
+
let src = srcIdx * track.dim;
|
|
4922
|
+
let end = src + track.dim;
|
|
4923
|
+
let trg = trgIdx * track.dim;
|
|
4924
|
+
for( ; src < end; ++src ){
|
|
4925
|
+
tmp = values[ src ];
|
|
4926
|
+
values[ src ] = values[ trg ];
|
|
4927
|
+
values[ trg ] = tmp;
|
|
4928
|
+
++trg;
|
|
4929
|
+
}
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4407
4932
|
selectKeyFrame( track, selectionInfo, index ) {
|
|
4408
4933
|
|
|
4409
4934
|
if(index == undefined || !track)
|
|
@@ -4416,38 +4941,48 @@ class CurvesTimeline extends Timeline {
|
|
|
4416
4941
|
this.currentTime = this.animationClip.tracks[track.clipIdx].times[ index ];
|
|
4417
4942
|
|
|
4418
4943
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime );
|
|
4419
|
-
if( this.onSetTime )
|
|
4420
|
-
|
|
4944
|
+
// if( this.onSetTime )
|
|
4945
|
+
// this.onSetTime( this.currentTime );
|
|
4421
4946
|
}
|
|
4422
4947
|
|
|
4423
4948
|
copyContent() {
|
|
4949
|
+
if (!this.lastKeyFramesSelected.length){
|
|
4950
|
+
return;
|
|
4951
|
+
}
|
|
4952
|
+
|
|
4953
|
+
// sort keyframes selected by track
|
|
4424
4954
|
let toCopy = {};
|
|
4425
4955
|
for(let i = 0; i < this.lastKeyFramesSelected.length; i++){
|
|
4426
|
-
let [id,
|
|
4427
|
-
|
|
4428
|
-
|
|
4956
|
+
let [id, localTrackIdx, keyIdx] = this.lastKeyFramesSelected[i];
|
|
4957
|
+
let track = this.tracksPerItem[id][localTrackIdx];
|
|
4958
|
+
let trackIdx = track.clipIdx;
|
|
4959
|
+
|
|
4960
|
+
if(toCopy[trackIdx]) {
|
|
4961
|
+
toCopy[trackIdx].idxs.push(keyIdx);
|
|
4429
4962
|
} else {
|
|
4430
|
-
toCopy[
|
|
4431
|
-
|
|
4432
|
-
}
|
|
4963
|
+
toCopy[trackIdx] = {track: track, idxs : [keyIdx]};
|
|
4964
|
+
}
|
|
4433
4965
|
if(i == 0) {
|
|
4434
|
-
this.copyKeyFrameValue(
|
|
4966
|
+
this.copyKeyFrameValue(track, keyIdx);
|
|
4435
4967
|
}
|
|
4436
4968
|
}
|
|
4437
|
-
|
|
4438
|
-
|
|
4439
|
-
|
|
4969
|
+
|
|
4970
|
+
// for each track selected, copy its values
|
|
4971
|
+
for(let trackIdx in toCopy) {
|
|
4972
|
+
this.copyKeyFrames(toCopy[trackIdx].track, toCopy[trackIdx].idxs);
|
|
4440
4973
|
}
|
|
4441
4974
|
}
|
|
4442
|
-
|
|
4443
|
-
|
|
4975
|
+
|
|
4976
|
+
// copies the current value of the keyframe. This value can be pasted across any track (as long as they are of the same type)
|
|
4977
|
+
copyKeyFrameValue( track, index ) {
|
|
4444
4978
|
|
|
4445
4979
|
// 1 element clipboard by now
|
|
4446
4980
|
|
|
4447
|
-
let values = [];
|
|
4448
4981
|
let start = index * track.dim;
|
|
4449
|
-
|
|
4450
|
-
|
|
4982
|
+
let values = this.animationClip.tracks[ track.clipIdx ].values.slice(start, start + track.dim);
|
|
4983
|
+
|
|
4984
|
+
if(!this.clipboard)
|
|
4985
|
+
this.clipboard = {};
|
|
4451
4986
|
|
|
4452
4987
|
this.clipboard = {
|
|
4453
4988
|
type: track.type,
|
|
@@ -4455,23 +4990,49 @@ class CurvesTimeline extends Timeline {
|
|
|
4455
4990
|
};
|
|
4456
4991
|
}
|
|
4457
4992
|
|
|
4458
|
-
|
|
4993
|
+
// each track will have its own entry of copied keyframes. When pasting, only the apropiate track's keyframes are pasted
|
|
4994
|
+
copyKeyFrames( track, indices ) {
|
|
4995
|
+
|
|
4996
|
+
let trackIdx = track.clipIdx;
|
|
4459
4997
|
if(!this.clipboard)
|
|
4998
|
+
this.clipboard = {};
|
|
4999
|
+
if(!this.clipboard.keyframes) {
|
|
5000
|
+
this.clipboard.keyframes = {};
|
|
5001
|
+
}
|
|
5002
|
+
|
|
5003
|
+
this.clipboard.keyframes[trackIdx] = { track: track, values:{}, times:{} };
|
|
5004
|
+
|
|
5005
|
+
// 1 element clipboard by now
|
|
5006
|
+
for(let i = 0; i < indices.length; i++ ){
|
|
5007
|
+
let keyIdx = indices[i];
|
|
5008
|
+
let start = keyIdx * track.dim;
|
|
5009
|
+
let keyValues = track.values.slice(start, start + track.dim); // copy values into a new array
|
|
5010
|
+
this.clipboard.keyframes[trackIdx].values[keyIdx] = keyValues; // save to clipboard
|
|
5011
|
+
this.clipboard.keyframes[trackIdx].times[keyIdx] = track.times[keyIdx]; // save to clipboard
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
|
|
5015
|
+
pasteContent() {
|
|
5016
|
+
if(!this.clipboard) {
|
|
4460
5017
|
return;
|
|
5018
|
+
}
|
|
4461
5019
|
|
|
5020
|
+
// copy the value into the only selected keyframe
|
|
4462
5021
|
if(this.clipboard.value && this.lastKeyFramesSelected.length == 1) {
|
|
4463
5022
|
|
|
4464
|
-
let [id,
|
|
4465
|
-
this.pasteKeyFrameValue({}, this.tracksPerItem[id][
|
|
5023
|
+
let [id, localTrackIdx, keyIdx] = this.lastKeyFramesSelected[0];
|
|
5024
|
+
this.pasteKeyFrameValue({}, this.tracksPerItem[id][localTrackIdx], keyIdx);
|
|
4466
5025
|
}
|
|
5026
|
+
|
|
5027
|
+
// create new keyframes from the ones copied
|
|
4467
5028
|
if(this.clipboard.keyframes) {
|
|
4468
5029
|
let currentTime = this.currentTime;
|
|
4469
|
-
for(let
|
|
4470
|
-
|
|
4471
|
-
this.pasteKeyFrames({multipleSelection: this.clipboard.keyframes.length}, clipIdx, indices);
|
|
5030
|
+
for(let trackIdx in this.clipboard.keyframes) {
|
|
5031
|
+
this.pasteKeyFrames({multipleSelection: this.clipboard.keyframes.length}, trackIdx);
|
|
4472
5032
|
this.currentTime = currentTime;
|
|
4473
5033
|
}
|
|
4474
5034
|
}
|
|
5035
|
+
this.clipboard = {};
|
|
4475
5036
|
}
|
|
4476
5037
|
|
|
4477
5038
|
canPasteKeyFrame () {
|
|
@@ -4495,13 +5056,13 @@ class CurvesTimeline extends Timeline {
|
|
|
4495
5056
|
}
|
|
4496
5057
|
|
|
4497
5058
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
4498
|
-
if(this.onSetTime)
|
|
4499
|
-
|
|
5059
|
+
// if(this.onSetTime)
|
|
5060
|
+
// this.onSetTime(this.currentTime);
|
|
4500
5061
|
|
|
4501
5062
|
track.edited[ index ] = true;
|
|
4502
5063
|
}
|
|
4503
5064
|
|
|
4504
|
-
|
|
5065
|
+
pasteKeyFrameValue( e, track, index ) {
|
|
4505
5066
|
|
|
4506
5067
|
this.saveState(track.clipIdx);
|
|
4507
5068
|
|
|
@@ -4520,32 +5081,52 @@ class CurvesTimeline extends Timeline {
|
|
|
4520
5081
|
}
|
|
4521
5082
|
}
|
|
4522
5083
|
|
|
4523
|
-
|
|
5084
|
+
pasteKeyFrames( e, trackIdx ) {
|
|
5085
|
+
|
|
5086
|
+
if ( !this.clipboard.keyframes[trackIdx] ){
|
|
5087
|
+
return;
|
|
5088
|
+
}
|
|
5089
|
+
this.saveState(trackIdx);
|
|
4524
5090
|
|
|
4525
|
-
this.
|
|
5091
|
+
let clipboardInfo = this.clipboard.keyframes[trackIdx];
|
|
5092
|
+
let indices = Object.keys(clipboardInfo.values);
|
|
5093
|
+
indices.sort(); // just in case
|
|
4526
5094
|
|
|
4527
5095
|
// Copy to current key
|
|
4528
|
-
|
|
5096
|
+
for(let i = 0; i < indices.length; i++) {
|
|
5097
|
+
let value = clipboardInfo.values[indices[i]];
|
|
5098
|
+
if(typeof value == 'number')
|
|
5099
|
+
value = [value];
|
|
5100
|
+
if(i > 0) {
|
|
5101
|
+
let delta = clipboardInfo.times[indices[i]] - clipboardInfo.times[indices[i-1]];
|
|
5102
|
+
this.currentTime += delta;
|
|
5103
|
+
}
|
|
5104
|
+
this.addKeyFrame( clipboardInfo.track, value);
|
|
5105
|
+
}
|
|
4529
5106
|
|
|
4530
|
-
if(!e.multipleSelection)
|
|
4531
|
-
return;
|
|
5107
|
+
// if(!e.multipleSelection)
|
|
5108
|
+
// return;
|
|
4532
5109
|
|
|
4533
|
-
// Don't want anything after this
|
|
4534
|
-
this.clearState();
|
|
5110
|
+
// // Don't want anything after this
|
|
5111
|
+
// this.clearState();
|
|
4535
5112
|
|
|
4536
|
-
// Copy to every selected key
|
|
4537
|
-
for(let [name, idx, keyIndex] of this.lastKeyFramesSelected) {
|
|
4538
|
-
|
|
4539
|
-
}
|
|
5113
|
+
// // Copy to every selected key
|
|
5114
|
+
// for(let [name, idx, keyIndex] of this.lastKeyFramesSelected) {
|
|
5115
|
+
// this.#paste( this.tracksPerItem[name][idx], keyIndex );
|
|
5116
|
+
// }
|
|
4540
5117
|
}
|
|
4541
5118
|
|
|
4542
5119
|
addKeyFrame( track, value = undefined, time = this.currentTime ) {
|
|
4543
|
-
|
|
5120
|
+
|
|
5121
|
+
if(!track) {
|
|
5122
|
+
return;
|
|
5123
|
+
}
|
|
5124
|
+
|
|
4544
5125
|
// Update animationClip information
|
|
4545
5126
|
const clipIdx = track.clipIdx;
|
|
4546
5127
|
|
|
4547
5128
|
// Time slot with other key?
|
|
4548
|
-
const keyInCurrentSlot = this.animationClip.tracks[clipIdx].times.find( t => { return !LX.UTILS.compareThreshold(
|
|
5129
|
+
const keyInCurrentSlot = this.animationClip.tracks[clipIdx].times.find( t => { return !LX.UTILS.compareThreshold(time, t, t, 0.001 ); });
|
|
4549
5130
|
if( keyInCurrentSlot ) {
|
|
4550
5131
|
console.warn("There is already a keyframe stored in time slot ", keyInCurrentSlot)
|
|
4551
5132
|
return;
|
|
@@ -4604,8 +5185,8 @@ class CurvesTimeline extends Timeline {
|
|
|
4604
5185
|
// }
|
|
4605
5186
|
|
|
4606
5187
|
// Reset this key's properties
|
|
4607
|
-
track.hovered[newIdx] =
|
|
4608
|
-
track.selected[newIdx] =
|
|
5188
|
+
track.hovered[newIdx] = false;
|
|
5189
|
+
track.selected[newIdx] = false;
|
|
4609
5190
|
track.edited[newIdx] = true;
|
|
4610
5191
|
|
|
4611
5192
|
|
|
@@ -4614,8 +5195,8 @@ class CurvesTimeline extends Timeline {
|
|
|
4614
5195
|
this.onUpdateTrack( clipIdx );
|
|
4615
5196
|
|
|
4616
5197
|
LX.emit( "@on_current_time_" + this.constructor.name, this.currentTime);
|
|
4617
|
-
if(this.onSetTime)
|
|
4618
|
-
|
|
5198
|
+
// if(this.onSetTime)
|
|
5199
|
+
// this.onSetTime(this.currentTime);
|
|
4619
5200
|
|
|
4620
5201
|
return newIdx;
|
|
4621
5202
|
}
|
|
@@ -4625,51 +5206,46 @@ class CurvesTimeline extends Timeline {
|
|
|
4625
5206
|
this.deleteKeyFrame({ multipleSelection: this.lastKeyFramesSelected.length > 1});
|
|
4626
5207
|
}
|
|
4627
5208
|
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
5209
|
+
/**
|
|
5210
|
+
* Delete a keyframe given the track and the its index
|
|
5211
|
+
* @param {Number} trackIdx track that keyframe belongs to
|
|
5212
|
+
* @param {Number} index index of the keyframe on the track
|
|
5213
|
+
* @returns
|
|
4631
5214
|
*/
|
|
4632
|
-
#delete(
|
|
5215
|
+
#delete( trackIdx, index ) {
|
|
4633
5216
|
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
5217
|
+
const track = this.animationClip.tracks[trackIdx];
|
|
5218
|
+
|
|
5219
|
+
// Don't remove by now the first key (and avoid impossible indices)
|
|
5220
|
+
if(index < 1 || index >= track.times.length ) {
|
|
5221
|
+
console.warn("Operation not supported! " + (index==0 ?"[removing first keyframe track]":"[removing invalid keyframe " + i + " from " + track.times.length + "]"));
|
|
5222
|
+
return false;
|
|
4638
5223
|
}
|
|
4639
5224
|
|
|
4640
|
-
//
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
// console.warn("Operation not supported! [remove last keyframe track]");
|
|
4646
|
-
// return;
|
|
4647
|
-
// }
|
|
4648
|
-
|
|
4649
|
-
// Reset this key's properties
|
|
4650
|
-
track.hovered[index] = undefined;
|
|
4651
|
-
track.selected[index] = undefined;
|
|
4652
|
-
track.edited[index] = undefined;
|
|
4653
|
-
|
|
4654
|
-
// Delete time key
|
|
4655
|
-
this.animationClip.tracks[clipIdx].times = this.animationClip.tracks[clipIdx].times.filter( (v, i) => i != index);
|
|
5225
|
+
// Delete time key (TypedArrays do not have splice )
|
|
5226
|
+
track.times = track.times.filter( (v, i) => i != index);
|
|
5227
|
+
track.edited = track.edited.filter( (v, i) => i != index);
|
|
5228
|
+
track.selected = track.selected.filter( (v, i) => i != index);
|
|
5229
|
+
track.hovered = track.hovered.filter( (v, i) => i != index);
|
|
4656
5230
|
|
|
4657
5231
|
// Delete values
|
|
4658
5232
|
const indexDim = track.dim * index;
|
|
4659
|
-
const slice1 =
|
|
4660
|
-
const slice2 =
|
|
5233
|
+
const slice1 = track.values.slice(0, indexDim);
|
|
5234
|
+
const slice2 = track.values.slice(indexDim + track.dim);
|
|
4661
5235
|
|
|
4662
|
-
|
|
5236
|
+
track.values = LX.UTILS.concatTypedArray([slice1, slice2], Float32Array);
|
|
4663
5237
|
|
|
4664
|
-
// Move the other's key properties
|
|
4665
|
-
for(let i = index; i <
|
|
4666
|
-
|
|
4667
|
-
|
|
5238
|
+
// // Move the other's key properties
|
|
5239
|
+
// for(let i = index; i < track.times.length; ++i) {
|
|
5240
|
+
// track.edited[i] = track.edited[i + 1];
|
|
5241
|
+
// track.hovered[i] = track.hovered[i + 1];
|
|
5242
|
+
// track.selected[i] = track.selected[i + 1];
|
|
5243
|
+
// }
|
|
4668
5244
|
|
|
4669
5245
|
// Update animation action interpolation info
|
|
4670
5246
|
if(this.onDeleteKeyFrame)
|
|
4671
|
-
this.onDeleteKeyFrame(
|
|
4672
|
-
|
|
5247
|
+
this.onDeleteKeyFrame( trackIdx, index );
|
|
5248
|
+
|
|
4673
5249
|
return true;
|
|
4674
5250
|
}
|
|
4675
5251
|
|
|
@@ -4690,15 +5266,13 @@ class CurvesTimeline extends Timeline {
|
|
|
4690
5266
|
|
|
4691
5267
|
if(!pts) continue;
|
|
4692
5268
|
|
|
4693
|
-
pts = pts.sort( (a,b) =>
|
|
4694
|
-
|
|
4695
|
-
let deletedIndices = 0;
|
|
5269
|
+
pts = pts.sort( (a,b) => b[2] - a[2] ); // sort by keyframe index (descending)
|
|
4696
5270
|
|
|
4697
|
-
// Delete every selected key
|
|
4698
|
-
for(let [name,
|
|
4699
|
-
this.
|
|
4700
|
-
|
|
4701
|
-
|
|
5271
|
+
// Delete every selected key starting with the last one in the track
|
|
5272
|
+
for(let [name, localIdx, keyIndex] of pts) {
|
|
5273
|
+
let track = this.tracksPerItem[name][localIdx];
|
|
5274
|
+
this.saveState(track.clipIdx);
|
|
5275
|
+
this.#delete(track.clipIdx, keyIndex);
|
|
4702
5276
|
}
|
|
4703
5277
|
}
|
|
4704
5278
|
}
|
|
@@ -4706,53 +5280,20 @@ class CurvesTimeline extends Timeline {
|
|
|
4706
5280
|
|
|
4707
5281
|
// Key pressed
|
|
4708
5282
|
if(!track && this.lastKeyFramesSelected.length > 0) {
|
|
4709
|
-
const [itemName,
|
|
4710
|
-
track = this.tracksPerItem[itemName][
|
|
5283
|
+
const [itemName, localTrackIndex, keyIndex] = this.lastKeyFramesSelected[0];
|
|
5284
|
+
track = this.tracksPerItem[itemName][localTrackIndex];
|
|
4711
5285
|
index = keyIndex;
|
|
4712
5286
|
}
|
|
4713
5287
|
|
|
4714
5288
|
if ( track ){
|
|
4715
5289
|
this.saveState(track.clipIdx);
|
|
4716
|
-
this.#delete(
|
|
5290
|
+
this.#delete(track.clipIdx, index);
|
|
4717
5291
|
}
|
|
4718
5292
|
}
|
|
4719
5293
|
|
|
4720
5294
|
this.unSelectAllKeyFrames();
|
|
4721
5295
|
}
|
|
4722
5296
|
|
|
4723
|
-
/**
|
|
4724
|
-
* @method clearTrack
|
|
4725
|
-
*/
|
|
4726
|
-
|
|
4727
|
-
clearTrack(idx, defaultValue) {
|
|
4728
|
-
|
|
4729
|
-
let track = this.animationClip.tracks[idx];
|
|
4730
|
-
|
|
4731
|
-
if(track.locked )
|
|
4732
|
-
{
|
|
4733
|
-
return;
|
|
4734
|
-
}
|
|
4735
|
-
|
|
4736
|
-
const count = track.times.length;
|
|
4737
|
-
for(let i = count - 1; i >= 0; i--)
|
|
4738
|
-
{
|
|
4739
|
-
this.saveState(track.clipIdx);
|
|
4740
|
-
this.#delete(track, i );
|
|
4741
|
-
}
|
|
4742
|
-
if(defaultValue != undefined) {
|
|
4743
|
-
if(typeof(defaultValue) == 'number') {
|
|
4744
|
-
track.values[0] = defaultValue;
|
|
4745
|
-
}
|
|
4746
|
-
else {
|
|
4747
|
-
for(let i = 0; i < defaultValue.length; i++) {
|
|
4748
|
-
track.values[i] = defaultValue[i];
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
|
|
4752
|
-
}
|
|
4753
|
-
return idx;
|
|
4754
|
-
}
|
|
4755
|
-
|
|
4756
5297
|
getNumKeyFramesSelected() {
|
|
4757
5298
|
return this.lastKeyFramesSelected.length;
|
|
4758
5299
|
}
|
|
@@ -4766,6 +5307,9 @@ class CurvesTimeline extends Timeline {
|
|
|
4766
5307
|
}
|
|
4767
5308
|
}
|
|
4768
5309
|
|
|
5310
|
+
/**
|
|
5311
|
+
* @param {Array} itemsName
|
|
5312
|
+
*/
|
|
4769
5313
|
setSelectedItems( itemsName ) {
|
|
4770
5314
|
|
|
4771
5315
|
if(itemsName.constructor !== Array)
|
|
@@ -4781,7 +5325,7 @@ class CurvesTimeline extends Timeline {
|
|
|
4781
5325
|
return this.tracksPerItem[ name ][trackIndex];
|
|
4782
5326
|
}
|
|
4783
5327
|
|
|
4784
|
-
|
|
5328
|
+
processTrackName( uglyName ) {
|
|
4785
5329
|
|
|
4786
5330
|
let name, type;
|
|
4787
5331
|
|
|
@@ -4802,7 +5346,7 @@ class CurvesTimeline extends Timeline {
|
|
|
4802
5346
|
return [name, type];
|
|
4803
5347
|
}
|
|
4804
5348
|
|
|
4805
|
-
|
|
5349
|
+
getKeyFrame( track, time, threshold ) {
|
|
4806
5350
|
|
|
4807
5351
|
if(!track || !track.times.length)
|
|
4808
5352
|
return;
|
|
@@ -4861,6 +5405,15 @@ class CurvesTimeline extends Timeline {
|
|
|
4861
5405
|
});
|
|
4862
5406
|
}
|
|
4863
5407
|
|
|
5408
|
+
unHoverAll() {
|
|
5409
|
+
if(this.lastHovered) {
|
|
5410
|
+
this.tracksPerItem[ this.lastHovered[0] ][ this.lastHovered[1] ].hovered[ this.lastHovered[2] ] = false;
|
|
5411
|
+
}
|
|
5412
|
+
let h = this.lastHovered;
|
|
5413
|
+
this.lastHovered = null;
|
|
5414
|
+
return h;
|
|
5415
|
+
}
|
|
5416
|
+
|
|
4864
5417
|
unSelectAllKeyFrames() {
|
|
4865
5418
|
|
|
4866
5419
|
for(let [name, idx, keyIndex] of this.lastKeyFramesSelected) {
|
|
@@ -4875,8 +5428,11 @@ class CurvesTimeline extends Timeline {
|
|
|
4875
5428
|
|
|
4876
5429
|
processCurrentKeyFrame( e, keyFrameIndex, track, localX, multiple ) {
|
|
4877
5430
|
|
|
5431
|
+
if(track.locked)
|
|
5432
|
+
return;
|
|
5433
|
+
|
|
4878
5434
|
e.multipleSelection = multiple;
|
|
4879
|
-
keyFrameIndex = keyFrameIndex ?? this.
|
|
5435
|
+
keyFrameIndex = keyFrameIndex ?? this.getKeyFrame( track, this.xToTime( localX ), this.pixelsToSeconds * 5 );
|
|
4880
5436
|
|
|
4881
5437
|
if(!multiple && e.button != 2) {
|
|
4882
5438
|
this.unSelectAllKeyFrames();
|
|
@@ -4885,7 +5441,8 @@ class CurvesTimeline extends Timeline {
|
|
|
4885
5441
|
const name = this.tracksDictionary[track.fullname];
|
|
4886
5442
|
let t = this.tracksPerItem[ name ][track.idx];
|
|
4887
5443
|
let currentSelection = [name, track.idx, keyFrameIndex];
|
|
4888
|
-
|
|
5444
|
+
|
|
5445
|
+
if(!multiple)
|
|
4889
5446
|
this.selectKeyFrame(t, currentSelection, keyFrameIndex);
|
|
4890
5447
|
else
|
|
4891
5448
|
this.lastKeyFramesSelected.push( currentSelection );
|
|
@@ -4896,7 +5453,7 @@ class CurvesTimeline extends Timeline {
|
|
|
4896
5453
|
}
|
|
4897
5454
|
|
|
4898
5455
|
if(keyFrameIndex == undefined)
|
|
4899
|
-
|
|
5456
|
+
return;
|
|
4900
5457
|
|
|
4901
5458
|
// Select if not handled
|
|
4902
5459
|
t.selected[keyFrameIndex] = true;
|
|
@@ -4904,9 +5461,61 @@ class CurvesTimeline extends Timeline {
|
|
|
4904
5461
|
if( !multiple) {
|
|
4905
5462
|
|
|
4906
5463
|
LX.emit( "@on_current_time_" + this.constructor.name, track.times[ keyFrameIndex]);
|
|
4907
|
-
if(this.onSetTime)
|
|
4908
|
-
|
|
5464
|
+
// if(this.onSetTime)
|
|
5465
|
+
// this.onSetTime( track.times[ keyFrameIndex ] );
|
|
5466
|
+
}
|
|
5467
|
+
}
|
|
5468
|
+
|
|
5469
|
+
/**
|
|
5470
|
+
* @method addNewTrack
|
|
5471
|
+
*/
|
|
5472
|
+
|
|
5473
|
+
addNewTrack() {
|
|
5474
|
+
|
|
5475
|
+
if(!this.animationClip)
|
|
5476
|
+
this.animationClip = {tracks:[]};
|
|
5477
|
+
|
|
5478
|
+
let trackInfo = {
|
|
5479
|
+
idx: this.animationClip.tracks.length,
|
|
5480
|
+
values: [], times: [],
|
|
5481
|
+
selected: [], edited: [], hovered: []
|
|
5482
|
+
};
|
|
5483
|
+
|
|
5484
|
+
this.animationClip.tracks.push(trackInfo);
|
|
5485
|
+
this.updateLeftPanel();
|
|
5486
|
+
return trackInfo.idx;
|
|
5487
|
+
}
|
|
5488
|
+
|
|
5489
|
+
/**
|
|
5490
|
+
* @method clearTrack
|
|
5491
|
+
*/
|
|
5492
|
+
clearTrack(idx, defaultValue) {
|
|
5493
|
+
|
|
5494
|
+
let track = this.animationClip.tracks[idx];
|
|
5495
|
+
|
|
5496
|
+
if(track.locked )
|
|
5497
|
+
{
|
|
5498
|
+
return;
|
|
5499
|
+
}
|
|
5500
|
+
|
|
5501
|
+
const count = track.times.length;
|
|
5502
|
+
for(let i = count - 1; i >= 0; i--)
|
|
5503
|
+
{
|
|
5504
|
+
this.saveState(track.clipIdx);
|
|
5505
|
+
this.#delete(track, i );
|
|
5506
|
+
}
|
|
5507
|
+
if(defaultValue != undefined) {
|
|
5508
|
+
if(typeof(defaultValue) == 'number') {
|
|
5509
|
+
track.values[0] = defaultValue;
|
|
5510
|
+
}
|
|
5511
|
+
else {
|
|
5512
|
+
for(let i = 0; i < defaultValue.length; i++) {
|
|
5513
|
+
track.values[i] = defaultValue[i];
|
|
5514
|
+
}
|
|
5515
|
+
}
|
|
5516
|
+
|
|
4909
5517
|
}
|
|
5518
|
+
return idx;
|
|
4910
5519
|
}
|
|
4911
5520
|
}
|
|
4912
5521
|
|
|
@@ -4974,8 +5583,15 @@ LX.UTILS.HexToRgb = (hex) => {
|
|
|
4974
5583
|
throw new Error('Bad Hex');
|
|
4975
5584
|
}
|
|
4976
5585
|
|
|
4977
|
-
LX.UTILS.concatTypedArray = (
|
|
4978
|
-
|
|
5586
|
+
LX.UTILS.concatTypedArray = (arrays, ArrayType) => {
|
|
5587
|
+
let size = arrays.reduce((acc,arr) => acc + arr.length, 0);
|
|
5588
|
+
let result = new ArrayType( size ); // generate just one array
|
|
5589
|
+
let offset = 0;
|
|
5590
|
+
for( let i = 0; i < arrays.length; ++i ){
|
|
5591
|
+
result.set(arrays[i], offset ); // copy values
|
|
5592
|
+
offset += arrays[i].length;
|
|
5593
|
+
}
|
|
5594
|
+
return result;
|
|
4979
5595
|
}
|
|
4980
5596
|
|
|
4981
|
-
export { Timeline, KeyFramesTimeline, ClipsTimeline, CurvesTimeline };
|
|
5597
|
+
export { Timeline, KeyFramesTimeline, ClipsTimeline, CurvesTimeline, CurvesKeyFramesTimeline };
|