lexgui 8.0.0 → 8.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/build/components/AlertDialog.d.ts +7 -0
- package/build/components/Counter.d.ts +1 -0
- package/build/components/Dialog.d.ts +1 -1
- package/build/components/Footer.d.ts +1 -1
- package/build/components/Menubar.d.ts +2 -2
- package/build/core/Area.d.ts +22 -22
- package/build/core/Namespace.js +34 -34
- package/build/core/Namespace.js.map +1 -1
- package/build/core/Panel.d.ts +2 -3
- package/build/extensions/AssetView.d.ts +136 -134
- package/build/extensions/AssetView.js +1367 -1320
- package/build/extensions/AssetView.js.map +1 -1
- package/build/extensions/Audio.js +19 -19
- package/build/extensions/Audio.js.map +1 -1
- package/build/extensions/CodeEditor.js +867 -647
- package/build/extensions/CodeEditor.js.map +1 -1
- package/build/extensions/DocMaker.d.ts +1 -1
- package/build/extensions/DocMaker.js +73 -61
- package/build/extensions/DocMaker.js.map +1 -1
- package/build/extensions/GraphEditor.js +406 -305
- package/build/extensions/GraphEditor.js.map +1 -1
- package/build/extensions/ImUi.js +21 -20
- package/build/extensions/ImUi.js.map +1 -1
- package/build/extensions/Timeline.d.ts +29 -36
- package/build/extensions/Timeline.js +421 -424
- package/build/extensions/Timeline.js.map +1 -1
- package/build/extensions/VideoEditor.js +101 -97
- package/build/extensions/VideoEditor.js.map +1 -1
- package/build/extensions/index.d.ts +8 -8
- package/build/extensions/index.js +1 -1
- package/build/index.all.d.ts +2 -2
- package/build/index.css.d.ts +1 -1
- package/build/index.d.ts +56 -55
- package/build/lexgui.all.js +28488 -27640
- package/build/lexgui.all.js.map +1 -1
- package/build/lexgui.all.min.js +1 -1
- package/build/lexgui.all.module.js +28412 -27565
- package/build/lexgui.all.module.js.map +1 -1
- package/build/lexgui.all.module.min.js +1 -1
- package/build/lexgui.css +176 -69
- package/build/lexgui.js +13796 -13330
- package/build/lexgui.js.map +1 -1
- package/build/lexgui.min.css +1 -1
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +13733 -13268
- package/build/lexgui.module.js.map +1 -1
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +22 -1
- package/demo.js +6 -5
- package/examples/all-components.html +3 -0
- package/examples/asset-view.html +52 -6
- package/examples/dialogs.html +3 -3
- package/examples/editor.html +1 -1
- package/examples/index.html +1 -1
- package/package.json +4 -1
|
@@ -3,11 +3,11 @@ import { LX } from '../core/Namespace.js';
|
|
|
3
3
|
|
|
4
4
|
// Timeline.ts @evallsg, @japopra
|
|
5
5
|
if (!LX) {
|
|
6
|
-
throw (
|
|
6
|
+
throw ('Missing LX namespace!');
|
|
7
7
|
}
|
|
8
8
|
LX.extensions.push('Timeline');
|
|
9
|
-
LX.registerIcon(
|
|
10
|
-
LX.registerIcon(
|
|
9
|
+
LX.registerIcon('TimelineLock', '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path fill="none" d="M7 11V7a4 4 0 0 1 9 0v4 M5,11h13 a2 2 0 0 1 2 2 v7 a2 2 0 0 1 -2 2 h-13 a2 2 0 0 1 -2 -2 v-7 a2 2 0 0 1 2 -2 M12 16 v2"/></svg>');
|
|
10
|
+
LX.registerIcon('TimelineLockOpen', '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path fill="none" d="M14 11V7a4 4 0 0 1 9 0v2 M3,11h13 a2 2 0 0 1 2 2 v7 a2 2 0 0 1 -2 2 h-13 a2 2 0 0 1 -2 -2 v-7 a2 2 0 0 1 2 -2 M8 17 h3"/></svg>');
|
|
11
11
|
const Area = LX.Area;
|
|
12
12
|
const Panel = LX.Panel;
|
|
13
13
|
const Dialog = LX.Dialog;
|
|
@@ -63,8 +63,8 @@ class Timeline {
|
|
|
63
63
|
grabbingScroll = false;
|
|
64
64
|
movingKeys = false;
|
|
65
65
|
timeBeforeMove = 0;
|
|
66
|
-
currentScroll = 0; //in percentage
|
|
67
|
-
currentScrollInPixels = 0; //in pixels
|
|
66
|
+
currentScroll = 0; // in percentage
|
|
67
|
+
currentScrollInPixels = 0; // in pixels
|
|
68
68
|
trackHeight = 32;
|
|
69
69
|
timeSeparators = [0.01, 0.1, 0.5, 1, 5];
|
|
70
70
|
playing = false;
|
|
@@ -128,8 +128,8 @@ class Timeline {
|
|
|
128
128
|
this.onShowOptimizeMenu = options.onShowOptimizeMenu;
|
|
129
129
|
this.onShowConfiguration = options.onShowConfiguration;
|
|
130
130
|
this.canvas = document.createElement('canvas');
|
|
131
|
-
this.canvas.style.width =
|
|
132
|
-
this.canvas.style.height =
|
|
131
|
+
this.canvas.style.width = '100%';
|
|
132
|
+
this.canvas.style.height = '100%';
|
|
133
133
|
this.secondsPerPixel = 1 / this.pixelsPerSecond;
|
|
134
134
|
this.animationClip = this.instantiateAnimationClip();
|
|
135
135
|
this.loop = options.loop ?? true;
|
|
@@ -139,35 +139,37 @@ class Timeline {
|
|
|
139
139
|
// main area -- root
|
|
140
140
|
this.mainArea = new Area({ className: 'lextimeline' });
|
|
141
141
|
this.root = this.mainArea.root;
|
|
142
|
-
this.mainArea.split({ type:
|
|
142
|
+
this.mainArea.split({ type: 'vertical', sizes: [this.header_offset, 'auto'], resize: false });
|
|
143
143
|
// header
|
|
144
144
|
this.header = new Panel({ id: 'lextimelineheader' });
|
|
145
145
|
this.mainArea.sections[0].attach(this.header);
|
|
146
146
|
this.updateHeader();
|
|
147
147
|
// content area
|
|
148
148
|
const contentArea = this.mainArea.sections[1];
|
|
149
|
-
contentArea.root.id =
|
|
150
|
-
contentArea.split({ type:
|
|
149
|
+
contentArea.root.id = 'bottom-timeline-area';
|
|
150
|
+
contentArea.split({ type: 'horizontal', sizes: ['15%', '85%'] });
|
|
151
151
|
let [left, right] = contentArea.sections;
|
|
152
152
|
right.attach(this.canvas);
|
|
153
153
|
this.canvasArea = right;
|
|
154
|
-
this.canvasArea.root.classList.add(
|
|
155
|
-
this.leftPanel = left.addPanel({ className: 'lextimelinepanel', width:
|
|
154
|
+
this.canvasArea.root.classList.add('lextimelinearea');
|
|
155
|
+
this.leftPanel = left.addPanel({ className: 'lextimelinepanel', width: '100%', height: '100%' });
|
|
156
156
|
this.updateLeftPanel();
|
|
157
157
|
if (this.uniqueID.length) {
|
|
158
158
|
this.root.id = this.uniqueID;
|
|
159
159
|
this.canvas.id = this.uniqueID + '-canvas';
|
|
160
160
|
}
|
|
161
161
|
// Process mouse events
|
|
162
|
-
this.canvas.addEventListener(
|
|
163
|
-
this.canvas.addEventListener(
|
|
164
|
-
this.canvas.addEventListener(
|
|
165
|
-
this.canvas.addEventListener(
|
|
166
|
-
this.canvas.addEventListener(
|
|
167
|
-
this.canvas.addEventListener(
|
|
162
|
+
this.canvas.addEventListener('mousedown', this.processMouse.bind(this));
|
|
163
|
+
this.canvas.addEventListener('mouseup', this.processMouse.bind(this));
|
|
164
|
+
this.canvas.addEventListener('mousemove', this.processMouse.bind(this));
|
|
165
|
+
this.canvas.addEventListener('wheel', this.processMouse.bind(this));
|
|
166
|
+
this.canvas.addEventListener('dblclick', this.processMouse.bind(this));
|
|
167
|
+
this.canvas.addEventListener('contextmenu', this.processMouse.bind(this));
|
|
168
168
|
this.canvas.tabIndex = 1;
|
|
169
169
|
// Process keys events
|
|
170
|
-
this.canvasArea.root.addEventListener(
|
|
170
|
+
this.canvasArea.root.addEventListener('keydown', (e) => {
|
|
171
|
+
this.processKeys(e);
|
|
172
|
+
}); // so this.processKeys can be overwritten by the user
|
|
171
173
|
this.canvasArea.onresize = (bounding) => {
|
|
172
174
|
if (!(bounding.width && bounding.height)) {
|
|
173
175
|
return;
|
|
@@ -179,23 +181,23 @@ class Timeline {
|
|
|
179
181
|
* updates theme ( light - dark ) based on LX's current theme
|
|
180
182
|
*/
|
|
181
183
|
function updateTheme() {
|
|
182
|
-
Timeline.BACKGROUND_COLOR = LX.getThemeColor(
|
|
183
|
-
Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor(
|
|
184
|
-
Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor(
|
|
185
|
-
Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor(
|
|
186
|
-
Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor(
|
|
187
|
-
Timeline.FONT = LX.getThemeColor(
|
|
188
|
-
Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor(
|
|
189
|
-
Timeline.FONT_COLOR_TERTIARY = LX.getThemeColor(
|
|
190
|
-
Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor(
|
|
191
|
-
Timeline.KEYFRAME_COLOR = LX.getThemeColor(
|
|
192
|
-
Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor(
|
|
193
|
-
Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor(
|
|
194
|
-
Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor(
|
|
195
|
-
Timeline.KEYFRAME_COLOR_INACTIVE = LX.getThemeColor(
|
|
184
|
+
Timeline.BACKGROUND_COLOR = LX.getThemeColor('global-blur-background');
|
|
185
|
+
Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor('global-color-primary');
|
|
186
|
+
Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor('global-color-secondary');
|
|
187
|
+
Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor('global-color-terciary');
|
|
188
|
+
Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor('global-color-quaternary');
|
|
189
|
+
Timeline.FONT = LX.getThemeColor('global-font');
|
|
190
|
+
Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor('global-text-primary');
|
|
191
|
+
Timeline.FONT_COLOR_TERTIARY = LX.getThemeColor('global-text-tertiary');
|
|
192
|
+
Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor('global-text-quaternary');
|
|
193
|
+
Timeline.KEYFRAME_COLOR = LX.getThemeColor('lxTimeline-keyframe');
|
|
194
|
+
Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor('lxTimeline-keyframe-selected');
|
|
195
|
+
Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor('lxTimeline-keyframe-locked');
|
|
196
|
+
Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor('lxTimeline-keyframe-edited');
|
|
197
|
+
Timeline.KEYFRAME_COLOR_INACTIVE = LX.getThemeColor('lxTimeline-keyframe-inactive');
|
|
196
198
|
}
|
|
197
199
|
this.updateTheme = updateTheme.bind(this);
|
|
198
|
-
LX.addSignal(
|
|
200
|
+
LX.addSignal('@on_new_color_scheme', this.updateTheme);
|
|
199
201
|
}
|
|
200
202
|
// makes it ready to be deleted
|
|
201
203
|
clear() {
|
|
@@ -206,7 +208,7 @@ class Timeline {
|
|
|
206
208
|
this.leftPanel.clear();
|
|
207
209
|
}
|
|
208
210
|
if (this.updateTheme) {
|
|
209
|
-
let signals = LX.signals[
|
|
211
|
+
let signals = LX.signals['@on_new_color_scheme'] ?? [];
|
|
210
212
|
for (let i = 0; i < signals.length; ++i) {
|
|
211
213
|
if (signals[i] == this.updateTheme) {
|
|
212
214
|
signals.splice(i, 1);
|
|
@@ -222,73 +224,78 @@ class Timeline {
|
|
|
222
224
|
const header = this.header;
|
|
223
225
|
header.sameLine();
|
|
224
226
|
if (this.timelineTitle) {
|
|
225
|
-
header.addTitle(this.timelineTitle, {
|
|
227
|
+
header.addTitle(this.timelineTitle, {
|
|
228
|
+
style: { background: 'none', fontSize: '18px', fontStyle: 'bold', alignItems: 'center' }
|
|
229
|
+
});
|
|
226
230
|
}
|
|
227
|
-
const buttonContainer = LX.makeContainer([
|
|
231
|
+
const buttonContainer = LX.makeContainer(['auto', '100%'], 'flex flex-row gap-1');
|
|
228
232
|
header.queue(buttonContainer);
|
|
229
|
-
const playbtn = header.addButton(
|
|
233
|
+
const playbtn = header.addButton('playBtn', '', (value, event) => {
|
|
230
234
|
this.changeState();
|
|
231
|
-
}, { buttonClass:
|
|
235
|
+
}, { buttonClass: 'accept', title: 'Play', hideName: true, icon: 'Play@solid', swap: 'Pause@solid' });
|
|
232
236
|
playbtn.setState(this.playing, true);
|
|
233
|
-
header.addButton(
|
|
237
|
+
header.addButton('stopBtn', '', (value, event) => {
|
|
234
238
|
this.setState(false, true); // skip callback of set state
|
|
235
239
|
if (this.onStateStop) {
|
|
236
240
|
this.onStateStop();
|
|
237
241
|
}
|
|
238
|
-
}, { buttonClass:
|
|
239
|
-
header.addButton(
|
|
242
|
+
}, { buttonClass: 'accept', title: 'Stop', hideName: true, icon: 'Stop@solid' });
|
|
243
|
+
header.addButton('loopBtn', '', (value, event) => {
|
|
240
244
|
this.setLoopMode(!this.loop);
|
|
241
|
-
}, { selectable: true, selected: this.loop, title: 'Loop', hideName: true, icon:
|
|
245
|
+
}, { selectable: true, selected: this.loop, title: 'Loop', hideName: true, icon: 'RefreshCw' });
|
|
242
246
|
if (this.onCreateControlsButtons) {
|
|
243
247
|
this.onCreateControlsButtons(header);
|
|
244
248
|
}
|
|
245
249
|
header.clearQueue(buttonContainer);
|
|
246
|
-
header.addContent(
|
|
250
|
+
header.addContent('header-buttons', buttonContainer);
|
|
247
251
|
// time number inputs - duration, current time, etc
|
|
248
252
|
if (this.onCreateBeforeTopBar) {
|
|
249
253
|
this.onCreateBeforeTopBar(header);
|
|
250
254
|
}
|
|
251
|
-
header.addNumber(
|
|
255
|
+
header.addNumber('Current Time', this.currentTime, (value, event) => {
|
|
252
256
|
this.setTime(value);
|
|
253
257
|
}, {
|
|
254
|
-
units:
|
|
255
|
-
step: 0.01,
|
|
258
|
+
units: 's',
|
|
259
|
+
step: 0.01,
|
|
260
|
+
min: 0,
|
|
261
|
+
precision: 3,
|
|
256
262
|
skipSlider: true,
|
|
257
263
|
skipReset: true,
|
|
258
|
-
nameWidth:
|
|
264
|
+
nameWidth: 'auto'
|
|
259
265
|
});
|
|
260
|
-
header.addNumber(
|
|
266
|
+
header.addNumber('Duration', +this.duration.toFixed(3), (value, event) => {
|
|
261
267
|
this.setDuration(value, false, false);
|
|
262
268
|
}, {
|
|
263
|
-
units:
|
|
264
|
-
step: 0.01,
|
|
269
|
+
units: 's',
|
|
270
|
+
step: 0.01,
|
|
271
|
+
min: 0,
|
|
265
272
|
skipReset: true,
|
|
266
|
-
nameWidth:
|
|
273
|
+
nameWidth: 'auto'
|
|
267
274
|
});
|
|
268
275
|
if (this.onCreateAfterTopBar) {
|
|
269
276
|
this.onCreateAfterTopBar(header);
|
|
270
277
|
}
|
|
271
278
|
// settings buttons - optimize, settings, etc
|
|
272
|
-
const buttonContainerEnd = LX.makeContainer([
|
|
279
|
+
const buttonContainerEnd = LX.makeContainer(['auto', '100%'], 'flex flex-row gap-1');
|
|
273
280
|
header.queue(buttonContainerEnd);
|
|
274
281
|
if (this.onCreateSettingsButtons) {
|
|
275
282
|
this.onCreateSettingsButtons(header);
|
|
276
283
|
}
|
|
277
284
|
if (this.onShowOptimizeMenu) {
|
|
278
|
-
header.addButton(null,
|
|
285
|
+
header.addButton(null, '', (value, event) => {
|
|
279
286
|
if (this.onShowOptimizeMenu) {
|
|
280
287
|
this.onShowOptimizeMenu(event);
|
|
281
288
|
}
|
|
282
|
-
}, { tooltip: true, title:
|
|
289
|
+
}, { tooltip: true, title: 'Optimize', icon: 'Filter' });
|
|
283
290
|
}
|
|
284
291
|
if (this.onShowConfiguration) {
|
|
285
|
-
header.addButton(null,
|
|
292
|
+
header.addButton(null, '', (value, event) => {
|
|
286
293
|
if (this.configurationDialog) {
|
|
287
294
|
this.configurationDialog.close();
|
|
288
295
|
this.configurationDialog = null;
|
|
289
296
|
return;
|
|
290
297
|
}
|
|
291
|
-
this.configurationDialog = new Dialog(
|
|
298
|
+
this.configurationDialog = new Dialog('Configuration', (p) => {
|
|
292
299
|
if (this.onShowConfiguration) {
|
|
293
300
|
this.onShowConfiguration(p);
|
|
294
301
|
}
|
|
@@ -298,41 +305,43 @@ class Timeline {
|
|
|
298
305
|
this.configurationDialog = null;
|
|
299
306
|
}
|
|
300
307
|
});
|
|
301
|
-
}, { title:
|
|
308
|
+
}, { title: 'Settings', icon: 'Settings', tooltip: true });
|
|
302
309
|
}
|
|
303
310
|
header.clearQueue(buttonContainerEnd);
|
|
304
|
-
header.addContent(
|
|
305
|
-
header.endLine(
|
|
311
|
+
header.addContent('header-buttons-end', buttonContainerEnd);
|
|
312
|
+
header.endLine('justify-between');
|
|
306
313
|
}
|
|
307
314
|
/**
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
*/
|
|
315
|
+
* @method updateLeftPanel
|
|
316
|
+
*/
|
|
311
317
|
updateLeftPanel() {
|
|
312
318
|
this.leftPanel.clear();
|
|
313
319
|
const panel = this.leftPanel;
|
|
314
320
|
panel.sameLine();
|
|
315
|
-
let titleComponent = panel.addTitle(
|
|
321
|
+
let titleComponent = panel.addTitle('Tracks', { style: { background: 'none' },
|
|
322
|
+
className: 'fg-secondary text-lg px-4' });
|
|
316
323
|
let title = titleComponent.root;
|
|
317
324
|
if (!this.disableNewTracks) {
|
|
318
|
-
panel.addButton(
|
|
325
|
+
panel.addButton('addTrackBtn', '', (value, event) => {
|
|
319
326
|
if (this.onAddNewTrackButton) {
|
|
320
327
|
this.onAddNewTrackButton();
|
|
321
328
|
}
|
|
322
329
|
else {
|
|
323
330
|
this.addNewTrack();
|
|
324
331
|
}
|
|
325
|
-
}, { hideName: true, title:
|
|
332
|
+
}, { hideName: true, title: 'Add Track', icon: 'Plus' });
|
|
326
333
|
}
|
|
327
334
|
panel.endLine();
|
|
328
335
|
const styles = window.getComputedStyle(title);
|
|
329
|
-
const titleHeight = title.clientHeight + parseFloat(styles['marginTop'])
|
|
330
|
-
|
|
336
|
+
const titleHeight = title.clientHeight + parseFloat(styles['marginTop'])
|
|
337
|
+
+ parseFloat(styles['marginBottom']);
|
|
338
|
+
let p = new LX.Panel({ height: 'calc(100% - ' + titleHeight + 'px )' });
|
|
331
339
|
let treeTracks = [];
|
|
332
340
|
if (this.animationClip && this.selectedItems.length) {
|
|
333
341
|
treeTracks = this.generateSelectedItemsTreeData();
|
|
334
342
|
}
|
|
335
|
-
this.trackTreesComponent = p.addTree(null, treeTracks, { filter: false, rename: false, draggable: false,
|
|
343
|
+
this.trackTreesComponent = p.addTree(null, treeTracks, { filter: false, rename: false, draggable: false,
|
|
344
|
+
onevent: (e) => {
|
|
336
345
|
switch (e.type) {
|
|
337
346
|
case LX.TreeEvent.NODE_SELECTED:
|
|
338
347
|
if (!e.event.shiftKey) {
|
|
@@ -363,13 +372,14 @@ class Timeline {
|
|
|
363
372
|
this._updateTrackTreeSelection(); // select visible tracks
|
|
364
373
|
}
|
|
365
374
|
// setting a name in the addTree function adds an undesired node
|
|
366
|
-
this.trackTreesComponent.name =
|
|
375
|
+
this.trackTreesComponent.name = 'tracksTrees';
|
|
367
376
|
p.components[this.trackTreesComponent.name] = this.trackTreesComponent;
|
|
368
377
|
this.trackTreesPanel = p;
|
|
369
378
|
panel.attach(p.root);
|
|
370
|
-
p.root.addEventListener(
|
|
379
|
+
p.root.addEventListener('scroll', (e) => {
|
|
371
380
|
if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) {
|
|
372
|
-
this.currentScroll = e.currentTarget.scrollTop
|
|
381
|
+
this.currentScroll = e.currentTarget.scrollTop
|
|
382
|
+
/ (e.currentTarget.scrollHeight - e.currentTarget.clientHeight);
|
|
373
383
|
this.currentScrollInPixels = e.currentTarget.scrollTop;
|
|
374
384
|
}
|
|
375
385
|
else {
|
|
@@ -379,7 +389,7 @@ class Timeline {
|
|
|
379
389
|
});
|
|
380
390
|
this.trackTreesPanel.root.scrollTop = this.currentScrollInPixels;
|
|
381
391
|
this.setTrackHeight(this.trackHeight);
|
|
382
|
-
if (this.leftPanel.parent.root.classList.contains(
|
|
392
|
+
if (this.leftPanel.parent.root.classList.contains('hidden') || !this.root.parentElement) {
|
|
383
393
|
return;
|
|
384
394
|
}
|
|
385
395
|
this.resizeCanvas();
|
|
@@ -393,9 +403,9 @@ class Timeline {
|
|
|
393
403
|
return;
|
|
394
404
|
}
|
|
395
405
|
trackHeight -= gapSize;
|
|
396
|
-
const tracks = this.trackTreesComponent.root.querySelector(
|
|
406
|
+
const tracks = this.trackTreesComponent.root.querySelector('ul').children;
|
|
397
407
|
for (let i = 0; i < tracks.length; ++i) {
|
|
398
|
-
tracks[i].style.height = trackHeight +
|
|
408
|
+
tracks[i].style.height = trackHeight + 'px';
|
|
399
409
|
}
|
|
400
410
|
}
|
|
401
411
|
/**
|
|
@@ -467,9 +477,9 @@ class Timeline {
|
|
|
467
477
|
return this.animationClip;
|
|
468
478
|
}
|
|
469
479
|
drawTimeInfo(w, h = this.topMargin) {
|
|
470
|
-
let ctx = this.canvas.getContext(
|
|
471
|
-
ctx.font =
|
|
472
|
-
ctx.textAlign =
|
|
480
|
+
let ctx = this.canvas.getContext('2d');
|
|
481
|
+
ctx.font = '11px ' + Timeline.FONT; // "11px Calibri";
|
|
482
|
+
ctx.textAlign = 'center';
|
|
473
483
|
// Draw time markers
|
|
474
484
|
ctx.save();
|
|
475
485
|
// background of timeinfo
|
|
@@ -478,15 +488,12 @@ class Timeline {
|
|
|
478
488
|
ctx.strokeStyle = Timeline.FONT_COLOR_PRIMARY;
|
|
479
489
|
// set tick and sub tick times
|
|
480
490
|
let tickTime = 4;
|
|
481
|
-
if (this.pixelsPerSecond > 900)
|
|
491
|
+
if (this.pixelsPerSecond > 900)
|
|
482
492
|
tickTime = 1;
|
|
483
|
-
|
|
484
|
-
else if (this.pixelsPerSecond > 100) {
|
|
493
|
+
else if (this.pixelsPerSecond > 100)
|
|
485
494
|
tickTime = 2;
|
|
486
|
-
|
|
487
|
-
else if (this.pixelsPerSecond > 50) {
|
|
495
|
+
else if (this.pixelsPerSecond > 50)
|
|
488
496
|
tickTime = 3;
|
|
489
|
-
}
|
|
490
497
|
let subtickTime = this.timeSeparators[tickTime - 1];
|
|
491
498
|
tickTime = this.timeSeparators[tickTime];
|
|
492
499
|
const startTime = this.visualTimeRange[0];
|
|
@@ -520,14 +527,14 @@ class Timeline {
|
|
|
520
527
|
}
|
|
521
528
|
drawTracksBackground(w, h) {
|
|
522
529
|
let canvas = this.canvas;
|
|
523
|
-
let ctx = canvas.getContext(
|
|
530
|
+
let ctx = canvas.getContext('2d');
|
|
524
531
|
let duration = this.duration;
|
|
525
532
|
ctx.globalAlpha = 1;
|
|
526
533
|
// Content
|
|
527
534
|
const topMargin = this.topMargin;
|
|
528
535
|
const treeOffset = this.lastTrackTreesComponentOffset;
|
|
529
536
|
const line_height = this.trackHeight;
|
|
530
|
-
//fill track lines
|
|
537
|
+
// fill track lines
|
|
531
538
|
w = w || canvas.width;
|
|
532
539
|
let max_tracks = Math.ceil((h - topMargin) / line_height) + 1;
|
|
533
540
|
ctx.save();
|
|
@@ -537,13 +544,12 @@ class Timeline {
|
|
|
537
544
|
for (let i = blackOrWhite; i <= max_tracks; i += 2) {
|
|
538
545
|
ctx.fillRect(0, treeOffset - rectsOffset + i * line_height, w, line_height);
|
|
539
546
|
}
|
|
540
|
-
//bg lines
|
|
547
|
+
// bg lines
|
|
541
548
|
ctx.strokeStyle = Timeline.TRACK_COLOR_TERCIARY;
|
|
542
549
|
ctx.beginPath();
|
|
543
550
|
let pos = this.timeToX(0);
|
|
544
|
-
if (pos < 0)
|
|
551
|
+
if (pos < 0)
|
|
545
552
|
pos = 0;
|
|
546
|
-
}
|
|
547
553
|
ctx.lineWidth = 1;
|
|
548
554
|
ctx.moveTo(pos + 0.5, topMargin);
|
|
549
555
|
ctx.lineTo(pos + 0.5, canvas.height);
|
|
@@ -556,19 +562,20 @@ class Timeline {
|
|
|
556
562
|
* @method draw
|
|
557
563
|
*/
|
|
558
564
|
draw() {
|
|
559
|
-
let ctx = this.canvas.getContext(
|
|
560
|
-
ctx.textBaseline =
|
|
561
|
-
ctx.font =
|
|
565
|
+
let ctx = this.canvas.getContext('2d');
|
|
566
|
+
ctx.textBaseline = 'bottom';
|
|
567
|
+
ctx.font = '11px ' + Timeline.FONT; // "11px Calibri";
|
|
562
568
|
ctx.globalAlpha = 1;
|
|
563
569
|
const w = ctx.canvas.width;
|
|
564
570
|
const h = ctx.canvas.height;
|
|
565
571
|
const scrollableHeight = this.trackTreesComponent.root.scrollHeight;
|
|
566
572
|
// tree has gaps of 0.25rem (4px ) inbetween entries but not in the beginning nor ending. Move half gap upwards.
|
|
567
|
-
const treeOffset = this.lastTrackTreesComponentOffset = this.trackTreesComponent.innerTree.domEl.offsetTop
|
|
568
|
-
|
|
569
|
-
|
|
573
|
+
const treeOffset = this.lastTrackTreesComponentOffset = this.trackTreesComponent.innerTree.domEl.offsetTop
|
|
574
|
+
- this.canvas.offsetTop - 2;
|
|
575
|
+
// zoom
|
|
576
|
+
let startTime = this.visualOriginTime; // seconds
|
|
570
577
|
startTime = Math.min(this.duration, Math.max(0, startTime));
|
|
571
|
-
let endTime = this.visualOriginTime + w * this.secondsPerPixel; //seconds
|
|
578
|
+
let endTime = this.visualOriginTime + w * this.secondsPerPixel; // seconds
|
|
572
579
|
endTime = Math.max(startTime, Math.min(this.duration, endTime));
|
|
573
580
|
this.visualTimeRange[0] = startTime;
|
|
574
581
|
this.visualTimeRange[1] = endTime;
|
|
@@ -585,9 +592,9 @@ class Timeline {
|
|
|
585
592
|
this.drawContent(ctx);
|
|
586
593
|
ctx.translate(0, -treeOffset);
|
|
587
594
|
}
|
|
588
|
-
//scrollbar
|
|
595
|
+
// scrollbar
|
|
589
596
|
if ((h - this.topMargin) < scrollableHeight) {
|
|
590
|
-
ctx.fillStyle =
|
|
597
|
+
ctx.fillStyle = '#222';
|
|
591
598
|
ctx.fillRect(w - 10, 0, 10, h);
|
|
592
599
|
ctx.fillStyle = this.grabbingScroll ? Timeline.FONT_COLOR_TERTIARY : Timeline.FONT_COLOR_QUATERNARY;
|
|
593
600
|
let scrollBarHeight = Math.max(10, (h - this.topMargin) * (h - this.topMargin) / this.trackTreesPanel.root.scrollHeight);
|
|
@@ -603,7 +610,7 @@ class Timeline {
|
|
|
603
610
|
ctx.globalAlpha = 1;
|
|
604
611
|
ctx.beginPath();
|
|
605
612
|
ctx.moveTo(posx, posy * 0.6);
|
|
606
|
-
ctx.lineTo(posx, this.canvas.height); //line
|
|
613
|
+
ctx.lineTo(posx, this.canvas.height); // line
|
|
607
614
|
ctx.stroke();
|
|
608
615
|
ctx.closePath();
|
|
609
616
|
ctx.shadowBlur = 8;
|
|
@@ -617,9 +624,9 @@ class Timeline {
|
|
|
617
624
|
ctx.shadowOffsetY = 0;
|
|
618
625
|
}
|
|
619
626
|
// Current time seconds in text
|
|
620
|
-
ctx.font =
|
|
621
|
-
ctx.textAlign =
|
|
622
|
-
//ctx.textBaseline = "middle";
|
|
627
|
+
ctx.font = '11px ' + Timeline.FONT; // "11px Calibri";
|
|
628
|
+
ctx.textAlign = 'center';
|
|
629
|
+
// ctx.textBaseline = "middle";
|
|
623
630
|
ctx.fillStyle = Timeline.TIME_MARKER_COLOR_TEXT;
|
|
624
631
|
ctx.fillText((Math.floor(this.currentTime * 10) * 0.1).toFixed(1), posx, this.topMargin * 0.6);
|
|
625
632
|
// Selections
|
|
@@ -650,7 +657,7 @@ class Timeline {
|
|
|
650
657
|
let v = Math.max(0, t);
|
|
651
658
|
this.duration = this.animationClip.duration = v;
|
|
652
659
|
if (updateHeader) {
|
|
653
|
-
this.header.components[
|
|
660
|
+
this.header.components['Duration'].set(+this.duration.toFixed(2), true); // skipcallback = true
|
|
654
661
|
}
|
|
655
662
|
if (this.onSetDuration && !skipCallback) {
|
|
656
663
|
this.onSetDuration(this.duration);
|
|
@@ -658,7 +665,7 @@ class Timeline {
|
|
|
658
665
|
}
|
|
659
666
|
setTime(time, skipCallback = false) {
|
|
660
667
|
this.currentTime = Math.max(0, Math.min(time, this.duration));
|
|
661
|
-
this.header.components[
|
|
668
|
+
this.header.components['Current Time'].set(+this.currentTime.toFixed(2), true); // skipcallback = true
|
|
662
669
|
if (this.onSetTime && !skipCallback) {
|
|
663
670
|
this.onSetTime(this.currentTime);
|
|
664
671
|
}
|
|
@@ -731,22 +738,22 @@ class Timeline {
|
|
|
731
738
|
let localX = e.offsetX;
|
|
732
739
|
let localY = e.offsetY;
|
|
733
740
|
let timeX = this.timeToX(this.currentTime);
|
|
734
|
-
let isHoveringTimeBar = localY < this.topMargin
|
|
735
|
-
localX > (timeX - 6) && localX < (timeX + 6);
|
|
741
|
+
let isHoveringTimeBar = localY < this.topMargin
|
|
742
|
+
&& localX > (timeX - 6) && localX < (timeX + 6);
|
|
736
743
|
const time = this.xToTime(x);
|
|
737
744
|
if (isHoveringTimeBar) {
|
|
738
|
-
this.canvas.style.cursor =
|
|
745
|
+
this.canvas.style.cursor = 'col-resize';
|
|
739
746
|
}
|
|
740
747
|
else if (this.movingKeys) {
|
|
741
|
-
this.canvas.style.cursor =
|
|
748
|
+
this.canvas.style.cursor = 'grabbing';
|
|
742
749
|
}
|
|
743
750
|
else if (e.shiftKey) {
|
|
744
|
-
this.canvas.style.cursor =
|
|
751
|
+
this.canvas.style.cursor = 'crosshair';
|
|
745
752
|
}
|
|
746
753
|
else {
|
|
747
|
-
this.canvas.style.cursor =
|
|
754
|
+
this.canvas.style.cursor = 'default';
|
|
748
755
|
}
|
|
749
|
-
if (e.type ==
|
|
756
|
+
if (e.type == 'wheel') {
|
|
750
757
|
if (e.shiftKey) {
|
|
751
758
|
if (e.wheelDelta) {
|
|
752
759
|
let mouseTime = this.xToTime(localX);
|
|
@@ -762,14 +769,14 @@ class Timeline {
|
|
|
762
769
|
}
|
|
763
770
|
return;
|
|
764
771
|
}
|
|
765
|
-
const is_inside = x >= 0 && x <= this.size[0]
|
|
766
|
-
y >= 0 && y <= this.size[1];
|
|
772
|
+
const is_inside = x >= 0 && x <= this.size[0]
|
|
773
|
+
&& y >= 0 && y <= this.size[1];
|
|
767
774
|
let track = this.getTracksInRange(localY, localY);
|
|
768
775
|
track = track.length ? track[0] : null;
|
|
769
776
|
e.track = track;
|
|
770
777
|
e.localX = localX;
|
|
771
778
|
e.localY = localY;
|
|
772
|
-
if (e.type ==
|
|
779
|
+
if (e.type == 'mouseup') {
|
|
773
780
|
if (!this.active) {
|
|
774
781
|
this.grabbing = false;
|
|
775
782
|
this.grabbingTimeBar = false;
|
|
@@ -792,7 +799,7 @@ class Timeline {
|
|
|
792
799
|
this.timeBeforeMove = 0;
|
|
793
800
|
this.boxSelection = false; // after mouseup
|
|
794
801
|
}
|
|
795
|
-
if (e.type ==
|
|
802
|
+
if (e.type == 'mousedown') {
|
|
796
803
|
window.getSelection()?.empty(); // if canvas DOM is selected, dragging does not work properly. Deselect it
|
|
797
804
|
// e.preventDefault();
|
|
798
805
|
this.clickTime = LX.getTime();
|
|
@@ -807,13 +814,12 @@ class Timeline {
|
|
|
807
814
|
this.grabbingTimeBar = true;
|
|
808
815
|
this.setTime(time);
|
|
809
816
|
}
|
|
810
|
-
else if ((h - this.topMargin) < this.trackTreesComponent.root.scrollHeight && x > w - 10) // grabbing scroll bar
|
|
811
|
-
{
|
|
817
|
+
else if ((h - this.topMargin) < this.trackTreesComponent.root.scrollHeight && x > w - 10) { // grabbing scroll bar
|
|
812
818
|
this.grabbing = true;
|
|
813
819
|
this.grabbingScroll = true;
|
|
814
820
|
}
|
|
815
|
-
|
|
816
|
-
|
|
821
|
+
// grabbing canvas
|
|
822
|
+
else {
|
|
817
823
|
this.grabbing = true;
|
|
818
824
|
this.grabTime = time;
|
|
819
825
|
this.grabbingTimeBar = isHoveringTimeBar;
|
|
@@ -822,15 +828,14 @@ class Timeline {
|
|
|
822
828
|
}
|
|
823
829
|
}
|
|
824
830
|
}
|
|
825
|
-
else if (e.type ==
|
|
831
|
+
else if (e.type == 'mousemove') {
|
|
826
832
|
if (e.shiftKey && this.active && this.boxSelection) {
|
|
827
833
|
this.boxSelectionEnd[0] = localX;
|
|
828
834
|
this.boxSelectionEnd[1] = localY;
|
|
829
835
|
return; // Handled
|
|
830
836
|
}
|
|
831
|
-
else if (this.grabbing && e.button != 2 && !this.movingKeys) // e.buttons != 2 on mousemove needs to be plural
|
|
832
|
-
|
|
833
|
-
this.canvas.style.cursor = "grabbing";
|
|
837
|
+
else if (this.grabbing && e.button != 2 && !this.movingKeys) { // e.buttons != 2 on mousemove needs to be plural
|
|
838
|
+
this.canvas.style.cursor = 'grabbing';
|
|
834
839
|
if (this.grabbingTimeBar && this.active) {
|
|
835
840
|
this.setTime(time);
|
|
836
841
|
}
|
|
@@ -840,14 +845,15 @@ class Timeline {
|
|
|
840
845
|
this.trackTreesPanel.root.scrollTop = 0;
|
|
841
846
|
}
|
|
842
847
|
else {
|
|
843
|
-
this.trackTreesPanel.root.scrollTop += this.trackTreesPanel.root.scrollHeight * e.deltay
|
|
848
|
+
this.trackTreesPanel.root.scrollTop += this.trackTreesPanel.root.scrollHeight * e.deltay
|
|
849
|
+
/ (h - this.topMargin);
|
|
844
850
|
}
|
|
845
851
|
}
|
|
846
852
|
else {
|
|
847
853
|
// Move timeline in X ( independent of current time )
|
|
848
854
|
var old = this.xToTime(this.lastMouse[0]);
|
|
849
855
|
var now = this.xToTime(e.offsetX);
|
|
850
|
-
this.visualOriginTime +=
|
|
856
|
+
this.visualOriginTime += old - now;
|
|
851
857
|
this.trackTreesPanel.root.scrollTop -= e.deltay; // will automatically call scroll event
|
|
852
858
|
}
|
|
853
859
|
}
|
|
@@ -855,10 +861,10 @@ class Timeline {
|
|
|
855
861
|
this.onMouseMove(e, time);
|
|
856
862
|
}
|
|
857
863
|
}
|
|
858
|
-
else if (e.type ==
|
|
864
|
+
else if (e.type == 'dblclick' && this.onDblClick) {
|
|
859
865
|
this.onDblClick(e);
|
|
860
866
|
}
|
|
861
|
-
else if (e.type ==
|
|
867
|
+
else if (e.type == 'contextmenu' && this.onShowContextMenu && this.active) {
|
|
862
868
|
this.onShowContextMenu(e);
|
|
863
869
|
}
|
|
864
870
|
this.lastMouse[0] = x;
|
|
@@ -884,23 +890,21 @@ class Timeline {
|
|
|
884
890
|
break;
|
|
885
891
|
case 'c':
|
|
886
892
|
case 'C':
|
|
887
|
-
if (e.ctrlKey)
|
|
893
|
+
if (e.ctrlKey)
|
|
888
894
|
this.copySelectedContent();
|
|
889
|
-
}
|
|
890
895
|
break;
|
|
891
896
|
case 'v':
|
|
892
897
|
case 'V':
|
|
893
|
-
if (e.ctrlKey)
|
|
898
|
+
if (e.ctrlKey)
|
|
894
899
|
this.pasteContent(this.currentTime);
|
|
895
|
-
}
|
|
896
900
|
break;
|
|
897
901
|
case ' ':
|
|
898
902
|
e.preventDefault();
|
|
899
903
|
e.stopImmediatePropagation();
|
|
900
904
|
this.changeState();
|
|
901
905
|
break;
|
|
902
|
-
case
|
|
903
|
-
this.canvas.style.cursor =
|
|
906
|
+
case 'Shift':
|
|
907
|
+
this.canvas.style.cursor = 'crosshair';
|
|
904
908
|
break;
|
|
905
909
|
}
|
|
906
910
|
}
|
|
@@ -908,7 +912,7 @@ class Timeline {
|
|
|
908
912
|
* @method changeState
|
|
909
913
|
* @param {Boolean} skipCallback defaults false
|
|
910
914
|
* @description change play/pause state
|
|
911
|
-
|
|
915
|
+
*/
|
|
912
916
|
changeState(skipCallback = false) {
|
|
913
917
|
this.setState(!this.playing, skipCallback);
|
|
914
918
|
}
|
|
@@ -917,7 +921,7 @@ class Timeline {
|
|
|
917
921
|
* @param {Boolean} state
|
|
918
922
|
* @param {Boolean} skipCallback defaults false
|
|
919
923
|
* @description change play/pause state
|
|
920
|
-
|
|
924
|
+
*/
|
|
921
925
|
setState(state, skipCallback = false) {
|
|
922
926
|
this.playing = state;
|
|
923
927
|
this.header.components.playBtn.setState(this.playing, true);
|
|
@@ -934,10 +938,10 @@ class Timeline {
|
|
|
934
938
|
setLoopMode(loopState, skipCallback = false) {
|
|
935
939
|
this.loop = loopState;
|
|
936
940
|
if (this.loop) {
|
|
937
|
-
this.header.components.loopBtn.root.children[0].classList.add(
|
|
941
|
+
this.header.components.loopBtn.root.children[0].classList.add('selected');
|
|
938
942
|
}
|
|
939
943
|
else {
|
|
940
|
-
this.header.components.loopBtn.root.children[0].classList.remove(
|
|
944
|
+
this.header.components.loopBtn.root.children[0].classList.remove('selected');
|
|
941
945
|
}
|
|
942
946
|
if (this.onChangeLoopMode && !skipCallback) {
|
|
943
947
|
this.onChangeLoopMode(this.loop);
|
|
@@ -1076,12 +1080,12 @@ class Timeline {
|
|
|
1076
1080
|
deselectAllElements() {
|
|
1077
1081
|
}
|
|
1078
1082
|
/**
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1083
|
+
* @method setTrackState
|
|
1084
|
+
* @param {Int} trackIdx
|
|
1085
|
+
* @param {Boolean} isEnbaled
|
|
1086
|
+
* @param {Boolean} skipCallback onSetTrackState
|
|
1087
|
+
* @param {Boolean} updateTrackTree updates eye icon of the track, if it is visible in the timeline
|
|
1088
|
+
*/
|
|
1085
1089
|
setTrackState(trackIdx, isEnbaled = true, skipCallback = false, updateTrackTree = true) {
|
|
1086
1090
|
const track = this.animationClip.tracks[trackIdx];
|
|
1087
1091
|
const oldState = track.active;
|
|
@@ -1095,7 +1099,6 @@ class Timeline {
|
|
|
1095
1099
|
}
|
|
1096
1100
|
}
|
|
1097
1101
|
/**
|
|
1098
|
-
*
|
|
1099
1102
|
* @param {Int} trackIdx
|
|
1100
1103
|
* @param {Boolean} isLocked
|
|
1101
1104
|
* @param {Boolean} skipCallback onSetTrackLock
|
|
@@ -1128,9 +1131,8 @@ class Timeline {
|
|
|
1128
1131
|
else {
|
|
1129
1132
|
this.historyUndo.push([undoStep]);
|
|
1130
1133
|
}
|
|
1131
|
-
if (this.historyUndo.length > this.historyMaxSteps)
|
|
1132
|
-
this.historyUndo.shift();
|
|
1133
|
-
} // remove first ( oldest ) element
|
|
1134
|
+
if (this.historyUndo.length > this.historyMaxSteps)
|
|
1135
|
+
this.historyUndo.shift(); // remove first ( oldest ) element
|
|
1134
1136
|
this.historyRedo = [];
|
|
1135
1137
|
}
|
|
1136
1138
|
#undoRedo(isUndo = true) {
|
|
@@ -1155,8 +1157,12 @@ class Timeline {
|
|
|
1155
1157
|
toBeStored.push(combinedStateToStore);
|
|
1156
1158
|
return true;
|
|
1157
1159
|
}
|
|
1158
|
-
undo() {
|
|
1159
|
-
|
|
1160
|
+
undo() {
|
|
1161
|
+
return this.#undoRedo(true);
|
|
1162
|
+
}
|
|
1163
|
+
redo() {
|
|
1164
|
+
return this.#undoRedo(false);
|
|
1165
|
+
}
|
|
1160
1166
|
// historyApplyTrackStep( state, isUndo ) MUST BE IMPLEMENTED BY CHILD CLASS
|
|
1161
1167
|
// historyGenerateTrackStep( trackIdx ) MUST BE IMPLEMENTED BY CHILD CLASS
|
|
1162
1168
|
/**
|
|
@@ -1172,8 +1178,8 @@ class Timeline {
|
|
|
1172
1178
|
this.size[0] = this.root.parentElement.clientWidth;
|
|
1173
1179
|
this.size[1] = this.root.parentElement.clientHeight;
|
|
1174
1180
|
}
|
|
1175
|
-
//this.content_area.setSize([ size[ 0 ], size[ 1 ] - this.header_offset] );
|
|
1176
|
-
this.mainArea.sections[1].root.style.height =
|
|
1181
|
+
// this.content_area.setSize([ size[ 0 ], size[ 1 ] - this.header_offset] );
|
|
1182
|
+
this.mainArea.sections[1].root.style.height = 'calc(100% - ' + this.header_offset + 'px )';
|
|
1177
1183
|
this.size[0] - this.leftPanel.root.clientWidth - 8;
|
|
1178
1184
|
this.mainArea.sections[1]._update(); // update area's this.size attribute
|
|
1179
1185
|
this.resizeCanvas();
|
|
@@ -1183,16 +1189,16 @@ class Timeline {
|
|
|
1183
1189
|
this.canvas.height = this.canvasArea.root.clientHeight;
|
|
1184
1190
|
}
|
|
1185
1191
|
/**
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1192
|
+
* @method hide
|
|
1193
|
+
* Hide timeline area
|
|
1194
|
+
*/
|
|
1189
1195
|
hide() {
|
|
1190
1196
|
this.mainArea.hide();
|
|
1191
1197
|
}
|
|
1192
1198
|
/**
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1199
|
+
* @method show
|
|
1200
|
+
* Show timeline area if it is hidden
|
|
1201
|
+
*/
|
|
1196
1202
|
show() {
|
|
1197
1203
|
this.mainArea.show();
|
|
1198
1204
|
this.resize();
|
|
@@ -1213,7 +1219,8 @@ class Timeline {
|
|
|
1213
1219
|
const treeTracks = [];
|
|
1214
1220
|
for (let i = 0; i < this.selectedItems.length; i++) {
|
|
1215
1221
|
const track = this.selectedItems[i];
|
|
1216
|
-
treeTracks.push({ trackData: track, id: track.id, skipVisibility: this.skipVisibility,
|
|
1222
|
+
treeTracks.push({ trackData: track, id: track.id, skipVisibility: this.skipVisibility,
|
|
1223
|
+
visible: track.active, children: [], actions: this.skipLock ? null : [{
|
|
1217
1224
|
'name': 'Lock edition',
|
|
1218
1225
|
'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
|
|
1219
1226
|
'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
|
|
@@ -1225,14 +1232,13 @@ class Timeline {
|
|
|
1225
1232
|
return treeTracks;
|
|
1226
1233
|
}
|
|
1227
1234
|
/**
|
|
1228
|
-
*
|
|
1229
1235
|
* @param {Object} options set some values for the track instance ( groups and trackIdx not included )
|
|
1230
1236
|
* @returns
|
|
1231
1237
|
*/
|
|
1232
1238
|
instantiateTrack(options = {}, clone = false) {
|
|
1233
1239
|
return {
|
|
1234
1240
|
isTrack: true,
|
|
1235
|
-
id: options.id ?? (Math.floor(performance.now()) +
|
|
1241
|
+
id: options.id ?? (Math.floor(performance.now()) + '_' + Math.floor(Math.random() * 0xffff)), // must be unique, at least inside a group
|
|
1236
1242
|
active: options.active ?? true,
|
|
1237
1243
|
locked: options.locked ?? false,
|
|
1238
1244
|
isSelected: false, // render and lexgui tree
|
|
@@ -1248,39 +1254,39 @@ class Timeline {
|
|
|
1248
1254
|
*/
|
|
1249
1255
|
instantiateAnimationClip(options = {}, clone = false) {
|
|
1250
1256
|
const animationClip = {
|
|
1251
|
-
id: options.id ?? (options.name ??
|
|
1257
|
+
id: options.id ?? (options.name ?? 'animationClip'),
|
|
1252
1258
|
duration: options.duration ?? 0,
|
|
1253
1259
|
tracks: [],
|
|
1254
|
-
data: options.data ?? null
|
|
1260
|
+
data: options.data ?? null // user defined data
|
|
1255
1261
|
};
|
|
1256
1262
|
return animationClip;
|
|
1257
1263
|
}
|
|
1258
1264
|
}
|
|
1259
|
-
Timeline.BACKGROUND_COLOR = LX.getThemeColor(
|
|
1260
|
-
Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor(
|
|
1261
|
-
Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor(
|
|
1262
|
-
Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor(
|
|
1263
|
-
Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor(
|
|
1264
|
-
Timeline.TRACK_SELECTED = LX.getThemeColor(
|
|
1265
|
-
Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor(
|
|
1266
|
-
Timeline.FONT = LX.getThemeColor(
|
|
1267
|
-
Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor(
|
|
1268
|
-
Timeline.FONT_COLOR_TERTIARY = LX.getThemeColor(
|
|
1269
|
-
Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor(
|
|
1270
|
-
Timeline.TIME_MARKER_COLOR = LX.getThemeColor(
|
|
1271
|
-
Timeline.TIME_MARKER_COLOR_TEXT =
|
|
1272
|
-
LX.setThemeColor(
|
|
1273
|
-
LX.setThemeColor(
|
|
1274
|
-
LX.setThemeColor(
|
|
1275
|
-
LX.setThemeColor(
|
|
1276
|
-
LX.setThemeColor(
|
|
1277
|
-
LX.setThemeColor(
|
|
1278
|
-
Timeline.KEYFRAME_COLOR = LX.getThemeColor(
|
|
1279
|
-
Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor(
|
|
1280
|
-
Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor(
|
|
1281
|
-
Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor(
|
|
1282
|
-
Timeline.KEYFRAME_COLOR_INACTIVE = LX.getThemeColor(
|
|
1283
|
-
Timeline.BOX_SELECTION_COLOR =
|
|
1265
|
+
Timeline.BACKGROUND_COLOR = LX.getThemeColor('global-blur-background');
|
|
1266
|
+
Timeline.TRACK_COLOR_PRIMARY = LX.getThemeColor('global-color-primary');
|
|
1267
|
+
Timeline.TRACK_COLOR_SECONDARY = LX.getThemeColor('global-color-secondary');
|
|
1268
|
+
Timeline.TRACK_COLOR_TERCIARY = LX.getThemeColor('global-color-terciary');
|
|
1269
|
+
Timeline.TRACK_COLOR_QUATERNARY = LX.getThemeColor('global-color-quaternary');
|
|
1270
|
+
Timeline.TRACK_SELECTED = LX.getThemeColor('global-color-accent');
|
|
1271
|
+
Timeline.TRACK_SELECTED_LIGHT = LX.getThemeColor('global-color-accent-light');
|
|
1272
|
+
Timeline.FONT = LX.getThemeColor('global-font');
|
|
1273
|
+
Timeline.FONT_COLOR_PRIMARY = LX.getThemeColor('global-text-primary');
|
|
1274
|
+
Timeline.FONT_COLOR_TERTIARY = LX.getThemeColor('global-text-tertiary');
|
|
1275
|
+
Timeline.FONT_COLOR_QUATERNARY = LX.getThemeColor('global-text-quaternary');
|
|
1276
|
+
Timeline.TIME_MARKER_COLOR = LX.getThemeColor('global-color-accent');
|
|
1277
|
+
Timeline.TIME_MARKER_COLOR_TEXT = '#ffffff';
|
|
1278
|
+
LX.setThemeColor('lxTimeline-keyframe', 'light-dark(#2d69da,#2d69da )');
|
|
1279
|
+
LX.setThemeColor('lxTimeline-keyframe-selected', 'light-dark(#f5c700,#fafa14)');
|
|
1280
|
+
LX.setThemeColor('lxTimeline-keyframe-hovered', 'light-dark(#f5c700,#fafa14)');
|
|
1281
|
+
LX.setThemeColor('lxTimeline-keyframe-locked', 'light-dark(#c62e2e,#ff7d7d )');
|
|
1282
|
+
LX.setThemeColor('lxTimeline-keyframe-edited', 'light-dark(#00d000,#00d000 )');
|
|
1283
|
+
LX.setThemeColor('lxTimeline-keyframe-inactive', 'light-dark(#706b6b,#706b6b)');
|
|
1284
|
+
Timeline.KEYFRAME_COLOR = LX.getThemeColor('lxTimeline-keyframe');
|
|
1285
|
+
Timeline.KEYFRAME_COLOR_SELECTED = Timeline.KEYFRAME_COLOR_HOVERED = LX.getThemeColor('lxTimeline-keyframe-selected');
|
|
1286
|
+
Timeline.KEYFRAME_COLOR_LOCK = LX.getThemeColor('lxTimeline-keyframe-locked');
|
|
1287
|
+
Timeline.KEYFRAME_COLOR_EDITED = LX.getThemeColor('lxTimeline-keyframe-edited');
|
|
1288
|
+
Timeline.KEYFRAME_COLOR_INACTIVE = LX.getThemeColor('lxTimeline-keyframe-inactive');
|
|
1289
|
+
Timeline.BOX_SELECTION_COLOR = '#AAA';
|
|
1284
1290
|
LX.Timeline = Timeline;
|
|
1285
1291
|
/**
|
|
1286
1292
|
* @class KeyFramesTimeline
|
|
@@ -1313,15 +1319,15 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1313
1319
|
this.defaultCurvesRange = [0, 1]; // whn a track with dim == 1 has no curves attribute, defaultCurves will be used instead. If true, track is rendered using curves
|
|
1314
1320
|
this.keyframeSize = this.trackHeight * 0.5; // height of keyframe
|
|
1315
1321
|
this.keyframeSizeHovered = this.trackHeight * 0.5 + 5;
|
|
1316
|
-
if (options.onShowOptimizeMenu && typeof options.onShowOptimizeMenu ==
|
|
1322
|
+
if (options.onShowOptimizeMenu && typeof options.onShowOptimizeMenu == 'boolean') {
|
|
1317
1323
|
this.onShowOptimizeMenu = (e) => {
|
|
1318
1324
|
if (this.selectedItems.length == 0) {
|
|
1319
1325
|
return;
|
|
1320
1326
|
}
|
|
1321
|
-
LX.addContextMenu(
|
|
1322
|
-
this.selectedItems.forEach(item => {
|
|
1327
|
+
LX.addContextMenu('Optimize', e, (m) => {
|
|
1328
|
+
this.selectedItems.forEach((item) => {
|
|
1323
1329
|
if (item.isTrack) {
|
|
1324
|
-
m.add((item.groupId ? item.groupId :
|
|
1330
|
+
m.add((item.groupId ? item.groupId : '') + '@' + item.id, () => {
|
|
1325
1331
|
this.optimizeTrack(item.trackIdx, false);
|
|
1326
1332
|
});
|
|
1327
1333
|
}
|
|
@@ -1329,7 +1335,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1329
1335
|
const tracks = this.animationClip.tracksPerGroup[item];
|
|
1330
1336
|
for (let i = 0; i < tracks.length; ++i) {
|
|
1331
1337
|
const t = tracks[i];
|
|
1332
|
-
m.add((t.groupId ? t.groupId :
|
|
1338
|
+
m.add((t.groupId ? t.groupId : '') + '@' + t.id, () => {
|
|
1333
1339
|
this.optimizeTrack(t.trackIdx, false);
|
|
1334
1340
|
});
|
|
1335
1341
|
}
|
|
@@ -1344,20 +1350,20 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1344
1350
|
let actions = [];
|
|
1345
1351
|
if (this.lastKeyFramesSelected && this.lastKeyFramesSelected.length) {
|
|
1346
1352
|
actions.push({
|
|
1347
|
-
title:
|
|
1353
|
+
title: 'Copy',
|
|
1348
1354
|
callback: () => {
|
|
1349
1355
|
this.copySelectedContent();
|
|
1350
1356
|
}
|
|
1351
1357
|
});
|
|
1352
1358
|
actions.push({
|
|
1353
|
-
title:
|
|
1359
|
+
title: 'Delete',
|
|
1354
1360
|
callback: () => {
|
|
1355
1361
|
this.deleteSelectedContent();
|
|
1356
1362
|
}
|
|
1357
1363
|
});
|
|
1358
1364
|
if (this.lastKeyFramesSelected.length == 1 && this.clipboard && this.clipboard.value) {
|
|
1359
1365
|
actions.push({
|
|
1360
|
-
title:
|
|
1366
|
+
title: 'Paste Value',
|
|
1361
1367
|
callback: () => {
|
|
1362
1368
|
this.pasteContentValue();
|
|
1363
1369
|
}
|
|
@@ -1366,7 +1372,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1366
1372
|
}
|
|
1367
1373
|
else {
|
|
1368
1374
|
actions.push({
|
|
1369
|
-
title:
|
|
1375
|
+
title: 'Add Here',
|
|
1370
1376
|
callback: () => {
|
|
1371
1377
|
if (!e.track)
|
|
1372
1378
|
return;
|
|
@@ -1376,7 +1382,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1376
1382
|
}
|
|
1377
1383
|
});
|
|
1378
1384
|
actions.push({
|
|
1379
|
-
title:
|
|
1385
|
+
title: 'Add',
|
|
1380
1386
|
callback: () => {
|
|
1381
1387
|
if (!e.track)
|
|
1382
1388
|
return;
|
|
@@ -1388,19 +1394,19 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1388
1394
|
}
|
|
1389
1395
|
if (this.clipboard && this.clipboard.keyframes) {
|
|
1390
1396
|
actions.push({
|
|
1391
|
-
title:
|
|
1397
|
+
title: 'Paste Here',
|
|
1392
1398
|
callback: () => {
|
|
1393
1399
|
this.pasteContent(this.xToTime(e.localX));
|
|
1394
1400
|
}
|
|
1395
1401
|
});
|
|
1396
1402
|
actions.push({
|
|
1397
|
-
title:
|
|
1403
|
+
title: 'Paste',
|
|
1398
1404
|
callback: () => {
|
|
1399
1405
|
this.pasteContent(this.currentTime);
|
|
1400
1406
|
}
|
|
1401
1407
|
});
|
|
1402
1408
|
}
|
|
1403
|
-
LX.addContextMenu(
|
|
1409
|
+
LX.addContextMenu('Options', e, (m) => {
|
|
1404
1410
|
for (let i = 0; i < actions.length; i++) {
|
|
1405
1411
|
m.add(actions[i].title, actions[i].callback);
|
|
1406
1412
|
}
|
|
@@ -1421,7 +1427,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1421
1427
|
const nodes = [];
|
|
1422
1428
|
for (let j = 0; j < itemTracks.length; j++) {
|
|
1423
1429
|
const track = itemTracks[j];
|
|
1424
|
-
nodes.push({ 'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility,
|
|
1430
|
+
nodes.push({ 'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility,
|
|
1431
|
+
visible: track.active, 'children': [], actions: this.skipLock ? null : [{
|
|
1425
1432
|
'name': 'Lock edition',
|
|
1426
1433
|
'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
|
|
1427
1434
|
'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
|
|
@@ -1453,8 +1460,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1453
1460
|
instantiateTrack(options = {}, clone = false) {
|
|
1454
1461
|
const track = super.instantiateTrack(options);
|
|
1455
1462
|
track.dim = Math.max(1, options.dim ?? 1); // >= 1
|
|
1456
|
-
track.groupId = null,
|
|
1457
|
-
track.groupTrackIdx = -1, // track Idx inside group only if in group
|
|
1463
|
+
track.groupId = null, track.groupTrackIdx = -1, // track Idx inside group only if in group
|
|
1458
1464
|
track.values = new Float32Array(0);
|
|
1459
1465
|
track.times = new Float32Array(0);
|
|
1460
1466
|
track.selected = [];
|
|
@@ -1504,9 +1510,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1504
1510
|
let values = track.values ?? [];
|
|
1505
1511
|
let valueDim = track.dim;
|
|
1506
1512
|
if (!valueDim || valueDim < 0) {
|
|
1507
|
-
if (times.length && values.length)
|
|
1513
|
+
if (times.length && values.length)
|
|
1508
1514
|
valueDim = Math.round(values.length / times.length);
|
|
1509
|
-
}
|
|
1510
1515
|
else {
|
|
1511
1516
|
valueDim = 1;
|
|
1512
1517
|
}
|
|
@@ -1541,7 +1546,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1541
1546
|
// overwrite trackspergroup
|
|
1542
1547
|
if (animation.tracksPerGroup) {
|
|
1543
1548
|
// ungroup all tracks (just in case )
|
|
1544
|
-
animationClip.tracks.forEach((v, i) => {
|
|
1549
|
+
animationClip.tracks.forEach((v, i) => {
|
|
1550
|
+
v.groupId = null;
|
|
1551
|
+
v.groupTrackIdx = -1;
|
|
1552
|
+
});
|
|
1545
1553
|
animationClip.tracksPerGroup = {};
|
|
1546
1554
|
let tpg = animation.tracksPerGroup;
|
|
1547
1555
|
for (let groupId in tpg) {
|
|
@@ -1590,8 +1598,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1590
1598
|
if (itemsToAdd) {
|
|
1591
1599
|
for (let i = 0; i < itemsToAdd.length; ++i) {
|
|
1592
1600
|
const v = itemsToAdd[i];
|
|
1593
|
-
if (isNaN(v)) // assuming it is a string
|
|
1594
|
-
{
|
|
1601
|
+
if (isNaN(v)) { // assuming it is a string
|
|
1595
1602
|
if (tracksPerGroup[v]) {
|
|
1596
1603
|
this.selectedItems.push(v);
|
|
1597
1604
|
}
|
|
@@ -1642,7 +1649,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1642
1649
|
for (let i = 0; i < groupTracks.length; ++i) {
|
|
1643
1650
|
const v = groupTracks[i];
|
|
1644
1651
|
let track = null;
|
|
1645
|
-
if (typeof v ==
|
|
1652
|
+
if (typeof v == 'string') {
|
|
1646
1653
|
// v is an id ( string)
|
|
1647
1654
|
for (let t = 0; t < tracks.length; ++t) {
|
|
1648
1655
|
if (tracks[t].id == v) {
|
|
@@ -1692,7 +1699,6 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1692
1699
|
return null;
|
|
1693
1700
|
}
|
|
1694
1701
|
/**
|
|
1695
|
-
*
|
|
1696
1702
|
* @param {Number} size pixels, height of keyframe
|
|
1697
1703
|
* @param {Number} sizeHovered optional, size in pixels when hovered
|
|
1698
1704
|
*/
|
|
@@ -1710,9 +1716,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1710
1716
|
const thresholdPixels = this.keyframeSize * 0.5; // radius of circle ( curves ) or rotated square (keyframes )
|
|
1711
1717
|
const keyFrameIdx = this.getCurrentKeyFrame(track, this.xToTime(localX), this.secondsPerPixel * thresholdPixels);
|
|
1712
1718
|
if (keyFrameIdx > -1) {
|
|
1713
|
-
track.selected[keyFrameIdx]
|
|
1714
|
-
this.deselectKeyFrame(track.trackIdx, keyFrameIdx)
|
|
1715
|
-
this.processSelectionKeyFrame(track.trackIdx, keyFrameIdx, true);
|
|
1719
|
+
track.selected[keyFrameIdx]
|
|
1720
|
+
? this.deselectKeyFrame(track.trackIdx, keyFrameIdx)
|
|
1721
|
+
: this.processSelectionKeyFrame(track.trackIdx, keyFrameIdx, true);
|
|
1716
1722
|
}
|
|
1717
1723
|
}
|
|
1718
1724
|
// Box selection
|
|
@@ -1728,8 +1734,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1728
1734
|
}
|
|
1729
1735
|
}
|
|
1730
1736
|
}
|
|
1731
|
-
else if (!this.movingKeys && !discard) // if not moving timeline and not adding keyframes through e.shiftkey (just a click )
|
|
1732
|
-
{
|
|
1737
|
+
else if (!this.movingKeys && !discard) { // if not moving timeline and not adding keyframes through e.shiftkey (just a click )
|
|
1733
1738
|
if (this.lastKeyFramesSelected.length) {
|
|
1734
1739
|
if (this.onDeselectKeyFrames) {
|
|
1735
1740
|
this.onDeselectKeyFrames(this.lastKeyFramesSelected);
|
|
@@ -1751,17 +1756,15 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1751
1756
|
let localX = e.localX;
|
|
1752
1757
|
e.localY;
|
|
1753
1758
|
e.track;
|
|
1754
|
-
if ((e.ctrlKey || e.altKey) && this.lastKeyFramesSelected.length) // move keyframes
|
|
1755
|
-
{
|
|
1759
|
+
if ((e.ctrlKey || e.altKey) && this.lastKeyFramesSelected.length) { // move keyframes
|
|
1756
1760
|
this.movingKeys = true;
|
|
1757
|
-
this.canvas.style.cursor =
|
|
1761
|
+
this.canvas.style.cursor = 'grab';
|
|
1758
1762
|
this.canvas.classList.add('grabbing');
|
|
1759
1763
|
// Set pre-move state
|
|
1760
1764
|
this.moveKeyMinTime = Infinity;
|
|
1761
1765
|
const tracks = this.animationClip.tracks;
|
|
1762
1766
|
let lastTrackIdx = -1;
|
|
1763
|
-
for (let selectedKey of this.lastKeyFramesSelected) // WARNING assumes lasKeyFramesSelected is sorted, so all keyframes of the same track are grouped
|
|
1764
|
-
{
|
|
1767
|
+
for (let selectedKey of this.lastKeyFramesSelected) { // WARNING assumes lasKeyFramesSelected is sorted, so all keyframes of the same track are grouped
|
|
1765
1768
|
let [trackIdx, keyIndex, keyTime] = selectedKey;
|
|
1766
1769
|
const track = tracks[trackIdx];
|
|
1767
1770
|
selectedKey[2] = track.times[keyIndex]; // update original time just in case
|
|
@@ -1787,8 +1790,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1787
1790
|
let localX = e.localX;
|
|
1788
1791
|
e.localY;
|
|
1789
1792
|
let track = e.track;
|
|
1790
|
-
if (this.movingKeys) // move keyframes
|
|
1791
|
-
{
|
|
1793
|
+
if (this.movingKeys) { // move keyframes
|
|
1792
1794
|
let newTime = this.xToTime(localX);
|
|
1793
1795
|
let deltaTime = newTime - this.timeBeforeMove;
|
|
1794
1796
|
if (deltaTime + this.moveKeyMinTime < 0) {
|
|
@@ -1808,7 +1810,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1808
1810
|
if (track && track.locked) {
|
|
1809
1811
|
continue;
|
|
1810
1812
|
}
|
|
1811
|
-
this.canvas.style.cursor =
|
|
1813
|
+
this.canvas.style.cursor = 'grabbing';
|
|
1812
1814
|
const times = this.animationClip.tracks[track.trackIdx].times;
|
|
1813
1815
|
times[keyIndex] = Math.max(0, times[keyIndex] + deltaTime);
|
|
1814
1816
|
if (times[keyIndex] > this.duration) {
|
|
@@ -1866,8 +1868,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1866
1868
|
}
|
|
1867
1869
|
lastTrackChanged = track.trackIdx;
|
|
1868
1870
|
}
|
|
1869
|
-
if (this.onUpdateTrack && lastTrackChanged > -1) // do the last update, once the last track has been processed
|
|
1870
|
-
{
|
|
1871
|
+
if (this.onUpdateTrack && lastTrackChanged > -1) { // do the last update, once the last track has been processed
|
|
1871
1872
|
this.onUpdateTrack([track.trackIdx]);
|
|
1872
1873
|
}
|
|
1873
1874
|
return;
|
|
@@ -1902,14 +1903,14 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1902
1903
|
let offset = scrollY;
|
|
1903
1904
|
// compute track from which to start rendering ( avoid rendering unseen tracks )
|
|
1904
1905
|
let startElIdx = 0;
|
|
1905
|
-
if (offset < -this.lastTrackTreesComponentOffset) // offset 0 = ( 0 of canvas ) + track-Tree-Offset. This renders tracks under the time zone
|
|
1906
|
-
{
|
|
1906
|
+
if (offset < -this.lastTrackTreesComponentOffset) { // offset 0 = ( 0 of canvas ) + track-Tree-Offset. This renders tracks under the time zone
|
|
1907
1907
|
startElIdx = Math.floor(-(offset + this.lastTrackTreesComponentOffset) / this.trackHeight); // how many tracks to skip
|
|
1908
1908
|
offset += startElIdx * this.trackHeight;
|
|
1909
1909
|
}
|
|
1910
1910
|
ctx.translate(0, offset);
|
|
1911
1911
|
// compute track to end rendering ( avoid rendering unseen tracks )
|
|
1912
|
-
let endElIdx = startElIdx
|
|
1912
|
+
let endElIdx = startElIdx
|
|
1913
|
+
+ Math.ceil((ctx.canvas.height - this.lastTrackTreesComponentOffset - offset) / this.trackHeight);
|
|
1913
1914
|
endElIdx = endElIdx > visibleElements.length ? visibleElements.length : endElIdx;
|
|
1914
1915
|
for (let t = startElIdx; t < endElIdx; t++) {
|
|
1915
1916
|
const track = visibleElements[t].treeData.trackData;
|
|
@@ -1925,13 +1926,12 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1925
1926
|
}
|
|
1926
1927
|
ctx.restore();
|
|
1927
1928
|
}
|
|
1928
|
-
;
|
|
1929
1929
|
/**
|
|
1930
1930
|
* @method drawTrackWithKeyframes
|
|
1931
1931
|
* @param {*} ctx
|
|
1932
1932
|
* ...
|
|
1933
1933
|
* @description helper function, you can call it from drawContent to render all the keyframes
|
|
1934
|
-
|
|
1934
|
+
*/
|
|
1935
1935
|
drawTrackWithKeyframes(ctx, trackHeight, track) {
|
|
1936
1936
|
if (track.isSelected) {
|
|
1937
1937
|
ctx.globalAlpha = 0.2;
|
|
@@ -1990,23 +1990,27 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1990
1990
|
const values = track.values;
|
|
1991
1991
|
const defaultPointSize = this.keyframeSize * 0.5; // radius
|
|
1992
1992
|
const hoverPointSize = this.keyframeSizeHovered * 0.5; // radius
|
|
1993
|
-
const valueRange = track.curvesRange; //[ min, max ]
|
|
1993
|
+
const valueRange = track.curvesRange; // [ min, max ]
|
|
1994
1994
|
const displayRange = trackHeight - defaultPointSize * 2;
|
|
1995
1995
|
const startTime = this.visualTimeRange[0];
|
|
1996
1996
|
const endTime = this.visualTimeRange[1] + 0.0000001;
|
|
1997
|
-
//draw lines
|
|
1997
|
+
// draw lines
|
|
1998
1998
|
ctx.strokeStyle = KeyFramesTimeline.FONT_COLOR_PRIMARY;
|
|
1999
1999
|
ctx.beginPath();
|
|
2000
2000
|
if (keyframes.length > 1) {
|
|
2001
2001
|
let startPosX = this.timeToX(keyframes[0]);
|
|
2002
2002
|
let startValue = values[0];
|
|
2003
|
-
startValue =
|
|
2003
|
+
startValue =
|
|
2004
|
+
LX.clamp((startValue - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1) * (-displayRange)
|
|
2005
|
+
+ (trackHeight - defaultPointSize); // normalize and offset
|
|
2004
2006
|
ctx.moveTo(startPosX, startValue);
|
|
2005
2007
|
for (let j = 1; j < keyframes.length; ++j) {
|
|
2006
2008
|
let time = keyframes[j];
|
|
2007
2009
|
let keyframePosX = this.timeToX(time);
|
|
2008
2010
|
let value = values[j];
|
|
2009
|
-
value =
|
|
2011
|
+
value =
|
|
2012
|
+
LX.clamp((value - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1) * (-displayRange)
|
|
2013
|
+
+ (trackHeight - defaultPointSize); // normalize and offset
|
|
2010
2014
|
if (time < startTime) {
|
|
2011
2015
|
ctx.moveTo(keyframePosX, value);
|
|
2012
2016
|
continue;
|
|
@@ -2016,41 +2020,49 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2016
2020
|
let dt = keyframePosX - lastKeyframePosX;
|
|
2017
2021
|
if (dt > 0) {
|
|
2018
2022
|
let lastValue = values[j - 1];
|
|
2019
|
-
lastValue = LX.clamp((lastValue - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1)
|
|
2023
|
+
lastValue = LX.clamp((lastValue - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1)
|
|
2024
|
+
* (-displayRange) + (trackHeight - defaultPointSize); // normalize and offset
|
|
2020
2025
|
let f = (this.timeToX(endTime) - lastKeyframePosX) / dt;
|
|
2021
2026
|
ctx.lineTo(lastKeyframePosX + dt * f, lastValue * (1 - f) + value * f);
|
|
2022
2027
|
}
|
|
2023
|
-
break; //end loop, but print line
|
|
2028
|
+
break; // end loop, but print line
|
|
2024
2029
|
}
|
|
2025
|
-
//convert to timeline track range
|
|
2030
|
+
// convert to timeline track range
|
|
2026
2031
|
ctx.lineTo(keyframePosX, value);
|
|
2027
2032
|
}
|
|
2028
2033
|
ctx.stroke();
|
|
2029
2034
|
}
|
|
2030
|
-
//draw points
|
|
2035
|
+
// draw points
|
|
2031
2036
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR;
|
|
2032
2037
|
for (let j = 0; j < keyframes.length; ++j) {
|
|
2033
2038
|
let time = keyframes[j];
|
|
2034
|
-
if (time < startTime || time > endTime)
|
|
2039
|
+
if (time < startTime || time > endTime) {
|
|
2035
2040
|
continue;
|
|
2041
|
+
}
|
|
2036
2042
|
let size = defaultPointSize;
|
|
2037
2043
|
let keyframePosX = this.timeToX(time);
|
|
2038
|
-
if (!this.active || !track.active)
|
|
2044
|
+
if (!this.active || !track.active) {
|
|
2039
2045
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_INACTIVE;
|
|
2040
|
-
|
|
2046
|
+
}
|
|
2047
|
+
else if (track.locked) {
|
|
2041
2048
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_LOCK;
|
|
2049
|
+
}
|
|
2042
2050
|
else if (track.hovered[j]) {
|
|
2043
2051
|
size = hoverPointSize;
|
|
2044
2052
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_HOVERED;
|
|
2045
2053
|
}
|
|
2046
|
-
else if (track.selected[j])
|
|
2054
|
+
else if (track.selected[j]) {
|
|
2047
2055
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_SELECTED;
|
|
2048
|
-
|
|
2056
|
+
}
|
|
2057
|
+
else if (track.edited[j]) {
|
|
2049
2058
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_EDITED;
|
|
2050
|
-
|
|
2059
|
+
}
|
|
2060
|
+
else {
|
|
2051
2061
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR;
|
|
2062
|
+
}
|
|
2052
2063
|
let value = values[j];
|
|
2053
|
-
value = LX.clamp((value - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1) * (-displayRange)
|
|
2064
|
+
value = LX.clamp((value - valueRange[0]) / (valueRange[1] - valueRange[0]), 0, 1) * (-displayRange)
|
|
2065
|
+
+ (trackHeight - defaultPointSize); // normalize, clamp and offset
|
|
2054
2066
|
ctx.beginPath();
|
|
2055
2067
|
ctx.arc(keyframePosX, value, size, 0, Math.PI * 2);
|
|
2056
2068
|
ctx.fill();
|
|
@@ -2062,12 +2074,12 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2062
2074
|
let trackId = null;
|
|
2063
2075
|
let trackNameInfo;
|
|
2064
2076
|
// Support other versions
|
|
2065
|
-
if (uglyName.includes(
|
|
2077
|
+
if (uglyName.includes('[')) {
|
|
2066
2078
|
const nameIndex = uglyName.indexOf('[');
|
|
2067
|
-
trackNameInfo = uglyName.substring(nameIndex + 1).split(
|
|
2079
|
+
trackNameInfo = uglyName.substring(nameIndex + 1).split('].');
|
|
2068
2080
|
}
|
|
2069
2081
|
else {
|
|
2070
|
-
trackNameInfo = uglyName.split(
|
|
2082
|
+
trackNameInfo = uglyName.split('.');
|
|
2071
2083
|
}
|
|
2072
2084
|
if (trackNameInfo.length > 1) {
|
|
2073
2085
|
groupId = trackNameInfo[0];
|
|
@@ -2085,8 +2097,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2085
2097
|
* @returns
|
|
2086
2098
|
*/
|
|
2087
2099
|
updateTrack(trackIdx, newTrack) {
|
|
2088
|
-
if (!this.animationClip)
|
|
2100
|
+
if (!this.animationClip) {
|
|
2089
2101
|
return false;
|
|
2102
|
+
}
|
|
2090
2103
|
const track = this.animationClip.tracks[trackIdx];
|
|
2091
2104
|
track.values = newTrack.values;
|
|
2092
2105
|
track.times = newTrack.times;
|
|
@@ -2109,7 +2122,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2109
2122
|
if (track.locked) {
|
|
2110
2123
|
return;
|
|
2111
2124
|
}
|
|
2112
|
-
let cmpFunction = (v, p, n, t) => {
|
|
2125
|
+
let cmpFunction = (v, p, n, t) => {
|
|
2126
|
+
return Math.abs(v - p) >= t || Math.abs(v - n) >= t;
|
|
2127
|
+
};
|
|
2113
2128
|
let lastSavedIndex = 0;
|
|
2114
2129
|
const lastIndex = times.length - 1;
|
|
2115
2130
|
this.saveState(track.trackIdx);
|
|
@@ -2221,22 +2236,19 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2221
2236
|
track.times = state.t;
|
|
2222
2237
|
track.values = state.v;
|
|
2223
2238
|
track.edited = state.edited;
|
|
2224
|
-
if (track.selected.length != track.times.length)
|
|
2239
|
+
if (track.selected.length != track.times.length)
|
|
2225
2240
|
track.selected.length = track.times.length;
|
|
2226
|
-
|
|
2227
|
-
if (track.hovered.length != track.times.length) {
|
|
2241
|
+
if (track.hovered.length != track.times.length)
|
|
2228
2242
|
track.hovered.length = track.times.length;
|
|
2229
|
-
}
|
|
2230
2243
|
track.selected.fill(false);
|
|
2231
2244
|
track.hovered.fill(false);
|
|
2232
2245
|
return stateToReturn;
|
|
2233
2246
|
}
|
|
2234
2247
|
/**
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
*/
|
|
2248
|
+
* @param {*} track
|
|
2249
|
+
* @param {Number} srcIdx keyFrame index
|
|
2250
|
+
* @param {Number} trgIdx keyFrame index
|
|
2251
|
+
*/
|
|
2240
2252
|
swapKeyFrames(track, srcIdx, trgIdx) {
|
|
2241
2253
|
const times = track.times;
|
|
2242
2254
|
const values = track.values;
|
|
@@ -2352,9 +2364,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2352
2364
|
const clipboardItem = this.animationClip.tracks[trackIdx];
|
|
2353
2365
|
// ensure all tracks are visible
|
|
2354
2366
|
const idx = this.selectedItems.findIndex((item) => {
|
|
2355
|
-
if (item.isTrack)
|
|
2367
|
+
if (item.isTrack)
|
|
2356
2368
|
return (item === clipboardItem);
|
|
2357
|
-
}
|
|
2358
2369
|
return item === clipboardItem.groupId;
|
|
2359
2370
|
});
|
|
2360
2371
|
if (idx == -1) {
|
|
@@ -2499,7 +2510,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2499
2510
|
return resultIndices;
|
|
2500
2511
|
}
|
|
2501
2512
|
deleteSelectedContent(skipCallback = false) {
|
|
2502
|
-
|
|
2513
|
+
// *********** WARNING: RELIES ON SORTED lastKeyFramesSelected ***********
|
|
2503
2514
|
if (!this.lastKeyFramesSelected.length)
|
|
2504
2515
|
return;
|
|
2505
2516
|
const tracks = this.animationClip.tracks;
|
|
@@ -2560,8 +2571,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2560
2571
|
track.times = newTimes;
|
|
2561
2572
|
track.values = newValues;
|
|
2562
2573
|
// Update animation action interpolation info
|
|
2563
|
-
if (this.onDeleteKeyFrames && !skipCallback)
|
|
2574
|
+
if (this.onDeleteKeyFrames && !skipCallback) {
|
|
2564
2575
|
this.onDeleteKeyFrames(trackIdx, indices);
|
|
2576
|
+
}
|
|
2565
2577
|
if ((newTimes[newTimes.length - 1]) > this.duration) {
|
|
2566
2578
|
this.setDuration(newTimes[newTimes.length - 1]);
|
|
2567
2579
|
}
|
|
@@ -2583,7 +2595,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2583
2595
|
if (!track || !track.times || !track.times.length) {
|
|
2584
2596
|
return -1;
|
|
2585
2597
|
}
|
|
2586
|
-
//binary search
|
|
2598
|
+
// binary search
|
|
2587
2599
|
const times = track.times;
|
|
2588
2600
|
let min = 0, max = times.length - 1;
|
|
2589
2601
|
// edge cases
|
|
@@ -2596,12 +2608,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2596
2608
|
// time is between first and last frame
|
|
2597
2609
|
let half = Math.floor((min + max) / 2);
|
|
2598
2610
|
while (min < half && half < max) {
|
|
2599
|
-
if (time < times[half])
|
|
2611
|
+
if (time < times[half])
|
|
2600
2612
|
max = half;
|
|
2601
|
-
|
|
2602
|
-
else {
|
|
2613
|
+
else
|
|
2603
2614
|
min = half;
|
|
2604
|
-
}
|
|
2605
2615
|
half = Math.floor((min + max) / 2);
|
|
2606
2616
|
}
|
|
2607
2617
|
if (mode == 0) {
|
|
@@ -2620,8 +2630,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2620
2630
|
* @returns returns a postive/zero value if there is a frame inside the threshold range. Otherwise, -1
|
|
2621
2631
|
*/
|
|
2622
2632
|
getCurrentKeyFrame(track, time, threshold = 0.0) {
|
|
2623
|
-
if (!track || !track.times.length)
|
|
2633
|
+
if (!track || !track.times.length) {
|
|
2624
2634
|
return -1;
|
|
2635
|
+
}
|
|
2625
2636
|
let frame = this.getNearestKeyFrame(track, time);
|
|
2626
2637
|
if (frame > -1) {
|
|
2627
2638
|
frame = Math.abs(track.times[frame] - time) > threshold ? -1 : frame;
|
|
@@ -2679,8 +2690,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2679
2690
|
*/
|
|
2680
2691
|
selectKeyFrame(trackIdx, frameIdx, skipCallback = false) {
|
|
2681
2692
|
const track = this.animationClip.tracks[trackIdx];
|
|
2682
|
-
if (track.locked || !track.active || track.selected[frameIdx])
|
|
2693
|
+
if (track.locked || !track.active || track.selected[frameIdx]) {
|
|
2683
2694
|
return null;
|
|
2695
|
+
}
|
|
2684
2696
|
// [ track idx, keyframe, keyframe time ]
|
|
2685
2697
|
const selection = [track.trackIdx, frameIdx, track.times[frameIdx]];
|
|
2686
2698
|
// sort lastkeyframeselected ascending order ( track and frame )
|
|
@@ -2700,8 +2712,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2700
2712
|
}
|
|
2701
2713
|
deselectKeyFrame(trackIdx, frameIdx) {
|
|
2702
2714
|
const track = this.animationClip.tracks[trackIdx];
|
|
2703
|
-
if (track.locked || !track.active || !track.selected[frameIdx])
|
|
2715
|
+
if (track.locked || !track.active || !track.selected[frameIdx]) {
|
|
2704
2716
|
return false;
|
|
2717
|
+
}
|
|
2705
2718
|
track.selected[frameIdx] = false;
|
|
2706
2719
|
for (let i = 0; i < this.lastKeyFramesSelected.length; ++i) {
|
|
2707
2720
|
const sk = this.lastKeyFramesSelected[i];
|
|
@@ -2724,8 +2737,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2724
2737
|
*/
|
|
2725
2738
|
processSelectionKeyFrame(trackIdx, keyFrameIndex, multipleSelection = false) {
|
|
2726
2739
|
const track = this.animationClip.tracks[trackIdx];
|
|
2727
|
-
if (track.locked)
|
|
2740
|
+
if (track.locked) {
|
|
2728
2741
|
return;
|
|
2742
|
+
}
|
|
2729
2743
|
if (!multipleSelection) {
|
|
2730
2744
|
this.deselectAllKeyFrames();
|
|
2731
2745
|
}
|
|
@@ -2764,7 +2778,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2764
2778
|
static CLONEREASON_TRACKCLONE = 4;
|
|
2765
2779
|
lastClipsSelected = [];
|
|
2766
2780
|
lastTrackClipsMove = 0; // vertical movement of clips, onMouseMove onMousedown
|
|
2767
|
-
dragClipMode =
|
|
2781
|
+
dragClipMode = '';
|
|
2768
2782
|
lastHovered = null;
|
|
2769
2783
|
onSelectClip = null;
|
|
2770
2784
|
onContentMoved = null;
|
|
@@ -2778,7 +2792,7 @@ class ClipsTimeline extends Timeline {
|
|
|
2778
2792
|
super(name, options);
|
|
2779
2793
|
this.lastClipsSelected = [];
|
|
2780
2794
|
this.lastTrackClipsMove = 0; // vertical movement of clips, onMouseMove onMousedown
|
|
2781
|
-
this.dragClipMode =
|
|
2795
|
+
this.dragClipMode = '';
|
|
2782
2796
|
this.setAnimationClip(this.animationClip);
|
|
2783
2797
|
this.onDblClick = (e) => {
|
|
2784
2798
|
const track = e.track;
|
|
@@ -2794,11 +2808,13 @@ class ClipsTimeline extends Timeline {
|
|
|
2794
2808
|
let actions = [];
|
|
2795
2809
|
if (this.lastClipsSelected.length) {
|
|
2796
2810
|
actions.push({
|
|
2797
|
-
title:
|
|
2798
|
-
callback: () => {
|
|
2811
|
+
title: 'Copy',
|
|
2812
|
+
callback: () => {
|
|
2813
|
+
this.copySelectedContent();
|
|
2814
|
+
}
|
|
2799
2815
|
});
|
|
2800
2816
|
actions.push({
|
|
2801
|
-
title:
|
|
2817
|
+
title: 'Delete',
|
|
2802
2818
|
callback: () => {
|
|
2803
2819
|
this.deleteSelectedContent();
|
|
2804
2820
|
}
|
|
@@ -2807,20 +2823,20 @@ class ClipsTimeline extends Timeline {
|
|
|
2807
2823
|
else {
|
|
2808
2824
|
if (this.clipboard) {
|
|
2809
2825
|
actions.push({
|
|
2810
|
-
title:
|
|
2826
|
+
title: 'Paste',
|
|
2811
2827
|
callback: () => {
|
|
2812
2828
|
this.pasteContent();
|
|
2813
2829
|
}
|
|
2814
2830
|
});
|
|
2815
2831
|
actions.push({
|
|
2816
|
-
title:
|
|
2832
|
+
title: 'Paste Here',
|
|
2817
2833
|
callback: () => {
|
|
2818
2834
|
this.pasteContent(this.xToTime(e.localX));
|
|
2819
2835
|
}
|
|
2820
2836
|
});
|
|
2821
2837
|
}
|
|
2822
2838
|
}
|
|
2823
|
-
LX.addContextMenu(
|
|
2839
|
+
LX.addContextMenu('Options', e, (m) => {
|
|
2824
2840
|
for (let i = 0; i < actions.length; i++) {
|
|
2825
2841
|
m.add(actions[i].title, actions[i].callback);
|
|
2826
2842
|
}
|
|
@@ -2844,10 +2860,9 @@ class ClipsTimeline extends Timeline {
|
|
|
2844
2860
|
return animationClip;
|
|
2845
2861
|
}
|
|
2846
2862
|
/**
|
|
2847
|
-
*
|
|
2848
2863
|
* @param {Object} options set some values for the track instance ( groups and trackIdx not included )
|
|
2849
2864
|
* @returns
|
|
2850
|
-
|
|
2865
|
+
*/
|
|
2851
2866
|
instantiateTrack(options = {}, clone = false) {
|
|
2852
2867
|
const track = super.instantiateTrack(options);
|
|
2853
2868
|
track.trackIdx = this.animationClip.tracks.length;
|
|
@@ -2855,7 +2870,9 @@ class ClipsTimeline extends Timeline {
|
|
|
2855
2870
|
track.edited = [];
|
|
2856
2871
|
track.hovered = [];
|
|
2857
2872
|
if (options.clips) {
|
|
2858
|
-
track.clips = clone
|
|
2873
|
+
track.clips = clone
|
|
2874
|
+
? this.cloneClips(options.clips, 0, ClipsTimeline.CLONEREASON_TRACKCLONE)
|
|
2875
|
+
: options.clips;
|
|
2859
2876
|
}
|
|
2860
2877
|
else {
|
|
2861
2878
|
track.clips = [];
|
|
@@ -2888,15 +2905,15 @@ class ClipsTimeline extends Timeline {
|
|
|
2888
2905
|
// provides an base example of a proper clip
|
|
2889
2906
|
instantiateClip(options = {}) {
|
|
2890
2907
|
return {
|
|
2891
|
-
id: options.id ?? (options.name ??
|
|
2908
|
+
id: options.id ?? (options.name ?? 'clip'),
|
|
2892
2909
|
start: options.start ?? 0,
|
|
2893
2910
|
duration: options.duration ?? 1,
|
|
2894
2911
|
fadein: options.fadein ?? undefined,
|
|
2895
2912
|
fadeout: options.fadeout ?? undefined,
|
|
2896
|
-
clipColor: options.clipColor ?? LX.getThemeColor(
|
|
2913
|
+
clipColor: options.clipColor ?? LX.getThemeColor('global-color-accent'),
|
|
2897
2914
|
fadeColor: options.fadeColor ?? null,
|
|
2898
2915
|
active: options.active ?? true,
|
|
2899
|
-
trackIdx: -1
|
|
2916
|
+
trackIdx: -1 // filled by addClip
|
|
2900
2917
|
};
|
|
2901
2918
|
}
|
|
2902
2919
|
// use default updateleftpanel
|
|
@@ -2953,9 +2970,9 @@ class ClipsTimeline extends Timeline {
|
|
|
2953
2970
|
if (track) {
|
|
2954
2971
|
let clipIndex = this.getClipOnTime(track, this.xToTime(localX), this.secondsPerPixel * 5);
|
|
2955
2972
|
if (clipIndex > -1) {
|
|
2956
|
-
track.selected[clipIndex]
|
|
2957
|
-
this.deselectClip(track.trackIdx, clipIndex)
|
|
2958
|
-
this.selectClip(track.trackIdx, clipIndex, false);
|
|
2973
|
+
track.selected[clipIndex]
|
|
2974
|
+
? this.deselectClip(track.trackIdx, clipIndex)
|
|
2975
|
+
: this.selectClip(track.trackIdx, clipIndex, false);
|
|
2959
2976
|
}
|
|
2960
2977
|
}
|
|
2961
2978
|
}
|
|
@@ -2965,16 +2982,18 @@ class ClipsTimeline extends Timeline {
|
|
|
2965
2982
|
for (let t of tracks) {
|
|
2966
2983
|
let clipsIndices = this.getClipsInRange(t, this.xToTime(this.boxSelectionStart[0]), this.xToTime(this.boxSelectionEnd[0]), 0.000001);
|
|
2967
2984
|
if (clipsIndices) {
|
|
2968
|
-
for (let index of clipsIndices)
|
|
2985
|
+
for (let index of clipsIndices) {
|
|
2969
2986
|
this.selectClip(t.trackIdx, index, false);
|
|
2987
|
+
}
|
|
2970
2988
|
}
|
|
2971
2989
|
}
|
|
2972
2990
|
}
|
|
2973
2991
|
}
|
|
2974
2992
|
else {
|
|
2975
2993
|
let boundingBox = this.canvas.getBoundingClientRect();
|
|
2976
|
-
if (e.y < boundingBox.top || e.y > boundingBox.bottom)
|
|
2994
|
+
if (e.y < boundingBox.top || e.y > boundingBox.bottom) {
|
|
2977
2995
|
return;
|
|
2996
|
+
}
|
|
2978
2997
|
// Check exact track clip
|
|
2979
2998
|
if (!discard && track) {
|
|
2980
2999
|
if (e.button != 2) {
|
|
@@ -2993,12 +3012,11 @@ class ClipsTimeline extends Timeline {
|
|
|
2993
3012
|
if (e.button > 0) {
|
|
2994
3013
|
return;
|
|
2995
3014
|
}
|
|
2996
|
-
if (e.ctrlKey && track) // move clips
|
|
2997
|
-
{
|
|
3015
|
+
if (e.ctrlKey && track) { // move clips
|
|
2998
3016
|
let x = e.offsetX;
|
|
2999
3017
|
// clip selection is done on MouseUP
|
|
3000
3018
|
const selectedClips = this.lastClipsSelected;
|
|
3001
|
-
this.canvas.style.cursor =
|
|
3019
|
+
this.canvas.style.cursor = 'grab';
|
|
3002
3020
|
let curTrackIdx = -1;
|
|
3003
3021
|
this.lastTrackClipsMove = Math.floor((e.localY - this.topMargin + this.trackTreesPanel.root.scrollTop) / this.trackHeight);
|
|
3004
3022
|
for (let i = 0; i < selectedClips.length; i++) {
|
|
@@ -3006,13 +3024,13 @@ class ClipsTimeline extends Timeline {
|
|
|
3006
3024
|
const clip = this.animationClip.tracks[trackIndex].clips[clipIndex];
|
|
3007
3025
|
let endingX = this.timeToX(clip.start + clip.duration);
|
|
3008
3026
|
if (Math.abs(endingX - x) < 5) {
|
|
3009
|
-
this.dragClipMode =
|
|
3010
|
-
this.canvas.style.cursor =
|
|
3027
|
+
this.dragClipMode = 'duration';
|
|
3028
|
+
this.canvas.style.cursor = 'column-resize';
|
|
3011
3029
|
}
|
|
3012
3030
|
else {
|
|
3013
|
-
this.dragClipMode =
|
|
3031
|
+
this.dragClipMode = 'move';
|
|
3014
3032
|
}
|
|
3015
|
-
|
|
3033
|
+
// *********** WARNING: RELIES ON SORTED lastClipsSelected ***********
|
|
3016
3034
|
if (curTrackIdx != trackIndex) {
|
|
3017
3035
|
this.saveState(trackIndex, curTrackIdx != -1);
|
|
3018
3036
|
curTrackIdx = trackIndex;
|
|
@@ -3020,8 +3038,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3020
3038
|
}
|
|
3021
3039
|
this.movingKeys = true;
|
|
3022
3040
|
}
|
|
3023
|
-
else if (!track || track && this.getClipOnTime(track, time, 0.001) == -1) // clicked on empty space
|
|
3024
|
-
{
|
|
3041
|
+
else if (!track || track && this.getClipOnTime(track, time, 0.001) == -1) { // clicked on empty space
|
|
3025
3042
|
if (this.lastClipsSelected.length) {
|
|
3026
3043
|
this.deselectAllClips();
|
|
3027
3044
|
if (this.onSelectClip) {
|
|
@@ -3029,8 +3046,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3029
3046
|
}
|
|
3030
3047
|
}
|
|
3031
3048
|
}
|
|
3032
|
-
else if (track
|
|
3033
|
-
|
|
3049
|
+
else if (track
|
|
3050
|
+
&& (this.dragClipMode == 'duration' || this.dragClipMode == 'fadein' || this.dragClipMode == 'fadeout')) { // clicked while mouse was over fadeIn, fadeOut, duration
|
|
3034
3051
|
const clipIdx = this.getClipOnTime(track, this.xToTime(localX), 0.001);
|
|
3035
3052
|
this.selectClip(track.trackIdx, clipIdx); // select current clip if any ( deselect others )
|
|
3036
3053
|
if (this.lastClipsSelected.length) {
|
|
@@ -3048,30 +3065,29 @@ class ClipsTimeline extends Timeline {
|
|
|
3048
3065
|
this.unHoverAll();
|
|
3049
3066
|
let delta = time - this.grabTime;
|
|
3050
3067
|
this.grabTime = time;
|
|
3051
|
-
if (time < 0 && delta > 0)
|
|
3068
|
+
if (time < 0 && delta > 0)
|
|
3052
3069
|
delta = 0;
|
|
3053
|
-
|
|
3054
|
-
if (this.dragClipMode != "move" && this.lastClipsSelected.length == 1) // change fade and duration of clips
|
|
3055
|
-
{
|
|
3070
|
+
if (this.dragClipMode != 'move' && this.lastClipsSelected.length == 1) { // change fade and duration of clips
|
|
3056
3071
|
const track = this.animationClip.tracks[this.lastClipsSelected[0][0]];
|
|
3057
3072
|
let clip = track.clips[this.lastClipsSelected[0][1]];
|
|
3058
|
-
if (this.dragClipMode ==
|
|
3073
|
+
if (this.dragClipMode == 'fadein') {
|
|
3059
3074
|
clip.fadein = Math.min(Math.max(clip.fadein + delta, clip.start), clip.fadeout ?? (clip.start + clip.duration));
|
|
3060
3075
|
}
|
|
3061
|
-
else if (this.dragClipMode ==
|
|
3076
|
+
else if (this.dragClipMode == 'fadeout') {
|
|
3062
3077
|
clip.fadeout = Math.max(Math.min(clip.fadeout + delta, clip.start + clip.duration), clip.fadein ?? clip.start);
|
|
3063
3078
|
}
|
|
3064
|
-
else if (this.dragClipMode ==
|
|
3079
|
+
else if (this.dragClipMode == 'duration') {
|
|
3065
3080
|
let duration = Math.max(0, clip.duration + delta);
|
|
3066
|
-
if (this.lastClipsSelected[0][1] < track.clips.length - 1) // max next clip's start
|
|
3067
|
-
{
|
|
3081
|
+
if (this.lastClipsSelected[0][1] < track.clips.length - 1) { // max next clip's start
|
|
3068
3082
|
duration = Math.min(track.clips[this.lastClipsSelected[0][1] + 1].start - clip.start - 0.0001, duration);
|
|
3069
3083
|
}
|
|
3070
3084
|
clip.duration = duration;
|
|
3071
|
-
if (clip.fadeout != undefined)
|
|
3085
|
+
if (clip.fadeout != undefined) {
|
|
3072
3086
|
clip.fadeout = Math.max(Math.min((clip.fadeout ?? (clip.start + clip.duration)) + delta, clip.start + clip.duration), clip.start);
|
|
3073
|
-
|
|
3074
|
-
|
|
3087
|
+
}
|
|
3088
|
+
if (clip.fadein != undefined) {
|
|
3089
|
+
clip.fadein = Math.max(Math.min(clip.fadein ?? (clip.start + clip.duration), clip.fadeout ?? (clip.start + clip.duration)), clip.start);
|
|
3090
|
+
}
|
|
3075
3091
|
if (this.duration < clip.start + clip.duration)
|
|
3076
3092
|
this.setDuration(clip.start + clip.duration);
|
|
3077
3093
|
}
|
|
@@ -3079,9 +3095,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3079
3095
|
this.onContentMoved(clip, 0);
|
|
3080
3096
|
}
|
|
3081
3097
|
}
|
|
3082
|
-
else if (this.dragClipMode ==
|
|
3083
|
-
|
|
3084
|
-
//*********** WARNING: RELIES ON SORTED lastClipsSelected ***********
|
|
3098
|
+
else if (this.dragClipMode == 'move' && this.lastClipsSelected.length) { // move clips
|
|
3099
|
+
// *********** WARNING: RELIES ON SORTED lastClipsSelected ***********
|
|
3085
3100
|
const treeOffset = this.lastTrackTreesComponentOffset;
|
|
3086
3101
|
let newTrackClipsMove = Math.floor((e.localY - treeOffset) / this.trackHeight);
|
|
3087
3102
|
// move clips vertically
|
|
@@ -3117,7 +3132,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3117
3132
|
this.historySaveEnabler = false;
|
|
3118
3133
|
const selectedClips = this.lastClipsSelected;
|
|
3119
3134
|
this.lastClipsSelected = []; // avoid delete and addclips index reassignment loop ( not necessary because of order of operations in for )
|
|
3120
|
-
for (let i = selectedClips[selectedClips.length - 1][0] + deltaTracks
|
|
3135
|
+
for (let i = selectedClips[selectedClips.length - 1][0] + deltaTracks
|
|
3136
|
+
- this.animationClip.tracks.length + 1; i > 0; --i) {
|
|
3121
3137
|
this.addNewTrack(null, i == 1);
|
|
3122
3138
|
if (i == 1) {
|
|
3123
3139
|
this.updateLeftPanel();
|
|
@@ -3138,9 +3154,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3138
3154
|
const undoState = this.historyUndo[this.historyUndo.length - 1];
|
|
3139
3155
|
let state = 0;
|
|
3140
3156
|
for (; state < undoState.length; ++state) {
|
|
3141
|
-
if (newTrackIdx == undoState[state].trackIdx)
|
|
3157
|
+
if (newTrackIdx == undoState[state].trackIdx)
|
|
3142
3158
|
break;
|
|
3143
|
-
}
|
|
3144
3159
|
}
|
|
3145
3160
|
if (state == undoState.length) {
|
|
3146
3161
|
this.historySaveEnabler = true;
|
|
@@ -3183,21 +3198,20 @@ class ClipsTimeline extends Timeline {
|
|
|
3183
3198
|
const trackClips = track.clips;
|
|
3184
3199
|
const clip = track.clips[clipIdx];
|
|
3185
3200
|
if (delta >= 0) {
|
|
3186
|
-
if (trackClips.length - 1 == clipIdx)
|
|
3187
|
-
continue;
|
|
3188
|
-
} // all alowed
|
|
3201
|
+
if (trackClips.length - 1 == clipIdx)
|
|
3202
|
+
continue; // all alowed
|
|
3189
3203
|
if (!track.selected[clipIdx + 1]) { // if next is selected, force AllOrNothing and let next clip manage the leastDelta
|
|
3190
|
-
if (trackClips[clipIdx + 1].start >= (clip.start + clip.duration + delta))
|
|
3191
|
-
continue;
|
|
3192
|
-
} //has not reached next clip. Enough space. All allowed
|
|
3204
|
+
if (trackClips[clipIdx + 1].start >= (clip.start + clip.duration + delta))
|
|
3205
|
+
continue; // has not reached next clip. Enough space. All allowed
|
|
3193
3206
|
const nextClip = trackClips[clipIdx + 1];
|
|
3194
3207
|
leastDelta = Math.max(0, Math.min(leastDelta, nextClip.start - clip.start - clip.duration));
|
|
3195
3208
|
}
|
|
3196
3209
|
}
|
|
3197
3210
|
else if (delta < 0) {
|
|
3198
|
-
if (clipIdx > 0
|
|
3199
|
-
|
|
3200
|
-
|
|
3211
|
+
if (clipIdx > 0
|
|
3212
|
+
&& (trackClips[clipIdx - 1].start + trackClips[clipIdx - 1].duration)
|
|
3213
|
+
<= (clip.start + delta))
|
|
3214
|
+
continue; // has not reached previous clip. Enough space
|
|
3201
3215
|
if (clipIdx > 0) {
|
|
3202
3216
|
const prevClip = trackClips[clipIdx - 1];
|
|
3203
3217
|
leastDelta = Math.min(0, Math.max(leastDelta, prevClip.start + prevClip.duration - clip.start)); // delta is a negative value, that is why the leastDelta is the max
|
|
@@ -3210,7 +3224,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3210
3224
|
if (!moveAccepted)
|
|
3211
3225
|
continue;
|
|
3212
3226
|
let clipsInRange = this.getClipsInRange(track, clip.start + delta, clip.start + clip.duration + delta, 0.01);
|
|
3213
|
-
if (clipsInRange
|
|
3227
|
+
if (clipsInRange
|
|
3228
|
+
&& (clipsInRange[0] != clipIdx || clipsInRange[clipsInRange.length - 1] != clipIdx)) {
|
|
3214
3229
|
for (let c = 0; c < clipsInRange.length; ++c) {
|
|
3215
3230
|
if (!track.selected[clipsInRange[c]]) {
|
|
3216
3231
|
moveAccepted = false;
|
|
@@ -3221,25 +3236,22 @@ class ClipsTimeline extends Timeline {
|
|
|
3221
3236
|
}
|
|
3222
3237
|
// if moveAccepted -> use full delta
|
|
3223
3238
|
// if !moveAccepted -> use leastDelta
|
|
3224
|
-
if (moveAccepted)
|
|
3239
|
+
if (moveAccepted)
|
|
3225
3240
|
leastDelta = delta;
|
|
3226
|
-
}
|
|
3227
3241
|
this.grabTime = time - delta + leastDelta;
|
|
3228
|
-
|
|
3242
|
+
// *********** WARNING: RELIES ON SORTED lastClipsSelected ***********
|
|
3229
3243
|
// move all selected clips using the computed delta.
|
|
3230
3244
|
for (let i = 0; i < this.lastClipsSelected.length; ++i) {
|
|
3231
|
-
const lcs = this.lastClipsSelected[delta > 0 ? (this.lastClipsSelected.length - 1 - i) : i]; //delta > 0, move last-to-first; delta < 0, move first-to-last
|
|
3245
|
+
const lcs = this.lastClipsSelected[delta > 0 ? (this.lastClipsSelected.length - 1 - i) : i]; // delta > 0, move last-to-first; delta < 0, move first-to-last
|
|
3232
3246
|
const track = this.animationClip.tracks[lcs[0]];
|
|
3233
3247
|
const trackClips = track.clips;
|
|
3234
3248
|
let clipIdx = lcs[1];
|
|
3235
3249
|
const clip = track.clips[clipIdx];
|
|
3236
3250
|
clip.start += leastDelta;
|
|
3237
|
-
if (clip.fadein != undefined)
|
|
3251
|
+
if (clip.fadein != undefined)
|
|
3238
3252
|
clip.fadein += leastDelta;
|
|
3239
|
-
|
|
3240
|
-
if (clip.fadeout != undefined) {
|
|
3253
|
+
if (clip.fadeout != undefined)
|
|
3241
3254
|
clip.fadeout += leastDelta;
|
|
3242
|
-
}
|
|
3243
3255
|
// prepare swap
|
|
3244
3256
|
const editedFlag = track.edited[clipIdx];
|
|
3245
3257
|
const selectedFlag = track.selected[clipIdx];
|
|
@@ -3286,8 +3298,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3286
3298
|
}
|
|
3287
3299
|
return true;
|
|
3288
3300
|
}
|
|
3289
|
-
else if (e.track && e.buttons == 0) // mouse not dragging, just hovering
|
|
3290
|
-
{
|
|
3301
|
+
else if (e.track && e.buttons == 0) { // mouse not dragging, just hovering
|
|
3291
3302
|
this.unHoverAll();
|
|
3292
3303
|
let clips = this.getClipsInRange(e.track, time, time, 0.00001);
|
|
3293
3304
|
if (!e.track.locked && clips) {
|
|
@@ -3297,23 +3308,20 @@ class ClipsTimeline extends Timeline {
|
|
|
3297
3308
|
if (!clip) {
|
|
3298
3309
|
return;
|
|
3299
3310
|
}
|
|
3300
|
-
if (Math.abs(e.localX - this.timeToX(clip.start + clip.duration)) < 8) // duration
|
|
3301
|
-
|
|
3302
|
-
this.
|
|
3303
|
-
this.dragClipMode = "duration";
|
|
3311
|
+
if (Math.abs(e.localX - this.timeToX(clip.start + clip.duration)) < 8) { // duration
|
|
3312
|
+
this.canvas.style.cursor = 'col-resize';
|
|
3313
|
+
this.dragClipMode = 'duration';
|
|
3304
3314
|
}
|
|
3305
|
-
else if (clip.fadein != undefined && Math.abs(e.localX - this.timeToX(clip.fadein)) < 8) // fadein
|
|
3306
|
-
|
|
3307
|
-
this.
|
|
3308
|
-
this.dragClipMode = "fadein";
|
|
3315
|
+
else if (clip.fadein != undefined && Math.abs(e.localX - this.timeToX(clip.fadein)) < 8) { // fadein
|
|
3316
|
+
this.canvas.style.cursor = 'e-resize';
|
|
3317
|
+
this.dragClipMode = 'fadein';
|
|
3309
3318
|
}
|
|
3310
|
-
else if (clip.fadeout != undefined && Math.abs(e.localX - this.timeToX(clip.fadeout)) < 8) // fadeout
|
|
3311
|
-
|
|
3312
|
-
this.
|
|
3313
|
-
this.dragClipMode = "fadeout";
|
|
3319
|
+
else if (clip.fadeout != undefined && Math.abs(e.localX - this.timeToX(clip.fadeout)) < 8) { // fadeout
|
|
3320
|
+
this.canvas.style.cursor = 'e-resize';
|
|
3321
|
+
this.dragClipMode = 'fadeout';
|
|
3314
3322
|
}
|
|
3315
3323
|
else {
|
|
3316
|
-
this.dragClipMode =
|
|
3324
|
+
this.dragClipMode = '';
|
|
3317
3325
|
}
|
|
3318
3326
|
}
|
|
3319
3327
|
}
|
|
@@ -3350,13 +3358,13 @@ class ClipsTimeline extends Timeline {
|
|
|
3350
3358
|
const offset = (trackHeight * 0.4) * 0.5;
|
|
3351
3359
|
trackHeight *= 0.6;
|
|
3352
3360
|
let selectedClipArea = null;
|
|
3353
|
-
ctx.font = Math.floor(trackHeight * 0.8) +
|
|
3354
|
-
ctx.textAlign =
|
|
3355
|
-
ctx.textBaseline =
|
|
3361
|
+
ctx.font = Math.floor(trackHeight * 0.8) + 'px' + Timeline.FONT;
|
|
3362
|
+
ctx.textAlign = 'left';
|
|
3363
|
+
ctx.textBaseline = 'middle';
|
|
3356
3364
|
for (var j = 0; j < clips.length; ++j) {
|
|
3357
3365
|
selectedClipArea = null;
|
|
3358
3366
|
const clip = clips[j];
|
|
3359
|
-
//let selected = track.selected[ j ];
|
|
3367
|
+
// let selected = track.selected[ j ];
|
|
3360
3368
|
var x = Math.floor(this.timeToX(clip.start)) + 0.5;
|
|
3361
3369
|
var x2 = Math.floor(this.timeToX(clip.start + clip.duration)) + 0.5;
|
|
3362
3370
|
var w = x2 - x;
|
|
@@ -3365,21 +3373,24 @@ class ClipsTimeline extends Timeline {
|
|
|
3365
3373
|
}
|
|
3366
3374
|
// Overwrite clip color state depending on its state
|
|
3367
3375
|
ctx.globalAlpha = 1;
|
|
3368
|
-
ctx.fillStyle = clip.clipColor || (track.hovered[j]
|
|
3376
|
+
ctx.fillStyle = clip.clipColor || (track.hovered[j]
|
|
3377
|
+
? Timeline.KEYFRAME_COLOR_HOVERED
|
|
3378
|
+
: (track.selected[j] ? Timeline.TRACK_SELECTED : Timeline.KEYFRAME_COLOR));
|
|
3369
3379
|
if (!this.active || !track.active || !clip.active) {
|
|
3370
3380
|
ctx.fillStyle = Timeline.KEYFRAME_COLOR_INACTIVE;
|
|
3371
3381
|
}
|
|
3372
3382
|
// Draw clip background
|
|
3373
3383
|
drawRoundRect(ctx, x, y + offset, w, trackHeight, 5, true);
|
|
3374
3384
|
if (this.active && track.active && clip.active) {
|
|
3375
|
-
ctx.fillStyle = clip.fadeColor ??
|
|
3385
|
+
ctx.fillStyle = clip.fadeColor ?? '#0004';
|
|
3376
3386
|
if (clip.fadein != undefined) {
|
|
3377
3387
|
const fadeinX = this.pixelsPerSecond * (clip.fadein - clip.start);
|
|
3378
3388
|
drawRoundRect(ctx, x, y + offset, fadeinX, trackHeight, { tl: 5, bl: 5, tr: 0, br: 0 }, true);
|
|
3379
3389
|
}
|
|
3380
3390
|
if (clip.fadeout != undefined) {
|
|
3381
3391
|
const fadeoutX = this.pixelsPerSecond * (clip.start + clip.duration - (clip.fadeout));
|
|
3382
|
-
drawRoundRect(ctx, x + w - fadeoutX, y + offset, fadeoutX, trackHeight, { tl: 0, bl: 0, tr: 5,
|
|
3392
|
+
drawRoundRect(ctx, x + w - fadeoutX, y + offset, fadeoutX, trackHeight, { tl: 0, bl: 0, tr: 5,
|
|
3393
|
+
br: 5 }, true);
|
|
3383
3394
|
}
|
|
3384
3395
|
}
|
|
3385
3396
|
ctx.fillStyle = Timeline.TRACK_COLOR_PRIMARY;
|
|
@@ -3393,10 +3404,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3393
3404
|
ctx.shadowBlur = 0;
|
|
3394
3405
|
ctx.shadowOffsetX = 0;
|
|
3395
3406
|
ctx.shadowOffsetY = 0;
|
|
3396
|
-
ctx.font =
|
|
3407
|
+
ctx.font = 'bold' + Math.floor(trackHeight) + 'px ' + Timeline.FONT;
|
|
3397
3408
|
ctx.fillStyle = Timeline.FONT_COLOR_PRIMARY;
|
|
3398
3409
|
}
|
|
3399
|
-
let text = clip.id ??
|
|
3410
|
+
let text = clip.id ?? ''; // clip.id.replaceAll("_", " ").replaceAll("-", " ");
|
|
3400
3411
|
const textInfo = ctx.measureText(text);
|
|
3401
3412
|
let textWidth = textInfo.width;
|
|
3402
3413
|
if (textWidth > w && textWidth > 0) {
|
|
@@ -3405,12 +3416,12 @@ class ClipsTimeline extends Timeline {
|
|
|
3405
3416
|
textWidth = w;
|
|
3406
3417
|
}
|
|
3407
3418
|
ctx.fillText(text, x + (w - textWidth) * 0.5, y + offset + trackHeight * 0.5);
|
|
3408
|
-
ctx.fillStyle = track.hovered[j] ?
|
|
3409
|
-
ctx.strokeStyle =
|
|
3419
|
+
ctx.fillStyle = track.hovered[j] ? 'white' : '#f5f5f5'; // track.hovered[ j ] ? "white" : Timeline.FONT_COLOR_QUATERNARY;
|
|
3420
|
+
ctx.strokeStyle = 'rgba(125,125,125,0.4)';
|
|
3410
3421
|
// Draw resize bounding
|
|
3411
3422
|
drawRoundRect(ctx, x + w - 8, y + offset, 8, trackHeight, { tl: 4, bl: 4, tr: 4, br: 4 }, true, true);
|
|
3412
3423
|
}
|
|
3413
|
-
ctx.font =
|
|
3424
|
+
ctx.font = '12px' + Timeline.FONT;
|
|
3414
3425
|
}
|
|
3415
3426
|
/**
|
|
3416
3427
|
* @method optimizeTrack
|
|
@@ -3423,34 +3434,30 @@ class ClipsTimeline extends Timeline {
|
|
|
3423
3434
|
optimizeTracks() {
|
|
3424
3435
|
}
|
|
3425
3436
|
/**
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
*/
|
|
3437
|
+
* @param {Object} clip clip to be added
|
|
3438
|
+
* @param {Int} trackIdx ( optional ) track where to put the clip. -1 will find the first free slot. ***WARNING*** Must call getClipsInRange, before calling this function with a valid trackdIdx
|
|
3439
|
+
* @param {Number} offsetTime ( optional ) offset time of current time
|
|
3440
|
+
* @param {Number} searchStartTrackIdx ( optional ) if trackIdx is set to -1, this idx will be used as the starting point to find a valid track
|
|
3441
|
+
* @returns a zero/positive value if successful. Otherwise, -1
|
|
3442
|
+
*/
|
|
3433
3443
|
addClip(clip, trackIdx = -1, offsetTime = 0, searchStartTrackIdx = 0) {
|
|
3434
|
-
if (!this.animationClip)
|
|
3444
|
+
if (!this.animationClip)
|
|
3435
3445
|
return -1;
|
|
3436
|
-
}
|
|
3437
3446
|
this.deselectAllElements(); // TODO: consider adjusting values of hovered and selected instead of deselecting everything
|
|
3438
3447
|
// Update clip information
|
|
3439
3448
|
let newStart = clip.start + offsetTime;
|
|
3440
3449
|
if (clip.fadein != undefined)
|
|
3441
|
-
clip.fadein +=
|
|
3450
|
+
clip.fadein += newStart - clip.start;
|
|
3442
3451
|
if (clip.fadeout != undefined)
|
|
3443
|
-
clip.fadeout +=
|
|
3452
|
+
clip.fadeout += newStart - clip.start;
|
|
3444
3453
|
clip.start = newStart;
|
|
3445
3454
|
// sanity check
|
|
3446
3455
|
clip.active = clip.active ?? true;
|
|
3447
3456
|
// find appropriate track
|
|
3448
|
-
if (trackIdx >= this.animationClip.tracks.length) // new track ad the end
|
|
3449
|
-
{
|
|
3457
|
+
if (trackIdx >= this.animationClip.tracks.length) { // new track ad the end
|
|
3450
3458
|
trackIdx = this.addNewTrack();
|
|
3451
3459
|
}
|
|
3452
|
-
else if (trackIdx < 0) // find first free track slot
|
|
3453
|
-
{
|
|
3460
|
+
else if (trackIdx < 0) { // find first free track slot
|
|
3454
3461
|
for (let i = searchStartTrackIdx; i < this.animationClip.tracks.length; i++) {
|
|
3455
3462
|
let clipInCurrentSlot = this.animationClip.tracks[i].clips.find((t) => {
|
|
3456
3463
|
return LX.compareThresholdRange(newStart, clip.start + clip.duration, t.start, t.start + t.duration);
|
|
@@ -3473,10 +3480,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3473
3480
|
if (newIdx < 0) {
|
|
3474
3481
|
newIdx = track.clips.length;
|
|
3475
3482
|
}
|
|
3476
|
-
//Save track state before add the new clip
|
|
3483
|
+
// Save track state before add the new clip
|
|
3477
3484
|
this.saveState(trackIdx);
|
|
3478
3485
|
// Add clip
|
|
3479
|
-
track.clips.splice(newIdx, 0, clip); //insert clip into newIdx ( or push at the end )
|
|
3486
|
+
track.clips.splice(newIdx, 0, clip); // insert clip into newIdx ( or push at the end )
|
|
3480
3487
|
// Reset this clip's properties
|
|
3481
3488
|
track.hovered.splice(newIdx, 0, false);
|
|
3482
3489
|
track.selected.splice(newIdx, 0, false);
|
|
@@ -3513,10 +3520,11 @@ class ClipsTimeline extends Timeline {
|
|
|
3513
3520
|
if (c == 0) { // last search failed, move one track down and check again
|
|
3514
3521
|
++baseTrackIdx;
|
|
3515
3522
|
currTrackIdx = baseTrackIdx;
|
|
3516
|
-
while (currTrackIdx >= tracks.length)
|
|
3523
|
+
while (currTrackIdx >= tracks.length)
|
|
3517
3524
|
this.addNewTrack(null, false);
|
|
3518
|
-
|
|
3519
|
-
|
|
3525
|
+
let clipsInCurrentSlot = tracks[baseTrackIdx].clips.find((t) => {
|
|
3526
|
+
return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start + t.duration);
|
|
3527
|
+
});
|
|
3520
3528
|
// reset search
|
|
3521
3529
|
if (clipsInCurrentSlot) {
|
|
3522
3530
|
c = -1;
|
|
@@ -3527,21 +3535,23 @@ class ClipsTimeline extends Timeline {
|
|
|
3527
3535
|
}
|
|
3528
3536
|
else {
|
|
3529
3537
|
// check if it fits in current track
|
|
3530
|
-
let clipsInCurrentSlot = tracks[currTrackIdx].clips.find((t) => {
|
|
3538
|
+
let clipsInCurrentSlot = tracks[currTrackIdx].clips.find((t) => {
|
|
3539
|
+
return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start + t.duration);
|
|
3540
|
+
});
|
|
3531
3541
|
// check no previous added clips are in the way
|
|
3532
3542
|
for (let i = c - 1; i > -1; --i) {
|
|
3533
|
-
if (clipTrackIdxs[i] != currTrackIdx || clipsInCurrentSlot)
|
|
3543
|
+
if (clipTrackIdxs[i] != currTrackIdx || clipsInCurrentSlot)
|
|
3534
3544
|
break;
|
|
3535
|
-
}
|
|
3536
3545
|
clipsInCurrentSlot = LX.compareThresholdRange(clipStart, clipEnd, clips[i].start + offsetTime, clips[i].start + offsetTime + clips[i].duration);
|
|
3537
3546
|
}
|
|
3538
3547
|
// check if it fits in the next track
|
|
3539
3548
|
if (clipsInCurrentSlot) {
|
|
3540
3549
|
++currTrackIdx;
|
|
3541
|
-
if (currTrackIdx >= tracks.length)
|
|
3550
|
+
if (currTrackIdx >= tracks.length)
|
|
3542
3551
|
this.addNewTrack(null, false);
|
|
3543
|
-
|
|
3544
|
-
|
|
3552
|
+
clipsInCurrentSlot = tracks[currTrackIdx].clips.find((t) => {
|
|
3553
|
+
return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start + t.duration);
|
|
3554
|
+
});
|
|
3545
3555
|
}
|
|
3546
3556
|
// reset search
|
|
3547
3557
|
if (clipsInCurrentSlot) {
|
|
@@ -3571,7 +3581,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3571
3581
|
return true;
|
|
3572
3582
|
}
|
|
3573
3583
|
deleteSelectedContent(skipCallback = false) {
|
|
3574
|
-
|
|
3584
|
+
// *********** WARNING: RELIES ON SORTED lastClipsSelected ***********
|
|
3575
3585
|
if (!this.lastClipsSelected.length) {
|
|
3576
3586
|
return;
|
|
3577
3587
|
}
|
|
@@ -3594,7 +3604,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3594
3604
|
/** Delete clip from the timeline
|
|
3595
3605
|
* @param {Number} trackIdx
|
|
3596
3606
|
* @param {Number} clipIdx clip to be deleted
|
|
3597
|
-
|
|
3607
|
+
*/
|
|
3598
3608
|
deleteClip(trackIdx, clipIdx, skipCallback = false) {
|
|
3599
3609
|
this.saveState(trackIdx);
|
|
3600
3610
|
const clip = this.#delete(trackIdx, clipIdx);
|
|
@@ -3620,12 +3630,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3620
3630
|
}
|
|
3621
3631
|
}
|
|
3622
3632
|
if (this.lastHovered && this.lastHovered[0] == trackIdx) {
|
|
3623
|
-
if (this.lastHovered[1] == clipIdx)
|
|
3633
|
+
if (this.lastHovered[1] == clipIdx)
|
|
3624
3634
|
this.unHoverAll();
|
|
3625
|
-
|
|
3626
|
-
else if (this.lastHovered[1] > clipIdx) {
|
|
3635
|
+
else if (this.lastHovered[1] > clipIdx)
|
|
3627
3636
|
this.lastHovered[1]--;
|
|
3628
|
-
}
|
|
3629
3637
|
}
|
|
3630
3638
|
const clip = track[clipIdx];
|
|
3631
3639
|
track.hovered.splice(clipIdx, 1);
|
|
@@ -3646,18 +3654,14 @@ class ClipsTimeline extends Timeline {
|
|
|
3646
3654
|
for (let i = 0; i < clipsToReturn.length; ++i) {
|
|
3647
3655
|
let clip = clipsToReturn[i];
|
|
3648
3656
|
clip.start += timeOffset;
|
|
3649
|
-
if (clip.fadein == null || clip.fadein == undefined)
|
|
3657
|
+
if (clip.fadein == null || clip.fadein == undefined)
|
|
3650
3658
|
clip.fadein = undefined;
|
|
3651
|
-
|
|
3652
|
-
else {
|
|
3659
|
+
else
|
|
3653
3660
|
clip.fadein += timeOffset;
|
|
3654
|
-
|
|
3655
|
-
if (clip.fadeout == null || clip.fadeout == undefined) {
|
|
3661
|
+
if (clip.fadeout == null || clip.fadeout == undefined)
|
|
3656
3662
|
clip.fadeout = undefined;
|
|
3657
|
-
|
|
3658
|
-
else {
|
|
3663
|
+
else
|
|
3659
3664
|
clip.fadeout += timeOffset;
|
|
3660
|
-
}
|
|
3661
3665
|
}
|
|
3662
3666
|
return clipsToReturn;
|
|
3663
3667
|
}
|
|
@@ -3675,9 +3679,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3675
3679
|
for (let i = 0; i < lastClipsSelected.length; ++i) {
|
|
3676
3680
|
let clip = tracks[lastClipsSelected[i][0]].clips[lastClipsSelected[i][1]];
|
|
3677
3681
|
clipsToCopy.push(clip);
|
|
3678
|
-
if (globalStart > clip.start)
|
|
3682
|
+
if (globalStart > clip.start)
|
|
3679
3683
|
globalStart = clip.start;
|
|
3680
|
-
}
|
|
3681
3684
|
}
|
|
3682
3685
|
globalStart = Math.max(0, globalStart);
|
|
3683
3686
|
this.clipboard = this.cloneClips(clipsToCopy, -globalStart, ClipsTimeline.CLONEREASON_COPY);
|
|
@@ -3718,9 +3721,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3718
3721
|
break;
|
|
3719
3722
|
}
|
|
3720
3723
|
}
|
|
3721
|
-
if (this.lastHovered && this.lastHovered[0] == trackIdx)
|
|
3724
|
+
if (this.lastHovered && this.lastHovered[0] == trackIdx)
|
|
3722
3725
|
this.unHoverAll();
|
|
3723
|
-
}
|
|
3724
3726
|
return;
|
|
3725
3727
|
}
|
|
3726
3728
|
/**
|
|
@@ -3758,12 +3760,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3758
3760
|
};
|
|
3759
3761
|
track.clips = state.clips;
|
|
3760
3762
|
track.edited = state.edited;
|
|
3761
|
-
if (track.selected.length < track.clips.length)
|
|
3763
|
+
if (track.selected.length < track.clips.length)
|
|
3762
3764
|
track.selected.length = track.clips.length;
|
|
3763
|
-
|
|
3764
|
-
if (track.hovered.length < track.clips.length) {
|
|
3765
|
+
if (track.hovered.length < track.clips.length)
|
|
3765
3766
|
track.hovered.length = track.clips.length;
|
|
3766
|
-
}
|
|
3767
3767
|
track.selected.fill(false);
|
|
3768
3768
|
track.hovered.fill(false);
|
|
3769
3769
|
// sanity check. Also done in addClip
|
|
@@ -3788,7 +3788,6 @@ class ClipsTimeline extends Timeline {
|
|
|
3788
3788
|
}
|
|
3789
3789
|
return -1;
|
|
3790
3790
|
}
|
|
3791
|
-
;
|
|
3792
3791
|
deselectAllClips() {
|
|
3793
3792
|
for (let [trackIdx, clipIdx] of this.lastClipsSelected) {
|
|
3794
3793
|
this.animationClip.tracks[trackIdx].selected[clipIdx] = false;
|
|
@@ -3826,12 +3825,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3826
3825
|
let i = 0;
|
|
3827
3826
|
for (; i < this.lastClipsSelected.length; ++i) {
|
|
3828
3827
|
let t = this.lastClipsSelected[i];
|
|
3829
|
-
if (t[0] < track.trackIdx)
|
|
3828
|
+
if (t[0] < track.trackIdx)
|
|
3830
3829
|
continue;
|
|
3831
|
-
|
|
3832
|
-
if (t[0] > track.trackIdx || t[1] > clipIndex) {
|
|
3830
|
+
if (t[0] > track.trackIdx || t[1] > clipIndex)
|
|
3833
3831
|
break;
|
|
3834
|
-
}
|
|
3835
3832
|
}
|
|
3836
3833
|
this.lastClipsSelected.splice(i, 0, [track.trackIdx, clipIndex, track.clips[clipIndex]]); //
|
|
3837
3834
|
track.selected[clipIndex] = true;
|
|
@@ -3861,8 +3858,9 @@ class ClipsTimeline extends Timeline {
|
|
|
3861
3858
|
return clipIndex;
|
|
3862
3859
|
}
|
|
3863
3860
|
getClipsInRange(track, minTime, maxTime, threshold = 0) {
|
|
3864
|
-
if (!track || !track.clips.length)
|
|
3861
|
+
if (!track || !track.clips.length) {
|
|
3865
3862
|
return null;
|
|
3863
|
+
}
|
|
3866
3864
|
// Manage negative selection
|
|
3867
3865
|
if (minTime > maxTime) {
|
|
3868
3866
|
let aux = minTime;
|
|
@@ -3875,18 +3873,17 @@ class ClipsTimeline extends Timeline {
|
|
|
3875
3873
|
minTime -= threshold;
|
|
3876
3874
|
maxTime += threshold;
|
|
3877
3875
|
const clips = track.clips;
|
|
3878
|
-
if (maxTime < clips[0].start
|
|
3876
|
+
if (maxTime < clips[0].start
|
|
3877
|
+
|| minTime > (clips[clips.length - 1].start + clips[clips.length - 1].duration)) {
|
|
3879
3878
|
return null;
|
|
3880
3879
|
}
|
|
3881
3880
|
let indices = [];
|
|
3882
3881
|
for (let i = 0; i < clips.length; ++i) {
|
|
3883
3882
|
const c = clips[i];
|
|
3884
|
-
if (c.start + c.duration < minTime)
|
|
3883
|
+
if (c.start + c.duration < minTime)
|
|
3885
3884
|
continue;
|
|
3886
|
-
|
|
3887
|
-
if (c.start > maxTime) {
|
|
3885
|
+
if (c.start > maxTime)
|
|
3888
3886
|
break;
|
|
3889
|
-
}
|
|
3890
3887
|
indices.push(i);
|
|
3891
3888
|
}
|
|
3892
3889
|
return indices.length ? indices : null;
|