lexgui 0.1.39 → 0.1.41

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 CHANGED
@@ -1,6 +1,8 @@
1
1
  # lexgui.js
2
2
 
3
- **lexgui.js** is a lightweight JavaScript library that allows you to create web interfaces using only JavaScript, HTML, and CSS. It provides an easy-to-use API for building dynamic and interactive common and complex editor interfaces without the need for tedious frameworks or big libraries. With lexgui.js, you can create custom UI components, handle user interactions, and update the interface dynamically.
3
+ **lexgui.js** is a lightweight JavaScript library that allows you to create web interfaces using only JavaScript, HTML, and CSS. It provides an easy API for building dynamic and interactive editor interfaces without the need for tedious frameworks or big libraries. With lexgui.js, you can create custom UI components, handle user interactions, and update the interface dynamically.
4
+
5
+ NPM Package: [npmjs.com/package/lexgui](https://www.npmjs.com/package/lexgui)
4
6
 
5
7
  <table>
6
8
  <tr>
@@ -44,12 +44,29 @@ Panel.prototype.addKnob = function( name, value, min, max, callback, options = {
44
44
  });
45
45
  }
46
46
 
47
+ const snapEnabled = ( options.snap && options.snap.constructor == Number );
48
+ const ticks = [];
49
+ if( snapEnabled )
50
+ {
51
+ const range = (max - min) / options.snap;
52
+ for( let i = 0; i < ( options.snap + 1 ); ++i )
53
+ {
54
+ ticks.push( min + (i * range) );
55
+ }
56
+ }
57
+
47
58
  var container = document.createElement( 'div' );
48
- container.className = "lexknob " + ( options.size ?? '' );
59
+ container.className = "lexknob";
60
+ container.addClass( options.size );
61
+ container.addClass( snapEnabled ? "show-ticks" : null );
49
62
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
50
63
 
51
64
  let knobCircle = document.createElement( 'div' );
52
65
  knobCircle.className = "knobcircle";
66
+ if( snapEnabled )
67
+ {
68
+ knobCircle.style.setProperty( "--knob-snap-mark", ( 270 / options.snap ) + "deg" );
69
+ }
53
70
 
54
71
  let innerKnobCircle = document.createElement( 'div' );
55
72
  innerKnobCircle.className = "innerknobcircle";
