lexgui 0.1.36 → 0.1.39

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.
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  var LX = {
11
- version: "0.1.36",
11
+ version: "0.1.39",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -25,7 +25,12 @@ LX.CURVE_MOVEOUT_CLAMP = 0;
25
25
  LX.CURVE_MOVEOUT_DELETE = 1;
26
26
 
27
27
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
28
- function round( num, n ) { return +num.toFixed( n ); }
28
+ function round( number, precision ) { return +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
29
+ function remapRange( oldValue, oldMin, oldMax, newMin, newMax ) { return ((( oldValue - oldMin ) * ( newMax - newMin )) / ( oldMax - oldMin )) + newMin; }
30
+
31
+ LX.clamp = clamp;
32
+ LX.round = round;
33
+ LX.remapRange = remapRange;
29
34
 
30
35
  function getSupportedDOMName( string )
31
36
  {
@@ -102,6 +107,18 @@ function rgbToHex( rgb ) {
102
107
 
103
108
  LX.rgbToHex = rgbToHex;
104
109
 
110
+ function measureRealWidth( value, paddingPlusMargin = 8 ) {
111
+ var i = document.createElement( "span" );
112
+ i.className = "lexinputmeasure";
113
+ i.innerHTML = value;
114
+ document.body.appendChild( i );
115
+ var rect = i.getBoundingClientRect();
116
+ LX.UTILS.deleteElement( i );
117
+ return rect.width + paddingPlusMargin;
118
+ }
119
+
120
+ LX.measureRealWidth = measureRealWidth;
121
+
105
122
  function simple_guidGenerator() {
106
123
  var S4 = function() {
107
124
  return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
@@ -109,6 +126,8 @@ function simple_guidGenerator() {
109
126
  return (S4()+"-"+S4()+"-"+S4());
110
127
  }
111
128
 
129
+ LX.guidGenerator = simple_guidGenerator;
130
+
112
131
  // Timer that works everywhere (from litegraph.js)
113
132
  if (typeof performance != "undefined") {
114
133
  LX.getTime = performance.now.bind(performance);
@@ -161,36 +180,69 @@ class vec2 {
161
180
  len () { return Math.sqrt( this.len2() ); }
162
181
  nrm ( v0 = new vec2() ) { v0.set( this.x, this.y ); return v0.mul( 1.0 / this.len(), v0 ); }
163
182
  dst ( v ) { return v.sub( this ).len(); }
183
+ clp ( min, max, v0 = new vec2() ) { v0.set( clamp( this.x, min, max ), clamp( this.y, min, max ) ); return v0; }
164
184
  };
165
185
 
166
186
  LX.vec2 = vec2;
167
187
 
168
188
  // Other utils
169
189
 
190
+ /**
191
+ * @method makeDraggable
192
+ * @param {Element} domEl
193
+ * @param {Object} options
194
+ * autoAdjust (Bool): Sets in a correct position at the beggining
195
+ * dragMargin (Number): Margin of drag container
196
+ * onMove (Function): Called each move event
197
+ * onDragStart (Function): Called when drag event starts
198
+ */
170
199
  function makeDraggable( domEl, options = { } ) {
171
200
 
172
- let offsetX;
173
- let offsetY;
201
+ let offsetX = 0;
202
+ let offsetY = 0;
174
203
  let currentTarget = null;
175
204
  let targetClass = options.targetClass;
205
+ let dragMargin = options.dragMargin ?? 3;
206
+
207
+ let _computePosition = ( e, top, left ) => {
208
+ const nullRect = { x: 0, y: 0, width: 0, height: 0 };
209
+ const parentRect = domEl.parentElement ? domEl.parentElement.getBoundingClientRect() : nullRect;
210
+ const isFixed = ( domEl.style.position == "fixed" );
211
+ const fixedOffset = isFixed ? new LX.vec2( parentRect.x, parentRect.y ) : new LX.vec2();
212
+ left = left ?? e.clientX - offsetX - parentRect.x;
213
+ top = top ?? e.clientY - offsetY - parentRect.y;
214
+ domEl.style.left = clamp( left, dragMargin + fixedOffset.x, fixedOffset.x + parentRect.width - domEl.offsetWidth - dragMargin ) + 'px';
215
+ domEl.style.top = clamp( top, dragMargin + fixedOffset.y, fixedOffset.y + parentRect.height - domEl.offsetHeight - dragMargin ) + 'px';
216
+ };
217
+
218
+ // Initial adjustment
219
+ if( options.autoAdjust )
220
+ {
221
+ _computePosition( null, parseInt( domEl.style.left ), parseInt( domEl.style.top ) )
222
+ }
176
223
 
177
224
  let id = LX.UTILS.uidGenerator();
178
225
  domEl[ 'draggable-id' ] = id;
179
226
 
180
227
  const defaultMoveFunc = e => {
181
- if( !currentTarget ) return;
182
- let left = e.clientX - offsetX;
183
- let top = e.clientY - offsetY;
184
- if( left > 3 && ( left + domEl.offsetWidth + 6 ) <= window.innerWidth )
185
- domEl.style.left = left + 'px';
186
- if( top > 3 && ( top + domEl.offsetHeight + 6 ) <= window.innerHeight )
187
- domEl.style.top = top + 'px';
228
+ if( !currentTarget )
229
+ {
230
+ return;
231
+ }
232
+
233
+ _computePosition( e );
188
234
  };
189
235
 
190
236
  const customMoveFunc = e => {
191
- if( !currentTarget ) return;
237
+ if( !currentTarget )
238
+ {
239
+ return;
240
+ }
241
+
192
242
  if( options.onMove )
243
+ {
193
244
  options.onMove( currentTarget );
245
+ }
194
246
  };
195
247
 
196
248
  let onMove = options.onMove ? customMoveFunc : defaultMoveFunc;
@@ -198,7 +250,7 @@ function makeDraggable( domEl, options = { } ) {
198
250
 
199
251
  domEl.setAttribute( 'draggable', true );
200
252
  domEl.addEventListener( "mousedown", function( e ) {
201
- currentTarget = (e.target.classList.contains(targetClass) || !targetClass) ? e.target : null;
253
+ currentTarget = ( e.target.classList.contains( targetClass ) || !targetClass ) ? e.target : null;
202
254
  } );
203
255
 
204
256
  domEl.addEventListener( "dragstart", function( e ) {
@@ -212,15 +264,21 @@ function makeDraggable( domEl, options = { } ) {
212
264
  e.dataTransfer.setDragImage( img, 0, 0 );
213
265
  e.dataTransfer.effectAllowed = "move";
214
266
  const rect = e.target.getBoundingClientRect();
215
- offsetX = e.clientX - rect.x;
216
- offsetY = e.clientY - rect.y;
267
+ const parentRect = currentTarget.parentElement.getBoundingClientRect();
268
+ const isFixed = ( currentTarget.style.position == "fixed" );
269
+ const fixedOffset = isFixed ? new LX.vec2( parentRect.x, parentRect.y ) : new LX.vec2();
270
+ offsetX = e.clientX - rect.x - fixedOffset.x;
271
+ offsetY = e.clientY - rect.y - fixedOffset.y;
217
272
  document.addEventListener( "mousemove", onMove );
218
273
  if( onDragStart )
274
+ {
219
275
  onDragStart( currentTarget, e );
276
+ }
220
277
  }, false );
221
278
 
222
279
  document.addEventListener( 'mouseup', () => {
223
- if( currentTarget ) {
280
+ if( currentTarget )
281
+ {
224
282
  currentTarget = null;
225
283
  document.removeEventListener( "mousemove", onMove );
226
284
  }
@@ -300,11 +358,18 @@ function create_global_searchbar( root ) {
300
358
  else
301
359
  {
302
360
  for( let c of LX.components )
303
- if( LX[c].prototype.onKeyPressed )
361
+ {
362
+ if( !LX[c] || !LX[c].prototype.onKeyPressed )
363
+ {
364
+ continue;
365
+ }
366
+
367
+ const instances = LX.CodeEditor.getInstances();
368
+ for( let i of instances )
304
369
  {
305
- const instances = LX.CodeEditor.getInstances();
306
- for( let i of instances ) i.onKeyPressed( e );
370
+ i.onKeyPressed( e );
307
371
  }
372
+ }
308
373
  }
309
374
  });
310
375
 
@@ -631,6 +696,8 @@ class IEvent {
631
696
  }
632
697
  };
633
698
 
699
+ LX.IEvent = IEvent;
700
+
634
701
  class TreeEvent {
635
702
 
636
703
  static NONE = 0;
@@ -652,7 +719,8 @@ class TreeEvent {
652
719
  }
653
720
 
654
721
  string() {
655
- switch(this.type) {
722
+ switch( this.type )
723
+ {
656
724
  case TreeEvent.NONE: return "tree_event_none";
657
725
  case TreeEvent.NODE_SELECTED: return "tree_event_selected";
658
726
  case TreeEvent.NODE_DELETED: return "tree_event_deleted";
@@ -806,7 +874,9 @@ class Area {
806
874
 
807
875
  const draggable = options.draggable ?? true;
808
876
  if( draggable )
809
- makeDraggable( root );
877
+ {
878
+ makeDraggable( root, options );
879
+ }
810
880
 
811
881
  if( options.resizeable ) {
812
882
  root.classList.add("resizeable");
@@ -1168,23 +1238,33 @@ class Area {
1168
1238
  * @method resize
1169
1239
  * Resize element
1170
1240
  */
1171
- setSize(size) {
1241
+ setSize( size ) {
1172
1242
 
1173
- let [width, height] = size;
1243
+ let [ width, height ] = size;
1174
1244
 
1175
- if(width != undefined && width.constructor == Number)
1245
+ if( width != undefined && width.constructor == Number )
1246
+ {
1176
1247
  width += "px";
1177
- if(height != undefined && height.constructor == Number)
1248
+ }
1249
+
1250
+ if( height != undefined && height.constructor == Number )
1251
+ {
1178
1252
  height += "px";
1253
+ }
1179
1254
 
1180
- if(width)
1255
+ if( width )
1256
+ {
1181
1257
  this.root.style.width = width;
1182
- if(height)
1258
+ }
1259
+
1260
+ if( height )
1261
+ {
1183
1262
  this.root.style.height = height;
1263
+ }
1184
1264
 
1185
- this.size = [this.root.clientWidth, this.root.clientHeight];
1265
+ this.size = [ this.root.clientWidth, this.root.clientHeight ];
1186
1266
 
1187
- this.propagateEvent("onresize");
1267
+ this.propagateEvent( "onresize" );
1188
1268
  }
1189
1269
 
1190
1270
  /**
@@ -1194,7 +1274,9 @@ class Area {
1194
1274
  extend() {
1195
1275
 
1196
1276
  if( this.split_extended )
1197
- return;
1277
+ {
1278
+ return;
1279
+ }
1198
1280
 
1199
1281
  let [area1, area2] = this.sections;
1200
1282
  this.split_extended = true;
@@ -1863,11 +1945,15 @@ class Tabs {
1863
1945
 
1864
1946
  const tabEl = this.tabDOMs[ name ];
1865
1947
 
1866
- if(!tabEl || tabEl.fixed)
1867
- return;
1948
+ if( !tabEl || tabEl.fixed )
1949
+ {
1950
+ return;
1951
+ }
1868
1952
 
1869
1953
  if( this.onclose )
1954
+ {
1870
1955
  this.onclose( name );
1956
+ }
1871
1957
 
1872
1958
  // Delete tab element
1873
1959
  this.tabDOMs[ name ].remove();
@@ -1878,11 +1964,12 @@ class Tabs {
1878
1964
  delete this.tabs[ name ];
1879
1965
 
1880
1966
  // Select last tab
1881
- const last_tab = this.root.lastChild;
1882
- if(last_tab && !last_tab.fixed)
1967
+ const lastTab = this.root.lastChild;
1968
+ if( lastTab && !lastTab.fixed )
1969
+ {
1883
1970
  this.root.lastChild.click();
1971
+ }
1884
1972
  }
1885
-
1886
1973
  }
1887
1974
 
1888
1975
  LX.Tabs = Tabs;
@@ -2448,6 +2535,9 @@ class Widget {
2448
2535
  static CONTENT = 20;
2449
2536
  static CUSTOM = 21;
2450
2537
  static SEPARATOR = 22;
2538
+ static KNOB = 23;
2539
+ static SIZE = 24;
2540
+ static PAD = 25;
2451
2541
 
2452
2542
  static NO_CONTEXT_TYPES = [
2453
2543
  Widget.BUTTON,
@@ -2456,7 +2546,7 @@ class Widget {
2456
2546
  Widget.PROGRESS
2457
2547
  ];
2458
2548
 
2459
- constructor(name, type, options) {
2549
+ constructor( name, type, options ) {
2460
2550
  this.name = name;
2461
2551
  this.type = type;
2462
2552
  this.options = options;
@@ -2464,10 +2554,12 @@ class Widget {
2464
2554
 
2465
2555
  value() {
2466
2556
 
2467
- if(this.onGetValue)
2557
+ if( this.onGetValue )
2558
+ {
2468
2559
  return this.onGetValue();
2560
+ }
2469
2561
 
2470
- console.warn("Can't get value of " + this.typeName());
2562
+ console.warn( "Can't get value of " + this.typeName() );
2471
2563
  }
2472
2564
 
2473
2565
  set( value, skipCallback = false, signalName = "" ) {
@@ -2503,14 +2595,16 @@ class Widget {
2503
2595
 
2504
2596
  paste() {
2505
2597
  if( !this._can_paste() )
2506
- return;
2598
+ {
2599
+ return;
2600
+ }
2507
2601
 
2508
- this.set(navigator.clipboard.data);
2602
+ this.set( navigator.clipboard.data );
2509
2603
  }
2510
2604
 
2511
2605
  typeName() {
2512
2606
 
2513
- switch(this.type) {
2607
+ switch( this.type ) {
2514
2608
  case Widget.TEXT: return "Text";
2515
2609
  case Widget.TEXTAREA: return "TextArea";
2516
2610
  case Widget.BUTTON: return "Button";
@@ -2527,6 +2621,9 @@ class Widget {
2527
2621
  case Widget.LIST: return "List";
2528
2622
  case Widget.TAGS: return "Tags";
2529
2623
  case Widget.CURVE: return "Curve";
2624
+ case Widget.KNOB: return "Knob";
2625
+ case Widget.SIZE: return "Size";
2626
+ case Widget.PAD: return "Pad";
2530
2627
  case Widget.CUSTOM: return this.customName;
2531
2628
  }
2532
2629
  }
@@ -2621,40 +2718,50 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2621
2718
  custom_widgets.className = "lexcustomitems";
2622
2719
  custom_widgets.toggleAttribute('hidden', true);
2623
2720
 
2624
- element.appendChild(container);
2625
- element.appendChild(custom_widgets);
2721
+ element.appendChild( container );
2722
+ element.appendChild( custom_widgets );
2723
+
2724
+ if( instance )
2725
+ {
2626
2726
 
2627
- if( instance ) {
2628
-
2629
2727
  this.queue( custom_widgets );
2630
2728
 
2631
- const on_instance_changed = (key, value, event) => {
2632
- instance[key] = value;
2633
- this._trigger( new IEvent(name, instance, event), callback );
2729
+ const on_instance_changed = ( key, value, event ) => {
2730
+ instance[ key ] = value;
2731
+ this._trigger( new IEvent( name, instance, event ), callback );
2634
2732
  };
2635
2733
 
2636
2734
  for( let key in default_instance )
2637
2735
  {
2638
- const value = instance[key] ?? default_instance[key];
2736
+ const value = instance[ key ] ?? default_instance[ key ];
2639
2737
 
2640
- switch(value.constructor) {
2738
+ switch( value.constructor )
2739
+ {
2641
2740
  case String:
2642
- if(value[0] === '#')
2643
- this.addColor(key, value, on_instance_changed.bind(this, key));
2741
+ if( value[ 0 ] === '#' )
2742
+ {
2743
+ this.addColor( key, value, on_instance_changed.bind( this, key ) );
2744
+ }
2644
2745
  else
2645
- this.addText(key, value, on_instance_changed.bind(this, key));
2746
+ {
2747
+ this.addText( key, value, on_instance_changed.bind( this, key ) );
2748
+ }
2646
2749
  break;
2647
2750
  case Number:
2648
- this.addNumber(key, value, on_instance_changed.bind(this, key));
2751
+ this.addNumber( key, value, on_instance_changed.bind( this, key ) );
2649
2752
  break;
2650
2753
  case Boolean:
2651
- this.addCheckbox(key, value, on_instance_changed.bind(this, key));
2754
+ this.addCheckbox( key, value, on_instance_changed.bind( this, key ) );
2652
2755
  break;
2653
2756
  case Array:
2654
2757
  if( value.length > 4 )
2655
- this.addArray(key, value, on_instance_changed.bind(this, key));
2758
+ {
2759
+ this.addArray( key, value, on_instance_changed.bind( this, key ) );
2760
+ }
2656
2761
  else
2657
- this._add_vector(value.length, key, value, on_instance_changed.bind(this, key));
2762
+ {
2763
+ this._add_vector( value.length, key, value, on_instance_changed.bind( this, key ) );
2764
+ }
2658
2765
  break;
2659
2766
  }
2660
2767
  }
@@ -2693,37 +2800,40 @@ class NodeTree {
2693
2800
  _create_item( parent, node, level = 0, selectedId ) {
2694
2801
 
2695
2802
  const that = this;
2696
- const node_filter_input = this.domEl.querySelector("#lexnodetree_filter");
2803
+ const node_filter_input = this.domEl.querySelector( "#lexnodetree_filter" );
2697
2804
 
2698
2805
  node.children = node.children ?? [];
2699
- if(node_filter_input && !node.id.includes(node_filter_input.value) || (selectedId != undefined) && selectedId != node.id)
2806
+ if( node_filter_input && !node.id.includes( node_filter_input.value ) || (selectedId != undefined) && selectedId != node.id )
2700
2807
  {
2701
2808
  for( var i = 0; i < node.children.length; ++i )
2702
- this._create_item( node, node.children[i], level + 1, selectedId );
2809
+ {
2810
+ this._create_item( node, node.children[ i ], level + 1, selectedId );
2811
+ }
2703
2812
  return;
2704
2813
  }
2705
2814
 
2706
- const list = this.domEl.querySelector("ul");
2815
+ const list = this.domEl.querySelector( 'ul' );
2707
2816
 
2708
2817
  node.visible = node.visible ?? true;
2709
2818
  node.parent = parent;
2710
- let is_parent = node.children.length > 0;
2711
- let is_selected = this.selected.indexOf( node ) > -1 || node.selected;
2819
+ let isParent = node.children.length > 0;
2820
+ let isSelected = this.selected.indexOf( node ) > -1 || node.selected;
2712
2821
 
2713
- if( this.options.only_folders ) {
2822
+ if( this.options.only_folders )
2823
+ {
2714
2824
  let has_folders = false;
2715
2825
  node.children.forEach( c => has_folders |= (c.type == 'folder') );
2716
- is_parent = !!has_folders;
2826
+ isParent = !!has_folders;
2717
2827
  }
2718
2828
 
2719
2829
  let item = document.createElement('li');
2720
- item.className = "lextreeitem " + "datalevel" + level + (is_parent ? " parent" : "") + (is_selected ? " selected" : "");
2830
+ item.className = "lextreeitem " + "datalevel" + level + (isParent ? " parent" : "") + (isSelected ? " selected" : "");
2721
2831
  item.id = LX.getSupportedDOMName( node.id );
2722
2832
  item.tabIndex = "0";
2723
2833
 
2724
2834
  // Select hierarchy icon
2725
2835
  let icon = (this.options.skip_default_icon ?? true) ? "" : "fa-solid fa-square"; // Default: no childs
2726
- if( is_parent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2836
+ if( isParent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2727
2837
  item.innerHTML = "<a class='" + icon + " hierarchy'></a>";
2728
2838
 
2729
2839
  // Add display icon
@@ -2743,18 +2853,20 @@ class NodeTree {
2743
2853
 
2744
2854
  item.innerHTML += (node.rename ? "" : node.id);
2745
2855
 
2746
- item.setAttribute('draggable', true);
2747
- item.style.paddingLeft = ((is_parent ? 0 : 3 ) + (3 + (level+1) * 15)) + "px";
2748
- list.appendChild(item);
2856
+ item.setAttribute( 'draggable', true );
2857
+ item.style.paddingLeft = ((isParent ? 0 : 3 ) + (3 + (level+1) * 15)) + "px";
2858
+ list.appendChild( item );
2749
2859
 
2750
2860
  // Callbacks
2751
2861
  item.addEventListener("click", e => {
2752
- if( handled ) {
2862
+ if( handled )
2863
+ {
2753
2864
  handled = false;
2754
2865
  return;
2755
2866
  }
2756
2867
 
2757
- if( !e.shiftKey ) {
2868
+ if( !e.shiftKey )
2869
+ {
2758
2870
  list.querySelectorAll( "li" ).forEach( e => { e.classList.remove( 'selected' ); } );
2759
2871
  this.selected.length = 0;
2760
2872
  }
@@ -2770,16 +2882,18 @@ class NodeTree {
2770
2882
  }
2771
2883
 
2772
2884
  // Only Show children...
2773
- if( is_parent && node.id.length > 1 /* Strange case... */) {
2885
+ if( isParent && node.id.length > 1 /* Strange case... */) {
2774
2886
  node.closed = false;
2775
- if( that.onevent ) {
2776
- const event = new TreeEvent(TreeEvent.NODE_CARETCHANGED, node, node.closed);
2887
+ if( that.onevent )
2888
+ {
2889
+ const event = new TreeEvent( TreeEvent.NODE_CARETCHANGED, node, node.closed );
2777
2890
  that.onevent( event );
2778
2891
  }
2779
2892
  that.frefresh( node.id );
2780
2893
  }
2781
2894
 
2782
- if( that.onevent ) {
2895
+ if( that.onevent )
2896
+ {
2783
2897
  const event = new TreeEvent(TreeEvent.NODE_SELECTED, e.shiftKey ? this.selected : node );
2784
2898
  event.multiple = e.shiftKey;
2785
2899
  that.onevent( event );
@@ -2802,85 +2916,87 @@ class NodeTree {
2802
2916
  }
2803
2917
  });
2804
2918
 
2805
- item.addEventListener("contextmenu", e => {
2919
+ item.addEventListener( "contextmenu", e => {
2920
+
2806
2921
  e.preventDefault();
2807
- if(that.onevent) {
2808
- const event = new TreeEvent(TreeEvent.NODE_CONTEXTMENU, this.selected.length > 1 ? this.selected : node, e);
2809
- event.multiple = this.selected.length > 1;
2810
2922
 
2811
- LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
2812
- event.panel = m;
2813
- });
2923
+ if( that.onevent )
2924
+ {
2925
+ return;
2926
+ }
2814
2927
 
2815
- that.onevent( event );
2928
+ const event = new TreeEvent(TreeEvent.NODE_CONTEXTMENU, this.selected.length > 1 ? this.selected : node, e);
2929
+ event.multiple = this.selected.length > 1;
2816
2930
 
2817
- if( ( this.options.addDefault ?? false ) == true )
2931
+ LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
2932
+ event.panel = m;
2933
+ });
2934
+
2935
+ that.onevent( event );
2936
+
2937
+ if( ( this.options.addDefault ?? false ) == true )
2938
+ {
2939
+ if( event.panel.items )
2818
2940
  {
2819
- if( event.panel.items )
2820
- {
2821
- event.panel.add( "" );
2822
- }
2823
-
2824
- event.panel.add( "Select Children", () => {
2825
-
2826
- const selectChildren = ( n ) => {
2827
-
2828
- if( n.closed )
2829
- {
2830
- return;
2831
- }
2832
-
2833
- for( let child of n.children ?? [] )
2834
- {
2835
- if( !child )
2836
- {
2837
- continue;
2838
- }
2839
-
2840
- let nodeItem = this.domEl.querySelector( '#' + child.id );
2841
- nodeItem.classList.add('selected');
2842
- this.selected.push( child );
2843
- selectChildren( child );
2844
- }
2845
- };
2846
-
2847
- // Add childs of the clicked node
2848
- selectChildren( node );
2849
-
2850
- } );
2851
-
2852
- // event.panel.add( "Clone", { callback: () => {
2853
-
2854
- // } } );
2855
-
2856
- event.panel.add( "Delete", { callback: () => {
2857
-
2858
- // It's the root node
2859
- if( !node.parent )
2941
+ event.panel.add( "" );
2942
+ }
2943
+
2944
+ event.panel.add( "Select Children", () => {
2945
+
2946
+ const selectChildren = ( n ) => {
2947
+
2948
+ if( n.closed )
2860
2949
  {
2861
2950
  return;
2862
2951
  }
2863
-
2864
- if( that.onevent ) {
2865
- const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
2866
- that.onevent( event );
2952
+
2953
+ for( let child of n.children ?? [] )
2954
+ {
2955
+ if( !child )
2956
+ {
2957
+ continue;
2958
+ }
2959
+
2960
+ let nodeItem = this.domEl.querySelector( '#' + child.id );
2961
+ nodeItem.classList.add('selected');
2962
+ this.selected.push( child );
2963
+ selectChildren( child );
2867
2964
  }
2868
-
2869
- // Delete nodes now
2870
- let childs = node.parent.children;
2871
- const index = childs.indexOf( node );
2872
- childs.splice( index, 1 );
2873
-
2874
- this.refresh();
2875
- } } );
2876
- }
2965
+ };
2966
+
2967
+ // Add childs of the clicked node
2968
+ selectChildren( node );
2969
+ } );
2970
+
2971
+ event.panel.add( "Delete", { callback: () => {
2972
+
2973
+ // It's the root node
2974
+ if( !node.parent )
2975
+ {
2976
+ return;
2977
+ }
2978
+
2979
+ if( that.onevent ) {
2980
+ const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
2981
+ that.onevent( event );
2982
+ }
2983
+
2984
+ // Delete nodes now
2985
+ let childs = node.parent.children;
2986
+ const index = childs.indexOf( node );
2987
+ childs.splice( index, 1 );
2988
+
2989
+ this.refresh();
2990
+ } } );
2877
2991
  }
2878
2992
  });
2879
2993
 
2880
2994
  item.addEventListener("keydown", e => {
2881
2995
 
2882
2996
  if( node.rename )
2997
+ {
2883
2998
  return;
2999
+ }
2884
3000
 
2885
3001
  e.preventDefault();
2886
3002
 
@@ -2909,7 +3025,10 @@ class NodeTree {
2909
3025
  var selected = this.selected.length > 1 ? ( e.key == "ArrowUp" ? this.selected.shift() : this.selected.pop() ) : this.selected[ 0 ];
2910
3026
  var el = this.domEl.querySelector( "#" + LX.getSupportedDOMName( selected.id ) );
2911
3027
  var sibling = e.key == "ArrowUp" ? el.previousSibling : el.nextSibling;
2912
- if( sibling ) sibling.click();
3028
+ if( sibling )
3029
+ {
3030
+ sibling.click();
3031
+ }
2913
3032
  }
2914
3033
  });
2915
3034
 
@@ -3015,7 +3134,7 @@ class NodeTree {
3015
3134
  let handled = false;
3016
3135
 
3017
3136
  // Show/hide children
3018
- if(is_parent) {
3137
+ if(isParent) {
3019
3138
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3020
3139
 
3021
3140
  handled = true;
@@ -3153,19 +3272,25 @@ class Panel {
3153
3272
  getValue( name ) {
3154
3273
 
3155
3274
  let widget = this.widgets[ name ];
3156
- if(!widget)
3157
- throw("No widget called " + name);
3275
+
3276
+ if( !widget )
3277
+ {
3278
+ throw( "No widget called " + name );
3279
+ }
3158
3280
 
3159
3281
  return widget.value();
3160
3282
  }
3161
3283
 
3162
- setValue( name, value ) {
3284
+ setValue( name, value, skipCallback ) {
3163
3285
 
3164
3286
  let widget = this.widgets[ name ];
3165
- if(!widget)
3166
- throw("No widget called " + name);
3167
3287
 
3168
- return widget.set(value);
3288
+ if( !widget )
3289
+ {
3290
+ throw( "No widget called " + name );
3291
+ }
3292
+
3293
+ return widget.set( value, skipCallback );
3169
3294
  }
3170
3295
 
3171
3296
  /**
@@ -3345,8 +3470,8 @@ class Panel {
3345
3470
  }
3346
3471
 
3347
3472
  static _dispatch_event( element, type, data, bubbles, cancelable ) {
3348
- let event = new CustomEvent(type, { 'detail': data, 'bubbles': bubbles, 'cancelable': cancelable });
3349
- element.dispatchEvent(event);
3473
+ let event = new CustomEvent( type, { 'detail': data, 'bubbles': bubbles, 'cancelable': cancelable } );
3474
+ element.dispatchEvent( event );
3350
3475
  }
3351
3476
 
3352
3477
  static _add_reset_property( container, callback ) {
@@ -3354,8 +3479,8 @@ class Panel {
3354
3479
  domEl.style.display = "none";
3355
3480
  domEl.style.marginRight = "6px";
3356
3481
  domEl.className = "lexicon fa fa-rotate-left";
3357
- domEl.addEventListener("click", callback);
3358
- container.appendChild(domEl);
3482
+ domEl.addEventListener( "click", callback );
3483
+ container.appendChild( domEl );
3359
3484
  return domEl;
3360
3485
  }
3361
3486
 
@@ -3365,39 +3490,45 @@ class Panel {
3365
3490
 
3366
3491
  create_widget( name, type, options = {} ) {
3367
3492
 
3368
- let widget = new Widget(name, type, options);
3493
+ let widget = new Widget( name, type, options );
3369
3494
 
3370
- let element = document.createElement('div');
3495
+ let element = document.createElement( 'div' );
3371
3496
  element.className = "lexwidget";
3372
- if(options.id)
3373
- element.id = options.id;
3374
- if(options.className)
3497
+ element.id = options.id ?? "";
3498
+ element.title = options.title ?? "";
3499
+
3500
+ if( options.className )
3501
+ {
3375
3502
  element.className += " " + options.className;
3376
- if(options.title)
3377
- element.title = options.title;
3503
+ }
3378
3504
 
3379
3505
  if( type != Widget.TITLE )
3380
3506
  {
3381
3507
  element.style.width = "calc(100% - " + (this.current_branch || type == Widget.FILE ? 10 : 20) + "px)";
3382
- if( options.width ) {
3508
+
3509
+ if( options.width )
3510
+ {
3383
3511
  element.style.width = element.style.minWidth = options.width;
3384
3512
  }
3385
- if( options.maxWidth ) {
3513
+ if( options.maxWidth )
3514
+ {
3386
3515
  element.style.maxWidth = options.maxWidth;
3387
3516
  }
3388
- if( options.minWidth ) {
3517
+ if( options.minWidth )
3518
+ {
3389
3519
  element.style.minWidth = options.minWidth;
3390
3520
  }
3391
- if( options.height ) {
3521
+ if( options.height )
3522
+ {
3392
3523
  element.style.height = element.style.minHeight = options.height;
3393
3524
  }
3394
3525
  }
3395
3526
 
3396
- if(name != undefined) {
3397
-
3398
- if(!(options.no_name ?? false) )
3527
+ if( name != undefined )
3528
+ {
3529
+ if( !(options.no_name ?? false) )
3399
3530
  {
3400
- let domName = document.createElement('div');
3531
+ let domName = document.createElement( 'div' );
3401
3532
  domName.className = "lexwidgetname";
3402
3533
  if( options.justifyName )
3403
3534
  {
@@ -3410,22 +3541,27 @@ class Panel {
3410
3541
  element.domName = domName;
3411
3542
 
3412
3543
  // Copy-paste info
3413
- domName.addEventListener('contextmenu', function(e) {
3544
+ domName.addEventListener('contextmenu', function( e ) {
3414
3545
  e.preventDefault();
3415
- widget.oncontextmenu(e);
3546
+ widget.oncontextmenu( e );
3416
3547
  });
3417
3548
  }
3418
3549
 
3419
3550
  this.widgets[ name ] = widget;
3420
3551
  }
3421
3552
 
3422
- if(options.signal)
3553
+ if( options.signal )
3423
3554
  {
3424
- if(!name) {
3425
- if(!this.signals)
3555
+ if( !name )
3556
+ {
3557
+ if( !this.signals )
3558
+ {
3426
3559
  this.signals = [];
3427
- this.signals.push({[options.signal]: widget})
3560
+ }
3561
+
3562
+ this.signals.push( { [ options.signal ]: widget } )
3428
3563
  }
3564
+
3429
3565
  LX.addSignal( options.signal, widget );
3430
3566
  }
3431
3567
 
@@ -5084,6 +5220,9 @@ class Panel {
5084
5220
  * precision: The number of digits to appear after the decimal point
5085
5221
  * min, max: Min and Max values for the input
5086
5222
  * skipSlider: If there are min and max values, skip the slider
5223
+ * units: Unit as string added to the end of the value
5224
+ * onPress: Callback function on mouse down
5225
+ * onRelease: Callback function on mouse up
5087
5226
  */
5088
5227
 
5089
5228
  addNumber( name, value, callback, options = {} ) {
@@ -5093,8 +5232,9 @@ class Panel {
5093
5232
  widget.onGetValue = () => {
5094
5233
  return +vecinput.value;
5095
5234
  };
5235
+
5096
5236
  widget.onSetValue = ( newValue, skipCallback ) => {
5097
- vecinput.value = newValue;
5237
+ vecinput.value = round( newValue, options.precision );
5098
5238
  Panel._dispatch_event( vecinput, "change", skipCallback );
5099
5239
  };
5100
5240
 
@@ -5119,45 +5259,82 @@ class Panel {
5119
5259
  box.className = "numberbox";
5120
5260
 
5121
5261
  let vecinput = document.createElement( 'input' );
5262
+ vecinput.id = "number_" + simple_guidGenerator();
5122
5263
  vecinput.className = "vecinput";
5123
5264
  vecinput.min = options.min ?? -1e24;
5124
5265
  vecinput.max = options.max ?? 1e24;
5125
5266
  vecinput.step = options.step ?? "any";
5126
5267
  vecinput.type = "number";
5127
- vecinput.id = "number_" + simple_guidGenerator();
5128
5268
 
5129
5269
  if( value.constructor == Number )
5130
5270
  {
5131
5271
  value = clamp( value, +vecinput.min, +vecinput.max );
5132
- value = options.precision ? round( value, options.precision ) : value;
5272
+ value = round( value, options.precision );
5133
5273
  }
5134
5274
 
5135
5275
  vecinput.value = vecinput.iValue = value;
5136
5276
  box.appendChild( vecinput );
5137
5277
 
5138
- let drag_icon = document.createElement( 'a' );
5139
- drag_icon.className = "fa-solid fa-arrows-up-down drag-icon hidden";
5140
- box.appendChild( drag_icon );
5278
+ if( options.units )
5279
+ {
5280
+ let unitSpan = document.createElement( 'span' );
5281
+ unitSpan.className = "lexunit";
5282
+ unitSpan.innerText = options.units;
5283
+ unitSpan.style.left = measureRealWidth( vecinput.value ) + "px";
5284
+ vecinput.unitSpan = unitSpan;
5285
+ box.appendChild( unitSpan );
5286
+ }
5141
5287
 
5142
- if( options.disabled ) {
5288
+ let dragIcon = document.createElement( 'a' );
5289
+ dragIcon.className = "fa-solid fa-arrows-up-down drag-icon hidden";
5290
+ box.appendChild( dragIcon );
5291
+
5292
+ if( options.disabled )
5293
+ {
5143
5294
  vecinput.disabled = true;
5144
5295
  }
5145
5296
 
5146
- // add slider below
5147
- if( !options.skipSlider && options.min !== undefined && options.max !== undefined ) {
5297
+ // Add slider below
5298
+ if( !options.skipSlider && options.min !== undefined && options.max !== undefined )
5299
+ {
5148
5300
  let slider = document.createElement( 'input' );
5149
5301
  slider.className = "lexinputslider";
5150
- slider.step = options.step ?? 1;
5151
5302
  slider.min = options.min;
5152
5303
  slider.max = options.max;
5304
+ slider.step = options.step ?? 1;
5153
5305
  slider.type = "range";
5154
5306
  slider.value = value;
5307
+
5155
5308
  slider.addEventListener( "input", function( e ) {
5156
5309
  let new_value = +this.valueAsNumber;
5157
- vecinput.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
5310
+ vecinput.value = round( new_value, options.precision );
5158
5311
  Panel._dispatch_event( vecinput, "change" );
5159
5312
  }, false );
5313
+
5314
+ slider.addEventListener( "mousedown", function( e ) {
5315
+ if( options.onPress )
5316
+ {
5317
+ options.onPress.bind( slider )( e, slider );
5318
+ }
5319
+ }, false );
5320
+
5321
+ slider.addEventListener( "mouseup", function( e ) {
5322
+ if( options.onRelease )
5323
+ {
5324
+ options.onRelease.bind( slider )( e, slider );
5325
+ }
5326
+ }, false );
5327
+
5160
5328
  box.appendChild( slider );
5329
+
5330
+ // Method to change min, max, step parameters
5331
+ widget.setLimits = ( newMin, newMax, newStep ) => {
5332
+ vecinput.min = slider.min = newMin ?? vecinput.min;
5333
+ vecinput.max = slider.max = newMax ?? vecinput.max;
5334
+ vecinput.step = newStep ?? vecinput.step;
5335
+ slider.step = newStep ?? slider.step;
5336
+ Panel._dispatch_event( vecinput, "change", true );
5337
+ };
5161
5338
  }
5162
5339
 
5163
5340
  // Add wheel input
@@ -5165,30 +5342,42 @@ class Panel {
5165
5342
  vecinput.addEventListener( "wheel", function( e ) {
5166
5343
  e.preventDefault();
5167
5344
  if( this !== document.activeElement )
5345
+ {
5168
5346
  return;
5347
+ }
5169
5348
  let mult = options.step ?? 1;
5170
5349
  if( e.shiftKey ) mult *= 10;
5171
5350
  else if( e.altKey ) mult *= 0.1;
5172
5351
  let new_value = ( +this.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ) );
5173
- this.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
5352
+ this.value = round( new_value, options.precision );
5174
5353
  Panel._dispatch_event(vecinput, "change");
5175
5354
  }, { passive: false });
5176
5355
 
5177
5356
  vecinput.addEventListener( "change", e => {
5178
5357
 
5179
5358
  if( isNaN( e.target.valueAsNumber ) )
5359
+ {
5180
5360
  return;
5361
+ }
5181
5362
 
5182
5363
  const skipCallback = e.detail;
5183
5364
 
5184
5365
  let val = e.target.value = clamp( +e.target.valueAsNumber, +vecinput.min, +vecinput.max );
5185
5366
  val = options.precision ? round( val, options.precision ) : val;
5186
- // update slider!
5367
+
5368
+ // Update slider!
5187
5369
  if( box.querySelector( ".lexinputslider" ) )
5370
+ {
5188
5371
  box.querySelector( ".lexinputslider" ).value = val;
5372
+ }
5189
5373
 
5190
5374
  vecinput.value = val;
5191
5375
 
5376
+ if( options.units )
5377
+ {
5378
+ vecinput.unitSpan.style.left = measureRealWidth( vecinput.value ) + "px";
5379
+ }
5380
+
5192
5381
  // Reset button (default value)
5193
5382
  if( !skipCallback )
5194
5383
  {
@@ -5205,28 +5394,40 @@ class Panel {
5205
5394
 
5206
5395
  var that = this;
5207
5396
  var lastY = 0;
5208
- function inner_mousedown(e) {
5209
- if(document.activeElement == vecinput) return;
5397
+
5398
+ function inner_mousedown( e )
5399
+ {
5400
+ if( document.activeElement == vecinput )
5401
+ {
5402
+ return;
5403
+ }
5404
+
5210
5405
  var doc = that.root.ownerDocument;
5211
- doc.addEventListener("mousemove",inner_mousemove);
5212
- doc.addEventListener("mouseup",inner_mouseup);
5406
+ doc.addEventListener( 'mousemove', inner_mousemove );
5407
+ doc.addEventListener( 'mouseup', inner_mouseup );
5213
5408
  lastY = e.pageY;
5214
- document.body.classList.add('nocursor');
5215
- document.body.classList.add('noevents');
5216
- drag_icon.classList.remove('hidden');
5409
+ document.body.classList.add( 'nocursor' );
5410
+ document.body.classList.add( 'noevents' );
5411
+ dragIcon.classList.remove( 'hidden' );
5217
5412
  e.stopImmediatePropagation();
5218
5413
  e.stopPropagation();
5414
+
5415
+ if( options.onPress )
5416
+ {
5417
+ options.onPress.bind( vecinput )( e, vecinput );
5418
+ }
5219
5419
  }
5220
5420
 
5221
- function inner_mousemove(e) {
5222
- if (lastY != e.pageY) {
5421
+ function inner_mousemove( e )
5422
+ {
5423
+ if ( lastY != e.pageY ) {
5223
5424
  let dt = lastY - e.pageY;
5224
5425
  let mult = options.step ?? 1;
5225
- if(e.shiftKey) mult *= 10;
5226
- else if(e.altKey) mult *= 0.1;
5227
- let new_value = (+vecinput.valueAsNumber + mult * dt);
5228
- vecinput.value = (+new_value).toFixed(4).replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/,'$1');
5229
- Panel._dispatch_event(vecinput, "change");
5426
+ if( e.shiftKey ) mult *= 10;
5427
+ else if( e.altKey ) mult *= 0.1;
5428
+ let new_value = ( +vecinput.valueAsNumber + mult * dt );
5429
+ vecinput.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
5430
+ Panel._dispatch_event( vecinput, "change" );
5230
5431
  }
5231
5432
 
5232
5433
  lastY = e.pageY;
@@ -5234,20 +5435,27 @@ class Panel {
5234
5435
  e.preventDefault();
5235
5436
  }
5236
5437
 
5237
- function inner_mouseup(e) {
5438
+ function inner_mouseup( e )
5439
+ {
5238
5440
  var doc = that.root.ownerDocument;
5239
- doc.removeEventListener("mousemove",inner_mousemove);
5240
- doc.removeEventListener("mouseup",inner_mouseup);
5241
- document.body.classList.remove('nocursor');
5242
- document.body.classList.remove('noevents');
5243
- drag_icon.classList.add('hidden');
5441
+ doc.removeEventListener( 'mousemove', inner_mousemove );
5442
+ doc.removeEventListener( 'mouseup', inner_mouseup );
5443
+ document.body.classList.remove( 'nocursor' );
5444
+ document.body.classList.remove( 'noevents' );
5445
+ dragIcon.classList.add( 'hidden' );
5446
+
5447
+ if( options.onRelease )
5448
+ {
5449
+ options.onRelease.bind( vecinput )( e, vecinput );
5450
+ }
5244
5451
  }
5245
5452
 
5246
- container.appendChild(box);
5247
- element.appendChild(container);
5453
+ container.appendChild( box );
5454
+ element.appendChild( container );
5248
5455
 
5249
5456
  // Remove branch padding and margins
5250
- if(!widget.name) {
5457
+ if( !widget.name )
5458
+ {
5251
5459
  element.className += " noname";
5252
5460
  container.style.width = "100%";
5253
5461
  }
@@ -5255,14 +5463,15 @@ class Panel {
5255
5463
  return widget;
5256
5464
  }
5257
5465
 
5258
- static VECTOR_COMPONENTS = {0: 'x', 1: 'y', 2: 'z', 3: 'w'};
5466
+ static VECTOR_COMPONENTS = { 0: 'x', 1: 'y', 2: 'z', 3: 'w' };
5259
5467
 
5260
5468
  _add_vector( num_components, name, value, callback, options = {} ) {
5261
5469
 
5262
5470
  num_components = clamp( num_components, 2, 4 );
5263
5471
  value = value ?? new Array( num_components ).fill( 0 );
5264
5472
 
5265
- if( !name ) {
5473
+ if( !name )
5474
+ {
5266
5475
  throw( "Set Widget Name!" );
5267
5476
  }
5268
5477
 
@@ -5272,14 +5481,23 @@ class Panel {
5272
5481
  let inputs = element.querySelectorAll( "input" );
5273
5482
  let value = [];
5274
5483
  for( var v of inputs )
5484
+ {
5275
5485
  value.push( +v.value );
5486
+ }
5276
5487
  return value;
5277
5488
  };
5489
+
5278
5490
  widget.onSetValue = ( newValue, skipCallback ) => {
5279
5491
  const inputs = element.querySelectorAll( ".vecinput" );
5280
- for( var i = 0; i < inputs.length; ++i ) {
5492
+ if( inputs.length == newValue.length )
5493
+ {
5494
+ console.error( "Input length does not match vector length." );
5495
+ return;
5496
+ }
5497
+
5498
+ for( let i = 0; i < inputs.length; ++i ) {
5281
5499
  let value = newValue[ i ];
5282
- inputs[ i ].value = value ?? 0;
5500
+ inputs[ i ].value = round( value, options.precision ) ?? 0;
5283
5501
  Panel._dispatch_event( inputs[ i ], "change", skipCallback );
5284
5502
  }
5285
5503
  };
@@ -5297,7 +5515,7 @@ class Panel {
5297
5515
 
5298
5516
  // Add widget value
5299
5517
 
5300
- var container = document.createElement('div');
5518
+ var container = document.createElement( 'div' );
5301
5519
  container.className = "lexvector";
5302
5520
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5303
5521
 
@@ -5319,21 +5537,21 @@ class Panel {
5319
5537
  if( value[ i ].constructor == Number )
5320
5538
  {
5321
5539
  value[ i ] = clamp(value[ i ], +vecinput.min, +vecinput.max);
5322
- value[ i ] = options.precision ? round(value[ i ], options.precision) : value[ i ];
5540
+ value[ i ] = round( value[ i ], options.precision );
5323
5541
  }
5324
5542
 
5325
5543
  vecinput.value = vecinput.iValue = value[ i ];
5326
5544
 
5327
- let drag_icon = document.createElement( 'a' );
5328
- drag_icon.className = "fa-solid fa-arrows-up-down drag-icon hidden";
5329
- box.appendChild( drag_icon );
5545
+ let dragIcon = document.createElement( 'a' );
5546
+ dragIcon.className = "fa-solid fa-arrows-up-down drag-icon hidden";
5547
+ box.appendChild( dragIcon );
5330
5548
 
5331
- if( options.disabled ) {
5549
+ if( options.disabled )
5550
+ {
5332
5551
  vecinput.disabled = true;
5333
5552
  }
5334
5553
 
5335
5554
  // Add wheel input
5336
-
5337
5555
  vecinput.addEventListener( "wheel", function( e ) {
5338
5556
  e.preventDefault();
5339
5557
  if( this !== document.activeElement )
@@ -5342,14 +5560,17 @@ class Panel {
5342
5560
  if( e.shiftKey ) mult = 10;
5343
5561
  else if( e.altKey ) mult = 0.1;
5344
5562
 
5345
- if( lock_icon.locked )
5563
+ if( locker.locked )
5346
5564
  {
5347
- for( let v of element.querySelectorAll(".vecinput") ) {
5348
- v.value = ( +v.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ) ).toPrecision( 5 );
5349
- Panel._dispatch_event(v, "change");
5565
+ for( let v of element.querySelectorAll(".vecinput") )
5566
+ {
5567
+ v.value = round( +v.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ), options.precision );
5568
+ Panel._dispatch_event( v, "change" );
5350
5569
  }
5351
- } else {
5352
- this.value = ( +this.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ) ).toPrecision( 5 );
5570
+ }
5571
+ else
5572
+ {
5573
+ this.value = round( +this.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ), options.precision );
5353
5574
  Panel._dispatch_event( vecinput, "change" );
5354
5575
  }
5355
5576
  }, { passive: false } );
@@ -5362,7 +5583,7 @@ class Panel {
5362
5583
  const skipCallback = e.detail;
5363
5584
 
5364
5585
  let val = e.target.value = clamp( e.target.value, +vecinput.min, +vecinput.max );
5365
- val = options.precision ? round( val, options.precision ) : val;
5586
+ val = round( val, options.precision );
5366
5587
 
5367
5588
  // Reset button (default value)
5368
5589
  if( !skipCallback )
@@ -5371,13 +5592,14 @@ class Panel {
5371
5592
  if( btn ) btn.style.display = val != vecinput.iValue ? "block": "none";
5372
5593
  }
5373
5594
 
5374
- if( lock_icon.locked )
5595
+ if( locker.locked )
5375
5596
  {
5376
5597
  for( let v of element.querySelectorAll( ".vecinput" ) ) {
5377
5598
  v.value = val;
5378
5599
  value[ v.idx ] = val;
5379
5600
  }
5380
- } else {
5601
+ } else
5602
+ {
5381
5603
  vecinput.value = val;
5382
5604
  value[ e.target.idx ] = val;
5383
5605
  }
@@ -5391,35 +5613,48 @@ class Panel {
5391
5613
 
5392
5614
  var that = this;
5393
5615
  var lastY = 0;
5394
- function inner_mousedown(e) {
5395
- if(document.activeElement == vecinput) return;
5616
+ function inner_mousedown( e )
5617
+ {
5618
+ if( document.activeElement == vecinput )
5619
+ {
5620
+ return;
5621
+ }
5622
+
5396
5623
  var doc = that.root.ownerDocument;
5397
- doc.addEventListener("mousemove",inner_mousemove);
5398
- doc.addEventListener("mouseup",inner_mouseup);
5624
+ doc.addEventListener( 'mousemove', inner_mousemove );
5625
+ doc.addEventListener( 'mouseup', inner_mouseup );
5399
5626
  lastY = e.pageY;
5400
- document.body.classList.add('nocursor');
5401
- document.body.classList.add('noevents');
5402
- drag_icon.classList.remove('hidden');
5627
+ document.body.classList.add( 'nocursor' );
5628
+ document.body.classList.add( 'noevents' );
5629
+ dragIcon.classList.remove( 'hidden' );
5403
5630
  e.stopImmediatePropagation();
5404
5631
  e.stopPropagation();
5632
+
5633
+ if( options.onPress )
5634
+ {
5635
+ options.onPress.bind( vecinput )( e, vecinput );
5636
+ }
5405
5637
  }
5406
5638
 
5407
- function inner_mousemove(e) {
5408
- if (lastY != e.pageY) {
5639
+ function inner_mousemove( e )
5640
+ {
5641
+ if ( lastY != e.pageY ) {
5409
5642
  let dt = lastY - e.pageY;
5410
5643
  let mult = options.step ?? 1;
5411
- if(e.shiftKey) mult = 10;
5412
- else if(e.altKey) mult = 0.1;
5644
+ if( e.shiftKey ) mult = 10;
5645
+ else if( e.altKey ) mult = 0.1;
5413
5646
 
5414
- if( lock_icon.locked )
5647
+ if( locker.locked )
5415
5648
  {
5416
- for( let v of element.querySelectorAll(".vecinput") ) {
5417
- v.value = (+v.valueAsNumber + mult * dt).toPrecision(5);
5418
- Panel._dispatch_event(v, "change");
5649
+ for( let v of element.querySelectorAll( ".vecinput" ) ) {
5650
+ v.value = round( +v.valueAsNumber + mult * dt, options.precision );
5651
+ Panel._dispatch_event( v, "change" );
5419
5652
  }
5420
- } else {
5421
- vecinput.value = (+vecinput.valueAsNumber + mult * dt).toPrecision(5);
5422
- Panel._dispatch_event(vecinput, "change");
5653
+ }
5654
+ else
5655
+ {
5656
+ vecinput.value = round( +vecinput.valueAsNumber + mult * dt, options.precision );
5657
+ Panel._dispatch_event( vecinput, "change" );
5423
5658
  }
5424
5659
  }
5425
5660
 
@@ -5428,34 +5663,59 @@ class Panel {
5428
5663
  e.preventDefault();
5429
5664
  }
5430
5665
 
5431
- function inner_mouseup(e) {
5666
+ function inner_mouseup( e )
5667
+ {
5432
5668
  var doc = that.root.ownerDocument;
5433
- doc.removeEventListener("mousemove",inner_mousemove);
5434
- doc.removeEventListener("mouseup",inner_mouseup);
5435
- document.body.classList.remove('nocursor');
5436
- document.body.classList.remove('noevents');
5437
- drag_icon.classList.add('hidden');
5669
+ doc.removeEventListener( 'mousemove', inner_mousemove );
5670
+ doc.removeEventListener( 'mouseup', inner_mouseup );
5671
+ document.body.classList.remove( 'nocursor' );
5672
+ document.body.classList.remove( 'noevents' );
5673
+ dragIcon.classList.add('hidden');
5674
+
5675
+ if( options.onRelease )
5676
+ {
5677
+ options.onRelease.bind( vecinput )( e, vecinput );
5678
+ }
5438
5679
  }
5439
5680
 
5440
- box.appendChild(vecinput);
5441
- container.appendChild(box);
5681
+ box.appendChild( vecinput );
5682
+ container.appendChild( box );
5442
5683
  }
5443
5684
 
5444
- let lock_icon = document.createElement('a');
5445
- lock_icon.className = "fa-solid fa-lock-open lexicon";
5446
- container.appendChild(lock_icon);
5447
- lock_icon.addEventListener("click", function(e) {
5685
+ // Method to change min, max, step parameters
5686
+ if( options.min !== undefined || options.max !== undefined )
5687
+ {
5688
+ widget.setLimits = ( newMin, newMax, newStep ) => {
5689
+ const inputs = element.querySelectorAll(".vecinput");
5690
+ for( let v of inputs )
5691
+ {
5692
+ v.min = newMin ?? v.min;
5693
+ v.max = newMax ?? v.max;
5694
+ v.step = newStep ?? v.step;
5695
+ Panel._dispatch_event( v, "change", true );
5696
+ }
5697
+
5698
+ // To call onChange callback
5699
+ this._trigger( new IEvent( name, value ), callback );
5700
+ };
5701
+ }
5702
+
5703
+ let locker = document.createElement( 'a' );
5704
+ locker.className = "fa-solid fa-lock-open lexicon";
5705
+ container.appendChild( locker );
5706
+ locker.addEventListener( "click", function( e ) {
5448
5707
  this.locked = !this.locked;
5449
- if(this.locked){
5450
- this.classList.add("fa-lock");
5451
- this.classList.remove("fa-lock-open");
5708
+ if( this.locked )
5709
+ {
5710
+ this.classList.add( "fa-lock" );
5711
+ this.classList.remove( "fa-lock-open" );
5452
5712
  } else {
5453
- this.classList.add("fa-lock-open");
5454
- this.classList.remove("fa-lock");
5713
+ this.classList.add( "fa-lock-open" );
5714
+ this.classList.remove( "fa-lock" );
5455
5715
  }
5456
- }, false);
5716
+ }, false );
5457
5717
 
5458
- element.appendChild(container);
5718
+ element.appendChild( container );
5459
5719
 
5460
5720
  return widget;
5461
5721
  }
@@ -5469,21 +5729,231 @@ class Panel {
5469
5729
  * disabled: Make the widget disabled [false]
5470
5730
  * step: Step of the inputs
5471
5731
  * min, max: Min and Max values for the inputs
5732
+ * onPress: Callback function on mouse down
5733
+ * onRelease: Callback function on mouse is released
5472
5734
  */
5473
5735
 
5474
5736
  addVector2( name, value, callback, options ) {
5475
5737
 
5476
- return this._add_vector(2, name, value, callback, options);
5738
+ return this._add_vector( 2, name, value, callback, options );
5477
5739
  }
5478
5740
 
5479
5741
  addVector3( name, value, callback, options ) {
5480
5742
 
5481
- return this._add_vector(3, name, value, callback, options);
5743
+ return this._add_vector( 3, name, value, callback, options );
5482
5744
  }
5483
5745
 
5484
5746
  addVector4( name, value, callback, options ) {
5485
5747
 
5486
- return this._add_vector(4, name, value, callback, options);
5748
+ return this._add_vector( 4, name, value, callback, options );
5749
+ }
5750
+
5751
+ /**
5752
+ * @method addSize
5753
+ * @param {String} name Widget name
5754
+ * @param {Number} value Default number value
5755
+ * @param {Function} callback Callback function on change
5756
+ * @param {*} options:
5757
+ * disabled: Make the widget disabled [false]
5758
+ * units: Unit as string added to the end of the value
5759
+ */
5760
+
5761
+ addSize( name, value, callback, options = {} ) {
5762
+
5763
+ let widget = this.create_widget( name, Widget.SIZE, options );
5764
+
5765
+ widget.onGetValue = () => {
5766
+ const value = [];
5767
+ for( let i = 0; i < element.dimensions.length; ++i )
5768
+ {
5769
+ value.push( element.dimensions[ i ].onGetValue() );
5770
+ }
5771
+ return value;
5772
+ };
5773
+
5774
+ widget.onSetValue = ( newValue, skipCallback ) => {
5775
+ for( let i = 0; i < element.dimensions.length; ++i )
5776
+ {
5777
+ element.dimensions[ i ].onSetValue( newValue[ i ], skipCallback );
5778
+ }
5779
+ };
5780
+
5781
+ let element = widget.domEl;
5782
+
5783
+ this.queue( element );
5784
+
5785
+ element.dimensions = [];
5786
+
5787
+ for( let i = 0; i < value.length; ++i )
5788
+ {
5789
+ const size = measureRealWidth( JSON.stringify( value[ i ] ), 24 ) + 'px';
5790
+ element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
5791
+
5792
+ const value = [];
5793
+
5794
+ for( let i = 0; i < element.dimensions.length; ++i )
5795
+ {
5796
+ value.push( element.dimensions[ i ].onGetValue() );
5797
+ }
5798
+
5799
+ if( callback )
5800
+ {
5801
+ callback( value );
5802
+ }
5803
+
5804
+ }, { width: size, min: 0, disabled: options.disabled } );
5805
+
5806
+ if( ( i + 1 ) != value.length )
5807
+ {
5808
+ let cross = document.createElement( 'a' );
5809
+ cross.className = "lexsizecross fa-solid fa-xmark";
5810
+ element.appendChild( cross );
5811
+ }
5812
+ }
5813
+
5814
+ this.clearQueue();
5815
+
5816
+ if( options.units )
5817
+ {
5818
+ let unitSpan = document.createElement( 'span' );
5819
+ unitSpan.className = "lexunit";
5820
+ unitSpan.innerText = options.units;
5821
+ element.appendChild( unitSpan );
5822
+ }
5823
+
5824
+ // Remove branch padding and margins
5825
+ if( !widget.name )
5826
+ {
5827
+ element.className += " noname";
5828
+ container.style.width = "100%";
5829
+ }
5830
+
5831
+ return widget;
5832
+ }
5833
+
5834
+ /**
5835
+ * @method addPad
5836
+ * @param {String} name Widget name
5837
+ * @param {Number} value Pad value
5838
+ * @param {Function} callback Callback function on change
5839
+ * @param {*} options:
5840
+ * disabled: Make the widget disabled [false]
5841
+ * min, max: Min and Max values
5842
+ * onPress: Callback function on mouse down
5843
+ * onRelease: Callback function on mouse up
5844
+ */
5845
+
5846
+ addPad( name, value, callback, options = {} ) {
5847
+
5848
+ if( !name )
5849
+ {
5850
+ throw( "Set Widget Name!" );
5851
+ }
5852
+
5853
+ let widget = this.create_widget( name, Widget.PAD, options );
5854
+
5855
+ widget.onGetValue = () => {
5856
+ return thumb.value.xy;
5857
+ };
5858
+
5859
+ widget.onSetValue = ( newValue, skipCallback ) => {
5860
+ thumb.value.set( newValue[ 0 ], newValue[ 1 ] );
5861
+ _updateValue( thumb.value );
5862
+ if( !skipCallback )
5863
+ {
5864
+ this._trigger( new IEvent( name, thumb.value.xy ), callback );
5865
+ }
5866
+ };
5867
+
5868
+ let element = widget.domEl;
5869
+
5870
+ var container = document.createElement( 'div' );
5871
+ container.className = "lexpad";
5872
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5873
+
5874
+ let pad = document.createElement('div');
5875
+ pad.id = "lexpad-" + name;
5876
+ pad.className = "lexinnerpad";
5877
+ pad.style.width = options.padSize ?? '96px';
5878
+ pad.style.height = options.padSize ?? '96px';
5879
+
5880
+ let thumb = document.createElement('div');
5881
+ thumb.className = "lexpadthumb";
5882
+ thumb.value = new LX.vec2( value[ 0 ], value[ 1 ] );
5883
+ thumb.min = options.min ?? 0;
5884
+ thumb.max = options.max ?? 1;
5885
+
5886
+ let _updateValue = v => {
5887
+ const [ w, h ] = [ pad.offsetWidth, pad.offsetHeight ];
5888
+ const value0to1 = new LX.vec2( remapRange( v.x, thumb.min, thumb.max, 0.0, 1.0 ), remapRange( v.y, thumb.min, thumb.max, 0.0, 1.0 ) );
5889
+ thumb.style.transform = `translate(calc( ${ w * value0to1.x }px - 50% ), calc( ${ h * value0to1.y }px - 50%)`;
5890
+ }
5891
+
5892
+ doAsync( () => {
5893
+ _updateValue( thumb.value )
5894
+ } );
5895
+
5896
+ pad.appendChild( thumb );
5897
+ container.appendChild( pad );
5898
+ element.appendChild( container );
5899
+
5900
+ pad.addEventListener( "mousedown", innerMouseDown );
5901
+
5902
+ let that = this;
5903
+
5904
+ function innerMouseDown( e )
5905
+ {
5906
+ if( document.activeElement == thumb )
5907
+ {
5908
+ return;
5909
+ }
5910
+
5911
+ var doc = that.root.ownerDocument;
5912
+ doc.addEventListener( 'mousemove', innerMouseMove );
5913
+ doc.addEventListener( 'mouseup', innerMouseUp );
5914
+ document.body.classList.add( 'nocursor' );
5915
+ document.body.classList.add( 'noevents' );
5916
+ e.stopImmediatePropagation();
5917
+ e.stopPropagation();
5918
+
5919
+ if( options.onPress )
5920
+ {
5921
+ options.onPress.bind( thumb )( e, thumb );
5922
+ }
5923
+ }
5924
+
5925
+ function innerMouseMove( e )
5926
+ {
5927
+ const rect = pad.getBoundingClientRect();
5928
+ const relativePosition = new LX.vec2( e.x - rect.x, e.y - rect.y );
5929
+ relativePosition.clp( 0.0, pad.offsetWidth, relativePosition);
5930
+ const [ w, h ] = [ pad.offsetWidth, pad.offsetHeight ];
5931
+ const value0to1 = relativePosition.div( new LX.vec2( pad.offsetWidth, pad.offsetHeight ) );
5932
+
5933
+ thumb.style.transform = `translate(calc( ${ w * value0to1.x }px - 50% ), calc( ${ h * value0to1.y }px - 50%)`;
5934
+ thumb.value = new LX.vec2( remapRange( value0to1.x, 0.0, 1.0, thumb.min, thumb.max ), remapRange( value0to1.y, 0.0, 1.0, thumb.min, thumb.max ) );
5935
+
5936
+ that._trigger( new IEvent( name, thumb.value.xy, e ), callback );
5937
+
5938
+ e.stopPropagation();
5939
+ e.preventDefault();
5940
+ }
5941
+
5942
+ function innerMouseUp( e )
5943
+ {
5944
+ var doc = that.root.ownerDocument;
5945
+ doc.removeEventListener( 'mousemove', innerMouseMove );
5946
+ doc.removeEventListener( 'mouseup', innerMouseUp );
5947
+ document.body.classList.remove( 'nocursor' );
5948
+ document.body.classList.remove( 'noevents' );
5949
+
5950
+ if( options.onRelease )
5951
+ {
5952
+ options.onRelease.bind( thumb )( e, thumb );
5953
+ }
5954
+ }
5955
+
5956
+ return widget;
5487
5957
  }
5488
5958
 
5489
5959
  /**
@@ -5989,8 +6459,7 @@ class Branch {
5989
6459
 
5990
6460
  this.grabber = grabber;
5991
6461
 
5992
- function getBranchHeight(){
5993
-
6462
+ function getBranchHeight() {
5994
6463
  return that.root.offsetHeight - that.root.children[0].offsetHeight;
5995
6464
  }
5996
6465
 
@@ -6044,20 +6513,23 @@ class Branch {
6044
6513
  var size = this.grabber.style.marginLeft;
6045
6514
 
6046
6515
  // Update sizes of widgets inside
6047
- for(var i = 0; i < this.widgets.length;i++) {
6516
+ for(var i = 0; i < this.widgets.length; i++) {
6048
6517
 
6049
- let widget = this.widgets[i];
6518
+ let widget = this.widgets[ i ];
6050
6519
  let element = widget.domEl;
6051
6520
 
6052
- if(element.children.length < 2)
6521
+ if( element.children.length < 2 )
6522
+ {
6053
6523
  continue;
6524
+ }
6054
6525
 
6055
- var name = element.children[0];
6056
- var value = element.children[1];
6526
+ var name = element.children[ 0 ];
6527
+ var value = element.children[ 1 ];
6057
6528
 
6058
6529
  name.style.width = size;
6059
6530
  let padding = "0px";
6060
- switch(widget.type) {
6531
+ switch( widget.type )
6532
+ {
6061
6533
  case Widget.FILE:
6062
6534
  padding = "10%";
6063
6535
  break;
@@ -6070,7 +6542,10 @@ class Branch {
6070
6542
  value.style.width = "-webkit-calc( 100% - " + size + " - " + padding + " )";
6071
6543
  value.style.width = "calc( 100% - " + size + " - " + padding + " )";
6072
6544
 
6073
- if(widget.onresize) widget.onresize();
6545
+ if( widget.onresize )
6546
+ {
6547
+ widget.onresize();
6548
+ }
6074
6549
  }
6075
6550
  }
6076
6551
  };
@@ -6087,8 +6562,10 @@ class Dialog {
6087
6562
 
6088
6563
  constructor( title, callback, options = {} ) {
6089
6564
 
6090
- if(!callback)
6091
- console.warn("Content is empty, add some widgets using 'callback' parameter!");
6565
+ if( !callback )
6566
+ {
6567
+ console.warn("Content is empty, add some widgets using 'callback' parameter!");
6568
+ }
6092
6569
 
6093
6570
  this._oncreate = callback;
6094
6571
  this.id = simple_guidGenerator();
@@ -6098,8 +6575,10 @@ class Dialog {
6098
6575
  draggable = options.draggable ?? true,
6099
6576
  modal = options.modal ?? false;
6100
6577
 
6101
- if(modal)
6102
- LX.modal.toggle(false);
6578
+ if( modal )
6579
+ {
6580
+ LX.modal.toggle( false );
6581
+ }
6103
6582
 
6104
6583
  var root = document.createElement('div');
6105
6584
  root.className = "lexdialog " + (options.class ?? "");
@@ -6110,8 +6589,8 @@ class Dialog {
6110
6589
 
6111
6590
  var titleDiv = document.createElement('div');
6112
6591
 
6113
- if(title) {
6114
-
6592
+ if( title )
6593
+ {
6115
6594
  titleDiv.className = "lexdialogtitle";
6116
6595
  titleDiv.innerHTML = title;
6117
6596
  titleDiv.setAttribute('draggable', false);
@@ -6215,7 +6694,9 @@ class Dialog {
6215
6694
  this.title = titleDiv;
6216
6695
 
6217
6696
  if( draggable )
6218
- makeDraggable( root, { targetClass: 'lexdialogtitle' } );
6697
+ {
6698
+ makeDraggable( root, Object.assign( { targetClass: 'lexdialogtitle' }, options ) );
6699
+ }
6219
6700
 
6220
6701
  // Process position and size
6221
6702
  if(size.length && typeof(size[0]) != "string")
@@ -6380,7 +6861,8 @@ class ContextMenu {
6380
6861
  this.items = [];
6381
6862
  this.colors = {};
6382
6863
 
6383
- if(title) {
6864
+ if( title )
6865
+ {
6384
6866
  const item = {};
6385
6867
  item[ title ] = [];
6386
6868
  item[ 'className' ] = "cmtitle";
@@ -6389,28 +6871,42 @@ class ContextMenu {
6389
6871
  }
6390
6872
  }
6391
6873
 
6392
- _adjust_position(div, margin, useAbsolute = false) {
6874
+ _adjust_position( div, margin, useAbsolute = false ) {
6393
6875
 
6394
6876
  let rect = div.getBoundingClientRect();
6395
6877
 
6396
- if(!useAbsolute)
6878
+ if( !useAbsolute )
6397
6879
  {
6398
- let width = rect.width + 36; // this has paddings
6399
- if(window.innerWidth - rect.right < 0)
6880
+ let width = rect.width;
6881
+ if( rect.left < 0 )
6882
+ {
6883
+ div.style.left = margin + "px";
6884
+ }
6885
+ else if( window.innerWidth - rect.right < 0 )
6886
+ {
6400
6887
  div.style.left = (window.innerWidth - width - margin) + "px";
6401
-
6402
- if(rect.top + rect.height > window.innerHeight)
6888
+ }
6889
+
6890
+ if( rect.top < 0 )
6891
+ {
6892
+ div.style.top = margin + "px";
6893
+ }
6894
+ else if( (rect.top + rect.height) > window.innerHeight )
6895
+ {
6403
6896
  div.style.top = (window.innerHeight - rect.height - margin) + "px";
6897
+ }
6404
6898
  }
6405
6899
  else
6406
6900
  {
6407
6901
  let dt = window.innerWidth - rect.right;
6408
- if(dt < 0) {
6902
+ if( dt < 0 )
6903
+ {
6409
6904
  div.style.left = div.offsetLeft + (dt - margin) + "px";
6410
6905
  }
6411
6906
 
6412
6907
  dt = window.innerHeight - (rect.top + rect.height);
6413
- if(dt < 0) {
6908
+ if( dt < 0 )
6909
+ {
6414
6910
  div.style.top = div.offsetTop + (dt - margin + 20 ) + "px";
6415
6911
  }
6416
6912
  }
@@ -6436,7 +6932,6 @@ class ContextMenu {
6436
6932
  contextmenu.style.marginTop = 3.5 - c.offsetHeight + "px";
6437
6933
 
6438
6934
  // Set final width
6439
- // contextmenu.style.width = contextmenu.offsetWidth + "px";
6440
6935
  this._adjust_position( contextmenu, 6, true );
6441
6936
  }
6442
6937
 
@@ -6510,7 +7005,7 @@ class ContextMenu {
6510
7005
  }
6511
7006
 
6512
7007
  onCreate() {
6513
- this._adjust_position( this.root, 6 );
7008
+ doAsync( () => this._adjust_position( this.root, 6 ) );
6514
7009
  }
6515
7010
 
6516
7011
  add( path, options = {} ) {
@@ -6615,10 +7110,12 @@ LX.ContextMenu = ContextMenu;
6615
7110
  function addContextMenu( title, event, callback, options )
6616
7111
  {
6617
7112
  var menu = new ContextMenu( event, title, options );
6618
- LX.root.appendChild(menu.root);
7113
+ LX.root.appendChild( menu.root );
6619
7114
 
6620
- if(callback)
7115
+ if( callback )
7116
+ {
6621
7117
  callback( menu );
7118
+ }
6622
7119
 
6623
7120
  menu.onCreate();
6624
7121