lexgui 0.1.37 → 0.1.40

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.37",
11
+ version: "0.1.40",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -26,9 +26,11 @@ LX.CURVE_MOVEOUT_DELETE = 1;
26
26
 
27
27
  function clamp( num, min, max ) { return Math.min( Math.max( num, min ), max ); }
28
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; }
29
30
 
30
31
  LX.clamp = clamp;
31
32
  LX.round = round;
33
+ LX.remapRange = remapRange;
32
34
 
33
35
  function getSupportedDOMName( string )
34
36
  {
@@ -105,6 +107,18 @@ function rgbToHex( rgb ) {
105
107
 
106
108
  LX.rgbToHex = rgbToHex;
107
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
+
108
122
  function simple_guidGenerator() {
109
123
  var S4 = function() {
110
124
  return (((1+Math.random())*0x10000)|0).toString(16).substring(1);
@@ -166,36 +180,69 @@ class vec2 {
166
180
  len () { return Math.sqrt( this.len2() ); }
167
181
  nrm ( v0 = new vec2() ) { v0.set( this.x, this.y ); return v0.mul( 1.0 / this.len(), v0 ); }
168
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; }
169
184
  };
170
185
 
171
186
  LX.vec2 = vec2;
172
187
 
173
188
  // Other utils
174
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
+ */
175
199
  function makeDraggable( domEl, options = { } ) {
176
200
 
177
- let offsetX;
178
- let offsetY;
201
+ let offsetX = 0;
202
+ let offsetY = 0;
179
203
  let currentTarget = null;
180
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
+ }
181
223
 
182
224
  let id = LX.UTILS.uidGenerator();
183
225
  domEl[ 'draggable-id' ] = id;
184
226
 
185
227
  const defaultMoveFunc = e => {
186
- if( !currentTarget ) return;
187
- let left = e.clientX - offsetX;
188
- let top = e.clientY - offsetY;
189
- if( left > 3 && ( left + domEl.offsetWidth + 6 ) <= window.innerWidth )
190
- domEl.style.left = left + 'px';
191
- if( top > 3 && ( top + domEl.offsetHeight + 6 ) <= window.innerHeight )
192
- domEl.style.top = top + 'px';
228
+ if( !currentTarget )
229
+ {
230
+ return;
231
+ }
232
+
233
+ _computePosition( e );
193
234
  };
194
235
 
195
236
  const customMoveFunc = e => {
196
- if( !currentTarget ) return;
237
+ if( !currentTarget )
238
+ {
239
+ return;
240
+ }
241
+
197
242
  if( options.onMove )
243
+ {
198
244
  options.onMove( currentTarget );
245
+ }
199
246
  };
200
247
 
201
248
  let onMove = options.onMove ? customMoveFunc : defaultMoveFunc;
@@ -203,7 +250,7 @@ function makeDraggable( domEl, options = { } ) {
203
250
 
204
251
  domEl.setAttribute( 'draggable', true );
205
252
  domEl.addEventListener( "mousedown", function( e ) {
206
- currentTarget = (e.target.classList.contains(targetClass) || !targetClass) ? e.target : null;
253
+ currentTarget = ( e.target.classList.contains( targetClass ) || !targetClass ) ? e.target : null;
207
254
  } );
208
255
 
209
256
  domEl.addEventListener( "dragstart", function( e ) {
@@ -217,15 +264,21 @@ function makeDraggable( domEl, options = { } ) {
217
264
  e.dataTransfer.setDragImage( img, 0, 0 );
218
265
  e.dataTransfer.effectAllowed = "move";
219
266
  const rect = e.target.getBoundingClientRect();
220
- offsetX = e.clientX - rect.x;
221
- 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;
222
272
  document.addEventListener( "mousemove", onMove );
223
273
  if( onDragStart )
274
+ {
224
275
  onDragStart( currentTarget, e );
276
+ }
225
277
  }, false );
226
278
 
227
279
  document.addEventListener( 'mouseup', () => {
228
- if( currentTarget ) {
280
+ if( currentTarget )
281
+ {
229
282
  currentTarget = null;
230
283
  document.removeEventListener( "mousemove", onMove );
231
284
  }
@@ -666,7 +719,8 @@ class TreeEvent {
666
719
  }
667
720
 
668
721
  string() {
669
- switch(this.type) {
722
+ switch( this.type )
723
+ {
670
724
  case TreeEvent.NONE: return "tree_event_none";
671
725
  case TreeEvent.NODE_SELECTED: return "tree_event_selected";
672
726
  case TreeEvent.NODE_DELETED: return "tree_event_deleted";
@@ -820,7 +874,9 @@ class Area {
820
874
 
821
875
  const draggable = options.draggable ?? true;
822
876
  if( draggable )
823
- makeDraggable( root );
877
+ {
878
+ makeDraggable( root, options );
879
+ }
824
880
 
825
881
  if( options.resizeable ) {
826
882
  root.classList.add("resizeable");
@@ -1142,7 +1198,7 @@ class Area {
1142
1198
  // Send area resize to every widget in the area
1143
1199
  for( let widget of widgets )
1144
1200
  {
1145
- const jsInstance = widget.jsIinstance;
1201
+ const jsInstance = widget.jsInstance;
1146
1202
 
1147
1203
  if( jsInstance.onresize )
1148
1204
  {
@@ -1182,23 +1238,33 @@ class Area {
1182
1238
  * @method resize
1183
1239
  * Resize element
1184
1240
  */
1185
- setSize(size) {
1241
+ setSize( size ) {
1186
1242
 
1187
- let [width, height] = size;
1243
+ let [ width, height ] = size;
1188
1244
 
1189
- if(width != undefined && width.constructor == Number)
1245
+ if( width != undefined && width.constructor == Number )
1246
+ {
1190
1247
  width += "px";
1191
- if(height != undefined && height.constructor == Number)
1248
+ }
1249
+
1250
+ if( height != undefined && height.constructor == Number )
1251
+ {
1192
1252
  height += "px";
1253
+ }
1193
1254
 
1194
- if(width)
1255
+ if( width )
1256
+ {
1195
1257
  this.root.style.width = width;
1196
- if(height)
1258
+ }
1259
+
1260
+ if( height )
1261
+ {
1197
1262
  this.root.style.height = height;
1263
+ }
1198
1264
 
1199
- this.size = [this.root.clientWidth, this.root.clientHeight];
1265
+ this.size = [ this.root.clientWidth, this.root.clientHeight ];
1200
1266
 
1201
- this.propagateEvent("onresize");
1267
+ this.propagateEvent( "onresize" );
1202
1268
  }
1203
1269
 
1204
1270
  /**
@@ -1208,7 +1274,9 @@ class Area {
1208
1274
  extend() {
1209
1275
 
1210
1276
  if( this.split_extended )
1211
- return;
1277
+ {
1278
+ return;
1279
+ }
1212
1280
 
1213
1281
  let [area1, area2] = this.sections;
1214
1282
  this.split_extended = true;
@@ -1877,11 +1945,15 @@ class Tabs {
1877
1945
 
1878
1946
  const tabEl = this.tabDOMs[ name ];
1879
1947
 
1880
- if(!tabEl || tabEl.fixed)
1881
- return;
1948
+ if( !tabEl || tabEl.fixed )
1949
+ {
1950
+ return;
1951
+ }
1882
1952
 
1883
1953
  if( this.onclose )
1954
+ {
1884
1955
  this.onclose( name );
1956
+ }
1885
1957
 
1886
1958
  // Delete tab element
1887
1959
  this.tabDOMs[ name ].remove();
@@ -1892,11 +1964,12 @@ class Tabs {
1892
1964
  delete this.tabs[ name ];
1893
1965
 
1894
1966
  // Select last tab
1895
- const last_tab = this.root.lastChild;
1896
- if(last_tab && !last_tab.fixed)
1967
+ const lastTab = this.root.lastChild;
1968
+ if( lastTab && !lastTab.fixed )
1969
+ {
1897
1970
  this.root.lastChild.click();
1971
+ }
1898
1972
  }
1899
-
1900
1973
  }
1901
1974
 
1902
1975
  LX.Tabs = Tabs;
@@ -2463,6 +2536,9 @@ class Widget {
2463
2536
  static CUSTOM = 21;
2464
2537
  static SEPARATOR = 22;
2465
2538
  static KNOB = 23;
2539
+ static SIZE = 24;
2540
+ static PAD = 25;
2541
+ static FORM = 26;
2466
2542
 
2467
2543
  static NO_CONTEXT_TYPES = [
2468
2544
  Widget.BUTTON,
@@ -2520,14 +2596,16 @@ class Widget {
2520
2596
 
2521
2597
  paste() {
2522
2598
  if( !this._can_paste() )
2523
- return;
2599
+ {
2600
+ return;
2601
+ }
2524
2602
 
2525
- this.set(navigator.clipboard.data);
2603
+ this.set( navigator.clipboard.data );
2526
2604
  }
2527
2605
 
2528
2606
  typeName() {
2529
2607
 
2530
- switch(this.type) {
2608
+ switch( this.type ) {
2531
2609
  case Widget.TEXT: return "Text";
2532
2610
  case Widget.TEXTAREA: return "TextArea";
2533
2611
  case Widget.BUTTON: return "Button";
@@ -2545,6 +2623,9 @@ class Widget {
2545
2623
  case Widget.TAGS: return "Tags";
2546
2624
  case Widget.CURVE: return "Curve";
2547
2625
  case Widget.KNOB: return "Knob";
2626
+ case Widget.SIZE: return "Size";
2627
+ case Widget.PAD: return "Pad";
2628
+ case Widget.FORM: return "Form";
2548
2629
  case Widget.CUSTOM: return this.customName;
2549
2630
  }
2550
2631
  }
@@ -2639,40 +2720,50 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2639
2720
  custom_widgets.className = "lexcustomitems";
2640
2721
  custom_widgets.toggleAttribute('hidden', true);
2641
2722
 
2642
- element.appendChild(container);
2643
- element.appendChild(custom_widgets);
2723
+ element.appendChild( container );
2724
+ element.appendChild( custom_widgets );
2725
+
2726
+ if( instance )
2727
+ {
2644
2728
 
2645
- if( instance ) {
2646
-
2647
2729
  this.queue( custom_widgets );
2648
2730
 
2649
- const on_instance_changed = (key, value, event) => {
2650
- instance[key] = value;
2651
- this._trigger( new IEvent(name, instance, event), callback );
2731
+ const on_instance_changed = ( key, value, event ) => {
2732
+ instance[ key ] = value;
2733
+ this._trigger( new IEvent( name, instance, event ), callback );
2652
2734
  };
2653
2735
 
2654
2736
  for( let key in default_instance )
2655
2737
  {
2656
- const value = instance[key] ?? default_instance[key];
2738
+ const value = instance[ key ] ?? default_instance[ key ];
2657
2739
 
2658
- switch(value.constructor) {
2740
+ switch( value.constructor )
2741
+ {
2659
2742
  case String:
2660
- if(value[0] === '#')
2661
- this.addColor(key, value, on_instance_changed.bind(this, key));
2743
+ if( value[ 0 ] === '#' )
2744
+ {
2745
+ this.addColor( key, value, on_instance_changed.bind( this, key ) );
2746
+ }
2662
2747
  else
2663
- this.addText(key, value, on_instance_changed.bind(this, key));
2748
+ {
2749
+ this.addText( key, value, on_instance_changed.bind( this, key ) );
2750
+ }
2664
2751
  break;
2665
2752
  case Number:
2666
- this.addNumber(key, value, on_instance_changed.bind(this, key));
2753
+ this.addNumber( key, value, on_instance_changed.bind( this, key ) );
2667
2754
  break;
2668
2755
  case Boolean:
2669
- this.addCheckbox(key, value, on_instance_changed.bind(this, key));
2756
+ this.addCheckbox( key, value, on_instance_changed.bind( this, key ) );
2670
2757
  break;
2671
2758
  case Array:
2672
2759
  if( value.length > 4 )
2673
- this.addArray(key, value, on_instance_changed.bind(this, key));
2760
+ {
2761
+ this.addArray( key, value, on_instance_changed.bind( this, key ) );
2762
+ }
2674
2763
  else
2675
- this._add_vector(value.length, key, value, on_instance_changed.bind(this, key));
2764
+ {
2765
+ this._add_vector( value.length, key, value, on_instance_changed.bind( this, key ) );
2766
+ }
2676
2767
  break;
2677
2768
  }
2678
2769
  }
@@ -2711,37 +2802,40 @@ class NodeTree {
2711
2802
  _create_item( parent, node, level = 0, selectedId ) {
2712
2803
 
2713
2804
  const that = this;
2714
- const node_filter_input = this.domEl.querySelector("#lexnodetree_filter");
2805
+ const node_filter_input = this.domEl.querySelector( "#lexnodetree_filter" );
2715
2806
 
2716
2807
  node.children = node.children ?? [];
2717
- if(node_filter_input && !node.id.includes(node_filter_input.value) || (selectedId != undefined) && selectedId != node.id)
2808
+ if( node_filter_input && !node.id.includes( node_filter_input.value ) || (selectedId != undefined) && selectedId != node.id )
2718
2809
  {
2719
2810
  for( var i = 0; i < node.children.length; ++i )
2720
- this._create_item( node, node.children[i], level + 1, selectedId );
2811
+ {
2812
+ this._create_item( node, node.children[ i ], level + 1, selectedId );
2813
+ }
2721
2814
  return;
2722
2815
  }
2723
2816
 
2724
- const list = this.domEl.querySelector("ul");
2817
+ const list = this.domEl.querySelector( 'ul' );
2725
2818
 
2726
2819
  node.visible = node.visible ?? true;
2727
2820
  node.parent = parent;
2728
- let is_parent = node.children.length > 0;
2729
- let is_selected = this.selected.indexOf( node ) > -1 || node.selected;
2821
+ let isParent = node.children.length > 0;
2822
+ let isSelected = this.selected.indexOf( node ) > -1 || node.selected;
2730
2823
 
2731
- if( this.options.only_folders ) {
2824
+ if( this.options.only_folders )
2825
+ {
2732
2826
  let has_folders = false;
2733
2827
  node.children.forEach( c => has_folders |= (c.type == 'folder') );
2734
- is_parent = !!has_folders;
2828
+ isParent = !!has_folders;
2735
2829
  }
2736
2830
 
2737
2831
  let item = document.createElement('li');
2738
- item.className = "lextreeitem " + "datalevel" + level + (is_parent ? " parent" : "") + (is_selected ? " selected" : "");
2832
+ item.className = "lextreeitem " + "datalevel" + level + (isParent ? " parent" : "") + (isSelected ? " selected" : "");
2739
2833
  item.id = LX.getSupportedDOMName( node.id );
2740
2834
  item.tabIndex = "0";
2741
2835
 
2742
2836
  // Select hierarchy icon
2743
2837
  let icon = (this.options.skip_default_icon ?? true) ? "" : "fa-solid fa-square"; // Default: no childs
2744
- if( is_parent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2838
+ if( isParent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2745
2839
  item.innerHTML = "<a class='" + icon + " hierarchy'></a>";
2746
2840
 
2747
2841
  // Add display icon
@@ -2761,18 +2855,20 @@ class NodeTree {
2761
2855
 
2762
2856
  item.innerHTML += (node.rename ? "" : node.id);
2763
2857
 
2764
- item.setAttribute('draggable', true);
2765
- item.style.paddingLeft = ((is_parent ? 0 : 3 ) + (3 + (level+1) * 15)) + "px";
2766
- list.appendChild(item);
2858
+ item.setAttribute( 'draggable', true );
2859
+ item.style.paddingLeft = ((isParent ? 0 : 3 ) + (3 + (level+1) * 15)) + "px";
2860
+ list.appendChild( item );
2767
2861
 
2768
2862
  // Callbacks
2769
2863
  item.addEventListener("click", e => {
2770
- if( handled ) {
2864
+ if( handled )
2865
+ {
2771
2866
  handled = false;
2772
2867
  return;
2773
2868
  }
2774
2869
 
2775
- if( !e.shiftKey ) {
2870
+ if( !e.shiftKey )
2871
+ {
2776
2872
  list.querySelectorAll( "li" ).forEach( e => { e.classList.remove( 'selected' ); } );
2777
2873
  this.selected.length = 0;
2778
2874
  }
@@ -2788,16 +2884,18 @@ class NodeTree {
2788
2884
  }
2789
2885
 
2790
2886
  // Only Show children...
2791
- if( is_parent && node.id.length > 1 /* Strange case... */) {
2887
+ if( isParent && node.id.length > 1 /* Strange case... */) {
2792
2888
  node.closed = false;
2793
- if( that.onevent ) {
2794
- const event = new TreeEvent(TreeEvent.NODE_CARETCHANGED, node, node.closed);
2889
+ if( that.onevent )
2890
+ {
2891
+ const event = new TreeEvent( TreeEvent.NODE_CARETCHANGED, node, node.closed );
2795
2892
  that.onevent( event );
2796
2893
  }
2797
2894
  that.frefresh( node.id );
2798
2895
  }
2799
2896
 
2800
- if( that.onevent ) {
2897
+ if( that.onevent )
2898
+ {
2801
2899
  const event = new TreeEvent(TreeEvent.NODE_SELECTED, e.shiftKey ? this.selected : node );
2802
2900
  event.multiple = e.shiftKey;
2803
2901
  that.onevent( event );
@@ -2820,85 +2918,87 @@ class NodeTree {
2820
2918
  }
2821
2919
  });
2822
2920
 
2823
- item.addEventListener("contextmenu", e => {
2921
+ item.addEventListener( "contextmenu", e => {
2922
+
2824
2923
  e.preventDefault();
2825
- if(that.onevent) {
2826
- const event = new TreeEvent(TreeEvent.NODE_CONTEXTMENU, this.selected.length > 1 ? this.selected : node, e);
2827
- event.multiple = this.selected.length > 1;
2828
2924
 
2829
- LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
2830
- event.panel = m;
2831
- });
2925
+ if( that.onevent )
2926
+ {
2927
+ return;
2928
+ }
2832
2929
 
2833
- that.onevent( event );
2930
+ const event = new TreeEvent(TreeEvent.NODE_CONTEXTMENU, this.selected.length > 1 ? this.selected : node, e);
2931
+ event.multiple = this.selected.length > 1;
2834
2932
 
2835
- if( ( this.options.addDefault ?? false ) == true )
2933
+ LX.addContextMenu( event.multiple ? "Selected Nodes" : event.node.id, event.value, m => {
2934
+ event.panel = m;
2935
+ });
2936
+
2937
+ that.onevent( event );
2938
+
2939
+ if( ( this.options.addDefault ?? false ) == true )
2940
+ {
2941
+ if( event.panel.items )
2836
2942
  {
2837
- if( event.panel.items )
2838
- {
2839
- event.panel.add( "" );
2840
- }
2841
-
2842
- event.panel.add( "Select Children", () => {
2843
-
2844
- const selectChildren = ( n ) => {
2845
-
2846
- if( n.closed )
2847
- {
2848
- return;
2849
- }
2850
-
2851
- for( let child of n.children ?? [] )
2852
- {
2853
- if( !child )
2854
- {
2855
- continue;
2856
- }
2857
-
2858
- let nodeItem = this.domEl.querySelector( '#' + child.id );
2859
- nodeItem.classList.add('selected');
2860
- this.selected.push( child );
2861
- selectChildren( child );
2862
- }
2863
- };
2864
-
2865
- // Add childs of the clicked node
2866
- selectChildren( node );
2867
-
2868
- } );
2869
-
2870
- // event.panel.add( "Clone", { callback: () => {
2871
-
2872
- // } } );
2873
-
2874
- event.panel.add( "Delete", { callback: () => {
2875
-
2876
- // It's the root node
2877
- if( !node.parent )
2943
+ event.panel.add( "" );
2944
+ }
2945
+
2946
+ event.panel.add( "Select Children", () => {
2947
+
2948
+ const selectChildren = ( n ) => {
2949
+
2950
+ if( n.closed )
2878
2951
  {
2879
2952
  return;
2880
2953
  }
2881
-
2882
- if( that.onevent ) {
2883
- const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
2884
- that.onevent( event );
2954
+
2955
+ for( let child of n.children ?? [] )
2956
+ {
2957
+ if( !child )
2958
+ {
2959
+ continue;
2960
+ }
2961
+
2962
+ let nodeItem = this.domEl.querySelector( '#' + child.id );
2963
+ nodeItem.classList.add('selected');
2964
+ this.selected.push( child );
2965
+ selectChildren( child );
2885
2966
  }
2886
-
2887
- // Delete nodes now
2888
- let childs = node.parent.children;
2889
- const index = childs.indexOf( node );
2890
- childs.splice( index, 1 );
2891
-
2892
- this.refresh();
2893
- } } );
2894
- }
2967
+ };
2968
+
2969
+ // Add childs of the clicked node
2970
+ selectChildren( node );
2971
+ } );
2972
+
2973
+ event.panel.add( "Delete", { callback: () => {
2974
+
2975
+ // It's the root node
2976
+ if( !node.parent )
2977
+ {
2978
+ return;
2979
+ }
2980
+
2981
+ if( that.onevent ) {
2982
+ const event = new TreeEvent( TreeEvent.NODE_DELETED, node, e );
2983
+ that.onevent( event );
2984
+ }
2985
+
2986
+ // Delete nodes now
2987
+ let childs = node.parent.children;
2988
+ const index = childs.indexOf( node );
2989
+ childs.splice( index, 1 );
2990
+
2991
+ this.refresh();
2992
+ } } );
2895
2993
  }
2896
2994
  });
2897
2995
 
2898
2996
  item.addEventListener("keydown", e => {
2899
2997
 
2900
2998
  if( node.rename )
2999
+ {
2901
3000
  return;
3001
+ }
2902
3002
 
2903
3003
  e.preventDefault();
2904
3004
 
@@ -2927,7 +3027,10 @@ class NodeTree {
2927
3027
  var selected = this.selected.length > 1 ? ( e.key == "ArrowUp" ? this.selected.shift() : this.selected.pop() ) : this.selected[ 0 ];
2928
3028
  var el = this.domEl.querySelector( "#" + LX.getSupportedDOMName( selected.id ) );
2929
3029
  var sibling = e.key == "ArrowUp" ? el.previousSibling : el.nextSibling;
2930
- if( sibling ) sibling.click();
3030
+ if( sibling )
3031
+ {
3032
+ sibling.click();
3033
+ }
2931
3034
  }
2932
3035
  });
2933
3036
 
@@ -3033,7 +3136,7 @@ class NodeTree {
3033
3136
  let handled = false;
3034
3137
 
3035
3138
  // Show/hide children
3036
- if(is_parent) {
3139
+ if(isParent) {
3037
3140
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3038
3141
 
3039
3142
  handled = true;
@@ -3171,19 +3274,25 @@ class Panel {
3171
3274
  getValue( name ) {
3172
3275
 
3173
3276
  let widget = this.widgets[ name ];
3174
- if(!widget)
3175
- throw("No widget called " + name);
3277
+
3278
+ if( !widget )
3279
+ {
3280
+ throw( "No widget called " + name );
3281
+ }
3176
3282
 
3177
3283
  return widget.value();
3178
3284
  }
3179
3285
 
3180
- setValue( name, value ) {
3286
+ setValue( name, value, skipCallback ) {
3181
3287
 
3182
3288
  let widget = this.widgets[ name ];
3183
- if(!widget)
3184
- throw("No widget called " + name);
3185
3289
 
3186
- return widget.set(value);
3290
+ if( !widget )
3291
+ {
3292
+ throw( "No widget called " + name );
3293
+ }
3294
+
3295
+ return widget.set( value, skipCallback );
3187
3296
  }
3188
3297
 
3189
3298
  /**
@@ -3363,8 +3472,8 @@ class Panel {
3363
3472
  }
3364
3473
 
3365
3474
  static _dispatch_event( element, type, data, bubbles, cancelable ) {
3366
- let event = new CustomEvent(type, { 'detail': data, 'bubbles': bubbles, 'cancelable': cancelable });
3367
- element.dispatchEvent(event);
3475
+ let event = new CustomEvent( type, { 'detail': data, 'bubbles': bubbles, 'cancelable': cancelable } );
3476
+ element.dispatchEvent( event );
3368
3477
  }
3369
3478
 
3370
3479
  static _add_reset_property( container, callback ) {
@@ -3372,8 +3481,8 @@ class Panel {
3372
3481
  domEl.style.display = "none";
3373
3482
  domEl.style.marginRight = "6px";
3374
3483
  domEl.className = "lexicon fa fa-rotate-left";
3375
- domEl.addEventListener("click", callback);
3376
- container.appendChild(domEl);
3484
+ domEl.addEventListener( "click", callback );
3485
+ container.appendChild( domEl );
3377
3486
  return domEl;
3378
3487
  }
3379
3488
 
@@ -3383,39 +3492,45 @@ class Panel {
3383
3492
 
3384
3493
  create_widget( name, type, options = {} ) {
3385
3494
 
3386
- let widget = new Widget(name, type, options);
3495
+ let widget = new Widget( name, type, options );
3387
3496
 
3388
- let element = document.createElement('div');
3497
+ let element = document.createElement( 'div' );
3389
3498
  element.className = "lexwidget";
3390
- if(options.id)
3391
- element.id = options.id;
3392
- if(options.className)
3499
+ element.id = options.id ?? "";
3500
+ element.title = options.title ?? "";
3501
+
3502
+ if( options.className )
3503
+ {
3393
3504
  element.className += " " + options.className;
3394
- if(options.title)
3395
- element.title = options.title;
3505
+ }
3396
3506
 
3397
3507
  if( type != Widget.TITLE )
3398
3508
  {
3399
3509
  element.style.width = "calc(100% - " + (this.current_branch || type == Widget.FILE ? 10 : 20) + "px)";
3400
- if( options.width ) {
3510
+
3511
+ if( options.width )
3512
+ {
3401
3513
  element.style.width = element.style.minWidth = options.width;
3402
3514
  }
3403
- if( options.maxWidth ) {
3515
+ if( options.maxWidth )
3516
+ {
3404
3517
  element.style.maxWidth = options.maxWidth;
3405
3518
  }
3406
- if( options.minWidth ) {
3519
+ if( options.minWidth )
3520
+ {
3407
3521
  element.style.minWidth = options.minWidth;
3408
3522
  }
3409
- if( options.height ) {
3523
+ if( options.height )
3524
+ {
3410
3525
  element.style.height = element.style.minHeight = options.height;
3411
3526
  }
3412
3527
  }
3413
3528
 
3414
- if(name != undefined) {
3415
-
3416
- if(!(options.no_name ?? false) )
3529
+ if( name != undefined )
3530
+ {
3531
+ if( !(options.hideName ?? false) )
3417
3532
  {
3418
- let domName = document.createElement('div');
3533
+ let domName = document.createElement( 'div' );
3419
3534
  domName.className = "lexwidgetname";
3420
3535
  if( options.justifyName )
3421
3536
  {
@@ -3428,27 +3543,32 @@ class Panel {
3428
3543
  element.domName = domName;
3429
3544
 
3430
3545
  // Copy-paste info
3431
- domName.addEventListener('contextmenu', function(e) {
3546
+ domName.addEventListener('contextmenu', function( e ) {
3432
3547
  e.preventDefault();
3433
- widget.oncontextmenu(e);
3548
+ widget.oncontextmenu( e );
3434
3549
  });
3435
3550
  }
3436
3551
 
3437
3552
  this.widgets[ name ] = widget;
3438
3553
  }
3439
3554
 
3440
- if(options.signal)
3555
+ if( options.signal )
3441
3556
  {
3442
- if(!name) {
3443
- if(!this.signals)
3557
+ if( !name )
3558
+ {
3559
+ if( !this.signals )
3560
+ {
3444
3561
  this.signals = [];
3445
- this.signals.push({[options.signal]: widget})
3562
+ }
3563
+
3564
+ this.signals.push( { [ options.signal ]: widget } )
3446
3565
  }
3566
+
3447
3567
  LX.addSignal( options.signal, widget );
3448
3568
  }
3449
3569
 
3450
3570
  widget.domEl = element;
3451
- element.jsIinstance = widget;
3571
+ element.jsInstance = widget;
3452
3572
 
3453
3573
  const insert_widget = el => {
3454
3574
  if(options.container)
@@ -3518,7 +3638,7 @@ class Panel {
3518
3638
  element.className += " lexfilter noname";
3519
3639
 
3520
3640
  let input = document.createElement('input');
3521
- input.id = 'input-filter';
3641
+ input.className = 'lexinput-filter';
3522
3642
  input.setAttribute("placeholder", options.placeholder);
3523
3643
  input.style.width = "calc( 100% - 17px )";
3524
3644
  input.value = options.filterValue || "";
@@ -4106,7 +4226,7 @@ class Panel {
4106
4226
 
4107
4227
  addCard( name, options = {} ) {
4108
4228
 
4109
- options.no_name = true;
4229
+ options.hideName = true;
4110
4230
  let widget = this.create_widget(name, Widget.CARD, options);
4111
4231
  let element = widget.domEl;
4112
4232
 
@@ -4157,6 +4277,89 @@ class Panel {
4157
4277
  return widget;
4158
4278
  }
4159
4279
 
4280
+ /**
4281
+ * @method addForm
4282
+ * @param {String} name Widget name
4283
+ * @param {Object} data Form data
4284
+ * @param {Function} callback Callback function on submit form
4285
+ * @param {*} options:
4286
+ * actionName: Text to be shown in the button
4287
+ */
4288
+
4289
+ addForm( name, data, callback, options = {} ) {
4290
+
4291
+ if( data.constructor != Object )
4292
+ {
4293
+ console.error( "Form data must be an Object" );
4294
+ return;
4295
+ }
4296
+
4297
+ // Always hide name for this one
4298
+ options.hideName = true;
4299
+
4300
+ let widget = this.create_widget( name, Widget.FORM, options );
4301
+
4302
+ widget.onGetValue = () => {
4303
+ return container.formData;
4304
+ };
4305
+
4306
+ widget.onSetValue = ( newValue, skipCallback ) => {
4307
+ container.formData = newValue;
4308
+ const entries = container.querySelectorAll( ".lexwidget" );
4309
+ for( let i = 0; i < entries.length; ++i )
4310
+ {
4311
+ const entry = entries[ i ];
4312
+ if( entry.jsInstance.type != LX.Widget.TEXT )
4313
+ {
4314
+ continue;
4315
+ }
4316
+ let entryName = entries[ i ].querySelector( ".lexwidgetname" ).innerText;
4317
+ let entryInput = entries[ i ].querySelector( ".lextext input" );
4318
+ entryInput.value = newValue[ entryName ] ?? "";
4319
+ Panel._dispatch_event( entryInput, "focusout", skipCallback );
4320
+ }
4321
+ };
4322
+
4323
+ // Add widget value
4324
+
4325
+ let element = widget.domEl;
4326
+
4327
+ let container = document.createElement( 'div' );
4328
+ container.className = "lexformdata";
4329
+
4330
+ this.queue( container );
4331
+
4332
+ container.formData = {};
4333
+
4334
+ for( let entry in data )
4335
+ {
4336
+ const entryData = data[ entry ];
4337
+ this.addText( entry, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4338
+ container.formData[ entry ] = value;
4339
+ }, entryData );
4340
+
4341
+ container.formData[ entry ] = entryData.constructor == Object ? entryData.value : entryData;
4342
+ }
4343
+
4344
+ this.addButton( null, options.actionName ?? "Submit", ( value, event ) => {
4345
+ if( callback )
4346
+ {
4347
+ callback( container.formData, event );
4348
+ }
4349
+ } );
4350
+
4351
+ this.clearQueue();
4352
+
4353
+ element.appendChild( container );
4354
+
4355
+ if( !widget.name || options.hideName ) {
4356
+ element.className += " noname";
4357
+ container.style.width = "100%";
4358
+ }
4359
+
4360
+ return widget;
4361
+ }
4362
+
4160
4363
  /**
4161
4364
  * @method addContent
4162
4365
  * @param {HTMLElement} element
@@ -4181,27 +4384,29 @@ class Panel {
4181
4384
  async addImage( url, options = {} ) {
4182
4385
 
4183
4386
  if( !url )
4184
- return;
4387
+ {
4388
+ return;
4389
+ }
4185
4390
 
4186
- options.no_name = true;
4187
- let widget = this.create_widget(null, Widget.IMAGE, options);
4391
+ options.hideName = true;
4392
+ let widget = this.create_widget( null, Widget.IMAGE, options );
4188
4393
  let element = widget.domEl;
4189
4394
 
4190
- let container = document.createElement('div');
4395
+ let container = document.createElement( 'div' );
4191
4396
  container.className = "leximage";
4192
4397
  container.style.width = "100%";
4193
4398
 
4194
- let img = document.createElement('img');
4399
+ let img = document.createElement( 'img' );
4195
4400
  img.src = url;
4196
4401
 
4197
- for(let s in options.style) {
4198
-
4199
- img.style[s] = options.style[s];
4402
+ for( let s in options.style )
4403
+ {
4404
+ img.style[ s ] = options.style[ s ];
4200
4405
  }
4201
4406
 
4202
4407
  await img.decode();
4203
- container.appendChild(img);
4204
- element.appendChild(container);
4408
+ container.appendChild( img );
4409
+ element.appendChild( container );
4205
4410
 
4206
4411
  return widget;
4207
4412
  }
@@ -4300,7 +4505,7 @@ class Panel {
4300
4505
  setTimeout( () => delete this.unfocus_event, 200 );
4301
4506
  } else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" ) {
4302
4507
  return;
4303
- }else if ( e.target.id == 'input-filter' ) {
4508
+ }else if ( e.target.className == 'lexinput-filter' ) {
4304
4509
  return;
4305
4510
  }
4306
4511
  this.toggleAttribute( 'hidden', true );
@@ -5103,6 +5308,8 @@ class Panel {
5103
5308
  * min, max: Min and Max values for the input
5104
5309
  * skipSlider: If there are min and max values, skip the slider
5105
5310
  * units: Unit as string added to the end of the value
5311
+ * onPress: Callback function on mouse down
5312
+ * onRelease: Callback function on mouse up
5106
5313
  */
5107
5314
 
5108
5315
  addNumber( name, value, callback, options = {} ) {
@@ -5112,6 +5319,7 @@ class Panel {
5112
5319
  widget.onGetValue = () => {
5113
5320
  return +vecinput.value;
5114
5321
  };
5322
+
5115
5323
  widget.onSetValue = ( newValue, skipCallback ) => {
5116
5324
  vecinput.value = round( newValue, options.precision );
5117
5325
  Panel._dispatch_event( vecinput, "change", skipCallback );
@@ -5154,16 +5362,6 @@ class Panel {
5154
5362
  vecinput.value = vecinput.iValue = value;
5155
5363
  box.appendChild( vecinput );
5156
5364
 
5157
- let measureRealWidth = function( value, paddingPlusMargin = 8 ) {
5158
- var i = document.createElement( "span" );
5159
- i.className = "lexinputmeasure";
5160
- i.innerHTML = value;
5161
- document.body.appendChild( i );
5162
- var rect = i.getBoundingClientRect();
5163
- LX.UTILS.deleteElement( i );
5164
- return rect.width + paddingPlusMargin;
5165
- }
5166
-
5167
5365
  if( options.units )
5168
5366
  {
5169
5367
  let unitSpan = document.createElement( 'span' );
@@ -5183,21 +5381,47 @@ class Panel {
5183
5381
  vecinput.disabled = true;
5184
5382
  }
5185
5383
 
5186
- // add slider below
5187
- if( !options.skipSlider && options.min !== undefined && options.max !== undefined ) {
5384
+ // Add slider below
5385
+ if( !options.skipSlider && options.min !== undefined && options.max !== undefined )
5386
+ {
5188
5387
  let slider = document.createElement( 'input' );
5189
5388
  slider.className = "lexinputslider";
5190
- slider.step = options.step ?? 1;
5191
5389
  slider.min = options.min;
5192
5390
  slider.max = options.max;
5391
+ slider.step = options.step ?? 1;
5193
5392
  slider.type = "range";
5194
5393
  slider.value = value;
5394
+
5195
5395
  slider.addEventListener( "input", function( e ) {
5196
5396
  let new_value = +this.valueAsNumber;
5197
5397
  vecinput.value = round( new_value, options.precision );
5198
5398
  Panel._dispatch_event( vecinput, "change" );
5199
5399
  }, false );
5400
+
5401
+ slider.addEventListener( "mousedown", function( e ) {
5402
+ if( options.onPress )
5403
+ {
5404
+ options.onPress.bind( slider )( e, slider );
5405
+ }
5406
+ }, false );
5407
+
5408
+ slider.addEventListener( "mouseup", function( e ) {
5409
+ if( options.onRelease )
5410
+ {
5411
+ options.onRelease.bind( slider )( e, slider );
5412
+ }
5413
+ }, false );
5414
+
5200
5415
  box.appendChild( slider );
5416
+
5417
+ // Method to change min, max, step parameters
5418
+ widget.setLimits = ( newMin, newMax, newStep ) => {
5419
+ vecinput.min = slider.min = newMin ?? vecinput.min;
5420
+ vecinput.max = slider.max = newMax ?? vecinput.max;
5421
+ vecinput.step = newStep ?? vecinput.step;
5422
+ slider.step = newStep ?? slider.step;
5423
+ Panel._dispatch_event( vecinput, "change", true );
5424
+ };
5201
5425
  }
5202
5426
 
5203
5427
  // Add wheel input
@@ -5205,7 +5429,9 @@ class Panel {
5205
5429
  vecinput.addEventListener( "wheel", function( e ) {
5206
5430
  e.preventDefault();
5207
5431
  if( this !== document.activeElement )
5432
+ {
5208
5433
  return;
5434
+ }
5209
5435
  let mult = options.step ?? 1;
5210
5436
  if( e.shiftKey ) mult *= 10;
5211
5437
  else if( e.altKey ) mult *= 0.1;
@@ -5217,7 +5443,9 @@ class Panel {
5217
5443
  vecinput.addEventListener( "change", e => {
5218
5444
 
5219
5445
  if( isNaN( e.target.valueAsNumber ) )
5446
+ {
5220
5447
  return;
5448
+ }
5221
5449
 
5222
5450
  const skipCallback = e.detail;
5223
5451
 
@@ -5252,50 +5480,76 @@ class Panel {
5252
5480
  vecinput.addEventListener( "mousedown", inner_mousedown );
5253
5481
 
5254
5482
  var that = this;
5255
- var lastY = 0;
5256
- function inner_mousedown(e) {
5257
- if(document.activeElement == vecinput) return;
5483
+
5484
+ function inner_mousedown( e )
5485
+ {
5486
+ if( document.activeElement == vecinput )
5487
+ {
5488
+ return;
5489
+ }
5490
+
5258
5491
  var doc = that.root.ownerDocument;
5259
- doc.addEventListener("mousemove",inner_mousemove);
5260
- doc.addEventListener("mouseup",inner_mouseup);
5261
- lastY = e.pageY;
5262
- document.body.classList.add('nocursor');
5263
- document.body.classList.add('noevents');
5264
- dragIcon.classList.remove('hidden');
5492
+ doc.addEventListener( 'mousemove', inner_mousemove );
5493
+ doc.addEventListener( 'mouseup', inner_mouseup );
5494
+ document.body.classList.add( 'noevents' );
5495
+ dragIcon.classList.remove( 'hidden' );
5265
5496
  e.stopImmediatePropagation();
5266
5497
  e.stopPropagation();
5498
+
5499
+ if( !document.pointerLockElement )
5500
+ {
5501
+ vecinput.requestPointerLock();
5502
+ }
5503
+
5504
+ if( options.onPress )
5505
+ {
5506
+ options.onPress.bind( vecinput )( e, vecinput );
5507
+ }
5267
5508
  }
5268
5509
 
5269
- function inner_mousemove(e) {
5270
- if (lastY != e.pageY) {
5271
- let dt = lastY - e.pageY;
5510
+ function inner_mousemove( e )
5511
+ {
5512
+ let dt = -e.movementY;
5513
+
5514
+ if ( dt != 0 )
5515
+ {
5272
5516
  let mult = options.step ?? 1;
5273
- if(e.shiftKey) mult *= 10;
5274
- else if(e.altKey) mult *= 0.1;
5275
- let new_value = (+vecinput.valueAsNumber + mult * dt);
5276
- vecinput.value = (+new_value).toFixed( 4 ).replace(/([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/,'$1');
5517
+ if( e.shiftKey ) mult *= 10;
5518
+ else if( e.altKey ) mult *= 0.1;
5519
+ let new_value = ( +vecinput.valueAsNumber + mult * dt );
5520
+ vecinput.value = ( +new_value ).toFixed( 4 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' );
5277
5521
  Panel._dispatch_event( vecinput, "change" );
5278
5522
  }
5279
5523
 
5280
- lastY = e.pageY;
5281
5524
  e.stopPropagation();
5282
5525
  e.preventDefault();
5283
5526
  }
5284
5527
 
5285
- function inner_mouseup(e) {
5528
+ function inner_mouseup( e )
5529
+ {
5286
5530
  var doc = that.root.ownerDocument;
5287
- doc.removeEventListener("mousemove",inner_mousemove);
5288
- doc.removeEventListener("mouseup",inner_mouseup);
5289
- document.body.classList.remove('nocursor');
5290
- document.body.classList.remove('noevents');
5291
- dragIcon.classList.add('hidden');
5531
+ doc.removeEventListener( 'mousemove', inner_mousemove );
5532
+ doc.removeEventListener( 'mouseup', inner_mouseup );
5533
+ document.body.classList.remove( 'noevents' );
5534
+ dragIcon.classList.add( 'hidden' );
5535
+
5536
+ if( document.pointerLockElement )
5537
+ {
5538
+ document.exitPointerLock();
5539
+ }
5540
+
5541
+ if( options.onRelease )
5542
+ {
5543
+ options.onRelease.bind( vecinput )( e, vecinput );
5544
+ }
5292
5545
  }
5293
5546
 
5294
- container.appendChild(box);
5295
- element.appendChild(container);
5547
+ container.appendChild( box );
5548
+ element.appendChild( container );
5296
5549
 
5297
5550
  // Remove branch padding and margins
5298
- if(!widget.name) {
5551
+ if( !widget.name )
5552
+ {
5299
5553
  element.className += " noname";
5300
5554
  container.style.width = "100%";
5301
5555
  }
@@ -5303,14 +5557,15 @@ class Panel {
5303
5557
  return widget;
5304
5558
  }
5305
5559
 
5306
- static VECTOR_COMPONENTS = {0: 'x', 1: 'y', 2: 'z', 3: 'w'};
5560
+ static VECTOR_COMPONENTS = { 0: 'x', 1: 'y', 2: 'z', 3: 'w' };
5307
5561
 
5308
5562
  _add_vector( num_components, name, value, callback, options = {} ) {
5309
5563
 
5310
5564
  num_components = clamp( num_components, 2, 4 );
5311
5565
  value = value ?? new Array( num_components ).fill( 0 );
5312
5566
 
5313
- if( !name ) {
5567
+ if( !name )
5568
+ {
5314
5569
  throw( "Set Widget Name!" );
5315
5570
  }
5316
5571
 
@@ -5320,9 +5575,12 @@ class Panel {
5320
5575
  let inputs = element.querySelectorAll( "input" );
5321
5576
  let value = [];
5322
5577
  for( var v of inputs )
5578
+ {
5323
5579
  value.push( +v.value );
5580
+ }
5324
5581
  return value;
5325
5582
  };
5583
+
5326
5584
  widget.onSetValue = ( newValue, skipCallback ) => {
5327
5585
  const inputs = element.querySelectorAll( ".vecinput" );
5328
5586
  if( inputs.length == newValue.length )
@@ -5331,7 +5589,7 @@ class Panel {
5331
5589
  return;
5332
5590
  }
5333
5591
 
5334
- for( var i = 0; i < inputs.length; ++i ) {
5592
+ for( let i = 0; i < inputs.length; ++i ) {
5335
5593
  let value = newValue[ i ];
5336
5594
  inputs[ i ].value = round( value, options.precision ) ?? 0;
5337
5595
  Panel._dispatch_event( inputs[ i ], "change", skipCallback );
@@ -5351,7 +5609,7 @@ class Panel {
5351
5609
 
5352
5610
  // Add widget value
5353
5611
 
5354
- var container = document.createElement('div');
5612
+ var container = document.createElement( 'div' );
5355
5613
  container.className = "lexvector";
5356
5614
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5357
5615
 
@@ -5372,7 +5630,7 @@ class Panel {
5372
5630
 
5373
5631
  if( value[ i ].constructor == Number )
5374
5632
  {
5375
- value[ i ] = clamp(value[ i ], +vecinput.min, +vecinput.max);
5633
+ value[ i ] = clamp( value[ i ], +vecinput.min, +vecinput.max );
5376
5634
  value[ i ] = round( value[ i ], options.precision );
5377
5635
  }
5378
5636
 
@@ -5382,12 +5640,12 @@ class Panel {
5382
5640
  dragIcon.className = "fa-solid fa-arrows-up-down drag-icon hidden";
5383
5641
  box.appendChild( dragIcon );
5384
5642
 
5385
- if( options.disabled ) {
5643
+ if( options.disabled )
5644
+ {
5386
5645
  vecinput.disabled = true;
5387
5646
  }
5388
5647
 
5389
5648
  // Add wheel input
5390
-
5391
5649
  vecinput.addEventListener( "wheel", function( e ) {
5392
5650
  e.preventDefault();
5393
5651
  if( this !== document.activeElement )
@@ -5401,7 +5659,7 @@ class Panel {
5401
5659
  for( let v of element.querySelectorAll(".vecinput") )
5402
5660
  {
5403
5661
  v.value = round( +v.valueAsNumber - mult * ( e.deltaY > 0 ? 1 : -1 ), options.precision );
5404
- Panel._dispatch_event(v, "change");
5662
+ Panel._dispatch_event( v, "change" );
5405
5663
  }
5406
5664
  }
5407
5665
  else
@@ -5414,7 +5672,9 @@ class Panel {
5414
5672
  vecinput.addEventListener( "change", e => {
5415
5673
 
5416
5674
  if( isNaN( e.target.value ) )
5675
+ {
5417
5676
  return;
5677
+ }
5418
5678
 
5419
5679
  const skipCallback = e.detail;
5420
5680
 
@@ -5425,7 +5685,7 @@ class Panel {
5425
5685
  if( !skipCallback )
5426
5686
  {
5427
5687
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
5428
- if( btn ) btn.style.display = val != vecinput.iValue ? "block": "none";
5688
+ if( btn ) btn.style.display = val != vecinput.iValue ? "block" : "none";
5429
5689
  }
5430
5690
 
5431
5691
  if( locker.locked )
@@ -5434,7 +5694,9 @@ class Panel {
5434
5694
  v.value = val;
5435
5695
  value[ v.idx ] = val;
5436
5696
  }
5437
- } else {
5697
+ }
5698
+ else
5699
+ {
5438
5700
  vecinput.value = val;
5439
5701
  value[ e.target.idx ] = val;
5440
5702
  }
@@ -5447,70 +5709,118 @@ class Panel {
5447
5709
  vecinput.addEventListener( "mousedown", inner_mousedown );
5448
5710
 
5449
5711
  var that = this;
5450
- var lastY = 0;
5451
- function inner_mousedown(e) {
5452
- if(document.activeElement == vecinput) return;
5712
+
5713
+ function inner_mousedown( e )
5714
+ {
5715
+ if( document.activeElement == vecinput )
5716
+ {
5717
+ return;
5718
+ }
5719
+
5453
5720
  var doc = that.root.ownerDocument;
5454
- doc.addEventListener("mousemove",inner_mousemove);
5455
- doc.addEventListener("mouseup",inner_mouseup);
5456
- lastY = e.pageY;
5457
- document.body.classList.add('nocursor');
5458
- document.body.classList.add('noevents');
5459
- dragIcon.classList.remove('hidden');
5721
+ doc.addEventListener( 'mousemove', inner_mousemove );
5722
+ doc.addEventListener( 'mouseup', inner_mouseup );
5723
+ document.body.classList.add( 'noevents' );
5724
+ dragIcon.classList.remove( 'hidden' );
5460
5725
  e.stopImmediatePropagation();
5461
5726
  e.stopPropagation();
5727
+
5728
+ if( !document.pointerLockElement )
5729
+ {
5730
+ vecinput.requestPointerLock();
5731
+ }
5732
+
5733
+ if( options.onPress )
5734
+ {
5735
+ options.onPress.bind( vecinput )( e, vecinput );
5736
+ }
5462
5737
  }
5463
5738
 
5464
- function inner_mousemove(e) {
5465
- if (lastY != e.pageY) {
5466
- let dt = lastY - e.pageY;
5739
+ function inner_mousemove( e )
5740
+ {
5741
+ let dt = -e.movementY;
5742
+
5743
+ if ( dt != 0 )
5744
+ {
5467
5745
  let mult = options.step ?? 1;
5468
- if(e.shiftKey) mult = 10;
5469
- else if(e.altKey) mult = 0.1;
5746
+ if( e.shiftKey ) mult = 10;
5747
+ else if( e.altKey ) mult = 0.1;
5470
5748
 
5471
5749
  if( locker.locked )
5472
5750
  {
5473
- for( let v of element.querySelectorAll(".vecinput") ) {
5751
+ for( let v of element.querySelectorAll( ".vecinput" ) ) {
5474
5752
  v.value = round( +v.valueAsNumber + mult * dt, options.precision );
5475
- Panel._dispatch_event(v, "change");
5753
+ Panel._dispatch_event( v, "change" );
5476
5754
  }
5477
- } else {
5755
+ }
5756
+ else
5757
+ {
5478
5758
  vecinput.value = round( +vecinput.valueAsNumber + mult * dt, options.precision );
5479
- Panel._dispatch_event(vecinput, "change");
5759
+ Panel._dispatch_event( vecinput, "change" );
5480
5760
  }
5481
5761
  }
5482
5762
 
5483
- lastY = e.pageY;
5484
5763
  e.stopPropagation();
5485
5764
  e.preventDefault();
5486
5765
  }
5487
5766
 
5488
- function inner_mouseup( e ) {
5767
+ function inner_mouseup( e )
5768
+ {
5489
5769
  var doc = that.root.ownerDocument;
5490
- doc.removeEventListener("mousemove",inner_mousemove);
5491
- doc.removeEventListener("mouseup",inner_mouseup);
5492
- document.body.classList.remove('nocursor');
5493
- document.body.classList.remove('noevents');
5770
+ doc.removeEventListener( 'mousemove', inner_mousemove );
5771
+ doc.removeEventListener( 'mouseup', inner_mouseup );
5772
+ document.body.classList.remove( 'noevents' );
5494
5773
  dragIcon.classList.add('hidden');
5774
+
5775
+ if( document.pointerLockElement )
5776
+ {
5777
+ document.exitPointerLock();
5778
+ }
5779
+
5780
+ if( options.onRelease )
5781
+ {
5782
+ options.onRelease.bind( vecinput )( e, vecinput );
5783
+ }
5495
5784
  }
5496
5785
 
5497
5786
  box.appendChild( vecinput );
5498
5787
  container.appendChild( box );
5499
5788
  }
5500
5789
 
5501
- let locker = document.createElement('a');
5790
+ // Method to change min, max, step parameters
5791
+ if( options.min !== undefined || options.max !== undefined )
5792
+ {
5793
+ widget.setLimits = ( newMin, newMax, newStep ) => {
5794
+ const inputs = element.querySelectorAll(".vecinput");
5795
+ for( let v of inputs )
5796
+ {
5797
+ v.min = newMin ?? v.min;
5798
+ v.max = newMax ?? v.max;
5799
+ v.step = newStep ?? v.step;
5800
+ Panel._dispatch_event( v, "change", true );
5801
+ }
5802
+
5803
+ // To call onChange callback
5804
+ this._trigger( new IEvent( name, value ), callback );
5805
+ };
5806
+ }
5807
+
5808
+ let locker = document.createElement( 'a' );
5502
5809
  locker.className = "fa-solid fa-lock-open lexicon";
5503
- container.appendChild(locker);
5504
- locker.addEventListener("click", function(e) {
5810
+ container.appendChild( locker );
5811
+ locker.addEventListener( "click", function( e ) {
5505
5812
  this.locked = !this.locked;
5506
- if(this.locked){
5507
- this.classList.add("fa-lock");
5508
- this.classList.remove("fa-lock-open");
5509
- } else {
5510
- this.classList.add("fa-lock-open");
5511
- this.classList.remove("fa-lock");
5512
- }
5513
- }, false);
5813
+ if( this.locked )
5814
+ {
5815
+ this.classList.add( "fa-lock" );
5816
+ this.classList.remove( "fa-lock-open" );
5817
+ }
5818
+ else
5819
+ {
5820
+ this.classList.add( "fa-lock-open" );
5821
+ this.classList.remove( "fa-lock" );
5822
+ }
5823
+ }, false );
5514
5824
 
5515
5825
  element.appendChild( container );
5516
5826
 
@@ -5526,21 +5836,231 @@ class Panel {
5526
5836
  * disabled: Make the widget disabled [false]
5527
5837
  * step: Step of the inputs
5528
5838
  * min, max: Min and Max values for the inputs
5839
+ * onPress: Callback function on mouse down
5840
+ * onRelease: Callback function on mouse is released
5529
5841
  */
5530
5842
 
5531
5843
  addVector2( name, value, callback, options ) {
5532
5844
 
5533
- return this._add_vector(2, name, value, callback, options);
5845
+ return this._add_vector( 2, name, value, callback, options );
5534
5846
  }
5535
5847
 
5536
5848
  addVector3( name, value, callback, options ) {
5537
5849
 
5538
- return this._add_vector(3, name, value, callback, options);
5850
+ return this._add_vector( 3, name, value, callback, options );
5539
5851
  }
5540
5852
 
5541
5853
  addVector4( name, value, callback, options ) {
5542
5854
 
5543
- return this._add_vector(4, name, value, callback, options);
5855
+ return this._add_vector( 4, name, value, callback, options );
5856
+ }
5857
+
5858
+ /**
5859
+ * @method addSize
5860
+ * @param {String} name Widget name
5861
+ * @param {Number} value Default number value
5862
+ * @param {Function} callback Callback function on change
5863
+ * @param {*} options:
5864
+ * disabled: Make the widget disabled [false]
5865
+ * units: Unit as string added to the end of the value
5866
+ */
5867
+
5868
+ addSize( name, value, callback, options = {} ) {
5869
+
5870
+ let widget = this.create_widget( name, Widget.SIZE, options );
5871
+
5872
+ widget.onGetValue = () => {
5873
+ const value = [];
5874
+ for( let i = 0; i < element.dimensions.length; ++i )
5875
+ {
5876
+ value.push( element.dimensions[ i ].onGetValue() );
5877
+ }
5878
+ return value;
5879
+ };
5880
+
5881
+ widget.onSetValue = ( newValue, skipCallback ) => {
5882
+ for( let i = 0; i < element.dimensions.length; ++i )
5883
+ {
5884
+ element.dimensions[ i ].onSetValue( newValue[ i ], skipCallback );
5885
+ }
5886
+ };
5887
+
5888
+ let element = widget.domEl;
5889
+
5890
+ this.queue( element );
5891
+
5892
+ element.dimensions = [];
5893
+
5894
+ for( let i = 0; i < value.length; ++i )
5895
+ {
5896
+ const size = measureRealWidth( JSON.stringify( value[ i ] ), 24 ) + 'px';
5897
+ element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
5898
+
5899
+ const value = [];
5900
+
5901
+ for( let i = 0; i < element.dimensions.length; ++i )
5902
+ {
5903
+ value.push( element.dimensions[ i ].onGetValue() );
5904
+ }
5905
+
5906
+ if( callback )
5907
+ {
5908
+ callback( value );
5909
+ }
5910
+
5911
+ }, { width: size, min: 0, disabled: options.disabled } );
5912
+
5913
+ if( ( i + 1 ) != value.length )
5914
+ {
5915
+ let cross = document.createElement( 'a' );
5916
+ cross.className = "lexsizecross fa-solid fa-xmark";
5917
+ element.appendChild( cross );
5918
+ }
5919
+ }
5920
+
5921
+ this.clearQueue();
5922
+
5923
+ if( options.units )
5924
+ {
5925
+ let unitSpan = document.createElement( 'span' );
5926
+ unitSpan.className = "lexunit";
5927
+ unitSpan.innerText = options.units;
5928
+ element.appendChild( unitSpan );
5929
+ }
5930
+
5931
+ // Remove branch padding and margins
5932
+ if( !widget.name )
5933
+ {
5934
+ element.className += " noname";
5935
+ container.style.width = "100%";
5936
+ }
5937
+
5938
+ return widget;
5939
+ }
5940
+
5941
+ /**
5942
+ * @method addPad
5943
+ * @param {String} name Widget name
5944
+ * @param {Number} value Pad value
5945
+ * @param {Function} callback Callback function on change
5946
+ * @param {*} options:
5947
+ * disabled: Make the widget disabled [false]
5948
+ * min, max: Min and Max values
5949
+ * onPress: Callback function on mouse down
5950
+ * onRelease: Callback function on mouse up
5951
+ */
5952
+
5953
+ addPad( name, value, callback, options = {} ) {
5954
+
5955
+ if( !name )
5956
+ {
5957
+ throw( "Set Widget Name!" );
5958
+ }
5959
+
5960
+ let widget = this.create_widget( name, Widget.PAD, options );
5961
+
5962
+ widget.onGetValue = () => {
5963
+ return thumb.value.xy;
5964
+ };
5965
+
5966
+ widget.onSetValue = ( newValue, skipCallback ) => {
5967
+ thumb.value.set( newValue[ 0 ], newValue[ 1 ] );
5968
+ _updateValue( thumb.value );
5969
+ if( !skipCallback )
5970
+ {
5971
+ this._trigger( new IEvent( name, thumb.value.xy ), callback );
5972
+ }
5973
+ };
5974
+
5975
+ let element = widget.domEl;
5976
+
5977
+ var container = document.createElement( 'div' );
5978
+ container.className = "lexpad";
5979
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5980
+
5981
+ let pad = document.createElement('div');
5982
+ pad.id = "lexpad-" + name;
5983
+ pad.className = "lexinnerpad";
5984
+ pad.style.width = options.padSize ?? '96px';
5985
+ pad.style.height = options.padSize ?? '96px';
5986
+
5987
+ let thumb = document.createElement('div');
5988
+ thumb.className = "lexpadthumb";
5989
+ thumb.value = new LX.vec2( value[ 0 ], value[ 1 ] );
5990
+ thumb.min = options.min ?? 0;
5991
+ thumb.max = options.max ?? 1;
5992
+
5993
+ let _updateValue = v => {
5994
+ const [ w, h ] = [ pad.offsetWidth, pad.offsetHeight ];
5995
+ 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 ) );
5996
+ thumb.style.transform = `translate(calc( ${ w * value0to1.x }px - 50% ), calc( ${ h * value0to1.y }px - 50%)`;
5997
+ }
5998
+
5999
+ doAsync( () => {
6000
+ _updateValue( thumb.value )
6001
+ } );
6002
+
6003
+ pad.appendChild( thumb );
6004
+ container.appendChild( pad );
6005
+ element.appendChild( container );
6006
+
6007
+ pad.addEventListener( "mousedown", innerMouseDown );
6008
+
6009
+ let that = this;
6010
+
6011
+ function innerMouseDown( e )
6012
+ {
6013
+ if( document.activeElement == thumb )
6014
+ {
6015
+ return;
6016
+ }
6017
+
6018
+ var doc = that.root.ownerDocument;
6019
+ doc.addEventListener( 'mousemove', innerMouseMove );
6020
+ doc.addEventListener( 'mouseup', innerMouseUp );
6021
+ document.body.classList.add( 'nocursor' );
6022
+ document.body.classList.add( 'noevents' );
6023
+ e.stopImmediatePropagation();
6024
+ e.stopPropagation();
6025
+
6026
+ if( options.onPress )
6027
+ {
6028
+ options.onPress.bind( thumb )( e, thumb );
6029
+ }
6030
+ }
6031
+
6032
+ function innerMouseMove( e )
6033
+ {
6034
+ const rect = pad.getBoundingClientRect();
6035
+ const relativePosition = new LX.vec2( e.x - rect.x, e.y - rect.y );
6036
+ relativePosition.clp( 0.0, pad.offsetWidth, relativePosition);
6037
+ const [ w, h ] = [ pad.offsetWidth, pad.offsetHeight ];
6038
+ const value0to1 = relativePosition.div( new LX.vec2( pad.offsetWidth, pad.offsetHeight ) );
6039
+
6040
+ thumb.style.transform = `translate(calc( ${ w * value0to1.x }px - 50% ), calc( ${ h * value0to1.y }px - 50%)`;
6041
+ 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 ) );
6042
+
6043
+ that._trigger( new IEvent( name, thumb.value.xy, e ), callback );
6044
+
6045
+ e.stopPropagation();
6046
+ e.preventDefault();
6047
+ }
6048
+
6049
+ function innerMouseUp( e )
6050
+ {
6051
+ var doc = that.root.ownerDocument;
6052
+ doc.removeEventListener( 'mousemove', innerMouseMove );
6053
+ doc.removeEventListener( 'mouseup', innerMouseUp );
6054
+ document.body.classList.remove( 'nocursor' );
6055
+ document.body.classList.remove( 'noevents' );
6056
+
6057
+ if( options.onRelease )
6058
+ {
6059
+ options.onRelease.bind( thumb )( e, thumb );
6060
+ }
6061
+ }
6062
+
6063
+ return widget;
5544
6064
  }
5545
6065
 
5546
6066
  /**
@@ -6046,8 +6566,7 @@ class Branch {
6046
6566
 
6047
6567
  this.grabber = grabber;
6048
6568
 
6049
- function getBranchHeight(){
6050
-
6569
+ function getBranchHeight() {
6051
6570
  return that.root.offsetHeight - that.root.children[0].offsetHeight;
6052
6571
  }
6053
6572
 
@@ -6101,20 +6620,23 @@ class Branch {
6101
6620
  var size = this.grabber.style.marginLeft;
6102
6621
 
6103
6622
  // Update sizes of widgets inside
6104
- for(var i = 0; i < this.widgets.length;i++) {
6623
+ for(var i = 0; i < this.widgets.length; i++) {
6105
6624
 
6106
- let widget = this.widgets[i];
6625
+ let widget = this.widgets[ i ];
6107
6626
  let element = widget.domEl;
6108
6627
 
6109
- if(element.children.length < 2)
6628
+ if( element.children.length < 2 )
6629
+ {
6110
6630
  continue;
6631
+ }
6111
6632
 
6112
- var name = element.children[0];
6113
- var value = element.children[1];
6633
+ var name = element.children[ 0 ];
6634
+ var value = element.children[ 1 ];
6114
6635
 
6115
6636
  name.style.width = size;
6116
6637
  let padding = "0px";
6117
- switch(widget.type) {
6638
+ switch( widget.type )
6639
+ {
6118
6640
  case Widget.FILE:
6119
6641
  padding = "10%";
6120
6642
  break;
@@ -6127,7 +6649,10 @@ class Branch {
6127
6649
  value.style.width = "-webkit-calc( 100% - " + size + " - " + padding + " )";
6128
6650
  value.style.width = "calc( 100% - " + size + " - " + padding + " )";
6129
6651
 
6130
- if(widget.onresize) widget.onresize();
6652
+ if( widget.onresize )
6653
+ {
6654
+ widget.onresize();
6655
+ }
6131
6656
  }
6132
6657
  }
6133
6658
  };
@@ -6144,8 +6669,10 @@ class Dialog {
6144
6669
 
6145
6670
  constructor( title, callback, options = {} ) {
6146
6671
 
6147
- if(!callback)
6148
- console.warn("Content is empty, add some widgets using 'callback' parameter!");
6672
+ if( !callback )
6673
+ {
6674
+ console.warn("Content is empty, add some widgets using 'callback' parameter!");
6675
+ }
6149
6676
 
6150
6677
  this._oncreate = callback;
6151
6678
  this.id = simple_guidGenerator();
@@ -6155,8 +6682,10 @@ class Dialog {
6155
6682
  draggable = options.draggable ?? true,
6156
6683
  modal = options.modal ?? false;
6157
6684
 
6158
- if(modal)
6159
- LX.modal.toggle(false);
6685
+ if( modal )
6686
+ {
6687
+ LX.modal.toggle( false );
6688
+ }
6160
6689
 
6161
6690
  var root = document.createElement('div');
6162
6691
  root.className = "lexdialog " + (options.class ?? "");
@@ -6167,8 +6696,8 @@ class Dialog {
6167
6696
 
6168
6697
  var titleDiv = document.createElement('div');
6169
6698
 
6170
- if(title) {
6171
-
6699
+ if( title )
6700
+ {
6172
6701
  titleDiv.className = "lexdialogtitle";
6173
6702
  titleDiv.innerHTML = title;
6174
6703
  titleDiv.setAttribute('draggable', false);
@@ -6272,7 +6801,9 @@ class Dialog {
6272
6801
  this.title = titleDiv;
6273
6802
 
6274
6803
  if( draggable )
6275
- makeDraggable( root, { targetClass: 'lexdialogtitle' } );
6804
+ {
6805
+ makeDraggable( root, Object.assign( { targetClass: 'lexdialogtitle' }, options ) );
6806
+ }
6276
6807
 
6277
6808
  // Process position and size
6278
6809
  if(size.length && typeof(size[0]) != "string")
@@ -6447,28 +6978,42 @@ class ContextMenu {
6447
6978
  }
6448
6979
  }
6449
6980
 
6450
- _adjust_position(div, margin, useAbsolute = false) {
6981
+ _adjust_position( div, margin, useAbsolute = false ) {
6451
6982
 
6452
6983
  let rect = div.getBoundingClientRect();
6453
6984
 
6454
- if(!useAbsolute)
6985
+ if( !useAbsolute )
6455
6986
  {
6456
6987
  let width = rect.width;
6457
- if(window.innerWidth - rect.right < 0)
6988
+ if( rect.left < 0 )
6989
+ {
6990
+ div.style.left = margin + "px";
6991
+ }
6992
+ else if( window.innerWidth - rect.right < 0 )
6993
+ {
6458
6994
  div.style.left = (window.innerWidth - width - margin) + "px";
6459
-
6460
- if(rect.top + rect.height > window.innerHeight)
6995
+ }
6996
+
6997
+ if( rect.top < 0 )
6998
+ {
6999
+ div.style.top = margin + "px";
7000
+ }
7001
+ else if( (rect.top + rect.height) > window.innerHeight )
7002
+ {
6461
7003
  div.style.top = (window.innerHeight - rect.height - margin) + "px";
7004
+ }
6462
7005
  }
6463
7006
  else
6464
7007
  {
6465
7008
  let dt = window.innerWidth - rect.right;
6466
- if(dt < 0) {
7009
+ if( dt < 0 )
7010
+ {
6467
7011
  div.style.left = div.offsetLeft + (dt - margin) + "px";
6468
7012
  }
6469
7013
 
6470
7014
  dt = window.innerHeight - (rect.top + rect.height);
6471
- if(dt < 0) {
7015
+ if( dt < 0 )
7016
+ {
6472
7017
  div.style.top = div.offsetTop + (dt - margin + 20 ) + "px";
6473
7018
  }
6474
7019
  }