lexgui 0.6.4 → 0.6.5

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.
@@ -1010,9 +1010,9 @@ class Timeline {
1010
1010
  * [ trackIdx ]
1011
1011
  * @param {Array} itemsName array of numbers identifying tracks
1012
1012
  */
1013
- setSelectedItems( items ) {
1013
+ setSelectedItems( items, skipCallback = false ) {
1014
1014
  this.selectedItems = [];
1015
- this.changeSelectedItems( items, null );
1015
+ this.changeSelectedItems( items, null, skipCallback );
1016
1016
  }
1017
1017
 
1018
1018
  /**
@@ -1101,7 +1101,11 @@ class Timeline {
1101
1101
  tracks[ i ].isSelected = false;
1102
1102
  }
1103
1103
  }
1104
- // --------------------------------------------------
1104
+
1105
+ unselectAllElements(){
1106
+
1107
+ }
1108
+
1105
1109
  /**
1106
1110
  * @method setTrackState
1107
1111
  * @param {int} trackIdx
@@ -1117,6 +1121,62 @@ class Timeline {
1117
1121
  this.onSetTrackState(track, oldState);
1118
1122
  }
1119
1123
 
1124
+ /**
1125
+ * @param {Number} trackIdx index of track in the animation (not local index)
1126
+ * @param {Bool} combineWithPrevious whether to create a new entry or unify changes into a single undo entry
1127
+ */
1128
+ saveState( trackIdx, combineWithPrevious = false ) {
1129
+ if ( !this.historySaveEnabler ){ return; }
1130
+
1131
+ const undoStep = this.historyGenerateTrackStep( trackIdx );
1132
+ undoStep.trackIdx = trackIdx;
1133
+
1134
+ if ( combineWithPrevious && this.historyUndo.length ){
1135
+ this.historyUndo[ this.historyUndo.length - 1 ].push( undoStep );
1136
+ }
1137
+ else{
1138
+ this.historyUndo.push( [undoStep] );
1139
+ }
1140
+
1141
+ if ( this.historyUndo.length > this.historyMaxSteps ){ this.historyUndo.shift(); } // remove first (oldest) element
1142
+ this.historyRedo = [];
1143
+ }
1144
+
1145
+ #undoRedo(isUndo = true) {
1146
+
1147
+ let toBeShown = isUndo ? this.historyUndo : this.historyRedo;
1148
+ let toBeStored = isUndo ? this.historyRedo : this.historyUndo;
1149
+
1150
+ if (!toBeShown.length){ return false; }
1151
+
1152
+ this.unselectAllElements();
1153
+
1154
+ const combinedState = toBeShown.pop();
1155
+ const combinedStateToStore = [];
1156
+
1157
+ for( let i = 0; i < combinedState.length; ++i ){
1158
+ const state = combinedState[i];
1159
+ const trackIdx = state.trackIdx;
1160
+
1161
+ const stateToStore = this.historyApplyTrackStep( state, isUndo );
1162
+ stateToStore.trackIdx = trackIdx;
1163
+ combinedStateToStore.push( stateToStore );
1164
+
1165
+ // Update animation action interpolation info
1166
+ if(this.onUpdateTrack)
1167
+ this.onUpdateTrack( [state.trackIdx] );
1168
+ }
1169
+
1170
+ toBeStored.push(combinedStateToStore);
1171
+
1172
+ return true;
1173
+ }
1174
+
1175
+ undo() { return this.#undoRedo(true); }
1176
+ redo() { return this.#undoRedo(false); }
1177
+ // historyApplyTrackStep( state, isUndo ) MUST BE IMPLEMENTED BY CHILD CLASS
1178
+ // historyGenerateTrackStep( trackIdx ) MUST BE IMPLEMENTED BY CHILD CLASS
1179
+
1120
1180
  /**
1121
1181
  * @method resize
1122
1182
  * @param {*} size
@@ -1185,6 +1245,7 @@ class Timeline {
1185
1245
  Usually call a super.whateverFunction to generate its base form, and expand it with extra attributes
1186
1246
  */
1187
1247
 
1248
+
1188
1249
  /**
1189
1250
  * This functions uses the selectedItems and generates the data that will feed the LX.Tree widget.
1190
1251
  * This function is used by updateLeftPanel. Some timelines might allow grouping of tracks. Such timelines may overwrite this function
@@ -1249,7 +1310,7 @@ class Timeline {
1249
1310
  instantiateAnimationClip(options) {
1250
1311
  options = options ?? {};
1251
1312
  const animationClip = {
1252
- name: options.name ?? "animationClip",
1313
+ id: options.id ?? (options.name ?? "animationClip"),
1253
1314
  duration: options.duration ?? 0,
1254
1315
  tracks: [],
1255
1316
  tracksPerGroup: options.tracksPerGroup ?? {},
@@ -1315,25 +1376,6 @@ class KeyFramesTimeline extends Timeline {
1315
1376
  }
1316
1377
  }
1317
1378
 
1318
-
1319
- /**
1320
- * @param {object} options options for the new track
1321
- * { id: string, active: bool, locked: bool, }
1322
- * @returns
1323
- */
1324
- addNewTrack( options = {}, skipCallback = false ) {
1325
-
1326
- const trackInfo = this.instantiateTrack(options);
1327
- trackInfo.trackIdx = this.animationClip.tracks.length;
1328
- this.animationClip.tracks.push( trackInfo );
1329
-
1330
- if ( this.onAddNewTrack && !skipCallback ){
1331
- this.onAddNewTrack( trackInfo, options ); // if user wants it on a group, they should use these callback
1332
- }
1333
-
1334
- return trackInfo.trackIdx;
1335
- }
1336
-
1337
1379
  // OVERRIDE
1338
1380
  generateSelectedItemsTreeData(){
1339
1381
  const treeTracks = [];
@@ -1495,6 +1537,12 @@ class KeyFramesTimeline extends Timeline {
1495
1537
  return animationClip;
1496
1538
  }
1497
1539
 
1540
+ // OVERRIDE
1541
+ unselectAllElements(){
1542
+ this.unSelectAllKeyFrames();
1543
+ this.unHoverAll();
1544
+ }
1545
+
1498
1546
  /**
1499
1547
  * OVERRIDE
1500
1548
  * @param {*} itemsToAdd [ trackIdx, "groupId" ], array of strings and/or number identifying groups and/or tracks
@@ -2304,78 +2352,52 @@ class KeyFramesTimeline extends Timeline {
2304
2352
  })
2305
2353
  });
2306
2354
  }
2307
-
2355
+
2308
2356
  /**
2309
- * @param {Number} trackIdx index of track in the animation (not local index)
2310
- * @param {Bool} combineWithPrevious whether to create a new entry or unify changes into a single undo entry
2357
+ * saveState function uses this to generate a "copy" of the track.
2358
+ * @param {Number} trackIdx
2359
+ * @returns All necessary information to reconstruct the track state
2311
2360
  */
2312
- saveState( trackIdx, combineWithPrevious = false ) {
2313
- if ( !this.historySaveEnabler ){ return; }
2314
-
2361
+ historyGenerateTrackStep( trackIdx ){
2315
2362
  const trackInfo = this.animationClip.tracks[trackIdx];
2316
2363
 
2317
2364
  const undoStep = {
2318
- trackIdx: trackIdx,
2319
- t: trackInfo.times.slice(),
2320
- v: trackInfo.values.slice(),
2321
- edited: trackInfo.edited.slice(0, trackInfo.times.length)
2365
+ trackIdx: trackIdx, // already done by saveState
2366
+ t: trackInfo.times.slice(),
2367
+ v: trackInfo.values.slice(),
2368
+ edited: trackInfo.edited.slice(0, trackInfo.times.length)
2322
2369
  };
2323
2370
 
2324
- if ( combineWithPrevious && this.historyUndo.length ){
2325
- this.historyUndo[ this.historyUndo.length - 1 ].push( undoStep );
2326
- }
2327
- else{
2328
- this.historyUndo.push( [undoStep] );
2329
- }
2371
+ return undoStep;
2372
+ }
2330
2373
 
2331
- if ( this.historyUndo.length > this.historyMaxSteps ){ this.historyUndo.shift(); } // remove first (oldest) element
2332
- this.historyRedo = [];
2333
- }
2334
-
2335
- #undoRedo(isUndo = true){
2336
-
2337
- let toBeShown = isUndo ? this.historyUndo : this.historyRedo;
2338
- let toBeStored = isUndo ? this.historyRedo : this.historyUndo;
2339
-
2340
- if (!toBeShown.length){ return false; }
2341
-
2342
- this.unSelectAllKeyFrames();
2343
- this.unHoverAll();
2344
-
2345
- const combinedState = toBeShown.pop();
2346
- const combinedStateToStore = [];
2347
-
2348
- for( let i = 0; i < combinedState.length; ++i ){
2349
- const state = combinedState[i];
2350
- const track = this.animationClip.tracks[state.trackIdx];
2351
-
2352
- // same as savestate
2353
- combinedStateToStore.push({
2354
- trackIdx: state.trackIdx,
2355
- t: track.times,
2356
- v: track.values,
2357
- edited: track.edited
2358
- });
2359
-
2360
- track.times = state.t;
2361
- track.values = state.v;
2362
- track.edited = state.edited;
2363
- if ( track.selected.length != track.times.length ){ track.selected.length = track.times.length; }
2364
- if ( track.hovered.length != track.times.length ){ track.hovered.length = track.times.length; }
2365
- track.selected.fill(false);
2366
- track.hovered.fill(false);
2367
-
2368
- if(this.onUpdateTrack)
2369
- this.onUpdateTrack( [state.trackIdx] );
2370
- }
2374
+ /**
2375
+ * It should swap the previous state with the incoming state of the track. It must return the previous state.
2376
+ * historyGenerateTrackStep could be used to copy the previous state. However, as it is a swap, it suffices to just copy the references.
2377
+ * @param {Object} state object with a trackIdx:Number and whatever information was saved in historyGenerateTrackStep
2378
+ * @param {Boolean} isUndo
2379
+ * @returns previous state object
2380
+ */
2381
+ historyApplyTrackStep( state, isUndo ){
2382
+ const track = this.animationClip.tracks[state.trackIdx];
2383
+
2384
+ const stateToReturn = {
2385
+ trackIdx: state.trackIdx,
2386
+ t: track.times,
2387
+ v: track.values,
2388
+ edited: track.edited
2389
+ };
2371
2390
 
2372
- toBeStored.push(combinedStateToStore);
2391
+ track.times = state.t;
2392
+ track.values = state.v;
2393
+ track.edited = state.edited;
2394
+ if ( track.selected.length != track.times.length ){ track.selected.length = track.times.length; }
2395
+ if ( track.hovered.length != track.times.length ){ track.hovered.length = track.times.length; }
2396
+ track.selected.fill(false);
2397
+ track.hovered.fill(false);
2373
2398
 
2374
- return true;
2399
+ return stateToReturn;
2375
2400
  }
2376
-
2377
- undo() { return this.#undoRedo(true); }
2378
- redo() { return this.#undoRedo(false); }
2379
2401
 
2380
2402
  /**
2381
2403
  *
@@ -3079,6 +3101,12 @@ class ClipsTimeline extends Timeline {
3079
3101
  this.changeSelectedItems();
3080
3102
  }
3081
3103
 
3104
+ // OVERRIDE
3105
+ unselectAllElements(){
3106
+ this.unSelectAllClips();
3107
+ this.unHoverAll();
3108
+ }
3109
+
3082
3110
  /**
3083
3111
  * OVERRIDE ITEM SELECTION.
3084
3112
  * CLIPS WILL OFFER NO SELECTION. All tracks are visible
@@ -3363,7 +3391,7 @@ class ClipsTimeline extends Timeline {
3363
3391
 
3364
3392
  // save track state if necessary
3365
3393
  const undoState = this.historyUndo[this.historyUndo.length-1];
3366
- let state = 0
3394
+ let state = 0;
3367
3395
  for( ; state < undoState.length; ++state ){
3368
3396
  if ( newTrackIdx == undoState[state].trackIdx ){ break; }
3369
3397
  }
@@ -3554,8 +3582,10 @@ class ClipsTimeline extends Timeline {
3554
3582
  const track = e.track;
3555
3583
  const localX = e.localX;
3556
3584
 
3557
- const clipIdx = this.getClipOnTime(track, this.xToTime(localX), 0.001);
3558
- this.selectClip(track.trackIdx, clipIdx); // unselect and try to select clip in localX, if any
3585
+ if ( track ){
3586
+ const clipIdx = this.getClipOnTime(track, this.xToTime(localX), 0.001);
3587
+ this.selectClip(track.trackIdx, clipIdx); // unselect and try to select clip in localX, if any
3588
+ }
3559
3589
  }
3560
3590
 
3561
3591
  showContextMenu( e ) {
@@ -3751,9 +3781,10 @@ class ClipsTimeline extends Timeline {
3751
3781
  * @param {obj} clip clip to be added
3752
3782
  * @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
3753
3783
  * @param {float} offsetTime (optional) offset time of current time
3784
+ * @param {float} searchStartTrackIdx (optional) if trackIdx is set to -1, this idx will be used as the starting point to find a valid track
3754
3785
  * @returns a zero/positive value if successful. Otherwise, -1
3755
3786
  */
3756
- addClip( clip, trackIdx = -1, offsetTime = 0 ) {
3787
+ addClip( clip, trackIdx = -1, offsetTime = 0, searchStartTrackIdx = 0 ) {
3757
3788
  if ( !this.animationClip ){ return -1; }
3758
3789
 
3759
3790
  // Update clip information
@@ -3769,9 +3800,9 @@ class ClipsTimeline extends Timeline {
3769
3800
  trackIdx = this.addNewTrack();
3770
3801
  }
3771
3802
  else if ( trackIdx < 0 ){ // find first free track slot
3772
- for(let i = 0; i < this.animationClip.tracks.length; i++) {
3803
+ for(let i = searchStartTrackIdx; i < this.animationClip.tracks.length; i++) {
3773
3804
  let clipInCurrentSlot = this.animationClip.tracks[i].clips.find( t => {
3774
- return LX.UTILS.compareThresholdRange(newStart, clip.start + clip.duration, t.start, t.start+t.duration);
3805
+ return LX.compareThresholdRange(newStart, clip.start + clip.duration, t.start, t.start+t.duration);
3775
3806
  });
3776
3807
 
3777
3808
  if(!clipInCurrentSlot){
@@ -3823,17 +3854,18 @@ class ClipsTimeline extends Timeline {
3823
3854
  return newIdx;
3824
3855
  }
3825
3856
 
3826
-
3827
- /** Add an array of clips to the timeline in the first free track at the current time
3828
- * @clips: clips to be added
3829
- * @offsetTime: (optional) offset time of current time
3830
- */
3831
-
3832
- addClips( clips, offsetTime = 0 ){
3857
+ /**
3858
+ * Add an array of clips to the timeline in the first suitable tracks. It tries to put clips in the same track if possible. All clips will be in adjacent tracks to each other
3859
+ * @param {Array of objects} clips
3860
+ * @param {Number} offsetTime
3861
+ * @param {Int} searchStartTrackIdx
3862
+ * @returns
3863
+ */
3864
+ addClips( clips, offsetTime = 0, searchStartTrackIdx = 0 ){
3833
3865
  if( !this.animationClip || !clips.length ){ return false; }
3834
3866
 
3835
3867
  let clipTrackIdxs = new Int16Array( clips.length );
3836
- let baseTrackIdx = -1;
3868
+ let baseTrackIdx = searchStartTrackIdx -1; // every time the algorithm fails, it increments the starting track Idx
3837
3869
  let currTrackIdx = -1;
3838
3870
  const tracks = this.animationClip.tracks;
3839
3871
  const lastTrackLength = tracks.length;
@@ -3845,9 +3877,10 @@ class ClipsTimeline extends Timeline {
3845
3877
  if ( c == 0 ){ // last search failed, move one track down and check again
3846
3878
  ++baseTrackIdx;
3847
3879
  currTrackIdx = baseTrackIdx;
3848
- if ( currTrackIdx >= tracks.length ){ this.addNewTrack(null, false); }
3849
- let clipsInCurrentSlot = tracks[baseTrackIdx].clips.find( t => { return LX.UTILS.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3850
-
3880
+
3881
+ while ( currTrackIdx >= tracks.length ){ this.addNewTrack(null, false); }
3882
+ let clipsInCurrentSlot = tracks[baseTrackIdx].clips.find( t => { return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3883
+
3851
3884
  // reset search
3852
3885
  if (clipsInCurrentSlot){
3853
3886
  c = -1;
@@ -3859,19 +3892,19 @@ class ClipsTimeline extends Timeline {
3859
3892
  }else{
3860
3893
 
3861
3894
  // check if it fits in current track
3862
- let clipsInCurrentSlot = tracks[currTrackIdx].clips.find( t => { return LX.UTILS.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3895
+ let clipsInCurrentSlot = tracks[currTrackIdx].clips.find( t => { return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3863
3896
 
3864
3897
  // check no previous added clips are in the way
3865
3898
  for( let i = c-1; i > -1; --i ){
3866
3899
  if ( clipTrackIdxs[i] != currTrackIdx || clipsInCurrentSlot ){ break; }
3867
- clipsInCurrentSlot = LX.UTILS.compareThresholdRange(clipStart, clipEnd, clips[i].start + offsetTime, clips[i].start + offsetTime + clips[i].duration);
3900
+ clipsInCurrentSlot = LX.compareThresholdRange(clipStart, clipEnd, clips[i].start + offsetTime, clips[i].start + offsetTime + clips[i].duration);
3868
3901
  }
3869
3902
 
3870
3903
  // check if it fits in the next track
3871
3904
  if ( clipsInCurrentSlot ){
3872
3905
  ++currTrackIdx;
3873
3906
  if ( currTrackIdx >= tracks.length ){ this.addNewTrack(null, false); }
3874
- clipsInCurrentSlot = tracks[currTrackIdx].clips.find( t => { return LX.UTILS.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3907
+ clipsInCurrentSlot = tracks[currTrackIdx].clips.find( t => { return LX.compareThresholdRange(clipStart, clipEnd, t.start, t.start+t.duration); });
3875
3908
  }
3876
3909
 
3877
3910
  // reset search
@@ -4077,75 +4110,50 @@ class ClipsTimeline extends Timeline {
4077
4110
  return;
4078
4111
  }
4079
4112
 
4080
- saveState( trackIdx, combineWithPrevious = false ) {
4081
- if ( !this.historySaveEnabler ){ return; }
4082
-
4113
+ /**
4114
+ * saveState function uses this to generate a "copy" of the track.
4115
+ * @param {Number} trackIdx
4116
+ * @returns All necessary information to reconstruct the track state
4117
+ */
4118
+ historyGenerateTrackStep( trackIdx ){
4083
4119
  const track = this.animationClip.tracks[trackIdx];
4084
4120
  const clips = this.cloneClips(track.clips, 0);
4085
- // storing as array so multiple tracks can be in a same "undo" step
4086
4121
 
4087
- const undoStep = {
4088
- trackIdx: trackIdx,
4122
+ const undoStep = {
4123
+ trackIdx: trackIdx, // already done by saveState
4089
4124
  clips: clips,
4090
4125
  edited: track.edited.slice(0,track.clips.length)
4091
4126
  };
4092
4127
 
4093
- if ( combineWithPrevious && this.historyUndo.length ){
4094
- this.historyUndo[ this.historyUndo.length-1 ].push( undoStep );
4095
- }
4096
- else{
4097
- this.historyUndo.push( [ undoStep ] );
4098
- }
4099
-
4100
- if ( this.historyUndo.length > this.historyMaxSteps ){ this.historyUndo.shift(); } // remove first (oldest) element
4128
+ return undoStep;
4129
+ }
4101
4130
 
4102
- this.historyRedo = [];
4103
- }
4131
+ /**
4132
+ * It should swap the previous state with the incoming state of the track. It must return the previous state.
4133
+ * historyGenerateTrackStep could be used to copy the previous state. However, as it is a swap, it suffices to just copy the references.
4134
+ * @param {Object} state object with a trackIdx:Number and whatever information was saved in historyGenerateTrackStep
4135
+ * @param {Boolean} isUndo
4136
+ * @returns previous state object
4137
+ */
4138
+ historyApplyTrackStep( state, isUndo ){
4139
+ const track = this.animationClip.tracks[state.trackIdx];
4104
4140
 
4105
- #undoRedo(isUndo = true) {
4106
-
4107
- let toBeShown = isUndo ? this.historyUndo : this.historyRedo;
4108
- let toBeStored = isUndo ? this.historyRedo : this.historyUndo;
4109
-
4110
- if (!toBeShown.length){ return false; }
4111
-
4112
- this.unSelectAllClips();
4113
- this.unHoverAll();
4141
+ const stateToReturn = {
4142
+ trackIdx: state.trackIdx, // already done by saveState
4143
+ clips: track.clips,
4144
+ edited: track.edited
4145
+ };
4114
4146
 
4115
- const combinedState = toBeShown.pop();
4116
- const combinedStateToStore = [];
4117
-
4118
- for( let i = 0; i < combinedState.length; ++i ){
4119
- const state = combinedState[i];
4120
- const track = this.animationClip.tracks[state.trackIdx];
4121
-
4122
- // same as savestate
4123
- combinedStateToStore.push( {
4124
- trackIdx: state.trackIdx,
4125
- clips: track.clips,
4126
- edited: track.edited
4127
- });
4128
-
4129
- track.clips = state.clips;
4130
- track.edited = state.edited;
4131
- if ( track.selected.length < track.clips.length ){ track.selected.length = track.clips.length; }
4132
- if ( track.hovered.length < track.clips.length ){ track.hovered.length = track.clips.length; }
4133
- track.selected.fill(false);
4134
- track.hovered.fill(false);
4135
-
4136
- // Update animation action interpolation info
4137
- if(this.onUpdateTrack)
4138
- this.onUpdateTrack( [state.trackIdx] );
4139
- }
4140
-
4141
- toBeStored.push(combinedStateToStore);
4142
-
4143
- return true;
4147
+ track.clips = state.clips;
4148
+ track.edited = state.edited;
4149
+ if ( track.selected.length < track.clips.length ){ track.selected.length = track.clips.length; }
4150
+ if ( track.hovered.length < track.clips.length ){ track.hovered.length = track.clips.length; }
4151
+ track.selected.fill(false);
4152
+ track.hovered.fill(false);
4153
+
4154
+ return stateToReturn;
4144
4155
  }
4145
4156
 
4146
- undo() { return this.#undoRedo(true); }
4147
- redo() { return this.#undoRedo(false); }
4148
-
4149
4157
  getClipOnTime( track, time, threshold ) {
4150
4158
 
4151
4159
  if(!track || !track.clips.length){
@@ -4355,20 +4363,7 @@ CanvasRenderingContext2D.prototype.roundRect = function(x, y, width, height, rad
4355
4363
  }
4356
4364
  }
4357
4365
 
4358
- LX.UTILS.HexToRgb = (hex) => {
4359
- var c;
4360
- if(/^#([A-Fa-f0-9]{3}){1,2}$/.test(hex)){
4361
- c= hex.substring(1).split('');
4362
- if(c.length== 3){
4363
- c= [c[0], c[0], c[1], c[1], c[2], c[2]];
4364
- }
4365
- c= '0x'+c.join('');
4366
- return [(c>>16)&255, (c>>8)&255, c&255];
4367
- }
4368
- throw new Error('Bad Hex');
4369
- }
4370
-
4371
- LX.UTILS.concatTypedArray = (arrays, ArrayType) => {
4366
+ LX.concatTypedArray = (arrays, ArrayType) => {
4372
4367
  let size = arrays.reduce((acc,arr) => acc + arr.length, 0);
4373
4368
  let result = new ArrayType( size ); // generate just one array
4374
4369
  let offset = 0;