lexgui 0.7.10 → 0.7.12
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/extensions/timeline.js +275 -205
- package/build/lexgui.css +0 -1
- package/build/lexgui.js +327 -271
- package/build/lexgui.min.css +1 -1
- package/build/lexgui.min.js +1 -1
- package/build/lexgui.module.js +351 -295
- package/build/lexgui.module.min.js +1 -1
- package/changelog.md +34 -1
- package/examples/editor.html +2 -2
- package/package.json +1 -1
|
@@ -6,6 +6,9 @@ if(!LX) {
|
|
|
6
6
|
|
|
7
7
|
LX.extensions.push( 'Timeline' );
|
|
8
8
|
|
|
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
|
+
|
|
9
12
|
/**
|
|
10
13
|
* @class Timeline
|
|
11
14
|
* @description Agnostic timeline, do not impose any timeline content. Renders to a canvas
|
|
@@ -14,8 +17,8 @@ LX.extensions.push( 'Timeline' );
|
|
|
14
17
|
class Timeline {
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
|
-
* @param {
|
|
18
|
-
* @param {
|
|
20
|
+
* @param {String} name = string unique id
|
|
21
|
+
* @param {Object} options = {skipLock, skipVisibility}
|
|
19
22
|
*/
|
|
20
23
|
constructor( id, options = {} ) {
|
|
21
24
|
|
|
@@ -101,6 +104,7 @@ class Timeline {
|
|
|
101
104
|
this.canvasArea = right;
|
|
102
105
|
this.canvasArea.root.classList.add("lextimelinearea");
|
|
103
106
|
|
|
107
|
+
this.selectedTracks = []; // [track, track] contains selected (highlighted) tracks. That is, tracks with .isSelected == true. Elements in array are not ordered. Only visible tracks should be selected
|
|
104
108
|
this.selectedItems = []; // [trackInfo, "groupId"], contains the visible items (tracks or groups) of the timeline
|
|
105
109
|
this.leftPanel = left.addPanel( { className: 'lextimelinepanel', width: "100%", height: "100%" } );
|
|
106
110
|
this.trackTreesPanel = null;
|
|
@@ -180,7 +184,6 @@ class Timeline {
|
|
|
180
184
|
|
|
181
185
|
/**
|
|
182
186
|
* @method updateHeader
|
|
183
|
-
* @param {*}
|
|
184
187
|
*/
|
|
185
188
|
|
|
186
189
|
updateHeader() {
|
|
@@ -312,8 +315,7 @@ class Timeline {
|
|
|
312
315
|
if ( this.onAddNewTrackButton ){
|
|
313
316
|
this.onAddNewTrackButton();
|
|
314
317
|
}else{
|
|
315
|
-
|
|
316
|
-
this.changeSelectedItems( [trackIdx] );
|
|
318
|
+
this.addNewTrack();
|
|
317
319
|
}
|
|
318
320
|
}, { hideName: true, title: "Add Track", icon: "Plus" });
|
|
319
321
|
}
|
|
@@ -332,31 +334,37 @@ class Timeline {
|
|
|
332
334
|
this.trackTreesComponent = p.addTree(null, treeTracks, {filter: false, rename: false, draggable: false, onevent: (e) => {
|
|
333
335
|
switch(e.type) {
|
|
334
336
|
case LX.TreeEvent.NODE_SELECTED:
|
|
337
|
+
if ( !e.event.shiftKey ){
|
|
338
|
+
this.deselectAllTracks( false ); // no need to update left panel
|
|
339
|
+
}
|
|
335
340
|
if (e.node.trackData){
|
|
336
|
-
|
|
341
|
+
const flag = e.event.shiftKey? !e.node.trackData.isSelected : true;
|
|
342
|
+
this.setTrackSelection( e.node.trackData.trackIdx, flag, false, false ); // do callback, do not update left panel
|
|
337
343
|
}
|
|
338
344
|
break;
|
|
339
345
|
case LX.TreeEvent.NODE_VISIBILITY:
|
|
340
346
|
if (e.node.trackData){
|
|
341
|
-
this.setTrackState( e.node.trackData.trackIdx, e.value );
|
|
347
|
+
this.setTrackState( e.node.trackData.trackIdx, e.value, false, false ); // do not update left panel
|
|
342
348
|
}
|
|
343
349
|
break;
|
|
344
|
-
|
|
345
|
-
if ( !this.trackTreesComponent ){ return;}
|
|
346
|
-
/* hack
|
|
347
|
-
On NODE_CARETCHANGED, the html are regenerated. TrackHeight need to be forced on the htmls again.
|
|
348
|
-
This event is called BEFORE the regeneration, so a setTimeout of 1ms is called.
|
|
349
|
-
To avoid a flicker, hide the element while it is changing and show it again afterwards
|
|
350
|
-
*/
|
|
351
|
-
this.trackTreesComponent.root.classList.add("hidden");
|
|
352
|
-
setTimeout( ()=>{
|
|
353
|
-
this.setTrackHeight(this.trackHeight);
|
|
354
|
-
this.trackTreesComponent.root.classList.remove("hidden");
|
|
355
|
-
}, 1);
|
|
350
|
+
}
|
|
356
351
|
|
|
357
|
-
|
|
352
|
+
if ( this.onTrackTreeEvent ){
|
|
353
|
+
this.onTrackTreeEvent(e);
|
|
358
354
|
}
|
|
359
355
|
}});
|
|
356
|
+
|
|
357
|
+
const that = this;
|
|
358
|
+
this.trackTreesComponent.innerTree._refresh = this.trackTreesComponent.innerTree.refresh;
|
|
359
|
+
this.trackTreesComponent.innerTree.refresh = function( newData, selectedId ){
|
|
360
|
+
this._refresh( newData, selectedId );
|
|
361
|
+
that.setTrackHeight( that.trackHeight );
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if ( this.selectedTracks.length ){
|
|
365
|
+
this._updateTrackTreeSelection(); // select visible tracks
|
|
366
|
+
}
|
|
367
|
+
|
|
360
368
|
// setting a name in the addTree function adds an undesired node
|
|
361
369
|
this.trackTreesComponent.name = "tracksTrees";
|
|
362
370
|
p.components[this.trackTreesComponent.name] = this.trackTreesComponent;
|
|
@@ -382,6 +390,9 @@ class Timeline {
|
|
|
382
390
|
}
|
|
383
391
|
|
|
384
392
|
this.resizeCanvas();
|
|
393
|
+
|
|
394
|
+
this.setScroll( this.currentScroll ); // avoid scroll bugs
|
|
395
|
+
|
|
385
396
|
}
|
|
386
397
|
|
|
387
398
|
setTrackHeight( trackHeight ){
|
|
@@ -402,7 +413,7 @@ class Timeline {
|
|
|
402
413
|
}
|
|
403
414
|
|
|
404
415
|
/**
|
|
405
|
-
* @param {
|
|
416
|
+
* @param {Object} options options for the new track
|
|
406
417
|
* { id: string, active: bool, locked: bool, }
|
|
407
418
|
* @returns
|
|
408
419
|
*/
|
|
@@ -421,8 +432,8 @@ class Timeline {
|
|
|
421
432
|
/**
|
|
422
433
|
* Finds tracks (wholy and partially) inside the range minY maxY.
|
|
423
434
|
* (Full) Canvas local coordinates.
|
|
424
|
-
* @param {
|
|
425
|
-
* @param {
|
|
435
|
+
* @param {Number} minY
|
|
436
|
+
* @param {Number} maxY
|
|
426
437
|
* @returns array of trackDatas
|
|
427
438
|
*/
|
|
428
439
|
getTracksInRange( minY, maxY ) {
|
|
@@ -462,14 +473,14 @@ class Timeline {
|
|
|
462
473
|
/**
|
|
463
474
|
* @method setAnimationClip
|
|
464
475
|
* @param {*} animation
|
|
465
|
-
* @param {
|
|
466
|
-
* @param {
|
|
476
|
+
* @param {Boolean} needsToProcess
|
|
477
|
+
* @param {Object} processOptions
|
|
467
478
|
* [KeyFrameTimeline] - each track should contain an attribute "dim" to indicate the value dimension (e.g. vector3 -> dim=3). Otherwise dimensions will be infered from track's values and times. Default is 1
|
|
468
479
|
*/
|
|
469
480
|
setAnimationClip( animation, needsToProcess = true ) {
|
|
470
481
|
|
|
471
482
|
this.deselectAllElements();
|
|
472
|
-
this.deselectAllTracks();
|
|
483
|
+
this.deselectAllTracks( false ); // no need to update left panel yet
|
|
473
484
|
|
|
474
485
|
this.selectedItems = [];
|
|
475
486
|
|
|
@@ -798,7 +809,7 @@ class Timeline {
|
|
|
798
809
|
* @method setScroll
|
|
799
810
|
* not delta from last state, but full scroll amount.
|
|
800
811
|
* @param {Number} scrollY either pixels or [0,1]
|
|
801
|
-
* @param {
|
|
812
|
+
* @param {Boolean} normalized if true, scrollY is in range[0,1] being 1 fully scrolled. Otherwised scrollY represents pixels
|
|
802
813
|
* @returns
|
|
803
814
|
*/
|
|
804
815
|
|
|
@@ -926,7 +937,6 @@ class Timeline {
|
|
|
926
937
|
this.movingKeys = false;
|
|
927
938
|
this.timeBeforeMove = null;
|
|
928
939
|
this.boxSelection = false; // after mouseup
|
|
929
|
-
this.deselectAllTracks();
|
|
930
940
|
}
|
|
931
941
|
|
|
932
942
|
|
|
@@ -1053,7 +1063,7 @@ class Timeline {
|
|
|
1053
1063
|
|
|
1054
1064
|
/**
|
|
1055
1065
|
* @method changeState
|
|
1056
|
-
* @param {
|
|
1066
|
+
* @param {Boolean} skipCallback defaults false
|
|
1057
1067
|
* @description change play/pause state
|
|
1058
1068
|
**/
|
|
1059
1069
|
changeState(skipCallback = false) {
|
|
@@ -1061,8 +1071,8 @@ class Timeline {
|
|
|
1061
1071
|
}
|
|
1062
1072
|
/**
|
|
1063
1073
|
* @method setState
|
|
1064
|
-
* @param {
|
|
1065
|
-
* @param {
|
|
1074
|
+
* @param {Boolean} state
|
|
1075
|
+
* @param {Boolean} skipCallback defaults false
|
|
1066
1076
|
* @description change play/pause state
|
|
1067
1077
|
**/
|
|
1068
1078
|
setState(state, skipCallback = false) {
|
|
@@ -1077,8 +1087,8 @@ class Timeline {
|
|
|
1077
1087
|
|
|
1078
1088
|
/**
|
|
1079
1089
|
* @method setLoopMode
|
|
1080
|
-
* @param {
|
|
1081
|
-
* @param {
|
|
1090
|
+
* @param {Boolean} loopState
|
|
1091
|
+
* @param {Boolean} skipCallback defaults false
|
|
1082
1092
|
* @description change loop mode of the timeline
|
|
1083
1093
|
*/
|
|
1084
1094
|
setLoopMode(loopState, skipCallback = false){
|
|
@@ -1112,11 +1122,14 @@ class Timeline {
|
|
|
1112
1122
|
}
|
|
1113
1123
|
|
|
1114
1124
|
/**
|
|
1115
|
-
* @param {
|
|
1116
|
-
* @param {
|
|
1125
|
+
* @param {Array} itemsToAdd [ trackIdx ], array of numbers identifying tracks by their index
|
|
1126
|
+
* @param {Array} itemsToRemove [ trackIdx ], array of numbers identifying tracks by their index
|
|
1117
1127
|
*/
|
|
1118
1128
|
changeSelectedItems( itemsToAdd = null, itemsToRemove = null, skipCallback = false ) {
|
|
1119
1129
|
|
|
1130
|
+
this.deselectAllElements();
|
|
1131
|
+
this.deselectAllTracks( false ); // no need to update left panel. It is going to be rebuilt anyways
|
|
1132
|
+
|
|
1120
1133
|
const tracks = this.animationClip.tracks;
|
|
1121
1134
|
|
|
1122
1135
|
if ( itemsToRemove ){
|
|
@@ -1140,7 +1153,6 @@ class Timeline {
|
|
|
1140
1153
|
}
|
|
1141
1154
|
}
|
|
1142
1155
|
|
|
1143
|
-
|
|
1144
1156
|
this.updateLeftPanel();
|
|
1145
1157
|
|
|
1146
1158
|
if(this.onItemSelected && !skipCallback){
|
|
@@ -1150,7 +1162,7 @@ class Timeline {
|
|
|
1150
1162
|
|
|
1151
1163
|
/**
|
|
1152
1164
|
* It will find the first occurrence of trackId in animationClip.tracks
|
|
1153
|
-
* @param {
|
|
1165
|
+
* @param {String} trackId
|
|
1154
1166
|
* @returns
|
|
1155
1167
|
*/
|
|
1156
1168
|
getTrack( trackId ){
|
|
@@ -1164,38 +1176,83 @@ class Timeline {
|
|
|
1164
1176
|
}
|
|
1165
1177
|
|
|
1166
1178
|
/**
|
|
1167
|
-
*
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
*/
|
|
1172
|
-
selectTrack( trackIdx ) {
|
|
1179
|
+
* @param {Boolean} updateTrackTree whether the track tree needs a refresh
|
|
1180
|
+
* @returns
|
|
1181
|
+
*/
|
|
1182
|
+
deselectAllTracks( updateTrackTree = true ) {
|
|
1173
1183
|
|
|
1174
1184
|
if( !this.animationClip ){
|
|
1175
1185
|
return;
|
|
1176
1186
|
}
|
|
1177
1187
|
|
|
1178
|
-
this.
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1188
|
+
const tracks = this.animationClip.tracks;
|
|
1189
|
+
for(let i = 0; i < tracks.length; i++){
|
|
1190
|
+
tracks[ i ].isSelected = false;
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
this.selectedTracks.length = 0;
|
|
1194
|
+
|
|
1195
|
+
if ( updateTrackTree ){
|
|
1196
|
+
this._updateTrackTreeSelection();
|
|
1185
1197
|
}
|
|
1186
1198
|
}
|
|
1187
1199
|
|
|
1188
|
-
|
|
1189
|
-
|
|
1200
|
+
/**
|
|
1201
|
+
* @param {Int} trackIdx
|
|
1202
|
+
* @param {Boolean} isSelected new "selected" state of the track
|
|
1203
|
+
* @param {Boolean} skipCallback whether to call onSetTrackSelection
|
|
1204
|
+
* @param {Boolean} updateTrackTree whether track tree panel needs a refresh
|
|
1205
|
+
* @returns
|
|
1206
|
+
*/
|
|
1207
|
+
setTrackSelection( trackIdx, isSelected, skipCallback = false, updateTrackTree = true ){
|
|
1208
|
+
const track = this.animationClip.tracks[ trackIdx ];
|
|
1209
|
+
const oldValue = track.isSelected;
|
|
1210
|
+
track.isSelected = isSelected;
|
|
1211
|
+
|
|
1212
|
+
const idx = this.selectedTracks.indexOf( track );
|
|
1213
|
+
if ( ( idx == -1 && !isSelected ) || ( idx > -1 && isSelected ) ){
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1190
1216
|
|
|
1191
|
-
if(
|
|
1192
|
-
|
|
1217
|
+
if ( idx == -1 ){
|
|
1218
|
+
this.selectedTracks.push( track );
|
|
1219
|
+
}else{
|
|
1220
|
+
this.selectedTracks.splice( idx, 1 );
|
|
1193
1221
|
}
|
|
1194
1222
|
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1223
|
+
if( this.onSetTrackSelection && !skipCallback ){
|
|
1224
|
+
this.onSetTrackSelection(track, oldValue );
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
if ( updateTrackTree ){
|
|
1228
|
+
this._updateTrackTreeSelection();
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
/**
|
|
1233
|
+
* updates trackTreesComponent's nodes, to match the selectedTracks
|
|
1234
|
+
*/
|
|
1235
|
+
_updateTrackTreeSelection(){
|
|
1236
|
+
const data = this.trackTreesComponent.innerTree.data;
|
|
1237
|
+
const selected = this.trackTreesComponent.innerTree.selected;
|
|
1238
|
+
selected.length = 0;
|
|
1239
|
+
|
|
1240
|
+
const addToSelection = (nodes) =>{
|
|
1241
|
+
for( let i = 0; i < nodes.length; ++i ){
|
|
1242
|
+
if ( nodes[i].trackData && nodes[i].trackData.isSelected ){
|
|
1243
|
+
selected.push( nodes[i] );
|
|
1244
|
+
}
|
|
1245
|
+
if ( nodes[i].children ){
|
|
1246
|
+
addToSelection( nodes[i].children );
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
|
|
1251
|
+
// update innerTree (visible) selected nodes
|
|
1252
|
+
if ( this.selectedTracks.length ){
|
|
1253
|
+
addToSelection( data );
|
|
1198
1254
|
}
|
|
1255
|
+
this.trackTreesComponent.innerTree.refresh();
|
|
1199
1256
|
}
|
|
1200
1257
|
|
|
1201
1258
|
deselectAllElements(){
|
|
@@ -1204,22 +1261,53 @@ class Timeline {
|
|
|
1204
1261
|
|
|
1205
1262
|
/**
|
|
1206
1263
|
* @method setTrackState
|
|
1207
|
-
* @param {
|
|
1208
|
-
* @param {
|
|
1264
|
+
* @param {Int} trackIdx
|
|
1265
|
+
* @param {Boolean} isEnbaled
|
|
1266
|
+
* @param {Boolean} skipCallback onSetTrackState
|
|
1267
|
+
* @param {Boolean} updateTrackTree updates eye icon of the track, if it is visible in the timeline
|
|
1209
1268
|
*/
|
|
1210
|
-
setTrackState(trackIdx, isEnbaled = true, skipCallback = false) {
|
|
1269
|
+
setTrackState(trackIdx, isEnbaled = true, skipCallback = false, updateTrackTree = true ) {
|
|
1211
1270
|
const track = this.animationClip.tracks[trackIdx];
|
|
1212
1271
|
|
|
1213
1272
|
const oldState = track.active;
|
|
1214
1273
|
track.active = isEnbaled;
|
|
1215
1274
|
|
|
1216
|
-
if(this.onSetTrackState && !skipCallback)
|
|
1275
|
+
if ( this.onSetTrackState && !skipCallback ){
|
|
1217
1276
|
this.onSetTrackState(track, oldState);
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
if ( updateTrackTree && !this.skipVisibility ){
|
|
1280
|
+
// TODO: a bit of an overkill. Maybe searching the node in the tree is less expensive
|
|
1281
|
+
this.updateLeftPanel();
|
|
1282
|
+
}
|
|
1218
1283
|
}
|
|
1219
1284
|
|
|
1220
1285
|
/**
|
|
1221
|
-
*
|
|
1222
|
-
* @param {
|
|
1286
|
+
*
|
|
1287
|
+
* @param {Int} trackIdx
|
|
1288
|
+
* @param {Boolean} isLocked
|
|
1289
|
+
* @param {Boolean} skipCallback onSetTrackLock
|
|
1290
|
+
* @param {Boolean} updateTrackTree updates lock icon of the track, if it is visible in the timeline
|
|
1291
|
+
*/
|
|
1292
|
+
setTrackLock(trackIdx, isLocked = false, skipCallback = false, updateTrackTree = true ){
|
|
1293
|
+
const track = this.animationClip.tracks[trackIdx];
|
|
1294
|
+
|
|
1295
|
+
const oldState = track.locked;
|
|
1296
|
+
track.locked = isLocked;
|
|
1297
|
+
|
|
1298
|
+
if ( this.onSetTrackLock && !skipCallback ){
|
|
1299
|
+
this.onSetTrackLock( track, oldState );
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
if ( updateTrackTree && !this.skipLock ){
|
|
1303
|
+
// TODO: a bit of an overkill. Maybe searching the node in the tree is less expensive
|
|
1304
|
+
this.updateLeftPanel();
|
|
1305
|
+
}
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
/**
|
|
1309
|
+
* @param {Int} trackIdx index of track in the animation (not local index)
|
|
1310
|
+
* @param {Boolean} combineWithPrevious whether to create a new entry or unify changes into a single undo entry
|
|
1223
1311
|
*/
|
|
1224
1312
|
saveState( trackIdx, combineWithPrevious = false ) {
|
|
1225
1313
|
if ( !this.historySaveEnabler ){ return; }
|
|
@@ -1332,26 +1420,10 @@ class Timeline {
|
|
|
1332
1420
|
const track = this.selectedItems[ i ];
|
|
1333
1421
|
treeTracks.push({'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility, visible: track.active, 'children':[], actions : this.skipLock ? null : [{
|
|
1334
1422
|
'name':'Lock edition',
|
|
1335
|
-
'icon': (track.locked ? '
|
|
1336
|
-
'swap': (track.locked ? '
|
|
1337
|
-
'callback': (node,
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
if(value) {
|
|
1341
|
-
el.title = 'Lock edition';
|
|
1342
|
-
el.classList.remove('Lock');
|
|
1343
|
-
el.classList.add('LockOpen');
|
|
1344
|
-
}
|
|
1345
|
-
else {
|
|
1346
|
-
el.title = 'Unlock edition';
|
|
1347
|
-
el.classList.remove('LockOpen');
|
|
1348
|
-
el.classList.add('Lock');
|
|
1349
|
-
}
|
|
1350
|
-
|
|
1351
|
-
node.trackData.locked = !value;
|
|
1352
|
-
if(this.onLockTrack){
|
|
1353
|
-
this.onLockTrack(el, node.trackData, node)
|
|
1354
|
-
}
|
|
1423
|
+
'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
|
|
1424
|
+
'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
|
|
1425
|
+
'callback': (node, swapValue, event) => {
|
|
1426
|
+
this.setTrackLock( node.trackData.trackIdx, !node.trackData.locked, false, false ); // do not update left panel
|
|
1355
1427
|
}
|
|
1356
1428
|
}]});
|
|
1357
1429
|
}
|
|
@@ -1361,7 +1433,7 @@ class Timeline {
|
|
|
1361
1433
|
|
|
1362
1434
|
/**
|
|
1363
1435
|
*
|
|
1364
|
-
* @param {
|
|
1436
|
+
* @param {Object} options set some values for the track instance (groups and trackIdx not included)
|
|
1365
1437
|
* @returns
|
|
1366
1438
|
*/
|
|
1367
1439
|
instantiateTrack(options = {}, clone = false) {
|
|
@@ -1370,7 +1442,7 @@ class Timeline {
|
|
|
1370
1442
|
id: options.id ?? ( Math.floor(performance.now().toString()) + "_" + Math.floor(Math.random() * 0xffff) ), //must be unique, at least inside a group
|
|
1371
1443
|
active: options.active ?? true,
|
|
1372
1444
|
locked: options.locked ?? false,
|
|
1373
|
-
isSelected: false, // render
|
|
1445
|
+
isSelected: false, // render and lexgui tree
|
|
1374
1446
|
trackIdx: -1,
|
|
1375
1447
|
data: options.data ?? null // user defined data
|
|
1376
1448
|
}
|
|
@@ -1378,7 +1450,7 @@ class Timeline {
|
|
|
1378
1450
|
|
|
1379
1451
|
/**
|
|
1380
1452
|
* Generates an animationClip using either the parameters set in the animation argument or using default values
|
|
1381
|
-
* @param {
|
|
1453
|
+
* @param {Object} animation data with which to generate an animationClip
|
|
1382
1454
|
* @returns
|
|
1383
1455
|
*/
|
|
1384
1456
|
instantiateAnimationClip(options, clone = false) {
|
|
@@ -1430,8 +1502,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1430
1502
|
|
|
1431
1503
|
static ADDKEY_VALUESINARRAYS = 0x01; // addkeyframes as [ [k0v0, k0v1...], [k1v0, k1v1...] ] instead of [k0v0,k0v1,k1v0,k1v1]
|
|
1432
1504
|
/**
|
|
1433
|
-
* @param {
|
|
1434
|
-
* @param {
|
|
1505
|
+
* @param {String} name unique string
|
|
1506
|
+
* @param {Object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
|
|
1435
1507
|
*/
|
|
1436
1508
|
constructor(name, options = {}) {
|
|
1437
1509
|
|
|
@@ -1466,26 +1538,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1466
1538
|
const track = itemTracks[j];
|
|
1467
1539
|
nodes.push({'trackData': track, 'id': track.id, 'skipVisibility': this.skipVisibility, visible: track.active, 'children':[], actions : this.skipLock ? null : [{
|
|
1468
1540
|
'name':'Lock edition',
|
|
1469
|
-
'icon': (track.locked ? '
|
|
1470
|
-
'swap': (track.locked ? '
|
|
1471
|
-
'callback': (node,
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
if(value) {
|
|
1475
|
-
el.title = 'Lock edition';
|
|
1476
|
-
el.classList.remove('Lock');
|
|
1477
|
-
el.classList.add('LockOpen');
|
|
1478
|
-
}
|
|
1479
|
-
else {
|
|
1480
|
-
el.title = 'Unlock edition';
|
|
1481
|
-
el.classList.remove('LockOpen');
|
|
1482
|
-
el.classList.add('Lock');
|
|
1483
|
-
}
|
|
1484
|
-
|
|
1485
|
-
node.trackData.locked = !value;
|
|
1486
|
-
if(this.onLockTrack){
|
|
1487
|
-
this.onLockTrack(el, node.trackData, node)
|
|
1488
|
-
}
|
|
1541
|
+
'icon': (track.locked ? 'TimelineLock' : 'TimelineLockOpen'),
|
|
1542
|
+
'swap': (track.locked ? 'TimelineLockOpen' : 'TimelineLock'),
|
|
1543
|
+
'callback': (node, swapValue, event) => {
|
|
1544
|
+
this.setTrackLock( node.trackData.trackIdx, !node.trackData.locked, false, false ); // do not update left panel
|
|
1489
1545
|
}
|
|
1490
1546
|
}]});
|
|
1491
1547
|
}
|
|
@@ -1505,7 +1561,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1505
1561
|
|
|
1506
1562
|
/**
|
|
1507
1563
|
* OVERRIDE
|
|
1508
|
-
* @param {
|
|
1564
|
+
* @param {Object} options track information that wants to be set to the new track
|
|
1509
1565
|
* id, dim, values, times, selected, edited, hovered
|
|
1510
1566
|
* @returns
|
|
1511
1567
|
*/
|
|
@@ -1550,7 +1606,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1550
1606
|
|
|
1551
1607
|
/**
|
|
1552
1608
|
* Generates an animationClip using either the parameters set in the animation argument or using default values
|
|
1553
|
-
* @param {
|
|
1609
|
+
* @param {Object} animation data with which to generate an animationClip
|
|
1554
1610
|
* @returns
|
|
1555
1611
|
*/
|
|
1556
1612
|
instantiateAnimationClip(animation, clone = false) {
|
|
@@ -1639,12 +1695,13 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1639
1695
|
|
|
1640
1696
|
/**
|
|
1641
1697
|
* OVERRIDE
|
|
1642
|
-
* @param {
|
|
1643
|
-
* @param {
|
|
1698
|
+
* @param {Array} itemsToAdd [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
|
|
1699
|
+
* @param {Array} itemsToRemove [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
|
|
1644
1700
|
*/
|
|
1645
1701
|
changeSelectedItems( itemsToAdd = null, itemsToRemove = null, skipCallback = false ) {
|
|
1646
1702
|
|
|
1647
1703
|
this.deselectAllElements();
|
|
1704
|
+
this.deselectAllTracks( false ); // no need to update left panel. It is going to be rebuilt anyways
|
|
1648
1705
|
|
|
1649
1706
|
const tracks = this.animationClip.tracks;
|
|
1650
1707
|
const tracksPerGroup = this.animationClip.tracksPerGroup;
|
|
@@ -1675,7 +1732,6 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1675
1732
|
}
|
|
1676
1733
|
}
|
|
1677
1734
|
}
|
|
1678
|
-
|
|
1679
1735
|
|
|
1680
1736
|
this.updateLeftPanel();
|
|
1681
1737
|
|
|
@@ -1685,8 +1741,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1685
1741
|
}
|
|
1686
1742
|
|
|
1687
1743
|
/**
|
|
1688
|
-
* @param {
|
|
1689
|
-
* @param {
|
|
1744
|
+
* @param {String} groupId unique identifier
|
|
1745
|
+
* @param {Array} groupTracks [ "trackID", trackIdx ] array of strings and/or numbers of the existing tracks to include in this group. A track can only be part of 1 group
|
|
1690
1746
|
* if groupTracks == null, groupId is removed from the list
|
|
1691
1747
|
*/
|
|
1692
1748
|
setTracksGroup( groupId, groupTracks = null ){
|
|
@@ -1754,7 +1810,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1754
1810
|
}
|
|
1755
1811
|
|
|
1756
1812
|
/**
|
|
1757
|
-
* @param {
|
|
1813
|
+
* @param {String} groupId
|
|
1758
1814
|
* @returns array of tracks or null
|
|
1759
1815
|
*/
|
|
1760
1816
|
getTracksGroup( groupId ){
|
|
@@ -1763,8 +1819,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1763
1819
|
|
|
1764
1820
|
/**
|
|
1765
1821
|
* OVERRIDE
|
|
1766
|
-
* @param {
|
|
1767
|
-
* @param {
|
|
1822
|
+
* @param {String} trackId
|
|
1823
|
+
* @param {String} groupId optionl. If not set, it will find the first occurrence of trackId in animationClip.tracks
|
|
1768
1824
|
* @returns
|
|
1769
1825
|
*/
|
|
1770
1826
|
getTrack( trackId, groupId = null ){
|
|
@@ -1782,8 +1838,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1782
1838
|
|
|
1783
1839
|
/**
|
|
1784
1840
|
*
|
|
1785
|
-
* @param {
|
|
1786
|
-
* @param {
|
|
1841
|
+
* @param {Number} size pixels, height of keyframe
|
|
1842
|
+
* @param {Number} sizeHovered optional, size in pixels when hovered
|
|
1787
1843
|
*/
|
|
1788
1844
|
setKeyframeSize( size, sizeHovered = null ){
|
|
1789
1845
|
this.keyframeSizeHovered = sizeHovered ?? size;
|
|
@@ -1799,7 +1855,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1799
1855
|
if(e.shiftKey) {
|
|
1800
1856
|
// Manual multiple selection
|
|
1801
1857
|
if(!discard && track) {
|
|
1802
|
-
const
|
|
1858
|
+
const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
|
|
1859
|
+
const keyFrameIdx = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
|
|
1803
1860
|
if ( keyFrameIdx > -1 ){
|
|
1804
1861
|
track.selected[keyFrameIdx] ?
|
|
1805
1862
|
this.deselectKeyFrame(track.trackIdx, keyFrameIdx) :
|
|
@@ -1833,7 +1890,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1833
1890
|
this.deselectAllKeyFrames();
|
|
1834
1891
|
}
|
|
1835
1892
|
if (track){
|
|
1836
|
-
const
|
|
1893
|
+
const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
|
|
1894
|
+
const keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
|
|
1837
1895
|
if( keyFrameIndex > -1 ) {
|
|
1838
1896
|
this.processSelectionKeyFrame( track.trackIdx, keyFrameIndex, false ); // Settings this as multiple so time is not being set
|
|
1839
1897
|
}
|
|
@@ -1850,7 +1908,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1850
1908
|
let localY = e.localY;
|
|
1851
1909
|
let track = e.track;
|
|
1852
1910
|
|
|
1853
|
-
if(e.ctrlKey && this.lastKeyFramesSelected.length) { // move keyframes
|
|
1911
|
+
if( (e.ctrlKey || e.altKey) && this.lastKeyFramesSelected.length) { // move keyframes
|
|
1854
1912
|
this.movingKeys = true;
|
|
1855
1913
|
this.canvas.style.cursor = "grab";
|
|
1856
1914
|
this.canvas.classList.add('grabbing');
|
|
@@ -1858,31 +1916,28 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1858
1916
|
// Set pre-move state
|
|
1859
1917
|
this.moveKeyMinTime = Infinity;
|
|
1860
1918
|
const tracks = this.animationClip.tracks;
|
|
1861
|
-
|
|
1919
|
+
let lastTrackIdx = -1;
|
|
1920
|
+
for(let selectedKey of this.lastKeyFramesSelected) { // WARNING assumes lasKeyFramesSelected is sorted, so all keyframes of the same track are grouped
|
|
1862
1921
|
let [trackIdx, keyIndex, keyTime] = selectedKey;
|
|
1863
1922
|
const track = tracks[trackIdx];
|
|
1923
|
+
|
|
1924
|
+
selectedKey[2] = track.times[keyIndex]; // update original time just in case
|
|
1864
1925
|
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
let s = 0;
|
|
1869
|
-
for( s = 0; s < state.length; ++s){
|
|
1870
|
-
if ( state[s].trackIdx == track.trackIdx ){ break; }
|
|
1871
|
-
}
|
|
1872
|
-
if( s == state.length ){
|
|
1926
|
+
if ( lastTrackIdx != trackIdx ){
|
|
1927
|
+
// save track states only once
|
|
1928
|
+
if (this.moveKeyMinTime < Infinity){
|
|
1873
1929
|
this.saveState(track.trackIdx, true);
|
|
1930
|
+
}else{
|
|
1931
|
+
this.saveState(track.trackIdx, false);
|
|
1874
1932
|
}
|
|
1875
|
-
|
|
1876
|
-
|
|
1933
|
+
this.moveKeyMinTime = Math.min( this.moveKeyMinTime, selectedKey[2] );
|
|
1934
|
+
lastTrackIdx = trackIdx;
|
|
1877
1935
|
}
|
|
1878
1936
|
|
|
1879
|
-
selectedKey[2] = track.times[keyIndex]; // update original time just in case
|
|
1880
|
-
this.moveKeyMinTime = Math.min( this.moveKeyMinTime, selectedKey[2] );
|
|
1881
1937
|
}
|
|
1882
1938
|
|
|
1883
1939
|
this.timeBeforeMove = this.xToTime( localX );
|
|
1884
|
-
|
|
1885
|
-
else if( e.altKey ){ // if only altkey, do not grab timeline
|
|
1940
|
+
|
|
1886
1941
|
this.grabbing = false;
|
|
1887
1942
|
this.grabbingTimeBar = false;
|
|
1888
1943
|
}
|
|
@@ -1957,47 +2012,45 @@ class KeyFramesTimeline extends Timeline {
|
|
|
1957
2012
|
}
|
|
1958
2013
|
}
|
|
1959
2014
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
1970
|
-
|
|
1971
|
-
|
|
1972
|
-
|
|
1973
|
-
|
|
1974
|
-
|
|
2015
|
+
|
|
2016
|
+
// Track.dim == 1: move keyframes vertically (change values instead of time)
|
|
2017
|
+
// RELIES ON SORTED ARRAY OF lastKeyFramesSelected
|
|
2018
|
+
if ( e.altKey && e.buttons & 0x01 ){
|
|
2019
|
+
const tracks = this.animationClip.tracks;
|
|
2020
|
+
let lastTrackChanged = -1;
|
|
2021
|
+
for( let i = 0; i < this.lastKeyFramesSelected.length; ++i ){
|
|
2022
|
+
const [trackIdx, keyIndex, originalKeyTime] = this.lastKeyFramesSelected[i];
|
|
2023
|
+
track = tracks[trackIdx];
|
|
2024
|
+
if(track.locked || track.dim != 1 || !track.curves){
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2028
|
+
let value = track.values[keyIndex];
|
|
2029
|
+
let delta = e.deltay * this.keyValuePerPixel * (track.curvesRange[1]-track.curvesRange[0]);
|
|
2030
|
+
track.values[keyIndex] = Math.max(track.curvesRange[0], Math.min(track.curvesRange[1], value - delta)); // invert delta because of screen y
|
|
2031
|
+
track.edited[keyIndex] = true;
|
|
2032
|
+
|
|
2033
|
+
if ( this.onUpdateTrack && track.trackIdx != lastTrackChanged && lastTrackChanged > -1){ // do it only once all keyframes of the same track have been modified
|
|
2034
|
+
this.onUpdateTrack( [track.trackIdx] );
|
|
2035
|
+
}
|
|
2036
|
+
lastTrackChanged = track.trackIdx;
|
|
1975
2037
|
}
|
|
1976
|
-
|
|
1977
|
-
let value = track.values[keyIndex];
|
|
1978
|
-
let delta = e.deltay * this.keyValuePerPixel * (track.curvesRange[1]-track.curvesRange[0]);
|
|
1979
|
-
track.values[keyIndex] = Math.max(track.curvesRange[0], Math.min(track.curvesRange[1], value - delta)); // invert delta because of screen y
|
|
1980
|
-
track.edited[keyIndex] = true;
|
|
1981
|
-
|
|
1982
|
-
if ( this.onUpdateTrack && track.trackIdx != lastTrackChanged && lastTrackChanged > -1){ // do it only once all keyframes of the same track have been modified
|
|
2038
|
+
if( this.onUpdateTrack && lastTrackChanged > -1 ){ // do the last update, once the last track has been processed
|
|
1983
2039
|
this.onUpdateTrack( [track.trackIdx] );
|
|
1984
2040
|
}
|
|
1985
|
-
|
|
1986
|
-
}
|
|
1987
|
-
if( this.onUpdateTrack && lastTrackChanged > -1 ){ // do the last update, once the last track has been processed
|
|
1988
|
-
this.onUpdateTrack( [track.trackIdx] );
|
|
2041
|
+
return;
|
|
1989
2042
|
}
|
|
1990
|
-
return;
|
|
1991
2043
|
}
|
|
1992
2044
|
|
|
2045
|
+
|
|
1993
2046
|
if( this.grabbing && e.button != 2) {
|
|
1994
2047
|
|
|
1995
2048
|
}
|
|
1996
2049
|
else if(track) {
|
|
1997
2050
|
|
|
1998
2051
|
this.unHoverAll();
|
|
1999
|
-
const thresholdPixels =
|
|
2000
|
-
let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels
|
|
2052
|
+
const thresholdPixels = this.keyframeSize * 0.5; // radius of circle (curves) or rotated square (keyframes)
|
|
2053
|
+
let keyFrameIndex = this.getCurrentKeyFrame( track, this.xToTime( localX ), this.secondsPerPixel * thresholdPixels );
|
|
2001
2054
|
if(keyFrameIndex > -1 ) {
|
|
2002
2055
|
if(track && track.locked)
|
|
2003
2056
|
return;
|
|
@@ -2166,8 +2219,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2166
2219
|
const keyframes = track.times;
|
|
2167
2220
|
const startTime = this.visualTimeRange[0];
|
|
2168
2221
|
const endTime = this.visualTimeRange[1] + 0.0000001;
|
|
2169
|
-
const defaultPointSize =
|
|
2170
|
-
const hoverPointSize =
|
|
2222
|
+
const defaultPointSize = this.keyframeSize / Math.SQRT2; // pythagoras with equal sides h2 = c2 + c2 = 2 * c2
|
|
2223
|
+
const hoverPointSize = this.keyframeSizeHovered / Math.SQRT2;
|
|
2171
2224
|
|
|
2172
2225
|
for(let j = 0; j < keyframes.length; ++j)
|
|
2173
2226
|
{
|
|
@@ -2326,7 +2379,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2326
2379
|
|
|
2327
2380
|
/**
|
|
2328
2381
|
* updates an existing track with new values and times.
|
|
2329
|
-
* @param {
|
|
2382
|
+
* @param {Int} trackIdx index of track in the animationClip
|
|
2330
2383
|
* @param {*} newTrack object with two arrays: values and times. These will be set to the selected track
|
|
2331
2384
|
* @returns
|
|
2332
2385
|
*/
|
|
@@ -2348,8 +2401,8 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2348
2401
|
* removes equivalent sequential keys either because of equal times or values
|
|
2349
2402
|
* (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
|
|
2350
2403
|
* @param {Int} trackIdx index of track in the animation
|
|
2351
|
-
* @param {
|
|
2352
|
-
* @param {
|
|
2404
|
+
* @param {Boolean} onlyEqualTime if true, removes only keyframes with equal times. Otherwise, values are ALSO compared through the class threshold
|
|
2405
|
+
* @param {Boolean} skipCallback if false, triggers "onOptimizeTracks" after optimizing
|
|
2353
2406
|
*/
|
|
2354
2407
|
optimizeTrack(trackIdx, onlyEqualTime = false, skipCallback = false ) {
|
|
2355
2408
|
if ( !this.animationClip ){ return; }
|
|
@@ -2359,6 +2412,11 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2359
2412
|
values = track.values,
|
|
2360
2413
|
stride = track.dim,
|
|
2361
2414
|
threshold = this.optimizeThreshold;
|
|
2415
|
+
|
|
2416
|
+
if ( track.locked ){
|
|
2417
|
+
return;
|
|
2418
|
+
}
|
|
2419
|
+
|
|
2362
2420
|
let cmpFunction = (v, p, n, t) => { return Math.abs(v - p) >= t || Math.abs(v - n) >= t };
|
|
2363
2421
|
let lastSavedIndex = 0;
|
|
2364
2422
|
const lastIndex = times.length-1;
|
|
@@ -2701,7 +2759,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2701
2759
|
|
|
2702
2760
|
pasteKeyFrameValue( track, index ) {
|
|
2703
2761
|
|
|
2704
|
-
if(this.clipboard.value.type != track.type){
|
|
2762
|
+
if(track.locked || this.clipboard.value.type != track.type){
|
|
2705
2763
|
return;
|
|
2706
2764
|
}
|
|
2707
2765
|
|
|
@@ -2746,6 +2804,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2746
2804
|
const values = clipboardInfo.values;
|
|
2747
2805
|
const track = this.animationClip.tracks[trackIdx];
|
|
2748
2806
|
|
|
2807
|
+
if( track.locked ){
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
|
|
2749
2811
|
this.saveState(track.trackIdx, trackCount++);
|
|
2750
2812
|
this.historySaveEnabler = false;
|
|
2751
2813
|
this.addKeyFrames( track.trackIdx, values, times, -globalStart + pasteTime, KeyFramesTimeline.ADDKEY_VALUESINARRAYS );
|
|
@@ -2763,11 +2825,11 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2763
2825
|
|
|
2764
2826
|
/**
|
|
2765
2827
|
*
|
|
2766
|
-
* @param {
|
|
2767
|
-
* @param {
|
|
2768
|
-
* @param {
|
|
2769
|
-
* @param {
|
|
2770
|
-
* @param {
|
|
2828
|
+
* @param {Int} trackIdx
|
|
2829
|
+
* @param {Array} newValues array of values for each keyframe. It should be a flat array of size track.dim*numKeyframes. Check ADDKEY_VALUESINARRAYS flag
|
|
2830
|
+
* @param {Array of numbers} newTimes must be ordered ascendently
|
|
2831
|
+
* @param {Number} timeOffset
|
|
2832
|
+
* @param {Int} flags
|
|
2771
2833
|
* KeyFramesTimeline.ADDKEY_VALUESINARRAYS: if set, newValues is an array of arrays, one for each entry [ [1,2,3], [5,6,7] ]. Times is still a flat array of values [ 0, 0.2 ]
|
|
2772
2834
|
|
|
2773
2835
|
* @returns
|
|
@@ -2775,7 +2837,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2775
2837
|
addKeyFrames( trackIdx, newValues, newTimes, timeOffset = 0, flags = 0x00 ){
|
|
2776
2838
|
const track = this.animationClip.tracks[trackIdx];
|
|
2777
2839
|
|
|
2778
|
-
if ( !newTimes.length ){ return; }
|
|
2840
|
+
if ( !newTimes.length || track.locked ){ return null; }
|
|
2779
2841
|
|
|
2780
2842
|
const valueDim = track.dim;
|
|
2781
2843
|
const trackTimes = track.times;
|
|
@@ -2865,6 +2927,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2865
2927
|
return;
|
|
2866
2928
|
}
|
|
2867
2929
|
|
|
2930
|
+
const tracks = this.animationClip.tracks;
|
|
2868
2931
|
const firstTrack = this.lastKeyFramesSelected[0][0];
|
|
2869
2932
|
let trackToRemove = firstTrack;
|
|
2870
2933
|
let toDelete = []; // indices to delete of the same track
|
|
@@ -2874,6 +2937,12 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2874
2937
|
const numSelected = this.lastKeyFramesSelected.length;
|
|
2875
2938
|
for( let i = 0; i < numSelected; ++i ){
|
|
2876
2939
|
const [trackIdx, frameIdx] = this.lastKeyFramesSelected[i];
|
|
2940
|
+
|
|
2941
|
+
if ( tracks[trackIdx].locked ){
|
|
2942
|
+
tracks[trackIdx].selected[frameIdx] = false; // unselect
|
|
2943
|
+
continue;
|
|
2944
|
+
}
|
|
2945
|
+
|
|
2877
2946
|
if ( trackToRemove != trackIdx ){
|
|
2878
2947
|
this.saveState(trackToRemove, trackToRemove != firstTrack);
|
|
2879
2948
|
|
|
@@ -2885,7 +2954,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2885
2954
|
toDelete.length = 0;
|
|
2886
2955
|
}
|
|
2887
2956
|
|
|
2888
|
-
toDelete.push(frameIdx)
|
|
2957
|
+
toDelete.push( frameIdx );
|
|
2889
2958
|
}
|
|
2890
2959
|
|
|
2891
2960
|
this.saveState(trackToRemove, trackToRemove != firstTrack);
|
|
@@ -2900,7 +2969,7 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2900
2969
|
deleteKeyFrames( trackIdx, indices, skipCallback = false ){
|
|
2901
2970
|
const track = this.animationClip.tracks[trackIdx];
|
|
2902
2971
|
|
|
2903
|
-
if ( !indices.length ){
|
|
2972
|
+
if ( !indices.length || track.locked ){
|
|
2904
2973
|
return false;
|
|
2905
2974
|
}
|
|
2906
2975
|
|
|
@@ -2949,9 +3018,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2949
3018
|
|
|
2950
3019
|
/**
|
|
2951
3020
|
* Binary search. Relies on track.times being a sorted array
|
|
2952
|
-
* @param {
|
|
2953
|
-
* @param {
|
|
2954
|
-
* @param {
|
|
3021
|
+
* @param {Object} track
|
|
3022
|
+
* @param {Number} time
|
|
3023
|
+
* @param {Number} mode on of the possible values
|
|
2955
3024
|
* - -1 = nearest frame with t[f] <= time
|
|
2956
3025
|
* - 0 = nearest frame
|
|
2957
3026
|
* - 1 = nearest frame with t[f] >= time
|
|
@@ -2993,9 +3062,9 @@ class KeyFramesTimeline extends Timeline {
|
|
|
2993
3062
|
|
|
2994
3063
|
/**
|
|
2995
3064
|
* get the nearest keyframe to "time" given a maximum threshold.
|
|
2996
|
-
* @param {
|
|
2997
|
-
* @param {
|
|
2998
|
-
* @param {
|
|
3065
|
+
* @param {Object} track
|
|
3066
|
+
* @param {Number} time
|
|
3067
|
+
* @param {Number} threshold must be positive value
|
|
2999
3068
|
* @returns returns a postive/zero value if there is a frame inside the threshold range. Otherwise, -1
|
|
3000
3069
|
*/
|
|
3001
3070
|
getCurrentKeyFrame( track, time, threshold = 0.0 ) {
|
|
@@ -3013,10 +3082,10 @@ class KeyFramesTimeline extends Timeline {
|
|
|
3013
3082
|
|
|
3014
3083
|
/**
|
|
3015
3084
|
* Returns the interval of frames between minTime and maxTime (both included)
|
|
3016
|
-
* @param {
|
|
3017
|
-
* @param {
|
|
3018
|
-
* @param {
|
|
3019
|
-
* @param {
|
|
3085
|
+
* @param {Object} track
|
|
3086
|
+
* @param {Number} minTime
|
|
3087
|
+
* @param {Number} maxTime
|
|
3088
|
+
* @param {Number} threshold must be positive value
|
|
3020
3089
|
* @returns an array with two values [ minFrame, maxFrame ]. Otherwise null
|
|
3021
3090
|
*/
|
|
3022
3091
|
getKeyFramesInRange( track, minTime, maxTime, threshold = 0.0 ) {
|
|
@@ -3149,12 +3218,12 @@ class KeyFramesTimeline extends Timeline {
|
|
|
3149
3218
|
|
|
3150
3219
|
const track = this.animationClip.tracks[trackIdx];
|
|
3151
3220
|
|
|
3152
|
-
if(track.locked ){
|
|
3153
|
-
return;
|
|
3154
|
-
}
|
|
3155
|
-
|
|
3156
3221
|
this.unHoverAll();
|
|
3157
3222
|
this.deselectAllKeyFrames();
|
|
3223
|
+
|
|
3224
|
+
if( track.locked ){
|
|
3225
|
+
return;
|
|
3226
|
+
}
|
|
3158
3227
|
|
|
3159
3228
|
this.saveState(track.trackIdx);
|
|
3160
3229
|
|
|
@@ -3180,8 +3249,8 @@ class ClipsTimeline extends Timeline {
|
|
|
3180
3249
|
static CLONEREASON_TRACKCLONE = 4;
|
|
3181
3250
|
|
|
3182
3251
|
/**
|
|
3183
|
-
* @param {
|
|
3184
|
-
* @param {
|
|
3252
|
+
* @param {String} name
|
|
3253
|
+
* @param {Object} options = {animationClip, selectedItems, x, y, width, height, canvas, trackHeight}
|
|
3185
3254
|
*/
|
|
3186
3255
|
constructor(name, options = {}) {
|
|
3187
3256
|
|
|
@@ -3196,7 +3265,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3196
3265
|
|
|
3197
3266
|
/**
|
|
3198
3267
|
* Generates an animationClip using either the parameters set in the animation argument or using default values
|
|
3199
|
-
* @param {
|
|
3268
|
+
* @param {Object} animation data with which to generate an animationClip
|
|
3200
3269
|
* @returns
|
|
3201
3270
|
*/
|
|
3202
3271
|
instantiateAnimationClip(animation, clone = false) {
|
|
@@ -3218,7 +3287,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3218
3287
|
|
|
3219
3288
|
/**
|
|
3220
3289
|
*
|
|
3221
|
-
* @param {
|
|
3290
|
+
* @param {Object} options set some values for the track instance (groups and trackIdx not included)
|
|
3222
3291
|
* @returns
|
|
3223
3292
|
*/
|
|
3224
3293
|
instantiateTrack(options = {}, clone = false) {
|
|
@@ -3319,6 +3388,7 @@ class ClipsTimeline extends Timeline {
|
|
|
3319
3388
|
changeSelectedItems( ) {
|
|
3320
3389
|
|
|
3321
3390
|
this.deselectAllElements();
|
|
3391
|
+
this.deselectAllTracks( false ); // no need to update left
|
|
3322
3392
|
|
|
3323
3393
|
this.selectedItems = this.animationClip.tracks.slice();
|
|
3324
3394
|
|
|
@@ -3950,10 +4020,10 @@ class ClipsTimeline extends Timeline {
|
|
|
3950
4020
|
|
|
3951
4021
|
/**
|
|
3952
4022
|
*
|
|
3953
|
-
* @param {
|
|
3954
|
-
* @param {
|
|
3955
|
-
* @param {
|
|
3956
|
-
* @param {
|
|
4023
|
+
* @param {Object} clip clip to be added
|
|
4024
|
+
* @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
|
|
4025
|
+
* @param {Number} offsetTime (optional) offset time of current time
|
|
4026
|
+
* @param {Number} searchStartTrackIdx (optional) if trackIdx is set to -1, this idx will be used as the starting point to find a valid track
|
|
3957
4027
|
* @returns a zero/positive value if successful. Otherwise, -1
|
|
3958
4028
|
*/
|
|
3959
4029
|
addClip( clip, trackIdx = -1, offsetTime = 0, searchStartTrackIdx = 0 ) {
|
|
@@ -4200,8 +4270,8 @@ class ClipsTimeline extends Timeline {
|
|
|
4200
4270
|
/**
|
|
4201
4271
|
* User defined. Used when copying and pasting
|
|
4202
4272
|
* @param {Array of clips} clipsToClone array of original clips. Do not modify clips in this array
|
|
4203
|
-
* @param {
|
|
4204
|
-
* @param {
|
|
4273
|
+
* @param {Number} timeOffset Value of time that should be added (or subtracted) from the timing attributes
|
|
4274
|
+
* @param {Int} reason Flag to signal the reason of the clone
|
|
4205
4275
|
* @returns {Array of clips}
|
|
4206
4276
|
*/
|
|
4207
4277
|
cloneClips( clipsToClone, timeOffset, reason = 0 ){
|