@@ -69,6 +86,7 @@ Panel.prototype.addKnob = function( name, value, min, max, callback, options = {
69
86
 
70
87
  innerKnobCircle.value = innerKnobCircle.iValue = value;
71
88
 
89
+ let mustSnap = false;
72
90
  let innerSetValue = function( v ) {
73
91
  // Convert val between (-135 and 135)
74
92
  const angle = LX.remapRange( v, innerKnobCircle.min, innerKnobCircle.max, -135.0, 135.0 );
@@ -81,29 +99,20 @@ Panel.prototype.addKnob = function( name, value, min, max, callback, options = {
81
99
 
82
100
  if( options.disabled )
83
101
  {
84
- // vecinput.disabled = true;
102
+ container.addClass( "disabled" );
85
103
  }
86
104
 
87
- // Add wheel input
88
-
89
- innerKnobCircle.addEventListener( "wheel", function( e ) {
90
- e.preventDefault();
91
- if( this !== document.activeElement )
92
- return;
93
- let mult = options.step ?? 1;
94
- if( e.shiftKey ) mult *= 10;
95
- else if( e.altKey ) mult *= 0.1;
96
- let new_value = ( this.value - mult * ( e.deltaY > 0 ? 1 : -1 ) );
97
- this.value = new_value;// .toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
98
- Panel._dispatch_event( innerKnobCircle, "change" );
99
- }, { passive: false });
100
-
101
105
  innerKnobCircle.addEventListener( "change", e => {
102
106
 
103
107
  const knob = e.target;
104
108
 
105
109
  const skipCallback = e.detail;
106
110
 
111
+ if( mustSnap )
112
+ {
113
+ knob.value = ticks.reduce(( prev, curr ) => Math.abs( curr - knob.value ) < Math.abs( prev - knob.value ) ? curr : prev );
114
+ }
115
+
107
116
  let val = knob.value = LX.clamp( knob.value, knob.min, knob.max );
108
117
  val = options.precision ? LX.round( val, options.precision ) : val;
109
118
 
@@ -114,9 +123,14 @@ Panel.prototype.addKnob = function( name, value, min, max, callback, options = {
114
123
  {
115
124
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
116
125
  if( btn ) btn.style.display = val != innerKnobCircle.iValue ? "block": "none";
126
+
127
+ if( !( snapEnabled && !mustSnap ) )
128
+ {
129
+ this._trigger( new LX.IEvent( name, val, e ), callback );
130
+ mustSnap = false;
131
+ }
117
132
  }
118
133
 
119
- if( !skipCallback ) this._trigger( new LX.IEvent( name, val, e ), callback );
120
134
  }, { passive: false });
121
135
 
122
136
  // Add drag input
@@ -124,41 +138,64 @@ Panel.prototype.addKnob = function( name, value, min, max, callback, options = {
124
138
  innerKnobCircle.addEventListener( "mousedown", inner_mousedown );
125
139
 
126
140
  var that = this;
127
- var lastY = 0;
141
+
128
142
  function inner_mousedown( e ) {
129
- if( document.activeElement == innerKnobCircle ) return;
143
+
144
+ if( document.activeElement == innerKnobCircle || options.disabled )
145
+ {
146
+ return;
147
+ }
148
+
130
149
  var doc = that.root.ownerDocument;
131
150
  doc.addEventListener("mousemove",inner_mousemove);
132
151
  doc.addEventListener("mouseup",inner_mouseup);
133
- lastY = e.pageY;
134
- document.body.classList.add('nocursor');
135
152
  document.body.classList.add('noevents');
153
+
154
+ if( !document.pointerLockElement )
155
+ {
156
+ container.requestPointerLock();
157
+ }
158
+
136
159
  e.stopImmediatePropagation();
137
160
  e.stopPropagation();
138
161
  }
139
162
 
140
163
  function inner_mousemove( e ) {
141
- if (lastY != e.pageY) {
142
- let dt = lastY - e.pageY;
164
+
165
+ let dt = -e.movementY;
166
+
167
+ if ( dt != 0 )
168
+ {
143
169
  let mult = options.step ?? 1;
144
170
  if(e.shiftKey) mult *= 10;
145
171
  else if(e.altKey) mult *= 0.1;
146
172
  let new_value = (innerKnobCircle.value - mult * dt);
147
- innerKnobCircle.value = new_value;//.toFixed( 4 ).replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/,'$1');
173
+ innerKnobCircle.value = new_value;
148
174
  Panel._dispatch_event( innerKnobCircle, 'change' );
149
175
  }
150
176
 
151
- lastY = e.pageY;
152
177
  e.stopPropagation();
153
178
  e.preventDefault();
154
179
  }
155
180
 
156
181
  function inner_mouseup( e ) {
182
+
157
183
  var doc = that.root.ownerDocument;
158
184
  doc.removeEventListener( 'mousemove', inner_mousemove );
159
185
  doc.removeEventListener( 'mouseup', inner_mouseup );
160
- document.body.classList.remove( 'nocursor' );
161
186
  document.body.classList.remove( 'noevents' );
187
+
188
+ // Snap if necessary
189
+ if( snapEnabled )
190
+ {
191
+ mustSnap = true;
192
+ Panel._dispatch_event( innerKnobCircle, 'change' );
193
+ }
194
+
195
+ if( document.pointerLockElement )
196
+ {
197
+ document.exitPointerLock();
198
+ }
162
199
  }
163
200
 
164
201
  container.appendChild( knobCircle );
@@ -3624,7 +3624,7 @@ class CodeEditor {
3624
3624
  const scrollBarWidth = this.hScrollbar.thumb.parentElement.offsetWidth;
3625
3625
  const scrollThumbWidth = this.hScrollbar.thumb.offsetWidth;
3626
3626
 
3627
- this.hScrollbar.thumb._left = LX.UTILS.clamp( value, 0, ( scrollBarWidth - scrollThumbWidth ) );
3627
+ this.hScrollbar.thumb._left = LX.clamp( value, 0, ( scrollBarWidth - scrollThumbWidth ) );
3628
3628
  this.hScrollbar.thumb.style.left = this.hScrollbar.thumb._left + "px";
3629
3629
 
3630
3630
  // Scroll code
@@ -3646,7 +3646,7 @@ class CodeEditor {
3646
3646
  const scrollBarHeight = this.vScrollbar.thumb.parentElement.offsetHeight;
3647
3647
  const scrollThumbHeight = this.vScrollbar.thumb.offsetHeight;
3648
3648
 
3649
- this.vScrollbar.thumb._top = LX.UTILS.clamp( value, 0, ( scrollBarHeight - scrollThumbHeight ) );
3649
+ this.vScrollbar.thumb._top = LX.clamp( value, 0, ( scrollBarHeight - scrollThumbHeight ) );
3650
3650
  this.vScrollbar.thumb.style.top = this.vScrollbar.thumb._top + "px";
3651
3651
 
3652
3652
  // Scroll code
@@ -4222,7 +4222,7 @@ class CodeEditor {
4222
4222
  var r = document.querySelector( ':root' );
4223
4223
  var s = getComputedStyle( r );
4224
4224
  var pixels = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
4225
- pixels = LX.UTILS.clamp( pixels + 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
4225
+ pixels = LX.clamp( pixels + 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
4226
4226
  r.style.setProperty( "--code-editor-font-size", pixels + "px" );
4227
4227
  this.charWidth = this._measureChar( "a", true );
4228
4228
 
@@ -4248,7 +4248,7 @@ class CodeEditor {
4248
4248
  var r = document.querySelector( ':root' );
4249
4249
  var s = getComputedStyle( r );
4250
4250
  var pixels = parseInt( s.getPropertyValue( "--code-editor-font-size" ) );
4251
- pixels = LX.UTILS.clamp( pixels - 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
4251
+ pixels = LX.clamp( pixels - 1, CodeEditor.CODE_MIN_FONT_SIZE, CodeEditor.CODE_MAX_FONT_SIZE );
4252
4252
  r.style.setProperty( "--code-editor-font-size", pixels + "px" );
4253
4253
  this.charWidth = this._measureChar( "a", true );
4254
4254
 
@@ -195,7 +195,7 @@ class ImUI {
195
195
 
196
196
  if(active)
197
197
  {
198
- value = LX.UTILS.clamp((this.mousePosition.x - min) / (max - min), 0.0, 1.0);
198
+ value = LX.clamp((this.mousePosition.x - min) / (max - min), 0.0, 1.0);
199
199
  this.widgets[ text ].value = value;
200
200
  }
201
201
 
@@ -1728,7 +1728,7 @@ class GraphEditor {
1728
1728
  if( delta > 0.0 ) this.currentGraph.scale *= 0.9;
1729
1729
  else this.currentGraph.scale *= ( 1.0 / 0.9 );
1730
1730
 
1731
- this.currentGraph.scale = LX.UTILS.clamp( this.currentGraph.scale, GraphEditor.MIN_SCALE, GraphEditor.MAX_SCALE );
1731
+ this.currentGraph.scale = LX.clamp( this.currentGraph.scale, GraphEditor.MIN_SCALE, GraphEditor.MAX_SCALE );
1732
1732
 
1733
1733
  // Compute zoom center in pattern space using new scale
1734
1734
  // and get delta..
@@ -2218,7 +2218,7 @@ class GraphEditor {
2218
2218
  startPos = this._getPatternPosition( startPos );
2219
2219
  endPos = this._getPatternPosition( endPos );
2220
2220
 
2221
- const distanceX = LX.UTILS.clamp( Math.abs( startPos.x - endPos.x ), 0.0, 256.0 );
2221
+ const distanceX = LX.clamp( Math.abs( startPos.x - endPos.x ), 0.0, 256.0 );
2222
2222
  const cPDistance = 128.0 * Math.pow( distanceX / 256.0, 0.5 );
2223
2223
 
2224
2224
  let cPoint1 = startPos.add( new LX.vec2( cPDistance, 0 ) );
@@ -62,7 +62,7 @@ class Timeline {
62
62
  this.onBeforeCreateTopBar = options.onBeforeCreateTopBar;
63
63
  this.onAfterCreateTopBar = options.onAfterCreateTopBar;
64
64
  this.onChangePlayMode = options.onChangePlayMode;
65
- this.onConfiguration = options.onConfiguration;
65
+ this.onShowConfiguration = options.onShowConfiguration;
66
66
  this.onBeforeDrawContent = options.onBeforeDrawContent;
67
67
 
68
68
  this.playing = false;
@@ -107,7 +107,8 @@ class Timeline {
107
107
  let height = options.height ? options.height - this.header_offset : null;
108
108
 
109
109
  let area = new LX.Area( {id: "bottom-timeline-area", width: width || "calc(100% - 7px)", height: height || "100%"});
110
- area.split({ type: "horizontal", sizes: ["15%", "85%"]});
110
+ area.split({ type: "horizontal", sizes: ["15%", "85%"] });
111
+ area.split_bar.style.zIndex = 1; // for some reason this is needed here
111
112
  this.content_area = area;
112
113
  let [left, right] = area.sections;
113
114
 
@@ -197,23 +198,24 @@ class Timeline {
197
198
  if(this.onShowOptimizeMenu)
198
199
  header.addButton("", '<i class="fa-solid fa-filter"></i>', (value, event) => {this.onShowOptimizeMenu(event)}, {width: "40px"});
199
200
 
200
- header.addButton("", '<i class="fa-solid fa-gear"></i>', (value, event) => {
201
- if(this.configurationDialog){
202
- this.configurationDialog.close();
203
- this.configurationDialog = null;
204
- return;
205
- }
206
- this.configurationDialog = new LX.Dialog("Configuration", d => {
207
- if ( this.onConfiguration ){
208
- this.onConfiguration(d);
209
- }
210
- }, {
211
- onclose: (root) => {
212
- root.remove();
201
+ if(this.onShowConfiguration){
202
+ header.addButton("", '<i class="fa-solid fa-gear"></i>', (value, event) => {
203
+ if(this.configurationDialog){
204
+ this.configurationDialog.close();
213
205
  this.configurationDialog = null;
206
+ return;
214
207
  }
215
- })
216
- }, {width: "40px"})
208
+ this.configurationDialog = new LX.Dialog("Configuration", dialog => {
209
+ this.onShowConfiguration(dialog);
210
+ }, {
211
+ onclose: (root) => {
212
+ this.configurationDialog.panel.clear(); // clear signals
213
+ this.configurationDialog = null;
214
+ root.remove();
215
+ }
216
+ })
217
+ }, {width: "40px"})
218
+ }
217
219
 
218
220
  header.endLine();
219
221
  LX.DEFAULT_NAME_WIDTH = "30%";
@@ -509,19 +511,14 @@ class Timeline {
509
511
  let max_tracks = Math.ceil( (h - timeline_height + this.currentScrollInPixels) / line_height );
510
512
 
511
513
  ctx.save();
512
- ctx.fillStyle = Timeline.BACKGROUND_COLOR;
513
- for(let i = 0; i <= max_tracks; ++i)
514
+ ctx.fillStyle = "#f0f0f003"//Timeline.TRACK_COLOR_SECONDARY;
515
+ ctx.globalAlpha = 1;
516
+ for(let i = 0; i <= max_tracks; i+=2)
514
517
  {
515
- ctx.fillStyle = i % 2 == 0 ? Timeline.TRACK_COLOR_PRIMARY: Timeline.BACKGROUND_COLOR;
516
518
  ctx.fillRect(0, timeline_height + i * line_height - this.currentScrollInPixels, w, line_height );
517
519
  }
518
-
519
- //black bg
520
- ctx.globalAlpha = 0.7;
521
- ctx.fillStyle = Timeline.BACKGROUND_COLOR;
522
- ctx.fillRect( margin, 0, canvas.width - margin, canvas.height);
523
520
  ctx.globalAlpha = this.opacity;
524
-
521
+
525
522
  //bg lines
526
523
  ctx.strokeStyle = "#444";
527
524
  ctx.beginPath();
@@ -529,6 +526,7 @@ class Timeline {
529
526
  let pos = this.timeToX( 0 );
530
527
  if(pos < margin)
531
528
  pos = margin;
529
+ ctx.lineWidth = 1;
532
530
  ctx.moveTo( pos + 0.5, timeline_height);
533
531
  ctx.lineTo( pos + 0.5, canvas.height);
534
532
  ctx.moveTo( Math.round( this.timeToX( duration ) ) + 0.5, timeline_height);
@@ -704,7 +702,7 @@ class Timeline {
704
702
  * @param {Number} t
705
703
  */
706
704
 
707
- setDuration( t, updateHeader = true ) {
705
+ setDuration( t, updateHeader = true, skipCallback = false ) {
708
706
  let v = this.validateDuration(t);
709
707
  let decimals = t.toString().split('.')[1] ? t.toString().split('.')[1].length : 0;
710
708
  updateHeader = (updateHeader || +v.toFixed(decimals) != t);
@@ -714,7 +712,7 @@ class Timeline {
714
712
  LX.emit( "@on_set_duration_" + this.name, +this.duration.toFixed(3)); // skipcallback = true
715
713
  }
716
714
 
717
- if( this.onSetDuration )
715
+ if( this.onSetDuration && !skipCallback )
718
716
  this.onSetDuration( this.duration );
719
717
  }
720
718
 
@@ -733,19 +731,19 @@ class Timeline {
733
731
  * @param {Number} speed
734
732
  */
735
733
 
736
- setSpeed(speed) {
734
+ setSpeed(speed, skipCallback = false) {
737
735
  this.speed = speed;
738
736
  LX.emit( "@on_set_speed_" + this.name, +this.speed.toFixed(3)); // skipcallback = true
739
737
 
740
- if( this.onSetSpeed )
738
+ if( this.onSetSpeed && !skipCallback)
741
739
  this.onSetSpeed( this.speed );
742
740
  }
743
741
 
744
- setTime(time){
742
+ setTime(time, skipCallback = false ){
745
743
  this.currentTime = Math.max(0,Math.min(time,this.duration));
746
744
  LX.emit( "@on_set_time_" + this.name, +this.currentTime.toFixed(2)); // skipcallback = true
747
745
 
748
- if(this.onSetTime)
746
+ if(this.onSetTime && !skipCallback)
749
747
  this.onSetTime(this.currentTime);
750
748
  }
751
749
 
@@ -832,6 +830,9 @@ class Timeline {
832
830
  this.leftPanel.root.children[1].scrollTop += e.deltaY; // wheel deltaY
833
831
  }
834
832
 
833
+ if ( this.onMouse ){
834
+ this.onMouse(e, time);
835
+ }
835
836
  return;
836
837
  }
837
838
 
@@ -969,8 +970,8 @@ class Timeline {
969
970
  return true;
970
971
  }
971
972
 
972
- if( this.onMouse && this.onMouse( e, time, this ) )
973
- return;
973
+ if( this.onMouse )
974
+ this.onMouse( e, time, this );
974
975
 
975
976
  return true;
976
977
  }
@@ -2572,14 +2573,14 @@ class KeyFramesTimeline extends Timeline {
2572
2573
 
2573
2574
  const currentSelection = this.selectKeyFrame(t, keyFrameIndex, !multiple); // changes time
2574
2575
 
2576
+ if( !multiple ) {
2577
+ this.setTime(this.animationClip.tracks[t.clipIdx].times[ keyFrameIndex ]);
2578
+ }
2575
2579
  if( this.onSelectKeyFrame && this.onSelectKeyFrame(e, currentSelection)) {
2576
2580
  // Event handled
2577
2581
  return;
2578
2582
  }
2579
-
2580
- if( !multiple ) {
2581
- this.setTime(this.animationClip.tracks[t.clipIdx].times[ keyFrameIndex ]);
2582
- }
2583
+
2583
2584
  }
2584
2585
 
2585
2586
  /**
@@ -2605,7 +2606,6 @@ class KeyFramesTimeline extends Timeline {
2605
2606
  /**
2606
2607
  * @method clearTrack
2607
2608
  */
2608
-
2609
2609
  clearTrack(idx, defaultValue) {
2610
2610
 
2611
2611
  let track = this.animationClip.tracks[idx];
@@ -2615,11 +2615,14 @@ class KeyFramesTimeline extends Timeline {
2615
2615
  return;
2616
2616
  }
2617
2617
 
2618
+ this.unHoverAll();
2619
+ this.unSelectAllKeyFrames();
2620
+
2621
+ this.saveState(track.clipIdx);
2618
2622
  const count = track.times.length;
2619
2623
  for(let i = count - 1; i >= 0; i--)
2620
2624
  {
2621
- this.saveState(track.clipIdx);
2622
- this.#delete(track.clipIdx, i );
2625
+ this.#delete(track.clipIdx, i);
2623
2626
  }
2624
2627
  if(defaultValue != undefined) {
2625
2628
  if(typeof(defaultValue) == 'number') {
@@ -2855,7 +2858,10 @@ class ClipsTimeline extends Timeline {
2855
2858
  onMouseMove( e, time ) {
2856
2859
  // function not called if shift is pressed (boxselection)
2857
2860
 
2858
- if(this.grabbing && e.buttons != 2) {
2861
+ if ( this.grabbingTimeBar || this.grabbingScroll ){
2862
+ return;
2863
+ }
2864
+ else if(this.grabbing && e.buttons != 2) {
2859
2865
  this.unHoverAll();
2860
2866
 
2861
2867
  let delta = time - this.grabTime;
@@ -2879,7 +2885,8 @@ class ClipsTimeline extends Timeline {
2879
2885
  duration = Math.min( track.clips[this.lastClipsSelected[0][1] + 1].start - clip.start - 0.0001, duration );
2880
2886
  }
2881
2887
  clip.duration = duration;
2882
- clip.fadeout = Math.max(Math.min((clip.fadeout || clip.start+clip.duration) + delta, clip.start+clip.duration), clip.start);
2888
+ clip.fadeout = Math.max(Math.min((clip.fadeout ?? (clip.start+clip.duration)) + delta, clip.start+clip.duration), clip.start);
2889
+ clip.fadein = Math.max(Math.min((clip.fadein ?? (clip.start+clip.duration)), (clip.fadeout ?? (clip.start+clip.duration))), clip.start);
2883
2890
  if(this.duration < clip.start + clip.duration){
2884
2891
  this.setDuration(clip.start + clip.duration);
2885
2892
  }
@@ -3258,16 +3265,13 @@ class ClipsTimeline extends Timeline {
3258
3265
  /**
3259
3266
  * @method optimizeTrack
3260
3267
  */
3261
-
3262
3268
  optimizeTrack(trackIdx) {
3263
3269
  }
3264
3270
 
3265
3271
  /**
3266
3272
  * @method optimizeTracks
3267
3273
  */
3268
-
3269
3274
  optimizeTracks() {
3270
- this.addClip()
3271
3275
  }
3272
3276
 
3273
3277
  /**
@@ -5154,14 +5158,14 @@ class CurvesTimeline extends Timeline {
5154
5158
 
5155
5159
  const currentSelection = this.selectKeyFrame(t, keyFrameIndex, !multiple, multiple); // changes time on the first keyframe selected
5156
5160
 
5161
+ if (!multiple){
5162
+ this.setTime(this.animationClip.tracks[t.clipIdx].times[ keyFrameIndex ]);
5163
+ }
5164
+
5157
5165
  if( this.onSelectKeyFrame && this.onSelectKeyFrame(e, currentSelection)) {
5158
5166
  // Event handled
5159
5167
  return;
5160
5168
  }
5161
-
5162
- if (!multiple){
5163
- this.setTime(this.animationClip.tracks[t.clipIdx].times[ keyFrameIndex ]);
5164
- }
5165
5169
  }
5166
5170
 
5167
5171
  /**
@@ -5195,12 +5199,15 @@ class CurvesTimeline extends Timeline {
5195
5199
  {
5196
5200
  return;
5197
5201
  }
5198
-
5202
+
5203
+ this.unHoverAll();
5204
+ this.unSelectAllKeyFrames();
5205
+
5206
+ this.saveState(track.clipIdx);
5199
5207
  const count = track.times.length;
5200
5208
  for(let i = count - 1; i >= 0; i--)
5201
5209
  {
5202
- this.saveState(track.clipIdx);
5203
- this.#delete(track, i );
5210
+ this.#delete(track.clipIdx, i );
5204
5211
  }
5205
5212
  if(defaultValue != undefined) {
5206
5213
  if(typeof(defaultValue) == 'number') {
@@ -5292,4 +5299,4 @@ LX.UTILS.concatTypedArray = (arrays, ArrayType) => {
5292
5299
  return result;
5293
5300
  }
5294
5301
 
5295
- export { Timeline, KeyFramesTimeline, ClipsTimeline, CurvesTimeline };
5302
+ export { Timeline, KeyFramesTimeline, ClipsTimeline, CurvesTimeline };
@@ -359,16 +359,19 @@ class VideoEditor {
359
359
  this.startTimeString = "0:0";
360
360
  this.endTimeString = "0:0";
361
361
 
362
- let [videoArea, controlsArea] = area.split({ type: 'vertical', sizes: ["80%", null], minimizable: false, resize: false });
362
+ this.mainArea = area;
363
+
364
+ let [videoArea, controlsArea] = area.split({ type: 'vertical', sizes: ["85%", null], minimizable: false, resize: false });
363
365
  controlsArea.root.classList.add('lexconstrolsarea');
364
366
 
365
367
  // Create video element and load it
366
368
  let video = this.video = options.video ?? document.createElement( 'video' );
369
+ this.video.loop = true;
370
+
367
371
  if(options.src) {
368
372
  this.video.src = options.src;
373
+ this._loadVideo(options);
369
374
  }
370
- this.video.loop = true;
371
- this._loadVideo(options);
372
375
  if(options.videoArea) {
373
376
  options.videoArea.root.classList.add("lexvideoeditor");
374
377
  videoArea.attach(options.videoArea);
@@ -458,6 +461,24 @@ class VideoEditor {
458
461
  this.timebar.resize([availableWidth, timeBarArea.root.clientHeight]);
459
462
  })
460
463
 
464
+ this.onKeyUp = (event) => {
465
+ if(this.controls && event.key == " ") {
466
+ event.preventDefault();
467
+ event.stopPropagation();
468
+
469
+ if(!this.playing) {
470
+ this.video.play();
471
+ }
472
+ else {
473
+ this.video.pause();
474
+ }
475
+ this.playing = !this.playing;
476
+ this.controlsPanelLeft.refresh();
477
+ }
478
+ }
479
+
480
+ window.addEventListener( "keyup", this.onKeyUp);
481
+
461
482
  videoArea.onresize = (v) => {
462
483
  bottomArea.setSize([v.width, 40]);
463
484
  }
@@ -483,7 +504,7 @@ class VideoEditor {
483
504
  this.timebar.onMouseMove(event);
484
505
  }
485
506
  });
486
-
507
+
487
508
  }
488
509
 
489
510
  async _loadVideo( options = {} ) {
@@ -491,13 +512,17 @@ class VideoEditor {
491
512
  await new Promise(r => setTimeout(r, 1000));
492
513
  this.video.currentTime = 10000000 * Math.random();
493
514
  }
494
- this.video.currentTime = -1; // BUG: some videos will not play unless this line is present
495
- this.video.currentTime = 0;
515
+
516
+ this.timebar.startX = this.timebar.position.x;
517
+ this.timebar.endX = this.timebar.width;
518
+
519
+ this.video.currentTime = 0.01; // BUG: some videos will not play unless this line is present
496
520
  this.endTime = this.video.duration;
497
- this.timebar.currentX = this._timeToX(0);
521
+
498
522
  this._setEndValue(this.timebar.endX);
499
523
  this._setStartValue(this.timebar.startX);
500
- this._setCurrentValue(this.timebar.currentX);
524
+ this.timebar.currentX = this._timeToX(this.video.currentTime);
525
+ this._setCurrentValue(this.timebar.currentX, false);
501
526
  this.timebar.update(this.timebar.currentX);
502
527
 
503
528
  if ( !this.requestId ){ // only have one update on flight
@@ -508,6 +533,8 @@ class VideoEditor {
508
533
  this.hideControls();
509
534
  }
510
535
 
536
+ window.addEventListener( "keyup", this.onKeyUp);
537
+
511
538
  if(this.onVideoLoaded) {
512
539
  this.onVideoLoaded(this.video);
513
540
  }
@@ -627,9 +654,15 @@ class VideoEditor {
627
654
  }
628
655
  }
629
656
 
630
- delete ( ) {
657
+ unbind ( ) {
631
658
  this.stopUpdates();
632
- delete this;
659
+
660
+ this.video.pause();
661
+ this.playing = false;
662
+ this.controlsPanelLeft.refresh();
663
+ this.video.src = "";
664
+
665
+ window.removeEventListener("keyup", this.onKeyUp);
633
666
  }
634
667
  }
635
668
 
package/build/lexgui.css CHANGED
@@ -2562,6 +2562,45 @@ meter::-webkit-meter-even-less-good-value {
2562
2562
  border-radius: 50%;
2563
2563
  position: relative;
2564
2564
  box-shadow: 0px 3px 9px 2px #121212a9;
2565
+ --knob-snap-mark: 45deg;
2566
+ }
2567
+
2568
+ /* Arc Indicator */
2569
+ .lexknob .knobcircle::before {
2570
+ content: "";
2571
+ position: absolute;
2572
+ width: 125%;
2573
+ height: 125%;
2574
+ top: -15.5%;
2575
+ left: -15.5%;
2576
+ border-radius: 50%;
2577
+ border: 2px solid rgba(255, 255, 255, 0.2);
2578
+ border-bottom-color: transparent;
2579
+ }
2580
+
2581
+ /* Tick Marks */
2582
+ .lexknob.show-ticks .knobcircle::after {
2583
+ content: "";
2584
+ position: absolute;
2585
+ width: 130%;
2586
+ height: 130%;
2587
+ top: -15.5%;
2588
+ left: -15.5%;
2589
+ border-radius: 50%;
2590
+ pointer-events: none;
2591
+ mask-image: radial-gradient(circle, transparent 60%, black 60%), conic-gradient(
2592
+ transparent -135deg,
2593
+ black -135deg 275deg,
2594
+ transparent 275deg
2595
+ );
2596
+ -webkit-mask-composite: intersect;
2597
+ mask-composite: intersect;
2598
+ background: repeating-conic-gradient(
2599
+ transparent 0deg,
2600
+ rgba(255, 255, 255, 0.4) 1deg 3deg,
2601
+ transparent 4deg calc( var(--knob-snap-mark) - 1deg )
2602
+ );
2603
+ transform: rotate(-135deg);
2565
2604
  }
2566
2605
 
2567
2606
  .lexknob.sm .knobcircle {
@@ -2606,6 +2645,10 @@ meter::-webkit-meter-even-less-good-value {
2606
2645
  margin-top: 6px;
2607
2646
  }
2608
2647
 
2648
+ .lexknob.disabled .knobmarker {
2649
+ background-color: var(--global-text-terciary);
2650
+ }
2651
+
2609
2652
  .lexknob.sm .knobmarker {
2610
2653
  width: 5px;
2611
2654
  height: 5px;