lexgui 0.1.42 → 0.1.44

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.42",
11
+ version: "0.1.44",
12
12
  ready: false,
13
13
  components: [], // specific pre-build components
14
14
  signals: {} // events and triggers
@@ -25,7 +25,7 @@ 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( number, precision ) { return +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
28
+ function round( number, precision ) { return precision == 0 ? Math.floor( number ) : +(( number ).toFixed( precision ?? 2 ).replace( /([0-9]+(\.[0-9]+[1-9])?)(\.?0+$)/, '$1' )); }
29
29
  function remapRange( oldValue, oldMin, oldMax, newMin, newMax ) { return ((( oldValue - oldMin ) * ( newMax - newMin )) / ( oldMax - oldMin )) + newMin; }
30
30
 
31
31
  LX.clamp = clamp;
@@ -34,7 +34,7 @@ LX.remapRange = remapRange;
34
34
 
35
35
  function getSupportedDOMName( string )
36
36
  {
37
- return string.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
37
+ return string.replace(/\s/g, '').replaceAll('@', '_').replaceAll('+', '_plus_').replaceAll('.', '');
38
38
  }
39
39
 
40
40
  LX.getSupportedDOMName = getSupportedDOMName;
@@ -47,15 +47,15 @@ function has( component_name )
47
47
  LX.has = has;
48
48
 
49
49
  function getExtension( s )
50
- {
50
+ {
51
51
  return s.includes('.') ? s.split('.').pop() : null;
52
52
  }
53
53
 
54
54
  LX.getExtension = getExtension;
55
55
 
56
56
  function deepCopy( o )
57
- {
58
- return JSON.parse(JSON.stringify(o))
57
+ {
58
+ return JSON.parse(JSON.stringify(o))
59
59
  }
60
60
 
61
61
  LX.deepCopy = deepCopy;
@@ -70,8 +70,22 @@ LX.setThemeColor = setThemeColor;
70
70
 
71
71
  function getThemeColor( colorName )
72
72
  {
73
- var r = getComputedStyle( document.querySelector( ':root' ) );
74
- return r.getPropertyValue( '--' + colorName );
73
+ const r = getComputedStyle( document.querySelector( ':root' ) );
74
+ const value = r.getPropertyValue( '--' + colorName );
75
+
76
+ if( value.includes( "light-dark" ) && window.matchMedia )
77
+ {
78
+ if( window.matchMedia( "(prefers-color-scheme: light)" ).matches )
79
+ {
80
+ return value.substring( value.indexOf( '(' ) + 1, value.indexOf( ',' ) ).replace( /\s/g, '' );
81
+ }
82
+ else
83
+ {
84
+ return value.substring( value.indexOf( ',' ) + 1, value.indexOf( ')' ) ).replace( /\s/g, '' );
85
+ }
86
+ }
87
+
88
+ return value;
75
89
  }
76
90
 
77
91
  LX.getThemeColor = getThemeColor;
@@ -275,7 +289,7 @@ function makeDraggable( domEl, options = { } ) {
275
289
  onDragStart( currentTarget, e );
276
290
  }
277
291
  }, false );
278
-
292
+
279
293
  document.addEventListener( 'mouseup', () => {
280
294
  if( currentTarget )
281
295
  {
@@ -289,77 +303,90 @@ LX.makeDraggable = makeDraggable;
289
303
 
290
304
  function create_global_searchbar( root ) {
291
305
 
292
- let global_search = document.createElement("div");
293
- global_search.id = "global_search";
294
- global_search.className = "hidden";
295
- global_search.tabIndex = -1;
296
- root.appendChild( global_search );
306
+ let globalSearch = document.createElement("div");
307
+ globalSearch.id = "global-search";
308
+ globalSearch.className = "hidden";
309
+ globalSearch.tabIndex = -1;
310
+ root.appendChild( globalSearch );
297
311
 
298
312
  let allItems = [];
299
313
  let hoverElId = null;
300
314
 
301
- global_search.addEventListener('keydown', function(e) {
315
+ globalSearch.addEventListener('keydown', function( e ) {
302
316
  e.stopPropagation();
303
317
  e.stopImmediatePropagation();
304
318
  hoverElId = hoverElId ?? -1;
305
- if( e.key == 'Escape' ) {
319
+ if( e.key == 'Escape' )
320
+ {
306
321
  this.classList.add("hidden");
307
- reset_bar(true);
322
+ _resetBar( true );
308
323
  }
309
- else if( e.key == 'Enter' ) {
324
+ else if( e.key == 'Enter' )
325
+ {
310
326
  const el = allItems[ hoverElId ];
311
- if(el) {
312
- const is_checkbox = (el.item.type && el.item.type === 'checkbox');
327
+ if( el )
328
+ {
329
+ const isCheckbox = (el.item.type && el.item.type === 'checkbox');
313
330
  this.classList.toggle('hidden');
314
- if(is_checkbox) {
331
+ if( isCheckbox )
332
+ {
315
333
  el.item.checked = !el.item.checked;
316
- el.callback.call(window, el.item.checked, el.entry_name);
334
+ el.callback.call( window, el.item.checked, el.entry_name );
317
335
  }
318
336
  else
319
- el.callback.call(window, el.entry_name);
337
+ {
338
+ el.callback.call( window, el.entry_name );
339
+ }
320
340
  }
321
341
  }
322
- else if ( e.key == 'ArrowDown' && hoverElId < (allItems.length - 1) ) {
342
+ else if ( e.key == 'ArrowDown' && hoverElId < (allItems.length - 1) )
343
+ {
323
344
  hoverElId++;
324
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
345
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
325
346
  allItems[ hoverElId ].classList.add('hovered');
326
347
 
327
348
  let dt = allItems[ hoverElId ].offsetHeight * (hoverElId + 1) - itemContainer.offsetHeight;
328
- if( dt > 0) {
349
+ if( dt > 0 )
350
+ {
329
351
  itemContainer.scrollTo({
330
352
  top: dt,
331
353
  behavior: "smooth",
332
354
  });
333
355
  }
334
356
 
335
- } else if ( e.key == 'ArrowUp' && hoverElId > 0 ) {
357
+ } else if ( e.key == 'ArrowUp' && hoverElId > 0 )
358
+ {
336
359
  hoverElId--;
337
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
360
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
338
361
  allItems[ hoverElId ].classList.add('hovered');
339
362
  }
340
363
  });
341
364
 
342
- global_search.addEventListener('focusout', function(e) {
343
- if(e.relatedTarget == e.currentTarget) return;
365
+ globalSearch.addEventListener('focusout', function( e ) {
366
+ if( e.relatedTarget == e.currentTarget )
367
+ {
368
+ return;
369
+ }
344
370
  e.stopPropagation();
345
371
  e.stopImmediatePropagation();
346
- this.classList.add("hidden");
347
- reset_bar(true);
372
+ this.classList.add( "hidden" );
373
+ _resetBar( true );
348
374
  });
349
375
 
350
376
  root.addEventListener('keydown', e => {
351
- if( e.key == ' ' && e.ctrlKey ) {
377
+ if( e.key == ' ' && e.ctrlKey )
378
+ {
352
379
  e.stopImmediatePropagation();
353
380
  e.stopPropagation();
354
- global_search.classList.toggle('hidden');
355
- global_search.querySelector('input').focus();
356
- add_elements( undefined );
381
+ globalSearch.classList.toggle('hidden');
382
+ globalSearch.querySelector('input').focus();
383
+ _addElements( undefined );
357
384
  }
358
385
  else
359
386
  {
360
387
  for( let c of LX.components )
361
388
  {
362
- if( !LX[c] || !LX[c].prototype.onKeyPressed )
389
+ if( !LX[ c ] || !LX[ c ].prototype.onKeyPressed )
363
390
  {
364
391
  continue;
365
392
  }
@@ -373,37 +400,68 @@ function create_global_searchbar( root ) {
373
400
  }
374
401
  });
375
402
 
376
- let icon = document.createElement("a");
403
+ const header = document.createElement( "div" );
404
+ header.className = "gs-header";
405
+
406
+ const icon = document.createElement("a");
377
407
  icon.className = "fa-solid fa-magnifying-glass";
408
+ header.appendChild( icon );
378
409
 
379
- let input = document.createElement("input");
410
+ const input = document.createElement("input");
380
411
  input.placeholder = "Search...";
381
412
  input.value = "";
413
+ header.appendChild( input );
414
+
415
+ const tabArea = new Area( {
416
+ width: "100%",
417
+ skipAppend: true,
418
+ className: "gs-tabs"
419
+ } );
420
+
421
+ const gsTabs = tabArea.addTabs();
422
+ let gsFilter = null;
423
+
424
+ // These tabs will serve as buttons by now
425
+ // Filter stuff depending of the type of search
426
+ {
427
+ const _onSelectTab = ( e, tabName ) => {
428
+ gsFilter = tabName;
429
+ }
382
430
 
383
- let itemContainer = document.createElement("div");
431
+ gsTabs.add( "All", document.createElement('div'), { selected: true, onSelect: _onSelectTab } );
432
+ // gsTabs.add( "Main", document.createElement('div'), { onSelect: _onSelectTab } );
433
+ }
434
+
435
+ const itemContainer = document.createElement("div");
384
436
  itemContainer.className = "searchitembox";
385
437
 
386
- let ref_previous;
438
+ let refPrevious = null;
387
439
 
388
- const reset_bar = (reset_input) => {
440
+ const _resetBar = (reset_input) => {
389
441
  itemContainer.innerHTML = "";
390
442
  allItems.length = 0;
391
443
  hoverElId = null;
392
444
  if(reset_input) input.value = "";
393
445
  }
394
446
 
395
- const add_element = (t, c, p, i) => {
447
+ const _addElement = ( t, c, p, i ) => {
396
448
 
397
- if(!t.length) return;
449
+ if( !t.length )
450
+ {
451
+ return;
452
+ }
398
453
 
399
- if(ref_previous) ref_previous.classList.remove('last');
454
+ if( refPrevious ) refPrevious.classList.remove('last');
400
455
 
401
456
  let searchItem = document.createElement("div");
402
457
  searchItem.className = "searchitem last";
403
- const is_checkbox = (i && i.type && i.type === 'checkbox');
404
- if(is_checkbox) {
458
+ const isCheckbox = (i && i.type && i.type === 'checkbox');
459
+ if( isCheckbox )
460
+ {
405
461
  searchItem.innerHTML = "<a class='fa fa-check'></a><span>" + p + t + "</span>"
406
- } else {
462
+ }
463
+ else
464
+ {
407
465
  searchItem.innerHTML = p + t;
408
466
  }
409
467
  searchItem.entry_name = t;
@@ -411,29 +469,32 @@ function create_global_searchbar( root ) {
411
469
  searchItem.item = i;
412
470
  searchItem.addEventListener('click', function(e) {
413
471
  this.callback.call(window, this.entry_name);
414
- global_search.classList.toggle('hidden');
415
- reset_bar(true);
472
+ globalSearch.classList.toggle('hidden');
473
+ _resetBar( true );
416
474
  });
417
475
  searchItem.addEventListener('mouseenter', function(e) {
418
- global_search.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
476
+ globalSearch.querySelectorAll(".hovered").forEach(e => e.classList.remove('hovered'));
419
477
  this.classList.add('hovered');
420
- hoverElId = allItems.indexOf(this);
478
+ hoverElId = allItems.indexOf( this );
421
479
  });
422
480
  searchItem.addEventListener('mouseleave', function(e) {
423
481
  this.classList.remove('hovered');
424
482
  });
425
483
  allItems.push( searchItem );
426
- itemContainer.appendChild(searchItem);
427
- ref_previous = searchItem;
484
+ itemContainer.appendChild( searchItem );
485
+ refPrevious = searchItem;
428
486
  }
429
487
 
430
- const propagate_add = ( item, filter, path ) => {
488
+ const _propagateAdd = ( item, filter, path ) => {
431
489
 
432
490
  const key = Object.keys( item )[ 0 ];
433
491
  let name = item.name ?? path + key;
434
- if( name.toLowerCase().includes( filter ) ) {
492
+ if( name.toLowerCase().includes( filter ) )
493
+ {
435
494
  if( item.callback )
436
- add_element( item.name ?? key, item.callback, path, item );
495
+ {
496
+ _addElement( item.name ?? key, item.callback, path, item );
497
+ }
437
498
  }
438
499
 
439
500
  // is sidebar..
@@ -443,17 +504,20 @@ function create_global_searchbar( root ) {
443
504
  path += key + " > ";
444
505
 
445
506
  for( let c of item[ key ] )
446
- propagate_add( c, filter, path );
507
+ _propagateAdd( c, filter, path );
447
508
  };
448
509
 
449
- const add_elements = filter => {
450
-
451
- reset_bar();
510
+ const _addElements = filter => {
511
+
512
+ _resetBar();
452
513
 
453
514
  for( let m of LX.menubars )
454
- for( let i of m.items ) {
455
- propagate_add( i, filter, "" );
515
+ {
516
+ for( let i of m.items )
517
+ {
518
+ _propagateAdd( i, filter, "" );
456
519
  }
520
+ }
457
521
 
458
522
  if( LX.has('CodeEditor') )
459
523
  {
@@ -483,35 +547,38 @@ function create_global_searchbar( root ) {
483
547
  }
484
548
 
485
549
  input.addEventListener('input', function(e) {
486
- add_elements( this.value.toLowerCase() );
550
+ _addElements( this.value.toLowerCase() );
487
551
  });
488
-
489
- global_search.appendChild(icon);
490
- global_search.appendChild(input);
491
- global_search.appendChild(itemContainer);
492
552
 
493
- return global_search;
553
+ globalSearch.appendChild( header );
554
+ globalSearch.appendChild( tabArea.root );
555
+ globalSearch.appendChild( itemContainer );
556
+
557
+ return globalSearch;
494
558
  }
495
559
 
496
560
  /**
497
561
  * @method init
498
- * @param {Object} options
562
+ * @param {Object} options
499
563
  * container: Root location for the gui (default is the document body)
500
564
  * id: Id of the main area
565
+ * skipRoot: Skip adding LX root container
501
566
  * skipDefaultArea: Skip creation of main area
502
567
  */
503
568
 
504
569
  function init( options = { } )
505
570
  {
506
571
  if( this.ready )
572
+ {
507
573
  return this.main_area;
574
+ }
508
575
 
509
576
  // LexGUI root
510
577
 
511
578
  var root = document.createElement( 'div' );
512
579
  root.id = "lexroot";
513
580
  root.tabIndex = -1;
514
-
581
+
515
582
  var modal = document.createElement( 'div' );
516
583
  modal.id = "modal";
517
584
 
@@ -527,17 +594,29 @@ function init( options = { } )
527
594
 
528
595
  if( options.container )
529
596
  this.container = document.getElementById( options.container );
530
-
531
- this.global_search = create_global_searchbar( this.container );
597
+
598
+ this.globalSearch = create_global_searchbar( this.container );
532
599
 
533
600
  this.container.appendChild( modal );
534
- this.container.appendChild( root );
601
+
602
+ if( !options.skipRoot )
603
+ {
604
+ this.container.appendChild( root );
605
+ }
606
+ else
607
+ {
608
+ this.root = document.body;
609
+ }
535
610
 
536
611
  // Disable drag icon
537
612
  root.addEventListener( 'dragover', function( e ) {
538
613
  e.preventDefault();
539
614
  }, false );
540
615
 
616
+ document.addEventListener( 'contextmenu', function( e ) {
617
+ e.preventDefault();
618
+ }, false );
619
+
541
620
  // CSS fontawesome
542
621
  var head = document.getElementsByTagName( 'HEAD' )[ 0 ];
543
622
  var link = document.createElement( 'link' );
@@ -555,11 +634,16 @@ function init( options = { } )
555
634
  this.ready = true;
556
635
  this.menubars = [ ];
557
636
 
558
- if( !options.skipDefaultArea )
637
+ if( !options.skipRoot && !options.skipDefaultArea )
559
638
  {
560
639
  this.main_area = new Area( { id: options.id ?? 'mainarea' } );
561
640
  }
562
641
 
642
+ window.matchMedia( "(prefers-color-scheme: dark)" ).addEventListener( "change", event => {
643
+ const newColorScheme = event.matches ? "dark" : "light";
644
+ LX.emit( "@on_new_color_scheme", newColorScheme );
645
+ });
646
+
563
647
  return this.main_area;
564
648
  }
565
649
 
@@ -567,9 +651,9 @@ LX.init = init;
567
651
 
568
652
  /**
569
653
  * @method message
570
- * @param {String} text
654
+ * @param {String} text
571
655
  * @param {String} title (Optional)
572
- * @param {*} options
656
+ * @param {*} options
573
657
  * id: Id of the message dialog
574
658
  * position: Dialog position in screen [screen centered]
575
659
  * draggable: Dialog can be dragged [false]
@@ -593,9 +677,9 @@ LX.message = message;
593
677
 
594
678
  /**
595
679
  * @method popup
596
- * @param {String} text
680
+ * @param {String} text
597
681
  * @param {String} title (Optional)
598
- * @param {*} options
682
+ * @param {*} options
599
683
  * id: Id of the message dialog
600
684
  * time: (Number) Delay time before close automatically (ms). Defalut: [3000]
601
685
  * position: (Array) [x,y] Dialog position in screen. Default: [screen centered]
@@ -612,17 +696,17 @@ function popup( text, title, options = {} )
612
696
  options.size = options.size ?? [ "auto", "auto" ];
613
697
  options.class = "lexpopup";
614
698
 
615
- const time = options.timeout || 3000;
699
+ const time = options.timeout || 3000;
616
700
  const dialog = new Dialog( title, p => {
617
701
  p.addTextArea( null, text, null, { disabled: true, fitHeight: true } );
618
702
  }, options );
619
-
703
+
620
704
  dialog.root.classList.add( 'fadein' );
621
705
  setTimeout(() => {
622
706
  dialog.root.classList.remove( 'fadein' );
623
707
  dialog.root.classList.add( 'fadeout' );
624
708
  }, time - 1000 );
625
-
709
+
626
710
  setTimeout( dialog.close, time );
627
711
 
628
712
  return dialog;
@@ -632,9 +716,9 @@ LX.popup = popup;
632
716
 
633
717
  /**
634
718
  * @method prompt
635
- * @param {String} text
719
+ * @param {String} text
636
720
  * @param {String} title (Optional)
637
- * @param {*} options
721
+ * @param {*} options
638
722
  * id: Id of the prompt dialog
639
723
  * position: Dialog position in screen [screen centered]
640
724
  * draggable: Dialog can be dragged [false]
@@ -683,12 +767,31 @@ function prompt( text, title, callback, options = {} )
683
767
  {
684
768
  dialog.root.querySelector( 'input' ).focus();
685
769
  }
686
-
770
+
687
771
  return dialog;
688
772
  }
689
773
 
690
774
  LX.prompt = prompt;
691
775
 
776
+ /**
777
+ * @method badge
778
+ * @param {String} text
779
+ * @param {String} className
780
+ * @param {*} options
781
+ * style: Style attributes to override
782
+ */
783
+
784
+ function badge( text, className, options = {} )
785
+ {
786
+ const container = document.createElement( "div" );
787
+ container.innerHTML = text;
788
+ container.className = "lexbadge " + ( className ?? "" );
789
+ Object.assign( container.style, options.style ?? {} );
790
+ return container.outerHTML;
791
+ }
792
+
793
+ LX.badge = badge;
794
+
692
795
  /*
693
796
  * Events and Signals
694
797
  */
@@ -723,7 +826,7 @@ class TreeEvent {
723
826
  this.multiple = false; // Multiple selection
724
827
  this.panel = null;
725
828
  }
726
-
829
+
727
830
  string() {
728
831
  switch( this.type )
729
832
  {
@@ -768,7 +871,7 @@ function emit( signalName, value, options = {} )
768
871
  if( obj.constructor === Widget )
769
872
  {
770
873
  obj.set( value, options.skipCallback ?? true );
771
-
874
+
772
875
  if( obj.options && obj.options.callback )
773
876
  {
774
877
  obj.options.callback( value, data );
@@ -776,7 +879,9 @@ function emit( signalName, value, options = {} )
776
879
  }
777
880
  else
778
881
  {
779
- obj[ signalName ].call( obj, value );
882
+ // This is a function callback!
883
+ const fn = obj;
884
+ fn( null, value );
780
885
  }
781
886
  }
782
887
  }
@@ -791,7 +896,7 @@ function addSignal( name, obj, callback )
791
896
  {
792
897
  LX.signals[ name ] = [];
793
898
  }
794
-
899
+
795
900
  if( LX.signals[ name ].indexOf( obj ) > -1 )
796
901
  {
797
902
  return;
@@ -810,16 +915,20 @@ class Area {
810
915
 
811
916
  /**
812
917
  * @constructor Area
813
- * @param {*} options
918
+ * @param {*} options
814
919
  * id: Id of the element
815
920
  * className: Add class to the element
816
921
  * width: Width of the area element [fit space]
817
922
  * height: Height of the area element [fit space]
818
923
  * skipAppend: Create but not append to GUI root [false]
924
+ * minWidth: Minimum width to be applied when resizing
925
+ * minHeight: Minimum height to be applied when resizing
926
+ * maxWidth: Maximum width to be applied when resizing
927
+ * maxHeight: Maximum height to be applied when resizing
819
928
  */
820
929
 
821
930
  constructor( options = {} ) {
822
-
931
+
823
932
  var root = document.createElement( 'div' );
824
933
  root.className = "lexarea";
825
934
  if( options.id )
@@ -833,7 +942,7 @@ class Area {
833
942
 
834
943
  var width = options.width || "calc( 100% )";
835
944
  var height = options.height || "100%";
836
-
945
+
837
946
  // This has default options..
838
947
  this.setLimitBox( options.minWidth, options.minHeight, options.maxWidth, options.maxHeight );
839
948
 
@@ -883,7 +992,7 @@ class Area {
883
992
  {
884
993
  this.root.style.bottom = options.bottom;
885
994
  }
886
-
995
+
887
996
  const draggable = options.draggable ?? true;
888
997
  if( draggable )
889
998
  {
@@ -894,9 +1003,9 @@ class Area {
894
1003
  {
895
1004
  root.classList.add("resizeable");
896
1005
  }
897
-
1006
+
898
1007
  if( options.resize )
899
- {
1008
+ {
900
1009
  this.splitBar = document.createElement("div");
901
1010
  let type = (overlay == "left") || (overlay == "right") ? "horizontal" : "vertical";
902
1011
  this.type = overlay;
@@ -906,7 +1015,7 @@ class Area {
906
1015
  {
907
1016
  this.splitBar.style.width = LX.DEFAULT_SPLITBAR_SIZE + "px";
908
1017
  this.splitBar.style.left = -(LX.DEFAULT_SPLITBAR_SIZE / 2.0) + "px";
909
- }
1018
+ }
910
1019
  else if( overlay == "left" )
911
1020
  {
912
1021
  let size = Math.min(document.body.clientWidth - LX.DEFAULT_SPLITBAR_SIZE, this.root.clientWidth);
@@ -927,10 +1036,10 @@ class Area {
927
1036
 
928
1037
  this.splitBar.addEventListener("mousedown", inner_mousedown);
929
1038
  this.root.appendChild( this.splitBar );
930
-
1039
+
931
1040
  var that = this;
932
1041
  var lastMousePosition = [ 0, 0 ];
933
-
1042
+
934
1043
  function inner_mousedown( e )
935
1044
  {
936
1045
  var doc = that.root.ownerDocument;
@@ -970,13 +1079,13 @@ class Area {
970
1079
  that.root.style.height = size + "px";
971
1080
  break;
972
1081
  }
973
-
1082
+
974
1083
  lastMousePosition[ 0 ] = e.x;
975
1084
  lastMousePosition[ 1 ] = e.y;
976
1085
  e.stopPropagation();
977
1086
  e.preventDefault();
978
-
979
- // Resize events
1087
+
1088
+ // Resize events
980
1089
  if( that.onresize )
981
1090
  {
982
1091
  that.onresize( that.root.getBoundingClientRect() );
@@ -1022,11 +1131,11 @@ class Area {
1022
1131
 
1023
1132
  /**
1024
1133
  * @method split
1025
- * @param {*} options
1134
+ * @param {*} options
1026
1135
  * type: Split mode (horizontal, vertical) ["horizontal"]
1027
1136
  * sizes: Size of each new area (Array) ["50%", "50%"]
1028
1137
  */
1029
-
1138
+
1030
1139
  split( options = {} ) {
1031
1140
 
1032
1141
  if( this.sections.length )
@@ -1039,7 +1148,6 @@ class Area {
1039
1148
 
1040
1149
  var type = options.type || "horizontal";
1041
1150
  var sizes = options.sizes || [ "50%", "50%" ];
1042
- var infer_height = false;
1043
1151
  var auto = (options.sizes === 'auto');
1044
1152
 
1045
1153
  if( !sizes[ 1 ] )
@@ -1051,9 +1159,8 @@ class Area {
1051
1159
  size += margin;
1052
1160
  size += "px";
1053
1161
  }
1054
-
1162
+
1055
1163
  sizes[ 1 ] = "calc( 100% - " + size + " )";
1056
- infer_height = true;
1057
1164
  }
1058
1165
 
1059
1166
  // Create areas
@@ -1084,18 +1191,18 @@ class Area {
1084
1191
  this.splitBar.style.height = LX.DEFAULT_SPLITBAR_SIZE + "px";
1085
1192
  }
1086
1193
 
1087
- this.splitBar.addEventListener( 'mousedown', inner_mousedown );
1194
+ this.splitBar.addEventListener( 'mousedown', innerMouseDown );
1088
1195
 
1089
1196
  data = ( LX.DEFAULT_SPLITBAR_SIZE / 2 ) + "px"; // updates
1090
1197
 
1091
1198
  // Being minimizable means it's also resizeable!
1092
1199
  if( minimizable )
1093
1200
  {
1094
- this.split_extended = false;
1201
+ this.splitExtended = false;
1095
1202
 
1096
1203
  // Keep state of the animation when ends...
1097
1204
  area2.root.addEventListener('animationend', e => {
1098
- const opacity = getComputedStyle(area2.root).opacity;
1205
+ const opacity = getComputedStyle( area2.root ).opacity;
1099
1206
  area2.root.classList.remove( e.animationName + "-" + type );
1100
1207
  area2.root.style.opacity = opacity;
1101
1208
  flushCss(area2.root);
@@ -1104,8 +1211,8 @@ class Area {
1104
1211
  this.splitBar.addEventListener("contextmenu", e => {
1105
1212
  e.preventDefault();
1106
1213
  addContextMenu(null, e, c => {
1107
- c.add("Extend", { disabled: this.split_extended, callback: () => { this.extend() } });
1108
- c.add("Reduce", { disabled: !this.split_extended, callback: () => { this.reduce() } });
1214
+ c.add("Extend", { disabled: this.splitExtended, callback: () => { this.extend() } });
1215
+ c.add("Reduce", { disabled: !this.splitExtended, callback: () => { this.reduce() } });
1109
1216
  });
1110
1217
  });
1111
1218
  }
@@ -1117,9 +1224,14 @@ class Area {
1117
1224
  width2 = sizes[ 1 ];
1118
1225
 
1119
1226
  if( width1.constructor == Number )
1227
+ {
1120
1228
  width1 += "px";
1229
+ }
1230
+
1121
1231
  if( width2.constructor == Number )
1232
+ {
1122
1233
  width2 += "px";
1234
+ }
1123
1235
 
1124
1236
  area1.root.style.width = "calc( " + width1 + " - " + data + " )";
1125
1237
  area1.root.style.height = "calc(100% - 0px)";
@@ -1151,10 +1263,15 @@ class Area {
1151
1263
  var height1 = sizes[ 0 ],
1152
1264
  height2 = sizes[ 1 ];
1153
1265
 
1154
- if(height1.constructor == Number)
1266
+ if( height1.constructor == Number )
1267
+ {
1155
1268
  height1 += "px";
1156
- if(height2.constructor == Number)
1269
+ }
1270
+
1271
+ if( height2.constructor == Number )
1272
+ {
1157
1273
  height2 += "px";
1274
+ }
1158
1275
 
1159
1276
  area1.root.style.width = "100%";
1160
1277
  area1.root.style.height = "calc( " + height1 + " - " + data + " )";
@@ -1182,34 +1299,28 @@ class Area {
1182
1299
  }
1183
1300
 
1184
1301
  var that = this;
1185
- var lastMousePosition = [ 0, 0 ];
1186
1302
 
1187
- function inner_mousedown( e )
1303
+ function innerMouseDown( e )
1188
1304
  {
1189
1305
  var doc = that.root.ownerDocument;
1190
- doc.addEventListener( 'mousemove', inner_mousemove );
1191
- doc.addEventListener( 'mouseup', inner_mouseup );
1192
- lastMousePosition[0] = e.x;
1193
- lastMousePosition[1] = e.y;
1306
+ doc.addEventListener( 'mousemove', innerMouseMove );
1307
+ doc.addEventListener( 'mouseup', innerMouseUp );
1194
1308
  e.stopPropagation();
1195
1309
  e.preventDefault();
1196
1310
  document.body.classList.add( 'nocursor' );
1197
1311
  that.splitBar.classList.add( 'nocursor' );
1198
1312
  }
1199
1313
 
1200
- function inner_mousemove( e )
1314
+ function innerMouseMove( e )
1201
1315
  {
1202
- if(that.type == "horizontal")
1316
+ if( that.type == "horizontal" )
1203
1317
  {
1204
- that._moveSplit( lastMousePosition[ 0 ] - e.x );
1318
+ that._moveSplit( -e.movementX );
1205
1319
  }
1206
1320
  else
1207
1321
  {
1208
- that._moveSplit( lastMousePosition[ 1 ] - e.y );
1322
+ that._moveSplit( -e.movementY );
1209
1323
  }
1210
-
1211
- lastMousePosition[ 0 ] = e.x;
1212
- lastMousePosition[ 1 ] = e.y;
1213
1324
 
1214
1325
  const widgets = that.root.querySelectorAll( ".lexwidget" );
1215
1326
 
@@ -1228,11 +1339,11 @@ class Area {
1228
1339
  e.preventDefault();
1229
1340
  }
1230
1341
 
1231
- function inner_mouseup( e )
1342
+ function innerMouseUp( e )
1232
1343
  {
1233
1344
  var doc = that.root.ownerDocument;
1234
- doc.removeEventListener( 'mousemove', inner_mousemove );
1235
- doc.removeEventListener( 'mouseup', inner_mouseup );
1345
+ doc.removeEventListener( 'mousemove', innerMouseMove );
1346
+ doc.removeEventListener( 'mouseup', innerMouseUp );
1236
1347
  document.body.classList.remove( 'nocursor' );
1237
1348
  that.splitBar.classList.remove( 'nocursor' );
1238
1349
  }
@@ -1257,7 +1368,7 @@ class Area {
1257
1368
  * Resize element
1258
1369
  */
1259
1370
  setSize( size ) {
1260
-
1371
+
1261
1372
  let [ width, height ] = size;
1262
1373
 
1263
1374
  if( width != undefined && width.constructor == Number )
@@ -1291,22 +1402,22 @@ class Area {
1291
1402
  */
1292
1403
  extend() {
1293
1404
 
1294
- if( this.split_extended )
1405
+ if( this.splitExtended )
1295
1406
  {
1296
1407
  return;
1297
1408
  }
1298
1409
 
1299
1410
  let [area1, area2] = this.sections;
1300
- this.split_extended = true;
1411
+ this.splitExtended = true;
1301
1412
 
1302
1413
  if(this.type == "vertical")
1303
1414
  {
1304
1415
  this.offset = area2.root.offsetHeight;
1305
1416
  area2.root.classList.add("fadeout-vertical");
1306
- this._moveSplit(-Infinity, true);
1417
+ this._moveSplit(-Infinity, true);
1307
1418
 
1308
1419
  }
1309
- else
1420
+ else
1310
1421
  {
1311
1422
  this.offset = area2.root.offsetWidth - 8; // Force some height here...
1312
1423
  area2.root.classList.add("fadeout-horizontal");
@@ -1323,10 +1434,10 @@ class Area {
1323
1434
  */
1324
1435
  reduce() {
1325
1436
 
1326
- if( !this.split_extended )
1437
+ if( !this.splitExtended )
1327
1438
  return;
1328
-
1329
- this.split_extended = false;
1439
+
1440
+ this.splitExtended = false;
1330
1441
  let [area1, area2] = this.sections;
1331
1442
 
1332
1443
  if(this.type == "vertical")
@@ -1404,7 +1515,7 @@ class Area {
1404
1515
  */
1405
1516
 
1406
1517
  addMenubar( callback, options = {} ) {
1407
-
1518
+
1408
1519
  let menubar = new Menubar(options);
1409
1520
 
1410
1521
  if(callback) callback( menubar );
@@ -1448,10 +1559,11 @@ class Area {
1448
1559
  */
1449
1560
 
1450
1561
  addOverlayButtons( buttons, options = {} ) {
1451
-
1562
+
1452
1563
  // Add to last split section if area has been split
1453
- if(this.sections.length) {
1454
- this.sections[1].addOverlayButtons( buttons, options );
1564
+ if( this.sections.length )
1565
+ {
1566
+ this.sections[ 1 ].addOverlayButtons( buttons, options );
1455
1567
  return;
1456
1568
  }
1457
1569
 
@@ -1461,8 +1573,14 @@ class Area {
1461
1573
  this.root.style.position = "relative";
1462
1574
 
1463
1575
  options.className = "lexoverlaybuttons";
1464
- options.width = "calc( 100% - 24px )";
1465
- options.height = "auto";
1576
+
1577
+ let overlayPanel = this.addPanel( options );
1578
+ let overlayGroup = null;
1579
+
1580
+ const container = document.createElement("div");
1581
+ container.className = "lexoverlaybuttonscontainer";
1582
+ container.appendChild( overlayPanel.root );
1583
+ this.attach( container );
1466
1584
 
1467
1585
  const float = options.float;
1468
1586
 
@@ -1474,24 +1592,21 @@ class Area {
1474
1592
  switch( t )
1475
1593
  {
1476
1594
  case 'h': break;
1477
- case 'v': options.className += " vertical"; break;
1595
+ case 'v': container.className += " vertical"; break;
1478
1596
  case 't': break;
1479
- case 'm': options.className += " middle"; break;
1480
- case 'b': options.className += " bottom"; break;
1597
+ case 'm': container.className += " middle"; break;
1598
+ case 'b': container.className += " bottom"; break;
1481
1599
  case 'l': break;
1482
- case 'c': options.className += " center"; break;
1483
- case 'r': options.className += " right"; break;
1600
+ case 'c': container.className += " center"; break;
1601
+ case 'r': container.className += " right"; break;
1484
1602
  }
1485
1603
  }
1486
1604
  }
1487
1605
 
1488
- let overlayPanel = this.addPanel( options );
1489
- let overlaygroup;
1490
-
1491
- const add_button = function(b, group, last) {
1606
+ const _addButton = function( b, group, last ) {
1492
1607
 
1493
- const _options = {
1494
- width: "auto",
1608
+ const _options = {
1609
+ width: "auto",
1495
1610
  selectable: b.selectable,
1496
1611
  selected: b.selected,
1497
1612
  icon: b.icon,
@@ -1501,55 +1616,54 @@ class Area {
1501
1616
 
1502
1617
  if( group )
1503
1618
  {
1504
- if(!overlaygroup) {
1505
- overlaygroup = document.createElement('div');
1506
- overlaygroup.className = "lexoverlaygroup";
1507
- overlayPanel.queuedContainer = overlaygroup;
1619
+ if( !overlayGroup )
1620
+ {
1621
+ overlayGroup = document.createElement('div');
1622
+ overlayGroup.className = "lexoverlaygroup";
1623
+ overlayPanel.queuedContainer = overlayGroup;
1508
1624
  }
1509
1625
 
1510
- _options.parent = overlaygroup;
1626
+ _options.parent = overlayGroup;
1511
1627
  }
1512
1628
 
1513
1629
  let callback = b.callback;
1514
1630
 
1515
1631
  if( b.options )
1516
1632
  {
1517
- callback = function(value, event) {
1518
- LX.addContextMenu(null, event, function(c) {
1519
- for( let o of b.options )
1520
- c.add(o, () => {
1521
- if( b.name == o ) return;
1522
- b.name = o;
1523
- b.callback( o );
1524
- refresh_panel();
1525
- });
1526
- });
1527
- };
1633
+ overlayPanel.addDropdown( null, b.options, b.name, callback, _options );
1528
1634
  }
1529
-
1530
- overlayPanel.addButton( null, b.name, function(value, event) {
1531
- if(b.selectable) {
1532
- if( b.group ) {
1533
- let _prev = b.selected;
1534
- b.group.forEach( sub => sub.selected = false );
1535
- b.selected = !_prev;
1635
+ else
1636
+ {
1637
+ overlayPanel.addButton( null, b.name, function( value, event ) {
1638
+ if( b.selectable )
1639
+ {
1640
+ if( b.group )
1641
+ {
1642
+ let _prev = b.selected;
1643
+ b.group.forEach( sub => sub.selected = false );
1644
+ b.selected = !_prev;
1645
+ }
1646
+ else
1647
+ {
1648
+ b.selected = !b.selected;
1649
+ }
1536
1650
  }
1537
- else
1538
- b.selected = !b.selected;
1539
- }
1540
- callback( value, event );
1541
- }, _options );
1651
+
1652
+ callback( value, event );
1653
+
1654
+ }, _options );
1655
+ }
1542
1656
 
1543
1657
  // ends the group
1544
- if(overlaygroup && last)
1658
+ if( overlayGroup && last )
1545
1659
  {
1546
- overlayPanel.root.appendChild( overlaygroup );
1547
- overlaygroup = null;
1660
+ overlayPanel.root.appendChild( overlayGroup );
1661
+ overlayGroup = null;
1548
1662
  overlayPanel.clearQueue();
1549
1663
  }
1550
1664
  }
1551
1665
 
1552
- const refresh_panel = function() {
1666
+ const _refreshPanel = function() {
1553
1667
 
1554
1668
  overlayPanel.clear();
1555
1669
 
@@ -1559,13 +1673,14 @@ class Area {
1559
1673
  {
1560
1674
  for( let i = 0; i < b.length; ++i )
1561
1675
  {
1562
- let sub = b[i];
1676
+ let sub = b[ i ];
1563
1677
  sub.group = b;
1564
- add_button(sub, true, i == (b.length - 1));
1678
+ _addButton(sub, true, i == ( b.length - 1 ));
1565
1679
  }
1566
- }else
1680
+ }
1681
+ else
1567
1682
  {
1568
- add_button(b);
1683
+ _addButton( b );
1569
1684
  }
1570
1685
 
1571
1686
  }
@@ -1576,16 +1691,16 @@ class Area {
1576
1691
  var height = 0;
1577
1692
  overlayPanel.root.childNodes.forEach( c => { height += c.offsetHeight; } );
1578
1693
 
1579
- if( options.className.includes("middle") )
1694
+ if( container.className.includes( "middle" ) )
1580
1695
  {
1581
- overlayPanel.root.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
1582
- overlayPanel.root.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
1583
- overlayPanel.root.style.top = "calc( 50% - " + (height * 0.5) + "px )";
1696
+ container.style.top = "-moz-calc( 50% - " + (height * 0.5) + "px )";
1697
+ container.style.top = "-webkit-calc( 50% - " + (height * 0.5) + "px )";
1698
+ container.style.top = "calc( 50% - " + (height * 0.5) + "px )";
1584
1699
  }
1585
1700
  }
1586
1701
  }
1587
1702
 
1588
- refresh_panel();
1703
+ _refreshPanel();
1589
1704
  }
1590
1705
 
1591
1706
  /**
@@ -1601,65 +1716,77 @@ class Area {
1601
1716
  {
1602
1717
  this.parentArea._disableSplitResize();
1603
1718
  // Compensate split bar...
1604
- this.root.style.paddingTop = "4px";
1719
+ this.root.style.paddingTop = "4px";
1605
1720
  }
1606
1721
 
1607
1722
  return tabs;
1608
1723
  }
1609
1724
 
1610
- _moveSplit( dt, force_animation = false, force_width = 0 ) {
1725
+ _moveSplit( dt, forceAnimation = false, forceWidth = 0 ) {
1611
1726
 
1612
1727
  if( !this.type )
1728
+ {
1613
1729
  throw( "No split area" );
1730
+ }
1614
1731
 
1615
1732
  if( dt === undefined ) // Splitbar didn't move!
1733
+ {
1616
1734
  return;
1735
+ }
1736
+
1737
+ const a1 = this.sections[ 0 ];
1738
+ var a1Root = a1.root;
1739
+
1740
+ if( !a1Root.classList.contains( "origin" ) )
1741
+ {
1742
+ a1Root = a1Root.parentElement;
1743
+ }
1617
1744
 
1618
- var a1 = this.sections[ 0 ];
1619
- var a2 = this.sections[ 1 ];
1620
- var splitinfo = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1745
+ const a2 = this.sections[ 1 ];
1746
+ const a2Root = a2.root;
1747
+ const splitData = " - "+ LX.DEFAULT_SPLITBAR_SIZE + "px";
1621
1748
 
1622
1749
  let transition = null;
1623
- if( !force_animation )
1750
+ if( !forceAnimation )
1624
1751
  {
1625
1752
  // Remove transitions for this change..
1626
- transition = a1.root.style.transition;
1627
- a1.root.style.transition = a2.root.style.transition = "none";
1628
- flushCss( a1.root );
1629
- flushCss( a2.root );
1753
+ transition = a1Root.style.transition;
1754
+ a1Root.style.transition = a2Root.style.transition = "none";
1755
+ flushCss( a1Root );
1756
+ flushCss( a2Root );
1630
1757
  }
1631
1758
 
1632
1759
  if( this.type == "horizontal" )
1633
1760
  {
1634
- var size = Math.max( a2.root.offsetWidth + dt, parseInt( a2.minWidth ) );
1635
- if( force_width ) size = force_width;
1636
- a1.root.style.width = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1637
- a1.root.style.width = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1638
- a1.root.style.width = "calc( 100% - " + size + "px " + splitinfo + " )";
1639
- a1.root.style.minWidth = parseInt( a1.minWidth ) + "px";
1640
- a2.root.style.width = size + "px";
1641
- if( a1.maxWidth != Infinity ) a2.root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
1761
+ var size = Math.max( a2Root.offsetWidth + dt, parseInt( a2.minWidth ) );
1762
+ if( forceWidth ) size = forceWidth;
1763
+ a1Root.style.width = "-moz-calc( 100% - " + size + "px " + splitData + " )";
1764
+ a1Root.style.width = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
1765
+ a1Root.style.width = "calc( 100% - " + size + "px " + splitData + " )";
1766
+ a1Root.style.minWidth = parseInt( a1.minWidth ) + "px";
1767
+ a2Root.style.width = size + "px";
1768
+ if( a1.maxWidth != Infinity ) a2Root.style.minWidth = "calc( 100% - " + parseInt( a1.maxWidth ) + "px" + " )";
1642
1769
  }
1643
1770
  else
1644
1771
  {
1645
- var size = Math.max((a2.root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
1646
- if( force_width ) size = force_width;
1647
- a1.root.style.height = "-moz-calc( 100% - " + size + "px " + splitinfo + " )";
1648
- a1.root.style.height = "-webkit-calc( 100% - " + size + "px " + splitinfo + " )";
1649
- a1.root.style.height = "calc( 100% - " + size + "px " + splitinfo + " )";
1650
- a1.root.style.minHeight = a1.minHeight + "px";
1651
- a2.root.style.height = ( size - a2.offset ) + "px";
1772
+ var size = Math.max((a2Root.offsetHeight + dt) + a2.offset, parseInt(a2.minHeight));
1773
+ if( forceWidth ) size = forceWidth;
1774
+ a1Root.style.height = "-moz-calc( 100% - " + size + "px " + splitData + " )";
1775
+ a1Root.style.height = "-webkit-calc( 100% - " + size + "px " + splitData + " )";
1776
+ a1Root.style.height = "calc( 100% - " + size + "px " + splitData + " )";
1777
+ a1Root.style.minHeight = a1.minHeight + "px";
1778
+ a2Root.style.height = ( size - a2.offset ) + "px";
1652
1779
  }
1653
-
1654
- if( !force_animation )
1780
+
1781
+ if( !forceAnimation )
1655
1782
  {
1656
1783
  // Reapply transitions
1657
- a1.root.style.transition = a2.root.style.transition = transition;
1784
+ a1Root.style.transition = a2Root.style.transition = transition;
1658
1785
  }
1659
1786
 
1660
1787
  this._update();
1661
1788
 
1662
- // Resize events
1789
+ // Resize events
1663
1790
  this.propagateEvent( 'onresize' );
1664
1791
  }
1665
1792
 
@@ -1741,7 +1868,7 @@ class Tabs {
1741
1868
 
1742
1869
  // Show on drop
1743
1870
  el.click();
1744
-
1871
+
1745
1872
  // Store info
1746
1873
  that.tabs[ el.dataset["name"] ] = content;
1747
1874
  });
@@ -1782,11 +1909,11 @@ class Tabs {
1782
1909
  }
1783
1910
 
1784
1911
  // debug
1785
- if(folding)
1912
+ if(folding)
1786
1913
  {
1787
1914
  this.folded = true;
1788
1915
  this.folding = folding;
1789
-
1916
+
1790
1917
  if(folding == "up") area.root.insertChildAtIndex(area.sections[1].root, 0);
1791
1918
 
1792
1919
  // Listen resize event on parent area
@@ -1813,7 +1940,7 @@ class Tabs {
1813
1940
  this.root.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ) );
1814
1941
  this.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1815
1942
  }
1816
-
1943
+
1817
1944
  isSelected = !Object.keys( this.tabs ).length && !this.folding ? true : isSelected;
1818
1945
 
1819
1946
  let contentEl = content.root ? content.root : content;
@@ -1841,7 +1968,7 @@ class Tabs {
1841
1968
  tabEl.className = "lexareatab" + ( isSelected ? " selected" : "" );
1842
1969
  tabEl.innerHTML = ( options.icon ?? "" ) + name;
1843
1970
  tabEl.id = name.replace( /\s/g, '' ) + Tabs.TAB_ID++;
1844
- tabEl.title = options.title;
1971
+ tabEl.title = options.title ?? "";
1845
1972
  tabEl.selected = isSelected ?? false;
1846
1973
  tabEl.fixed = options.fixed;
1847
1974
  tabEl.instance = this;
@@ -1856,9 +1983,9 @@ class Tabs {
1856
1983
  if( this.parentElement.childNodes.length == 1 )
1857
1984
  {
1858
1985
  this.parentElement.childNodes[ 0 ].click(); // single tab!!
1859
- }
1986
+ }
1860
1987
  } );
1861
-
1988
+
1862
1989
  tabEl.addEventListener("click", e => {
1863
1990
 
1864
1991
  e.preventDefault();
@@ -1869,11 +1996,11 @@ class Tabs {
1869
1996
  // For folding tabs
1870
1997
  const lastValue = tabEl.selected;
1871
1998
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.selected = false );
1872
- tabEl.selected = !lastValue;
1999
+ tabEl.selected = !lastValue;
1873
2000
  // Manage selected
1874
2001
  tabEl.parentElement.querySelectorAll( 'span' ).forEach( s => s.classList.remove( 'selected' ));
1875
2002
  tabEl.classList.toggle('selected', ( this.folding && tabEl.selected ));
1876
- // Manage visibility
2003
+ // Manage visibility
1877
2004
  tabEl.instance.area.root.querySelectorAll( '.lextabcontent' ).forEach( c => c.style.display = 'none' );
1878
2005
  contentEl.style.display = contentEl.originalDisplay;
1879
2006
  tabEl.instance.selected = tabEl.dataset.name;
@@ -1917,16 +2044,16 @@ class Tabs {
1917
2044
  this.delete( tabEl.dataset[ "name" ] );
1918
2045
  }
1919
2046
  });
1920
-
2047
+
1921
2048
  tabEl.setAttribute( 'draggable', true );
1922
2049
  tabEl.addEventListener( 'dragstart', function( e ) {
1923
2050
  if( this.parentElement.childNodes.length == 1 ){
1924
2051
  e.preventDefault();
1925
2052
  return;
1926
- }
2053
+ }
1927
2054
  e.dataTransfer.setData( 'source', e.target.id );
1928
2055
  });
1929
-
2056
+
1930
2057
  // Attach content
1931
2058
  tabEl.childIndex = ( this.root.childElementCount - 1 );
1932
2059
  this.root.appendChild( tabEl );
@@ -2005,7 +2132,7 @@ class Menubar {
2005
2132
  if(options.float)
2006
2133
  this.root.style.justifyContent = options.float;
2007
2134
  this.items = [];
2008
-
2135
+
2009
2136
  this.icons = {};
2010
2137
  this.shorts = {};
2011
2138
  this.buttons = [];
@@ -2044,7 +2171,7 @@ class Menubar {
2044
2171
  } );
2045
2172
 
2046
2173
  if(found) {
2047
- insert( tokens[idx++], found );
2174
+ insert( tokens[idx++], found );
2048
2175
  }
2049
2176
  else {
2050
2177
  let item = {};
@@ -2057,7 +2184,7 @@ class Menubar {
2057
2184
  item[ 'checked' ] = options.checked;
2058
2185
  }
2059
2186
  list.push( item );
2060
- insert( next_token, item[ token ] );
2187
+ insert( next_token, item[ token ] );
2061
2188
  }
2062
2189
  };
2063
2190
 
@@ -2072,7 +2199,7 @@ class Menubar {
2072
2199
 
2073
2200
  // Item already created
2074
2201
  if( this.root.querySelector("#" + pKey) )
2075
- continue;
2202
+ continue;
2076
2203
 
2077
2204
  let entry = document.createElement('div');
2078
2205
  entry.className = "lexmenuentry";
@@ -2113,21 +2240,21 @@ class Menubar {
2113
2240
  const subitem = o[k][i];
2114
2241
  const subkey = Object.keys(subitem)[0];
2115
2242
  const hasSubmenu = subitem[ subkey ].length;
2116
- const is_checkbox = subitem[ 'type' ] == 'checkbox';
2243
+ const isCheckbox = subitem[ 'type' ] == 'checkbox';
2117
2244
  let subentry = document.createElement('div');
2118
2245
  subentry.className = "lexcontextmenuentry";
2119
2246
  subentry.className += (i == o[k].length - 1 ? " last" : "");
2120
2247
  if(subkey == '')
2121
2248
  subentry.className = " lexseparator";
2122
2249
  else {
2123
-
2250
+
2124
2251
  subentry.id = subkey;
2125
2252
  let subentrycont = document.createElement('div');
2126
2253
  subentrycont.innerHTML = "";
2127
2254
  subentrycont.classList = "lexcontextmenuentrycontainer";
2128
2255
  subentry.appendChild(subentrycont);
2129
2256
  const icon = that.icons[ subkey ];
2130
- if(is_checkbox){
2257
+ if(isCheckbox){
2131
2258
  subentrycont.innerHTML += "<input type='checkbox' >";
2132
2259
  }else if(icon) {
2133
2260
  subentrycont.innerHTML += "<a class='" + icon + " fa-sm'></a>";
@@ -2147,8 +2274,8 @@ class Menubar {
2147
2274
  const f = subitem[ 'callback' ];
2148
2275
  if(f) {
2149
2276
  f.call( this, subitem.checked, subkey, subentry );
2150
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2151
- }
2277
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2278
+ }
2152
2279
  e.stopPropagation();
2153
2280
  e.stopImmediatePropagation();
2154
2281
  })
@@ -2178,8 +2305,8 @@ class Menubar {
2178
2305
  const f = subitem[ 'callback' ];
2179
2306
  if(f) {
2180
2307
  f.call( this, checkbox_input ? subitem.checked : subkey, checkbox_input ? subkey : subentry );
2181
- that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2182
- }
2308
+ that.root.querySelectorAll(".lexcontextmenu").forEach(e => e.remove());
2309
+ }
2183
2310
  e.stopPropagation();
2184
2311
  e.stopImmediatePropagation();
2185
2312
  });
@@ -2225,7 +2352,7 @@ class Menubar {
2225
2352
  if(f) {
2226
2353
  f.call( this, key, entry );
2227
2354
  return;
2228
- }
2355
+ }
2229
2356
 
2230
2357
  // Manage selected
2231
2358
  this.root.querySelectorAll(".lexmenuentry").forEach( e => e.classList.remove( 'selected' ) );
@@ -2257,7 +2384,7 @@ class Menubar {
2257
2384
  * @param {Array} tokens: split path strings
2258
2385
  */
2259
2386
  getSubitem(item, tokens) {
2260
-
2387
+
2261
2388
  let subitem = null;
2262
2389
  let path = tokens[0];
2263
2390
  for(let i = 0; i < item.length; i++) {
@@ -2271,7 +2398,7 @@ class Menubar {
2271
2398
  tokens.splice(0,1);
2272
2399
  return this.getSubitem(item[i][path], tokens);
2273
2400
  }
2274
-
2401
+
2275
2402
  }
2276
2403
  }
2277
2404
  }
@@ -2283,7 +2410,7 @@ class Menubar {
2283
2410
  getItem( path ) {
2284
2411
  // process path
2285
2412
  const tokens = path.split("/");
2286
-
2413
+
2287
2414
  return this.getSubitem(this.items, tokens)
2288
2415
  }
2289
2416
 
@@ -2395,7 +2522,7 @@ class Menubar {
2395
2522
  }
2396
2523
  else {
2397
2524
  this.root.appendChild( this.buttonContainer );
2398
- }
2525
+ }
2399
2526
  }
2400
2527
 
2401
2528
  for( let i = 0; i < buttons.length; ++i )
@@ -2541,34 +2668,36 @@ LX.SideBar = SideBar;
2541
2668
  */
2542
2669
 
2543
2670
  class Widget {
2544
-
2671
+
2545
2672
  static NONE = 0;
2546
2673
  static TEXT = 1;
2547
2674
  static TEXTAREA = 2;
2548
2675
  static BUTTON = 3;
2549
2676
  static DROPDOWN = 4;
2550
2677
  static CHECKBOX = 5;
2551
- static COLOR = 6;
2552
- static NUMBER = 7;
2553
- static TITLE = 8;
2554
- static VECTOR = 9;
2555
- static TREE = 10;
2556
- static PROGRESS = 11;
2557
- static FILE = 12;
2558
- static LAYERS = 13;
2559
- static ARRAY = 14;
2560
- static LIST = 15;
2561
- static TAGS = 16;
2562
- static CURVE = 17;
2563
- static CARD = 18;
2564
- static IMAGE = 19;
2565
- static CONTENT = 20;
2566
- static CUSTOM = 21;
2567
- static SEPARATOR = 22;
2568
- static KNOB = 23;
2569
- static SIZE = 24;
2570
- static PAD = 25;
2571
- static FORM = 26;
2678
+ static TOGGLE = 6;
2679
+ static COLOR = 7;
2680
+ static NUMBER = 8;
2681
+ static TITLE = 9;
2682
+ static VECTOR = 10;
2683
+ static TREE = 11;
2684
+ static PROGRESS = 12;
2685
+ static FILE = 13;
2686
+ static LAYERS = 14;
2687
+ static ARRAY = 15;
2688
+ static LIST = 16;
2689
+ static TAGS = 17;
2690
+ static CURVE = 18;
2691
+ static CARD = 19;
2692
+ static IMAGE = 20;
2693
+ static CONTENT = 21;
2694
+ static CUSTOM = 22;
2695
+ static SEPARATOR = 23;
2696
+ static KNOB = 24;
2697
+ static SIZE = 25;
2698
+ static PAD = 26;
2699
+ static FORM = 27;
2700
+ static DIAL = 28;
2572
2701
 
2573
2702
  static NO_CONTEXT_TYPES = [
2574
2703
  Widget.BUTTON,
@@ -2641,6 +2770,7 @@ class Widget {
2641
2770
  case Widget.BUTTON: return "Button";
2642
2771
  case Widget.DROPDOWN: return "Dropdown";
2643
2772
  case Widget.CHECKBOX: return "Checkbox";
2773
+ case Widget.TOGGLE: return "Toggle";
2644
2774
  case Widget.COLOR: return "Color";
2645
2775
  case Widget.NUMBER: return "Number";
2646
2776
  case Widget.VECTOR: return "Vector";
@@ -2656,12 +2786,15 @@ class Widget {
2656
2786
  case Widget.SIZE: return "Size";
2657
2787
  case Widget.PAD: return "Pad";
2658
2788
  case Widget.FORM: return "Form";
2789
+ case Widget.DIAL: return "Dial";
2659
2790
  case Widget.CUSTOM: return this.customName;
2660
2791
  }
2792
+
2793
+ console.error( `Unknown Widget type: ${ this.type }` );
2661
2794
  }
2662
2795
 
2663
2796
  refresh() {
2664
-
2797
+
2665
2798
  }
2666
2799
  }
2667
2800
 
@@ -2712,15 +2845,15 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2712
2845
  buttonName += custom_widget_name + (!instance ? " [empty]" : "");
2713
2846
  // Add alwayis icon to keep spacing right
2714
2847
  buttonName += "<a class='fa-solid " + (instance ? "fa-bars-staggered" : " ") + " menu' style='float:right; width:5%;'></a>";
2715
-
2848
+
2716
2849
  let buttonEl = this.addButton(null, buttonName, (value, event) => {
2717
2850
 
2718
2851
  if( instance ) {
2719
2852
  element.querySelector(".lexcustomitems").toggleAttribute('hidden');
2720
2853
  }
2721
2854
  else {
2722
- addContextMenu(null, event, c => {
2723
- c.add("New " + custom_widget_name, () => {
2855
+ addContextMenu(null, event, c => {
2856
+ c.add("New " + custom_widget_name, () => {
2724
2857
  instance = {};
2725
2858
  refresh_widget();
2726
2859
  element.querySelector(".lexcustomitems").toggleAttribute('hidden', false);
@@ -2729,7 +2862,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2729
2862
  }
2730
2863
 
2731
2864
  }, { buttonClass: 'custom' });
2732
-
2865
+
2733
2866
  this.clearQueue();
2734
2867
 
2735
2868
  if(instance)
@@ -2749,7 +2882,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2749
2882
  custom_widgets = document.createElement('div');
2750
2883
  custom_widgets.className = "lexcustomitems";
2751
2884
  custom_widgets.toggleAttribute('hidden', true);
2752
-
2885
+
2753
2886
  element.appendChild( container );
2754
2887
  element.appendChild( custom_widgets );
2755
2888
 
@@ -2757,7 +2890,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2757
2890
  {
2758
2891
 
2759
2892
  this.queue( custom_widgets );
2760
-
2893
+
2761
2894
  const on_instance_changed = ( key, value, event ) => {
2762
2895
  instance[ key ] = value;
2763
2896
  this._trigger( new IEvent( name, instance, event ), callback );
@@ -2766,7 +2899,7 @@ function ADD_CUSTOM_WIDGET( custom_widget_name, options = {} )
2766
2899
  for( let key in default_instance )
2767
2900
  {
2768
2901
  const value = instance[ key ] ?? default_instance[ key ];
2769
-
2902
+
2770
2903
  switch( value.constructor )
2771
2904
  {
2772
2905
  case String:
@@ -2813,7 +2946,7 @@ LX.ADD_CUSTOM_WIDGET = ADD_CUSTOM_WIDGET;
2813
2946
  */
2814
2947
 
2815
2948
  class NodeTree {
2816
-
2949
+
2817
2950
  constructor( domEl, data, options ) {
2818
2951
 
2819
2952
  this.domEl = domEl;
@@ -2850,7 +2983,7 @@ class NodeTree {
2850
2983
  node.parent = parent;
2851
2984
  let isParent = node.children.length > 0;
2852
2985
  let isSelected = this.selected.indexOf( node ) > -1 || node.selected;
2853
-
2986
+
2854
2987
  if( this.options.onlyFolders )
2855
2988
  {
2856
2989
  let has_folders = false;
@@ -2867,7 +3000,7 @@ class NodeTree {
2867
3000
  let icon = (this.options.skip_default_icon ?? true) ? "" : "fa-solid fa-square"; // Default: no childs
2868
3001
  if( isParent ) icon = node.closed ? "fa-solid fa-caret-right" : "fa-solid fa-caret-down";
2869
3002
  item.innerHTML = "<a class='" + icon + " hierarchy'></a>";
2870
-
3003
+
2871
3004
  // Add display icon
2872
3005
  icon = node.icon;
2873
3006
 
@@ -2902,7 +3035,7 @@ class NodeTree {
2902
3035
  list.querySelectorAll( "li" ).forEach( e => { e.classList.remove( 'selected' ); } );
2903
3036
  this.selected.length = 0;
2904
3037
  }
2905
-
3038
+
2906
3039
  // Add or remove
2907
3040
  const idx = this.selected.indexOf( node );
2908
3041
  if( idx > -1 ) {
@@ -3069,7 +3202,7 @@ class NodeTree {
3069
3202
  let name_input = document.createElement('input');
3070
3203
  name_input.toggleAttribute('hidden', !node.rename);
3071
3204
  name_input.value = node.id;
3072
- item.appendChild(name_input);
3205
+ item.appendChild(name_input);
3073
3206
 
3074
3207
  if(node.rename) {
3075
3208
  item.classList.add('selected');
@@ -3162,13 +3295,13 @@ class NodeTree {
3162
3295
  delete window.__tree_node_dragged;
3163
3296
  });
3164
3297
  }
3165
-
3298
+
3166
3299
  let handled = false;
3167
3300
 
3168
3301
  // Show/hide children
3169
3302
  if(isParent) {
3170
3303
  item.querySelector('a.hierarchy').addEventListener("click", function(e) {
3171
-
3304
+
3172
3305
  handled = true;
3173
3306
  e.stopImmediatePropagation();
3174
3307
  e.stopPropagation();
@@ -3202,8 +3335,8 @@ class NodeTree {
3202
3335
 
3203
3336
  item.appendChild(visibility);
3204
3337
  }
3205
-
3206
- if(node.actions)
3338
+
3339
+ if(node.actions)
3207
3340
  {
3208
3341
  for(var i = 0; i < node.actions.length; ++i) {
3209
3342
  let a = node.actions[i];
@@ -3262,11 +3395,12 @@ class NodeTree {
3262
3395
  class Panel {
3263
3396
 
3264
3397
  /**
3265
- * @param {*} options
3398
+ * @param {*} options
3266
3399
  * id: Id of the element
3267
3400
  * className: Add class to the element
3268
3401
  * width: Width of the panel element [fit space]
3269
3402
  * height: Height of the panel element [fit space]
3403
+ * style: CSS Style object to be applied to the panel
3270
3404
  */
3271
3405
 
3272
3406
  constructor( options = {} ) {
@@ -3413,7 +3547,7 @@ class Panel {
3413
3547
  this._inlineContainer.style.justifyContent = justifyContent;
3414
3548
  }
3415
3549
  }
3416
-
3550
+
3417
3551
  // Push all elements single element or Array[element, container]
3418
3552
  for( let item of this._inlineWidgets )
3419
3553
  {
@@ -3421,17 +3555,17 @@ class Panel {
3421
3555
 
3422
3556
  if(is_pair)
3423
3557
  {
3424
- // eg. an array, inline items appended later to
3558
+ // eg. an array, inline items appended later to
3425
3559
  if(this._inline_queued_container)
3426
3560
  this._inlineContainer.appendChild( item[0] );
3427
3561
  // eg. a dropdown, item is appended to parent, not to inline cont.
3428
3562
  else
3429
3563
  item[1].appendChild(item[0]);
3430
- }
3564
+ }
3431
3565
  else
3432
3566
  this._inlineContainer.appendChild( item );
3433
3567
  }
3434
-
3568
+
3435
3569
  if(!this._inline_queued_container)
3436
3570
  {
3437
3571
  if(this.current_branch)
@@ -3451,7 +3585,7 @@ class Panel {
3451
3585
  /**
3452
3586
  * @method branch
3453
3587
  * @param {String} name Name of the branch/section
3454
- * @param {*} options
3588
+ * @param {*} options
3455
3589
  * id: Id of the branch
3456
3590
  * className: Add class to the branch
3457
3591
  * closed: Set branch collapsed/opened [false]
@@ -3578,7 +3712,7 @@ class Panel {
3578
3712
  widget.oncontextmenu( e );
3579
3713
  });
3580
3714
  }
3581
-
3715
+
3582
3716
  this.widgets[ name ] = widget;
3583
3717
  }
3584
3718
 
@@ -3607,7 +3741,7 @@ class Panel {
3607
3741
 
3608
3742
  if(this.current_branch)
3609
3743
  {
3610
- if(!options.skipWidget)
3744
+ if(!options.skipWidget)
3611
3745
  this.current_branch.widgets.push( widget );
3612
3746
  this.current_branch.content.appendChild( el );
3613
3747
  }
@@ -3616,7 +3750,7 @@ class Panel {
3616
3750
  el.classList.add("nobranch");
3617
3751
  this.root.appendChild( el );
3618
3752
  }
3619
- }
3753
+ }
3620
3754
  // Append content to queued tab container
3621
3755
  else {
3622
3756
  this.queuedContainer.appendChild( el );
@@ -3627,7 +3761,7 @@ class Panel {
3627
3761
 
3628
3762
  if(!this.queuedContainer) {
3629
3763
  this._inlineWidgets.push( el );
3630
- }
3764
+ }
3631
3765
  // Append content to queued tab container
3632
3766
  else {
3633
3767
  this._inlineWidgets.push( [el, this.queuedContainer] );
@@ -3666,7 +3800,7 @@ class Panel {
3666
3800
  let widget = this.create_widget(null, Widget.TEXT, options);
3667
3801
  let element = widget.domEl;
3668
3802
  element.className += " lexfilter noname";
3669
-
3803
+
3670
3804
  let input = document.createElement('input');
3671
3805
  input.className = 'lexinput-filter';
3672
3806
  input.setAttribute("placeholder", options.placeholder);
@@ -3678,9 +3812,9 @@ class Panel {
3678
3812
  element.appendChild(input);
3679
3813
  element.appendChild(searchIcon);
3680
3814
 
3681
- input.addEventListener("input", (e) => {
3815
+ input.addEventListener("input", (e) => {
3682
3816
  if(options.callback)
3683
- options.callback(input.value, e);
3817
+ options.callback(input.value, e);
3684
3818
  });
3685
3819
 
3686
3820
  return element;
@@ -3692,7 +3826,7 @@ class Panel {
3692
3826
 
3693
3827
  if(b.name !== branchName)
3694
3828
  continue;
3695
-
3829
+
3696
3830
  // remove all widgets
3697
3831
  for( let w of b.widgets ) {
3698
3832
  if(w.domEl.classList.contains('lexfilter'))
@@ -3829,41 +3963,50 @@ class Panel {
3829
3963
  /**
3830
3964
  * @method addTitle
3831
3965
  * @param {String} name Title name
3966
+ * @param {*} options:
3967
+ * link: Href in case title is an hyperlink
3968
+ * target: Target name of the iframe (if any)
3969
+ * icon: FA class of the icon (if any)
3970
+ * iconColor: Color of title icon (if any)
3971
+ * style: CSS to override
3832
3972
  */
3833
3973
 
3834
3974
  addTitle( name, options = {} ) {
3835
3975
 
3836
- if(!name) {
3837
- throw("Set Widget Name!");
3976
+ if( !name )
3977
+ {
3978
+ throw( "Can't create Title without text!" );
3838
3979
  }
3839
3980
 
3840
- let widget = this.create_widget(null, Widget.TITLE, options);
3981
+ let widget = this.create_widget( null, Widget.TITLE, options );
3841
3982
  let element = widget.domEl;
3842
3983
  element.className = "lextitle";
3843
3984
 
3844
- if(options.icon) {
3845
- let icon = document.createElement('a');
3985
+ if( options.icon )
3986
+ {
3987
+ let icon = document.createElement( 'a' );
3846
3988
  icon.className = options.icon;
3847
- icon.style.color = options.icon_color || "";
3848
- element.appendChild(icon);
3989
+ icon.style.color = options.iconColor || "";
3990
+ element.appendChild( icon );
3849
3991
  }
3850
3992
 
3851
- let text = document.createElement('span');
3993
+ let text = document.createElement( "span");
3852
3994
  text.innerText = name;
3853
- element.appendChild(text);
3995
+ element.appendChild( text );
3854
3996
 
3855
- Object.assign(element.style, options.style ?? {});
3997
+ Object.assign( element.style, options.style ?? {} );
3856
3998
 
3857
- if(options.link != undefined)
3999
+ if( options.link != undefined )
3858
4000
  {
3859
- let link_el = document.createElement('a');
3860
- link_el.innerText = name;
3861
- link_el.href = options.link;
3862
- link_el.target = options.target ?? "";
3863
- link_el.className = "lextitle link";
3864
- Object.assign(link_el.style, options.style ?? {});
3865
- element.replaceWith(link_el);
4001
+ let linkDom = document.createElement('a');
4002
+ linkDom.innerText = name;
4003
+ linkDom.href = options.link;
4004
+ linkDom.target = options.target ?? "";
4005
+ linkDom.className = "lextitle link";
4006
+ Object.assign( linkDom.style, options.style ?? {} );
4007
+ element.replaceWith( linkDom );
3866
4008
  }
4009
+
3867
4010
  return element;
3868
4011
  }
3869
4012
 
@@ -3904,7 +4047,7 @@ class Panel {
3904
4047
  Panel._dispatch_event( wValue, "focusout" );
3905
4048
  } );
3906
4049
  }
3907
-
4050
+
3908
4051
  // Add widget value
3909
4052
 
3910
4053
  let container = document.createElement( 'div' );
@@ -3912,6 +4055,11 @@ class Panel {
3912
4055
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " )";
3913
4056
  container.style.display = "flex";
3914
4057
 
4058
+ if( options.textClass )
4059
+ {
4060
+ container.classList.add( options.textClass );
4061
+ }
4062
+
3915
4063
  this.disabled = ( options.disabled || options.warning ) ?? ( options.url ? true : false );
3916
4064
  let wValue = null;
3917
4065
 
@@ -3982,7 +4130,7 @@ class Panel {
3982
4130
 
3983
4131
  container.appendChild( wValue );
3984
4132
  element.appendChild( container );
3985
-
4133
+
3986
4134
  // Remove branch padding and margins
3987
4135
  if( !widget.name ) {
3988
4136
  element.className += " noname";
@@ -4029,7 +4177,7 @@ class Panel {
4029
4177
  Panel._dispatch_event( wValue, "focusout" );
4030
4178
  });
4031
4179
  }
4032
-
4180
+
4033
4181
  // Add widget value
4034
4182
 
4035
4183
  let container = document.createElement( 'div' );
@@ -4082,7 +4230,7 @@ class Panel {
4082
4230
 
4083
4231
  container.appendChild(wValue);
4084
4232
  element.appendChild(container);
4085
-
4233
+
4086
4234
  // Remove branch padding and margins
4087
4235
  if(!widget.name) {
4088
4236
  element.className += " noname";
@@ -4111,7 +4259,7 @@ class Panel {
4111
4259
  options.disabled = true;
4112
4260
  return this.addText( null, value, null, options );
4113
4261
  }
4114
-
4262
+
4115
4263
  /**
4116
4264
  * @method addButton
4117
4265
  * @param {String} name Widget name
@@ -4133,8 +4281,8 @@ class Panel {
4133
4281
  };
4134
4282
 
4135
4283
  widget.onSetValue = ( newValue, skipCallback ) => {
4136
- wValue.innerHTML =
4137
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4284
+ wValue.innerHTML =
4285
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4138
4286
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (newValue || "") + "</span>" ));
4139
4287
  };
4140
4288
 
@@ -4154,8 +4302,8 @@ class Panel {
4154
4302
  wValue.classList.add( options.buttonClass );
4155
4303
  }
4156
4304
 
4157
- wValue.innerHTML =
4158
- (options.icon ? "<a class='" + options.icon + "'></a>" :
4305
+ wValue.innerHTML =
4306
+ (options.icon ? "<a class='" + options.icon + "'></a>" :
4159
4307
  ( options.img ? "<img src='" + options.img + "'>" : "<span>" + (value || "") + "</span>" ));
4160
4308
 
4161
4309
  wValue.style.width = "calc( 100% - " + (options.nameWidth ?? LX.DEFAULT_NAME_WIDTH) + ")";
@@ -4209,7 +4357,7 @@ class Panel {
4209
4357
  let container = document.createElement('div');
4210
4358
  container.className = "lexcombobuttons ";
4211
4359
  if( options.float ) container.className += options.float;
4212
- container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4360
+ container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4213
4361
 
4214
4362
  let should_select = !(options.noSelection ?? false);
4215
4363
  for( let b of values )
@@ -4222,27 +4370,27 @@ class Panel {
4222
4370
  if(options.buttonClass)
4223
4371
  buttonEl.classList.add(options.buttonClass);
4224
4372
 
4225
- if(options.selected == b.value)
4373
+ if(options.selected == b.value)
4226
4374
  buttonEl.classList.add("selected");
4227
-
4228
- if(b.id)
4375
+
4376
+ if(b.id)
4229
4377
  buttonEl.id = b.id;
4230
-
4378
+
4231
4379
  buttonEl.innerHTML = (b.icon ? "<a class='" + b.icon +"'></a>" : "") + "<span>" + (b.icon ? "" : b.value) + "</span>";
4232
-
4380
+
4233
4381
  if(options.disabled)
4234
4382
  buttonEl.setAttribute("disabled", true);
4235
-
4383
+
4236
4384
  buttonEl.addEventListener("click", function(e) {
4237
4385
  if(should_select) {
4238
4386
  container.querySelectorAll('button').forEach( s => s.classList.remove('selected'));
4239
4387
  this.classList.add('selected');
4240
4388
  }
4241
- that._trigger( new IEvent(name, b.value, e), b.callback );
4389
+ that._trigger( new IEvent(name, b.value, e), b.callback );
4242
4390
  });
4243
4391
 
4244
4392
  container.appendChild(buttonEl);
4245
-
4393
+
4246
4394
  // Remove branch padding and margins
4247
4395
  if(widget.name === undefined) {
4248
4396
  buttonEl.className += " noname";
@@ -4311,11 +4459,11 @@ class Panel {
4311
4459
  }
4312
4460
 
4313
4461
  container.appendChild(name_el);
4314
-
4462
+
4315
4463
  if( options.callback ) {
4316
4464
  container.style.cursor = "pointer";
4317
4465
  container.addEventListener("click", (e) => {
4318
- this._trigger( new IEvent(name, null, e), options.callback );
4466
+ this._trigger( new IEvent(name, null, e), options.callback );
4319
4467
  });
4320
4468
  }
4321
4469
 
@@ -4380,20 +4528,33 @@ class Panel {
4380
4528
 
4381
4529
  for( let entry in data )
4382
4530
  {
4383
- const entryData = data[ entry ];
4384
- this.addText( entry, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4531
+ let entryData = data[ entry ];
4532
+
4533
+ if( entryData.constructor != Object )
4534
+ {
4535
+ entryData = { };
4536
+ }
4537
+
4538
+ entryData.placeholder = entryData.placeholder ?? entry;
4539
+ entryData.width = "calc(100% - 10px)";
4540
+
4541
+ this.addLabel( entry, { textClass: "formlabel" } );
4542
+
4543
+ this.addText( null, entryData.constructor == Object ? entryData.value : entryData, ( value ) => {
4385
4544
  container.formData[ entry ] = value;
4386
4545
  }, entryData );
4387
4546
 
4388
4547
  container.formData[ entry ] = entryData.constructor == Object ? entryData.value : entryData;
4389
4548
  }
4390
4549
 
4550
+ this.addBlank( );
4551
+
4391
4552
  this.addButton( null, options.actionName ?? "Submit", ( value, event ) => {
4392
4553
  if( callback )
4393
4554
  {
4394
4555
  callback( container.formData, event );
4395
4556
  }
4396
- } );
4557
+ }, { buttonClass: "accept", width: "calc(100% - 10px)" } );
4397
4558
 
4398
4559
  this.clearQueue();
4399
4560
 
@@ -4409,16 +4570,32 @@ class Panel {
4409
4570
 
4410
4571
  /**
4411
4572
  * @method addContent
4412
- * @param {HTMLElement} element
4573
+ * @param {HTMLElement/String} element
4413
4574
  */
4414
4575
 
4415
4576
  addContent( element, options = {} ) {
4416
4577
 
4417
4578
  if( !element )
4418
- return;
4579
+ {
4580
+ return;
4581
+ }
4582
+
4583
+ if( element.constructor == String )
4584
+ {
4585
+ const tmp = document.createElement( "div" );
4586
+ tmp.innerHTML = element;
4587
+ if( tmp.childElementCount > 1 )
4588
+ {
4589
+ element = tmp;
4590
+ }
4591
+ else
4592
+ {
4593
+ element = tmp.firstElementChild;
4594
+ }
4595
+ }
4419
4596
 
4420
- let widget = this.create_widget(null, Widget.CONTENT, options);
4421
- widget.domEl.appendChild(element);
4597
+ let widget = this.create_widget( null, Widget.CONTENT, options );
4598
+ widget.domEl.appendChild( element );
4422
4599
  return widget;
4423
4600
  }
4424
4601
 
@@ -4502,40 +4679,53 @@ class Panel {
4502
4679
  let container = document.createElement( 'div' );
4503
4680
  container.className = "lexdropdown";
4504
4681
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4505
-
4682
+
4506
4683
  // Add widget value
4507
4684
  let wValue = document.createElement( 'div' );
4508
4685
  wValue.className = "lexdropdown lexoption";
4509
4686
  wValue.name = name;
4510
4687
  wValue.iValue = value;
4511
4688
 
4512
- // Add dropdown widget button
4689
+ // Add dropdown widget button
4513
4690
  let buttonName = value;
4514
4691
  buttonName += "<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>";
4515
4692
 
4516
4693
  this.queue(container);
4517
4694
 
4518
- let selectedOption = this.addButton( null, buttonName, (value, event) => {
4519
- if( list.unfocus_event ) {
4695
+ const _getMaxListWidth = () => {
4696
+
4697
+ let maxWidth = 0;
4698
+ for( let i of values )
4699
+ {
4700
+ const iString = String( i );
4701
+ maxWidth = Math.max( iString.length, maxWidth );
4702
+ }
4703
+ return Math.max( maxWidth * 10, 80 );
4704
+ };
4705
+
4706
+ let selectedOption = this.addButton( null, buttonName, ( value, event ) => {
4707
+ if( list.unfocus_event )
4708
+ {
4520
4709
  delete list.unfocus_event;
4521
4710
  return;
4522
4711
  }
4523
4712
  const topPosition = selectedOption.getBoundingClientRect().y;
4524
4713
  list.style.top = (topPosition + selectedOption.offsetHeight) + 'px';
4525
4714
  list.style.width = (event.currentTarget.clientWidth) + 'px';
4715
+ list.style.minWidth = (_getMaxListWidth()) + 'px';
4526
4716
  list.toggleAttribute('hidden');
4527
4717
  list.focus();
4528
4718
  }, { buttonClass: 'array', skipInlineCount: true });
4529
4719
 
4530
4720
  this.clearQueue();
4531
4721
 
4532
- selectedOption.style.width = "100%";
4722
+ selectedOption.style.width = "100%";
4533
4723
 
4534
4724
  selectedOption.refresh = (v) => {
4535
4725
  if(selectedOption.querySelector("span").innerText == "")
4536
4726
  selectedOption.querySelector("span").innerText = v;
4537
4727
  else
4538
- selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
4728
+ selectedOption.querySelector("span").innerHTML = selectedOption.querySelector("span").innerHTML.replaceAll(selectedOption.querySelector("span").innerText, v);
4539
4729
  }
4540
4730
 
4541
4731
  // Add dropdown options container
@@ -4547,13 +4737,18 @@ class Panel {
4547
4737
  list.addEventListener( 'focusout', function( e ) {
4548
4738
  e.stopPropagation();
4549
4739
  e.stopImmediatePropagation();
4550
- if(e.relatedTarget === selectedOption.querySelector( 'button' )) {
4740
+ if( e.relatedTarget === selectedOption.querySelector( 'button' ) )
4741
+ {
4551
4742
  this.unfocus_event = true;
4552
4743
  setTimeout( () => delete this.unfocus_event, 200 );
4553
- } else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" ) {
4554
- return;
4555
- }else if ( e.target.className == 'lexinput-filter' ) {
4556
- return;
4744
+ }
4745
+ else if ( e.relatedTarget && e.relatedTarget.tagName == "INPUT" )
4746
+ {
4747
+ return;
4748
+ }
4749
+ else if ( e.target.className == 'lexinput-filter' )
4750
+ {
4751
+ return;
4557
4752
  }
4558
4753
  this.toggleAttribute( 'hidden', true );
4559
4754
  });
@@ -4561,28 +4756,33 @@ class Panel {
4561
4756
  // Add filter options
4562
4757
  let filter = null;
4563
4758
  if(options.filter ?? false)
4759
+ {
4564
4760
  filter = this._addFilter("Search option", {container: list, callback: this._search_options.bind(list, values)});
4761
+ }
4565
4762
 
4566
4763
  // Create option list to empty it easily..
4567
- const list_options = document.createElement('span');
4568
- list.appendChild(list_options);
4569
-
4570
- if( filter ) {
4571
- list.prepend(filter);
4572
- list_options.style.height = "calc(100% - 25px)";
4573
-
4574
- filter.addEventListener('focusout', function(e) {
4764
+ const listOptions = document.createElement('span');
4765
+ list.appendChild( listOptions );
4766
+
4767
+ if( filter )
4768
+ {
4769
+ list.prepend( filter );
4770
+ listOptions.style.height = "calc(100% - 25px)";
4771
+
4772
+ filter.addEventListener('focusout', function( e ) {
4575
4773
  if (e.relatedTarget && e.relatedTarget.tagName == "UL" && e.relatedTarget.classList.contains("lexoptions"))
4774
+ {
4576
4775
  return;
4577
- list.toggleAttribute('hidden', true);
4776
+ }
4777
+ list.toggleAttribute( 'hidden', true );
4578
4778
  });
4579
4779
  }
4580
4780
 
4581
4781
  // Add dropdown options list
4582
- list.refresh = (options) => {
4782
+ list.refresh = options => {
4583
4783
 
4584
4784
  // Empty list
4585
- list_options.innerHTML = "";
4785
+ listOptions.innerHTML = "";
4586
4786
 
4587
4787
  for(let i = 0; i < options.length; i++)
4588
4788
  {
@@ -4602,7 +4802,7 @@ class Panel {
4602
4802
 
4603
4803
  let btn = element.querySelector(".lexwidgetname .lexicon");
4604
4804
  if(btn) btn.style.display = (value != wValue.iValue ? "block" : "none");
4605
- that._trigger( new IEvent(name, value, null), callback );
4805
+ that._trigger( new IEvent(name, value, null), callback );
4606
4806
 
4607
4807
  // Reset filter
4608
4808
  if(filter)
@@ -4641,18 +4841,20 @@ class Panel {
4641
4841
  option.setAttribute("title", iValue.value);
4642
4842
  if(value == iValue.value)
4643
4843
  li.classList.add("selected");
4644
- }
4645
- list_options.appendChild(li);
4844
+ }
4845
+
4846
+ listOptions.appendChild( li );
4646
4847
  }
4647
4848
  }
4648
4849
 
4649
- list.refresh(values);
4850
+ list.refresh( values );
4650
4851
 
4651
- container.appendChild(list);
4652
- element.appendChild(container);
4852
+ container.appendChild( list );
4853
+ element.appendChild( container );
4653
4854
 
4654
4855
  // Remove branch padding and margins
4655
- if(!widget.name) {
4856
+ if( !widget.name )
4857
+ {
4656
4858
  element.className += " noname";
4657
4859
  container.style.width = "100%";
4658
4860
  }
@@ -4741,6 +4943,86 @@ class Panel {
4741
4943
  return widget;
4742
4944
  }
4743
4945
 
4946
+ /**
4947
+ * @method addDial
4948
+ * @param {String} name Widget name
4949
+ * @param {Array of Array} values Array of 2N Arrays of each value of the dial
4950
+ * @param {Function} callback Callback function on change
4951
+ * @param {*} options:
4952
+ * skipReset: Don't add the reset value button when value changes
4953
+ * bgColor: Widget background color
4954
+ * pointsColor: Curve points color
4955
+ * lineColor: Curve line color
4956
+ * noOverlap: Points do not overlap, replacing themselves if necessary
4957
+ * allowAddValues: Support adding values on click
4958
+ * smooth: Curve smoothness
4959
+ * moveOutAction: Clamp or delete points moved out of the curve (LX.CURVE_MOVEOUT_CLAMP, LX.CURVE_MOVEOUT_DELETE)
4960
+ */
4961
+
4962
+ addDial( name, values, callback, options = {} ) {
4963
+
4964
+ let that = this;
4965
+ let widget = this.create_widget(name, Widget.DIAL, options);
4966
+
4967
+ widget.onGetValue = () => {
4968
+ return JSON.parse(JSON.stringify(curveInstance.element.value));
4969
+ };
4970
+
4971
+ widget.onSetValue = ( newValue, skipCallback ) => {
4972
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
4973
+ if( btn ) btn.style.display = ( newValue != curveInstance.element.value ? "block" : "none" );
4974
+ curveInstance.element.value = JSON.parse( JSON.stringify( newValue ) );
4975
+ curveInstance.redraw();
4976
+ if( !skipCallback ) that._trigger( new IEvent( name, curveInstance.element.value, null ), callback );
4977
+ };
4978
+
4979
+ let element = widget.domEl;
4980
+ let defaultValues = JSON.parse( JSON.stringify( values ) );
4981
+
4982
+ // Add reset functionality
4983
+ if( widget.name && !(options.skipReset ?? false) )
4984
+ {
4985
+ Panel._add_reset_property(element.domName, function(e) {
4986
+ this.style.display = "none";
4987
+ curveInstance.element.value = JSON.parse( JSON.stringify( defaultValues ) );
4988
+ curveInstance.redraw();
4989
+ that._trigger( new IEvent( name, curveInstance.element.value, e ), callback );
4990
+ });
4991
+ }
4992
+
4993
+ // Add widget value
4994
+
4995
+ var container = document.createElement( 'div' );
4996
+ container.className = "lexcurve";
4997
+ container.style.width = widget.name ? "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")" : '100%';
4998
+
4999
+ options.callback = (v, e) => {
5000
+ let btn = element.querySelector(".lexwidgetname .lexicon");
5001
+ if(btn) btn.style.display = (v != defaultValues ? "block" : "none");
5002
+ that._trigger( new IEvent(name, v, e), callback );
5003
+ };
5004
+
5005
+ options.name = name;
5006
+
5007
+ let curveInstance = new Dial( this, values, options );
5008
+ container.appendChild( curveInstance.element );
5009
+ element.appendChild( container );
5010
+
5011
+ // Resize
5012
+ widget.onresize = curveInstance.redraw.bind( curveInstance );
5013
+ widget.curveInstance = curveInstance;
5014
+
5015
+ doAsync(() => {
5016
+ curveInstance.element.style.height = curveInstance.element.offsetWidth + "px";
5017
+ curveInstance.canvas.width = curveInstance.element.offsetWidth;
5018
+ container.style.width = curveInstance.element.offsetWidth + "px";
5019
+ curveInstance.canvas.height = curveInstance.canvas.width;
5020
+ curveInstance.redraw();
5021
+ });
5022
+
5023
+ return widget;
5024
+ }
5025
+
4744
5026
  /**
4745
5027
  * @method addLayers
4746
5028
  * @param {String} name Widget name
@@ -4804,13 +5086,13 @@ class Panel {
4804
5086
  if( value != undefined )
4805
5087
  {
4806
5088
  const valueBit = binary[ 16 - bit - 1 ];
4807
- if(valueBit != undefined && valueBit == '1')
4808
- layer.classList.add('selected');
5089
+ if(valueBit != undefined && valueBit == '1')
5090
+ layer.classList.add('selected');
4809
5091
  }
4810
5092
  layer.innerText = bit + 1;
4811
5093
  layer.title = "Bit " + bit + ", value " + (1 << bit);
4812
5094
  container.appendChild( layer );
4813
-
5095
+
4814
5096
  layer.addEventListener("click", e => {
4815
5097
 
4816
5098
  e.stopPropagation();
@@ -4829,7 +5111,7 @@ class Panel {
4829
5111
  };
4830
5112
 
4831
5113
  setLayers();
4832
-
5114
+
4833
5115
  element.appendChild(container);
4834
5116
 
4835
5117
  return widget;
@@ -4874,7 +5156,7 @@ class Panel {
4874
5156
  var container = document.createElement('div');
4875
5157
  container.className = "lexarray";
4876
5158
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
4877
-
5159
+
4878
5160
  this.queue( container );
4879
5161
 
4880
5162
  const angle_down = `<a class='fa-solid fa-angle-down' style='float:right; margin-right: 3px;'></a>`;
@@ -4884,7 +5166,7 @@ class Panel {
4884
5166
  this.addButton(null, buttonName, () => {
4885
5167
  element.querySelector(".lexarrayitems").toggleAttribute('hidden');
4886
5168
  }, { buttonClass: 'array' });
4887
-
5169
+
4888
5170
  this.clearQueue();
4889
5171
 
4890
5172
  // Show elements
@@ -4892,7 +5174,7 @@ class Panel {
4892
5174
  let array_items = document.createElement('div');
4893
5175
  array_items.className = "lexarrayitems";
4894
5176
  array_items.toggleAttribute('hidden', true);
4895
-
5177
+
4896
5178
  element.appendChild(container);
4897
5179
  element.appendChild(array_items);
4898
5180
 
@@ -4990,7 +5272,7 @@ class Panel {
4990
5272
 
4991
5273
  values = newValues;
4992
5274
  listContainer.innerHTML = "";
4993
-
5275
+
4994
5276
  for( let i = 0; i < values.length; ++i )
4995
5277
  {
4996
5278
  let icon = null;
@@ -5112,7 +5394,7 @@ class Panel {
5112
5394
 
5113
5395
  tag_input.onkeydown = function( e ) {
5114
5396
  const val = this.value.replace(/\s/g, '');
5115
- if( e.key == ' ') {
5397
+ if( e.key == ' ') {
5116
5398
  e.preventDefault();
5117
5399
  if( !val.length || value.indexOf( val ) > -1 )
5118
5400
  return;
@@ -5148,85 +5430,169 @@ class Panel {
5148
5430
  * @param {*} options:
5149
5431
  * disabled: Make the widget disabled [false]
5150
5432
  * suboptions: Callback to add widgets in case of TRUE value
5433
+ * className: Customize colors
5151
5434
  */
5152
5435
 
5153
5436
  addCheckbox( name, value, callback, options = {} ) {
5154
5437
 
5155
- if( !name ) {
5438
+ if( !name )
5439
+ {
5156
5440
  throw( "Set Widget Name!" );
5157
5441
  }
5158
5442
 
5159
5443
  let widget = this.create_widget( name, Widget.CHECKBOX, options );
5160
5444
 
5161
5445
  widget.onGetValue = () => {
5162
- return flag.value;
5446
+ return checkbox.checked;
5163
5447
  };
5448
+
5164
5449
  widget.onSetValue = ( newValue, skipCallback ) => {
5165
- if( flag.value !== newValue )
5166
- Panel._dispatch_event( toggle, "click", skipCallback );
5450
+ if( checkbox.checked !== newValue )
5451
+ {
5452
+ checkbox.checked = newValue;
5453
+ Panel._dispatch_event( checkbox, "change", skipCallback );
5454
+ }
5167
5455
  };
5168
5456
 
5169
5457
  let element = widget.domEl;
5170
5458
 
5171
5459
  // Add reset functionality
5172
5460
  Panel._add_reset_property( element.domName, function() {
5173
- Panel._dispatch_event( toggle, "click" );
5461
+ checkbox.checked = !checkbox.checked;
5462
+ Panel._dispatch_event( checkbox, "change" );
5174
5463
  });
5175
-
5464
+
5176
5465
  // Add widget value
5177
5466
 
5178
5467
  var container = document.createElement('div');
5179
5468
  container.className = "lexcheckboxcont";
5180
5469
 
5181
- let toggle = document.createElement('span');
5182
- toggle.className = "lexcheckbox";
5470
+ let checkbox = document.createElement('input');
5471
+ checkbox.type = "checkbox";
5472
+ checkbox.className = "lexcheckbox " + ( options.className ?? "" );
5473
+ checkbox.checked = value;
5474
+ checkbox.iValue = value;
5475
+ checkbox.disabled = options.disabled ?? false;
5183
5476
 
5184
- let flag = document.createElement('span');
5185
- flag.value = flag.iValue = value || false;
5186
- flag.className = "checkbox " + (flag.value ? "on" : "");
5187
- flag.id = "checkbox"+simple_guidGenerator();
5188
- flag.innerHTML = "<a class='fa-solid fa-check' style='display: " + (flag.value ? "block" : "none") + "'></a>";
5189
-
5190
- if( options.disabled ) {
5191
- flag.disabled = true;
5192
- toggle.className += " disabled";
5477
+ let valueName = document.createElement( 'span' );
5478
+ valueName.className = "checkboxtext";
5479
+ valueName.innerHTML = "On";
5480
+
5481
+ container.appendChild( checkbox );
5482
+ container.appendChild( valueName );
5483
+
5484
+ checkbox.addEventListener( "change" , e => {
5485
+
5486
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
5487
+
5488
+ // Reset button (default value)
5489
+ if( !skipCallback )
5490
+ {
5491
+ let btn = element.querySelector( ".lexwidgetname .lexicon" );
5492
+ if( btn ) btn.style.display = checkbox.checked != checkbox.iValue ? "block": "none";
5493
+ }
5494
+
5495
+ // Open suboptions
5496
+ let submenu = element.querySelector( ".lexcheckboxsubmenu" );
5497
+ if( submenu ) submenu.toggleAttribute( 'hidden', !checkbox.checked );
5498
+
5499
+ if( !skipCallback ) this._trigger( new IEvent( name, checkbox.checked, e ), callback );
5500
+ });
5501
+
5502
+ element.appendChild( container );
5503
+
5504
+ if( options.suboptions )
5505
+ {
5506
+ element.style.flexWrap = "wrap";
5507
+ let suboptions = document.createElement('div');
5508
+ suboptions.className = "lexcheckboxsubmenu";
5509
+ suboptions.toggleAttribute( 'hidden', !checkbox.checked );
5510
+
5511
+ this.queue( suboptions );
5512
+ options.suboptions.call(this, this);
5513
+ this.clearQueue();
5514
+
5515
+ element.appendChild( suboptions );
5193
5516
  }
5194
5517
 
5195
- toggle.appendChild( flag );
5518
+ return widget;
5519
+ }
5520
+
5521
+ /**
5522
+ * @method addToggle
5523
+ * @param {String} name Widget name
5524
+ * @param {Boolean} value Value of the checkbox
5525
+ * @param {Function} callback Callback function on change
5526
+ * @param {*} options:
5527
+ * disabled: Make the widget disabled [false]
5528
+ * suboptions: Callback to add widgets in case of TRUE value
5529
+ * className: Customize colors
5530
+ */
5196
5531
 
5197
- let value_name = document.createElement( 'span' );
5198
- value_name.id = "checkboxtext";
5199
- value_name.innerHTML = "On";
5532
+ addToggle( name, value, callback, options = {} ) {
5200
5533
 
5201
- container.appendChild( toggle );
5202
- container.appendChild( value_name );
5534
+ if( !name )
5535
+ {
5536
+ throw( "Set Widget Name!" );
5537
+ }
5203
5538
 
5204
- toggle.addEventListener( "click" , e => {
5539
+ let widget = this.create_widget( name, Widget.TOGGLE, options );
5205
5540
 
5206
- let flag = toggle.querySelector( ".checkbox" );
5207
- if( flag.disabled )
5208
- return;
5541
+ widget.onGetValue = () => {
5542
+ return toggle.checked;
5543
+ };
5544
+
5545
+ widget.onSetValue = ( newValue, skipCallback ) => {
5546
+ if( toggle.checked !== newValue )
5547
+ {
5548
+ toggle.checked = newValue;
5549
+ Panel._dispatch_event( toggle, "change", skipCallback );
5550
+ }
5551
+ };
5552
+
5553
+ let element = widget.domEl;
5554
+
5555
+ // Add reset functionality
5556
+ Panel._add_reset_property( element.domName, function() {
5557
+ toggle.checked = !toggle.checked;
5558
+ Panel._dispatch_event( toggle, "change" );
5559
+ });
5560
+
5561
+ // Add widget value
5562
+
5563
+ var container = document.createElement('div');
5564
+ container.className = "lextogglecont";
5565
+
5566
+ let toggle = document.createElement('input');
5567
+ toggle.type = "checkbox";
5568
+ toggle.className = "lextoggle " + ( options.className ?? "" );
5569
+ toggle.checked = value;
5570
+ toggle.iValue = value;
5571
+ toggle.disabled = options.disabled ?? false;
5209
5572
 
5210
- const skipCallback = ( e.detail.constructor == Number ? null : e.detail );
5573
+ let valueName = document.createElement( 'span' );
5574
+ valueName.className = "toggletext";
5575
+ valueName.innerHTML = "On";
5576
+
5577
+ container.appendChild( toggle );
5578
+ container.appendChild( valueName );
5211
5579
 
5212
- let check = toggle.querySelector( ".checkbox a" );
5580
+ toggle.addEventListener( "change" , e => {
5213
5581
 
5214
- flag.value = !flag.value;
5215
- flag.className = "checkbox " + ( flag.value ? "on" : "" );
5216
- check.style.display = flag.value ? "block" : "none";
5582
+ const skipCallback = ( e.detail?.constructor == Number ? null : e.detail );
5217
5583
 
5218
5584
  // Reset button (default value)
5219
5585
  if( !skipCallback )
5220
5586
  {
5221
5587
  let btn = element.querySelector( ".lexwidgetname .lexicon" );
5222
- if( btn ) btn.style.display = flag.value != flag.iValue ? "block": "none";
5588
+ if( btn ) btn.style.display = toggle.checked != toggle.iValue ? "block": "none";
5223
5589
  }
5224
5590
 
5225
5591
  // Open suboptions
5226
- let submenu = element.querySelector( ".lexcheckboxsubmenu" );
5227
- if( submenu ) submenu.toggleAttribute( 'hidden', !flag.value );
5592
+ let submenu = element.querySelector( ".lextogglesubmenu" );
5593
+ if( submenu ) submenu.toggleAttribute( 'hidden', !toggle.checked );
5228
5594
 
5229
- if( !skipCallback ) this._trigger( new IEvent( name, flag.value, e ), callback );
5595
+ if( !skipCallback ) this._trigger( new IEvent( name, toggle.checked, e ), callback );
5230
5596
  });
5231
5597
 
5232
5598
  element.appendChild( container );
@@ -5235,14 +5601,14 @@ class Panel {
5235
5601
  {
5236
5602
  element.style.flexWrap = "wrap";
5237
5603
  let suboptions = document.createElement('div');
5238
- suboptions.className = "lexcheckboxsubmenu";
5239
- suboptions.toggleAttribute('hidden', !flag.value);
5604
+ suboptions.className = "lextogglesubmenu";
5605
+ suboptions.toggleAttribute( 'hidden', !toggle.checked );
5240
5606
 
5241
5607
  this.queue( suboptions );
5242
5608
  options.suboptions.call(this, this);
5243
5609
  this.clearQueue();
5244
5610
 
5245
- element.appendChild(suboptions);
5611
+ element.appendChild( suboptions );
5246
5612
  }
5247
5613
 
5248
5614
  return widget;
@@ -5297,7 +5663,7 @@ class Panel {
5297
5663
  color.id = "color" + simple_guidGenerator();
5298
5664
  color.useRGB = options.useRGB ?? false;
5299
5665
  color.value = color.iValue = value.constructor === Array ? rgbToHex( value ) : value;
5300
-
5666
+
5301
5667
  if( options.disabled ) {
5302
5668
  color.disabled = true;
5303
5669
  }
@@ -5333,7 +5699,7 @@ class Panel {
5333
5699
  widget.set( v );
5334
5700
  change_from_input = false;
5335
5701
  }, { width: "calc( 100% - 32px )"});
5336
-
5702
+
5337
5703
  text_widget.domEl.style.marginLeft = "4px";
5338
5704
 
5339
5705
  this.clearQueue();
@@ -5386,7 +5752,7 @@ class Panel {
5386
5752
  // add widget value
5387
5753
 
5388
5754
  var container = document.createElement( 'div' );
5389
- container.className = "lexnumber";
5755
+ container.className = "lexnumber";
5390
5756
  container.style.width = options.inputWidth || "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5391
5757
 
5392
5758
  let box = document.createElement( 'div' );
@@ -5471,7 +5837,14 @@ class Panel {
5471
5837
  };
5472
5838
  }
5473
5839
 
5474
- // Add wheel input
5840
+ vecinput.addEventListener( "input", function( e ) {
5841
+ let new_value = +this.valueAsNumber;
5842
+ vecinput.value = round( new_value, options.precision );
5843
+ if( options.units )
5844
+ {
5845
+ vecinput.unitSpan.style.left = measureRealWidth( vecinput.value ) + "px";
5846
+ }
5847
+ }, false );
5475
5848
 
5476
5849
  vecinput.addEventListener( "wheel", function( e ) {
5477
5850
  e.preventDefault();
@@ -5521,7 +5894,7 @@ class Panel {
5521
5894
 
5522
5895
  if( !skipCallback ) this._trigger( new IEvent( name, val, e ), callback );
5523
5896
  }, { passive: false });
5524
-
5897
+
5525
5898
  // Add drag input
5526
5899
 
5527
5900
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5590,7 +5963,7 @@ class Panel {
5590
5963
  options.onRelease.bind( vecinput )( e, vecinput );
5591
5964
  }
5592
5965
  }
5593
-
5966
+
5594
5967
  container.appendChild( box );
5595
5968
  element.appendChild( container );
5596
5969
 
@@ -5657,7 +6030,7 @@ class Panel {
5657
6030
  // Add widget value
5658
6031
 
5659
6032
  var container = document.createElement( 'div' );
5660
- container.className = "lexvector";
6033
+ container.className = "lexvector";
5661
6034
  container.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + ")";
5662
6035
 
5663
6036
  for( let i = 0; i < num_components; ++i ) {
@@ -5750,7 +6123,7 @@ class Panel {
5750
6123
 
5751
6124
  if( !skipCallback ) this._trigger( new IEvent( name, value, e ), callback );
5752
6125
  }, false );
5753
-
6126
+
5754
6127
  // Add drag input
5755
6128
 
5756
6129
  vecinput.addEventListener( "mousedown", inner_mousedown );
@@ -5829,7 +6202,7 @@ class Panel {
5829
6202
  options.onRelease.bind( vecinput )( e, vecinput );
5830
6203
  }
5831
6204
  }
5832
-
6205
+
5833
6206
  box.appendChild( vecinput );
5834
6207
  container.appendChild( box );
5835
6208
  }
@@ -5853,7 +6226,8 @@ class Panel {
5853
6226
  }
5854
6227
 
5855
6228
  let locker = document.createElement( 'a' );
5856
- locker.className = "fa-solid fa-lock-open lexicon";
6229
+ locker.title = "Lock";
6230
+ locker.className = "fa-solid fa-lock-open lexicon lock";
5857
6231
  container.appendChild( locker );
5858
6232
  locker.addEventListener( "click", function( e ) {
5859
6233
  this.locked = !this.locked;
@@ -5868,7 +6242,7 @@ class Panel {
5868
6242
  this.classList.remove( "fa-lock" );
5869
6243
  }
5870
6244
  }, false );
5871
-
6245
+
5872
6246
  element.appendChild( container );
5873
6247
 
5874
6248
  return widget;
@@ -5877,7 +6251,7 @@ class Panel {
5877
6251
  /**
5878
6252
  * @method addVector N (2, 3, 4)
5879
6253
  * @param {String} name Widget name
5880
- * @param {Array} value Array of N components
6254
+ * @param {Array} value Array of N components
5881
6255
  * @param {Function} callback Callback function on change
5882
6256
  * @param {*} options:
5883
6257
  * disabled: Make the widget disabled [false]
@@ -5936,18 +6310,21 @@ class Panel {
5936
6310
 
5937
6311
  this.queue( element );
5938
6312
 
6313
+ element.aspectRatio = ( value.length == 2 ? value[ 0 ] / value[ 1 ] : null );
5939
6314
  element.dimensions = [];
5940
6315
 
5941
6316
  for( let i = 0; i < value.length; ++i )
5942
6317
  {
5943
- const size = measureRealWidth( JSON.stringify( value[ i ] ), 24 ) + 'px';
5944
6318
  element.dimensions[ i ] = this.addNumber( null, value[ i ], ( v ) => {
5945
6319
 
5946
- const value = [];
6320
+ const value = widget.onGetValue();
5947
6321
 
5948
- for( let i = 0; i < element.dimensions.length; ++i )
6322
+ if( element.locked )
5949
6323
  {
5950
- value.push( element.dimensions[ i ].onGetValue() );
6324
+ const ar = ( i == 0 ? 1.0 / element.aspectRatio : element.aspectRatio );
6325
+ const index = ( 1 + i ) % 2;
6326
+ value[ index ] = v * ar;
6327
+ element.dimensions[ index ].onSetValue( value[ index ], true );
5951
6328
  }
5952
6329
 
5953
6330
  if( callback )
@@ -5955,7 +6332,7 @@ class Panel {
5955
6332
  callback( value );
5956
6333
  }
5957
6334
 
5958
- }, { width: size, min: 0, disabled: options.disabled } );
6335
+ }, { min: 0, disabled: options.disabled, precision: options.precision } );
5959
6336
 
5960
6337
  if( ( i + 1 ) != value.length )
5961
6338
  {
@@ -5975,6 +6352,32 @@ class Panel {
5975
6352
  element.appendChild( unitSpan );
5976
6353
  }
5977
6354
 
6355
+ // Lock aspect ratio
6356
+ if( element.aspectRatio )
6357
+ {
6358
+ let locker = document.createElement( 'a' );
6359
+ locker.title = "Lock Aspect Ratio";
6360
+ locker.className = "fa-solid fa-lock-open lexicon lock";
6361
+ element.appendChild( locker );
6362
+ locker.addEventListener( "click", function( e ) {
6363
+ element.locked = !element.locked;
6364
+ if( element.locked )
6365
+ {
6366
+ this.classList.add( "fa-lock" );
6367
+ this.classList.remove( "fa-lock-open" );
6368
+
6369
+ // Recompute ratio
6370
+ const value = widget.onGetValue();
6371
+ element.aspectRatio = value[ 0 ] / value[ 1 ];
6372
+ }
6373
+ else
6374
+ {
6375
+ this.classList.add( "fa-lock-open" );
6376
+ this.classList.remove( "fa-lock" );
6377
+ }
6378
+ }, false );
6379
+ }
6380
+
5978
6381
  // Remove branch padding and margins
5979
6382
  if( !widget.name )
5980
6383
  {
@@ -5988,11 +6391,12 @@ class Panel {
5988
6391
  /**
5989
6392
  * @method addPad
5990
6393
  * @param {String} name Widget name
5991
- * @param {Number} value Pad value
6394
+ * @param {Array} value Pad value
5992
6395
  * @param {Function} callback Callback function on change
5993
6396
  * @param {*} options:
5994
6397
  * disabled: Make the widget disabled [false]
5995
6398
  * min, max: Min and Max values
6399
+ * padSize: Size of the pad (css)
5996
6400
  * onPress: Callback function on mouse down
5997
6401
  * onRelease: Callback function on mouse up
5998
6402
  */
@@ -6113,7 +6517,7 @@ class Panel {
6113
6517
  /**
6114
6518
  * @method addProgress
6115
6519
  * @param {String} name Widget name
6116
- * @param {Number} value Progress value
6520
+ * @param {Number} value Progress value
6117
6521
  * @param {*} options:
6118
6522
  * min, max: Min and Max values
6119
6523
  * low, optimum, high: Low and High boundary values, Optimum point in the range
@@ -6136,8 +6540,11 @@ class Panel {
6136
6540
  };
6137
6541
  widget.onSetValue = ( newValue, skipCallback ) => {
6138
6542
  element.querySelector("meter").value = newValue;
6543
+ _updateColor();
6139
6544
  if( element.querySelector("span") )
6545
+ {
6140
6546
  element.querySelector("span").innerText = newValue;
6547
+ }
6141
6548
  };
6142
6549
 
6143
6550
  let element = widget.domEl;
@@ -6154,14 +6561,26 @@ class Panel {
6154
6561
  progress.step = "any";
6155
6562
  progress.min = options.min ?? 0;
6156
6563
  progress.max = options.max ?? 1;
6564
+ progress.low = options.low ?? progress.low;
6565
+ progress.high = options.high ?? progress.high;
6566
+ progress.optimum = options.optimum ?? progress.optimum;
6157
6567
  progress.value = value;
6158
-
6159
- if( options.low )
6160
- progress.low = options.low;
6161
- if( options.high )
6162
- progress.high = options.high;
6163
- if( options.optimum )
6164
- progress.optimum = options.optimum;
6568
+
6569
+ const _updateColor = () => {
6570
+
6571
+ let backgroundColor = LX.getThemeColor( "global-selected" );
6572
+
6573
+ if( progress.low != undefined && progress.value < progress.low )
6574
+ {
6575
+ backgroundColor = LX.getThemeColor( "global-color-error" );
6576
+ }
6577
+ else if( progress.high != undefined && progress.value < progress.high )
6578
+ {
6579
+ backgroundColor = LX.getThemeColor( "global-color-warning" );
6580
+ }
6581
+
6582
+ progress.style.background = `color-mix(in srgb, ${backgroundColor} 20%, transparent)`;
6583
+ };
6165
6584
 
6166
6585
  container.appendChild( progress );
6167
6586
  element.appendChild( container );
@@ -6227,6 +6646,8 @@ class Panel {
6227
6646
  }
6228
6647
  }
6229
6648
 
6649
+ _updateColor();
6650
+
6230
6651
  return widget;
6231
6652
  }
6232
6653
 
@@ -6236,13 +6657,15 @@ class Panel {
6236
6657
  * @param {Function} callback Callback function on change
6237
6658
  * @param {*} options:
6238
6659
  * local: Ask for local file
6660
+ * disabled: Make the widget disabled [false]
6239
6661
  * read: Return the file itself (False) or the contents (True)
6240
6662
  * type: type to read as [text (Default), buffer, bin, url]
6241
6663
  */
6242
6664
 
6243
6665
  addFile( name, callback, options = { } ) {
6244
6666
 
6245
- if( !name ) {
6667
+ if( !name )
6668
+ {
6246
6669
  throw( "Set Widget Name!" );
6247
6670
  }
6248
6671
 
@@ -6251,15 +6674,19 @@ class Panel {
6251
6674
 
6252
6675
  let local = options.local ?? true;
6253
6676
  let type = options.type ?? 'text';
6254
- let read = options.read ?? true;
6677
+ let read = options.read ?? true;
6255
6678
 
6256
6679
  // Create hidden input
6257
6680
  let input = document.createElement( 'input' );
6681
+ input.className = "lexfileinput";
6258
6682
  input.style.width = "calc( 100% - " + LX.DEFAULT_NAME_WIDTH + " - 10%)";
6259
6683
  input.type = 'file';
6684
+ input.disabled = options.disabled ?? false;
6260
6685
 
6261
6686
  if( options.placeholder )
6687
+ {
6262
6688
  input.placeholder = options.placeholder;
6689
+ }
6263
6690
 
6264
6691
  input.addEventListener( 'change', function( e ) {
6265
6692
 
@@ -6291,17 +6718,24 @@ class Panel {
6291
6718
  element.appendChild( input );
6292
6719
 
6293
6720
  this.queue( element );
6294
-
6721
+
6295
6722
  if( local )
6296
6723
  {
6724
+ let settingsDialog = null;
6725
+
6297
6726
  this.addButton(null, "<a style='margin-top: 0px;' class='fa-solid fa-gear'></a>", () => {
6298
-
6299
- new Dialog( "Load Settings", p => {
6727
+
6728
+ if( settingsDialog )
6729
+ {
6730
+ return;
6731
+ }
6732
+
6733
+ settingsDialog = new Dialog( "Load Settings", p => {
6300
6734
  p.addDropdown( "Type", [ 'text', 'buffer', 'bin', 'url' ], type, v => { type = v } );
6301
6735
  p.addButton( null, "Reload", v => { input.dispatchEvent( new Event( 'change' ) ) } );
6302
- });
6303
-
6304
- }, { className: "micro", skipInlineCount: true });
6736
+ }, { onclose: ( root ) => { root.remove(); settingsDialog = null; } } );
6737
+
6738
+ }, { className: "micro", skipInlineCount: true, title: "Settings" });
6305
6739
  }
6306
6740
 
6307
6741
  this.clearQueue();
@@ -6363,7 +6797,7 @@ class Panel {
6363
6797
  node_filter_input.addEventListener('input', function(){
6364
6798
  nodeTree.refresh();
6365
6799
  });
6366
-
6800
+
6367
6801
  let searchIcon = document.createElement('a');
6368
6802
  searchIcon.className = "lexicon fa-solid fa-magnifying-glass";
6369
6803
  toolsDiv.appendChild(node_filter_input);
@@ -6397,11 +6831,11 @@ class Panel {
6397
6831
  element.className = "lexseparator";
6398
6832
  let widget = new Widget( null, Widget.SEPARATOR );
6399
6833
  widget.domEl = element;
6400
-
6834
+
6401
6835
  if(this.current_branch) {
6402
6836
  this.current_branch.content.appendChild( element );
6403
6837
  this.current_branch.widgets.push( widget );
6404
- } else
6838
+ } else
6405
6839
  this.root.appendChild(element);
6406
6840
  }
6407
6841
 
@@ -6413,7 +6847,7 @@ class Panel {
6413
6847
  * onCreate: Func to be called at tab creation
6414
6848
  * onSelect: Func to be called on select tab (optional)
6415
6849
  * }
6416
- * @param {*} options
6850
+ * @param {*} options
6417
6851
  * vertical: Use vertical or horizontal tabs (vertical by default)
6418
6852
  * showNames: Show tab name only in horizontal tabs
6419
6853
  */
@@ -6495,7 +6929,7 @@ class Panel {
6495
6929
  this.clearQueue();
6496
6930
  }
6497
6931
  }
6498
-
6932
+
6499
6933
  this.addSeparator();
6500
6934
  }
6501
6935
  }
@@ -6507,7 +6941,7 @@ LX.Panel = Panel;
6507
6941
  */
6508
6942
 
6509
6943
  class Branch {
6510
-
6944
+
6511
6945
  constructor( name, options = {} ) {
6512
6946
 
6513
6947
  this.name = name;
@@ -6534,7 +6968,7 @@ class Branch {
6534
6968
  // create element
6535
6969
  var title = document.createElement( 'div' );
6536
6970
  title.className = "lexbranchtitle";
6537
-
6971
+
6538
6972
  title.innerHTML = "<a class='fa-solid fa-angle-up switch-branch-button'></a>";
6539
6973
  if( options.icon )
6540
6974
  {
@@ -6582,7 +7016,7 @@ class Branch {
6582
7016
  {
6583
7017
  return;
6584
7018
  }
6585
-
7019
+
6586
7020
  addContextMenu("Dock", e, p => {
6587
7021
  e.preventDefault();
6588
7022
  // p.add('<i class="fa-regular fa-window-maximize">', {id: 'dock_options0'});
@@ -6622,23 +7056,26 @@ class Branch {
6622
7056
 
6623
7057
  _addBranchSeparator() {
6624
7058
 
6625
- var element = document.createElement('div');
7059
+ const element = document.createElement('div');
6626
7060
  element.className = "lexwidgetseparator";
6627
7061
  element.style.width = "100%";
6628
7062
  element.style.background = "none";
6629
7063
 
6630
- var grabber = document.createElement('div');
7064
+ const grabber = document.createElement('div');
6631
7065
  grabber.innerHTML = "&#9662;";
6632
- grabber.style.marginLeft = LX.DEFAULT_NAME_WIDTH;
6633
7066
  element.appendChild(grabber);
6634
7067
 
6635
- var line = document.createElement('div');
7068
+ doAsync( () => {
7069
+ grabber.style.marginLeft = ((parseFloat(LX.DEFAULT_NAME_WIDTH) / 100.0) * this.content.offsetWidth) + "px";
7070
+ }, 10 )
7071
+
7072
+ const line = document.createElement('div');
6636
7073
  line.style.width = "1px";
6637
7074
  line.style.marginLeft = "6px";
6638
7075
  line.style.marginTop = "2px";
6639
7076
  line.style.height = "0px"; // get in time
6640
- grabber.appendChild(line);
6641
- grabber.addEventListener("mousedown", inner_mousedown);
7077
+ grabber.appendChild( line );
7078
+ grabber.addEventListener( "mousedown", innerMouseDown );
6642
7079
 
6643
7080
  this.grabber = grabber;
6644
7081
 
@@ -6646,45 +7083,40 @@ class Branch {
6646
7083
  return that.root.offsetHeight - that.root.children[0].offsetHeight;
6647
7084
  }
6648
7085
 
6649
- var that = this;
6650
- var lastX = 0;
6651
- var lastXLine = 0;
6652
- function inner_mousedown(e)
7086
+ let that = this;
7087
+
7088
+ function innerMouseDown( e )
6653
7089
  {
6654
7090
  var doc = that.root.ownerDocument;
6655
- doc.addEventListener("mouseup",inner_mouseup);
6656
- doc.addEventListener("mousemove",inner_mousemove);
6657
- lastX = e.pageX;
6658
- lastXLine = e.pageX;
7091
+ doc.addEventListener("mouseup", innerMouseUp);
7092
+ doc.addEventListener("mousemove", innerMouseMove);
6659
7093
  e.stopPropagation();
6660
7094
  e.preventDefault();
6661
- var h = getBranchHeight();
7095
+ const h = getBranchHeight();
6662
7096
  line.style.height = (h-3) + "px";
6663
7097
  document.body.classList.add('nocursor');
6664
7098
  }
6665
-
6666
- function inner_mousemove(e)
7099
+
7100
+ function innerMouseMove(e)
6667
7101
  {
6668
- if (lastXLine != e.pageX) {
6669
- var dt = lastXLine - e.pageX;
6670
- var margin = parseFloat( grabber.style.marginLeft );
6671
- grabber.style.marginLeft = clamp(margin - dt * 0.1, 10, 90) + "%";
6672
- }
7102
+ let dt = e.movementX;
6673
7103
 
6674
- lastXLine = e.pageX;
7104
+ if ( dt != 0 )
7105
+ {
7106
+ const margin = parseFloat( grabber.style.marginLeft );
7107
+ grabber.style.marginLeft = clamp( margin + dt, 32, that.content.offsetWidth - 32 ) + "px";
7108
+ }
6675
7109
  }
6676
7110
 
6677
- function inner_mouseup(e)
7111
+ function innerMouseUp(e)
6678
7112
  {
6679
- if (lastX != e.pageX)
6680
- that._updateWidgets();
6681
- lastX = e.pageX;
6682
- lastXLine = e.pageX;
7113
+ that._updateWidgets();
7114
+
6683
7115
  line.style.height = "0px";
6684
7116
 
6685
7117
  var doc = that.root.ownerDocument;
6686
- doc.removeEventListener("mouseup",inner_mouseup);
6687
- doc.removeEventListener("mousemove",inner_mousemove);
7118
+ doc.removeEventListener("mouseup", innerMouseUp);
7119
+ doc.removeEventListener("mousemove", innerMouseMove);
6688
7120
  document.body.classList.remove('nocursor');
6689
7121
  }
6690
7122
 
@@ -6744,7 +7176,7 @@ class Dialog {
6744
7176
  static _last_id = 0;
6745
7177
 
6746
7178
  constructor( title, callback, options = {} ) {
6747
-
7179
+
6748
7180
  if( !callback )
6749
7181
  {
6750
7182
  console.warn("Content is empty, add some widgets using 'callback' parameter!");
@@ -6787,25 +7219,30 @@ class Dialog {
6787
7219
 
6788
7220
  addContextMenu("Dock", e, p => {
6789
7221
  e.preventDefault();
6790
-
6791
- const get_next_panel = function(area) {
6792
- let p = area.panels[0];
7222
+
7223
+ const _getNextPanel = function( area ) {
7224
+ let p = area.panels[ 0 ];
6793
7225
  if( p ) return p;
6794
7226
  for(var s of area.sections){
6795
- p = get_next_panel(s);
7227
+ p = _getNextPanel( s );
6796
7228
  if( p ) return p;
6797
7229
  }
6798
7230
  }
6799
7231
 
6800
- const append_branch = function(panel) {
7232
+ const _appendBranch = function( panel ) {
6801
7233
  let branch = panel.branches.find( b => b.name === title );
6802
- if( !branch ) {
6803
- panel.branch(title);
6804
- branch = panel.branches.find( b => b.name === title );
6805
- }else
7234
+ if( !branch )
7235
+ {
7236
+ panel.branch( title );
7237
+ branch = panel.branches.find( b => b.name === title );
7238
+ }
7239
+ else
7240
+ {
6806
7241
  panel.root.appendChild( branch.root );
7242
+ }
6807
7243
 
6808
- for( let w of that.widgets ) {
7244
+ for( let w of that.widgets )
7245
+ {
6809
7246
  branch.content.appendChild( w.domEl );
6810
7247
  }
6811
7248
 
@@ -6816,16 +7253,16 @@ class Dialog {
6816
7253
  branch.root.classList.add('last');
6817
7254
  root.remove();
6818
7255
  }
6819
-
7256
+
6820
7257
  // Right
6821
- let rpanel = get_next_panel(LX.main_area.sections[1]);
7258
+ let rpanel = _getNextPanel(LX.main_area.sections[ 1 ]);
6822
7259
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-90">', {disabled: !rpanel, id: 'dock_options0', callback: () => {
6823
- append_branch(rpanel);
7260
+ _appendBranch(rpanel);
6824
7261
  }});
6825
7262
  // Left
6826
- let lpanel = get_next_panel(LX.main_area.sections[0]);
7263
+ let lpanel = _getNextPanel(LX.main_area.sections[ 0 ]);
6827
7264
  p.add('<i class="fa-regular fa-window-maximize fa-window-maximize fa-rotate-270">', {disabled: !lpanel, id: 'dock_options1', callback: () => {
6828
- append_branch(lpanel);
7265
+ _appendBranch(lpanel);
6829
7266
  }});
6830
7267
  }, { icon: "fa-regular fa-window-restore" });
6831
7268
  };
@@ -6837,41 +7274,61 @@ class Dialog {
6837
7274
  {
6838
7275
  this.close = () => {
6839
7276
 
7277
+ if( options.onBeforeClose )
7278
+ {
7279
+ options.onBeforeClose( this );
7280
+ }
7281
+
6840
7282
  if( !options.onclose )
6841
7283
  {
6842
7284
  that.panel.clear();
6843
7285
  root.remove();
6844
- } else
7286
+ }
7287
+ else
6845
7288
  {
6846
7289
  options.onclose( this.root );
6847
7290
  }
6848
7291
 
6849
- if(modal)
6850
- LX.modal.toggle(true);
7292
+ if( modal )
7293
+ {
7294
+ LX.modal.toggle( true );
7295
+ }
6851
7296
  };
6852
7297
 
6853
- var closeButton = document.createElement('a');
7298
+ var closeButton = document.createElement( 'a' );
6854
7299
  closeButton.className = "lexdialogcloser fa-solid fa-xmark";
6855
7300
  closeButton.title = "Close";
6856
- closeButton.addEventListener('click', this.close);
7301
+ closeButton.addEventListener( "click", this.close );
6857
7302
 
6858
- if(title) titleDiv.appendChild(closeButton);
6859
- else {
6860
- closeButton.classList.add("notitle");
6861
- root.appendChild(closeButton);
7303
+ if( title )
7304
+ {
7305
+ titleDiv.appendChild( closeButton );
7306
+ }
7307
+ else
7308
+ {
7309
+ closeButton.classList.add( "notitle" );
7310
+ root.appendChild( closeButton );
6862
7311
  }
6863
7312
  }
6864
7313
 
6865
7314
  const panel = new Panel();
6866
- panel.root.classList.add('lexdialogcontent');
6867
- if(!title) panel.root.classList.add('notitle');
6868
- if(callback)
6869
- callback.call(this, panel);
6870
- root.appendChild(panel.root);
7315
+ panel.root.classList.add( "lexdialogcontent" );
7316
+
7317
+ if( !title )
7318
+ {
7319
+ panel.root.classList.add( "notitle" );
7320
+ }
7321
+
7322
+ if( callback )
7323
+ {
7324
+ callback.call( this, panel );
7325
+ }
7326
+
7327
+ root.appendChild( panel.root );
6871
7328
 
6872
7329
  // Make branches have a distintive to manage some cases
6873
7330
  panel.root.querySelectorAll(".lexbranch").forEach( b => b.classList.add("dialog") );
6874
-
7331
+
6875
7332
  this.panel = panel;
6876
7333
  this.root = root;
6877
7334
  this.title = titleDiv;
@@ -6882,22 +7339,30 @@ class Dialog {
6882
7339
  }
6883
7340
 
6884
7341
  // Process position and size
6885
- if(size.length && typeof(size[0]) != "string")
6886
- size[0] += "px";
6887
- if(size.length && typeof(size[1]) != "string")
6888
- size[1] += "px";
7342
+ if( size.length && typeof(size[ 0 ]) != "string" )
7343
+ {
7344
+ size[ 0 ] += "px";
7345
+ }
7346
+
7347
+ if( size.length && typeof(size[ 1 ]) != "string" )
7348
+ {
7349
+ size[ 1 ] += "px";
7350
+ }
6889
7351
 
6890
- root.style.width = size[0] ? (size[0]) : "25%";
6891
- root.style.height = size[1] ? (size[1]) : "auto";
7352
+ root.style.width = size[ 0 ] ? (size[ 0 ]) : "25%";
7353
+ root.style.height = size[ 1 ] ? (size[ 1 ]) : "auto";
7354
+
7355
+ if( options.size )
7356
+ {
7357
+ this.size = size;
7358
+ }
6892
7359
 
6893
- if(options.size) this.size = size;
6894
-
6895
7360
  let rect = root.getBoundingClientRect();
6896
- root.style.left = position[0] ? (position[0]) : "calc( 50% - " + (rect.width * 0.5) + "px )";
6897
- root.style.top = position[1] ? (position[1]) : "calc( 50% - " + (rect.height * 0.5) + "px )";
7361
+ root.style.left = position[ 0 ] ? (position[ 0 ]) : "calc( 50% - " + ( rect.width * 0.5 ) + "px )";
7362
+ root.style.top = position[ 1 ] ? (position[ 1 ]) : "calc( 50% - " + ( rect.height * 0.5 ) + "px )";
6898
7363
 
6899
7364
  panel.root.style.width = "calc( 100% - 30px )";
6900
- panel.root.style.height = title ? "calc( 100% - " + (titleDiv.offsetHeight + 30) + "px )" : "calc( 100% - 51px )";
7365
+ panel.root.style.height = title ? "calc( 100% - " + ( titleDiv.offsetHeight + 30 ) + "px )" : "calc( 100% - 51px )";
6901
7366
  }
6902
7367
 
6903
7368
  destroy() {
@@ -6912,7 +7377,7 @@ class Dialog {
6912
7377
  }
6913
7378
 
6914
7379
  setPosition(x, y) {
6915
-
7380
+
6916
7381
  this.root.style.left = x + "px";
6917
7382
  this.root.style.top = y + "px";
6918
7383
  }
@@ -6941,9 +7406,9 @@ class PocketDialog extends Dialog {
6941
7406
 
6942
7407
  options.draggable = options.draggable ?? false;
6943
7408
  options.closable = options.closable ?? false;
6944
-
7409
+
6945
7410
  super( title, callback, options );
6946
-
7411
+
6947
7412
  let that = this;
6948
7413
  // Update margins on branch title closes/opens
6949
7414
  LX.addSignal("@on_branch_closed", this.panel, closed => {
@@ -6951,7 +7416,7 @@ class PocketDialog extends Dialog {
6951
7416
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6952
7417
  });
6953
7418
 
6954
- // Custom
7419
+ // Custom
6955
7420
  this.root.classList.add( "pocket" );
6956
7421
  if( !options.position ) {
6957
7422
  this.root.style.left = "calc(100% - " + (this.root.offsetWidth + 6) + "px)";
@@ -6965,7 +7430,7 @@ class PocketDialog extends Dialog {
6965
7430
  this.title.tabIndex = -1;
6966
7431
  this.title.addEventListener("click", e => {
6967
7432
 
6968
- // Sized dialogs have to keep their size
7433
+ // Sized dialogs have to keep their size
6969
7434
  if( this.size )
6970
7435
  {
6971
7436
  if( !this.minimized ) this.root.style.height = "auto";
@@ -6976,7 +7441,7 @@ class PocketDialog extends Dialog {
6976
7441
  this.minimized = !this.minimized;
6977
7442
 
6978
7443
  if( this.dock_pos == PocketDialog.BOTTOM )
6979
- that.root.style.top = this.root.classList.contains("minimized") ?
7444
+ that.root.style.top = this.root.classList.contains("minimized") ?
6980
7445
  "calc(100% - " + (that.title.offsetHeight + 6) + "px)" : "calc(100% - " + (that.root.offsetHeight + 6) + "px)";
6981
7446
  });
6982
7447
 
@@ -6991,10 +7456,10 @@ class PocketDialog extends Dialog {
6991
7456
  const t = float[i];
6992
7457
  switch( t )
6993
7458
  {
6994
- case 'b':
7459
+ case 'b':
6995
7460
  this.root.style.top = "calc(100% - " + (this.root.offsetHeight + 6) + "px)";
6996
7461
  break;
6997
- case 'l':
7462
+ case 'l':
6998
7463
  this.root.style.left = options.position ? options.position[ 1 ] : "0px";
6999
7464
  break;
7000
7465
  }
@@ -7028,19 +7493,19 @@ LX.PocketDialog = PocketDialog;
7028
7493
  class ContextMenu {
7029
7494
 
7030
7495
  constructor( event, title, options = {} ) {
7031
-
7496
+
7032
7497
  // remove all context menus
7033
7498
  document.body.querySelectorAll(".lexcontextmenubox").forEach(e => e.remove());
7034
7499
 
7035
7500
  this.root = document.createElement('div');
7036
7501
  this.root.className = "lexcontextmenubox";
7037
- this.root.style.left = (event.x - 48) + "px";
7038
- this.root.style.top = (event.y - 8) + "px";
7502
+ this.root.style.left = (event.x - 48 + document.scrollingElement.scrollLeft) + "px";
7503
+ this.root.style.top = (event.y - 8 + document.scrollingElement.scrollTop) + "px";
7039
7504
 
7040
7505
  this.root.addEventListener("mouseleave", function() {
7041
7506
  this.remove();
7042
7507
  });
7043
-
7508
+
7044
7509
  this.items = [];
7045
7510
  this.colors = {};
7046
7511
 
@@ -7055,11 +7520,11 @@ class ContextMenu {
7055
7520
  }
7056
7521
 
7057
7522
  _adjust_position( div, margin, useAbsolute = false ) {
7058
-
7523
+
7059
7524
  let rect = div.getBoundingClientRect();
7060
-
7525
+
7061
7526
  if( !useAbsolute )
7062
- {
7527
+ {
7063
7528
  let width = rect.width;
7064
7529
  if( rect.left < 0 )
7065
7530
  {
@@ -7069,7 +7534,7 @@ class ContextMenu {
7069
7534
  {
7070
7535
  div.style.left = (window.innerWidth - width - margin) + "px";
7071
7536
  }
7072
-
7537
+
7073
7538
  if( rect.top < 0 )
7074
7539
  {
7075
7540
  div.style.top = margin + "px";
@@ -7086,7 +7551,7 @@ class ContextMenu {
7086
7551
  {
7087
7552
  div.style.left = div.offsetLeft + (dt - margin) + "px";
7088
7553
  }
7089
-
7554
+
7090
7555
  dt = window.innerHeight - (rect.top + rect.height);
7091
7556
  if( dt < 0 )
7092
7557
  {
@@ -7146,14 +7611,14 @@ class ContextMenu {
7146
7611
  entry.addEventListener("click", e => {
7147
7612
  e.stopPropagation();
7148
7613
  e.stopImmediatePropagation();
7149
-
7614
+
7150
7615
  if(disabled) return;
7151
-
7616
+
7152
7617
  const f = o[ 'callback' ];
7153
7618
  if(f) {
7154
7619
  f.call( this, k, entry );
7155
7620
  this.root.remove();
7156
- }
7621
+ }
7157
7622
 
7158
7623
  if( !hasSubmenu )
7159
7624
  return;
@@ -7217,7 +7682,7 @@ class ContextMenu {
7217
7682
  } );
7218
7683
 
7219
7684
  if(found) {
7220
- insert( tokens[idx++], found );
7685
+ insert( tokens[idx++], found );
7221
7686
  }
7222
7687
  else {
7223
7688
  let item = {};
@@ -7228,10 +7693,10 @@ class ContextMenu {
7228
7693
  item[ 'id' ] = options.id;
7229
7694
  item[ 'callback' ] = options.callback;
7230
7695
  item[ 'disabled' ] = options.disabled ?? false;
7231
- }
7696
+ }
7232
7697
 
7233
7698
  list.push( item );
7234
- insert( next_token, item[ token ] );
7699
+ insert( next_token, item[ token ] );
7235
7700
  }
7236
7701
  };
7237
7702
 
@@ -7324,7 +7789,7 @@ class Curve {
7324
7789
  element.style.minWidth = "50px";
7325
7790
  element.style.minHeight = "20px";
7326
7791
 
7327
- element.bgcolor = options.bgColor || LX.getThemeColor( "global-dark-background" );
7792
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-intense-background" );
7328
7793
  element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
7329
7794
  element.linecolor = options.lineColor || "#555";
7330
7795
  element.value = value || [];
@@ -7339,6 +7804,12 @@ class Curve {
7339
7804
  element.smooth = (options.smooth && typeof( options.smooth ) == 'number' ? options.smooth : 0.3) || false;
7340
7805
  element.move_out = options.moveOutAction ?? LX.CURVE_MOVEOUT_DELETE;
7341
7806
 
7807
+ LX.addSignal( "@on_new_color_scheme", (el, value) => {
7808
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-intense-background" );
7809
+ element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
7810
+ this.redraw();
7811
+ } );
7812
+
7342
7813
  this.element = element;
7343
7814
 
7344
7815
  let canvas = document.createElement( "canvas" );
@@ -7416,7 +7887,7 @@ class Curve {
7416
7887
  var selected = -1;
7417
7888
 
7418
7889
  element.redraw = function( o = {} ) {
7419
-
7890
+
7420
7891
  if( o.value ) element.value = o.value;
7421
7892
  if( o.xrange ) element.xrange = o.xrange;
7422
7893
  if( o.yrange ) element.yrange = o.yrange;
@@ -7591,7 +8062,7 @@ class Curve {
7591
8062
  e.preventDefault();
7592
8063
  e.stopPropagation();
7593
8064
  }
7594
-
8065
+
7595
8066
  function onchange( e ) {
7596
8067
  if( options.callback )
7597
8068
  options.callback.call( element, element.value, e );
@@ -7630,7 +8101,7 @@ class Curve {
7630
8101
  selected = element.value.indexOf( v );
7631
8102
  }
7632
8103
  }
7633
-
8104
+
7634
8105
  element.redraw();
7635
8106
  return this;
7636
8107
  }
@@ -7642,6 +8113,335 @@ class Curve {
7642
8113
 
7643
8114
  LX.Curve = Curve;
7644
8115
 
8116
+ /**
8117
+ * @class Dial
8118
+ */
8119
+
8120
+ class Dial {
8121
+
8122
+ constructor( panel, value, options = {} ) {
8123
+
8124
+ let element = document.createElement( "div" );
8125
+ element.className = "dial " + ( options.className ? options.className : "" );
8126
+ element.style.width = element.style.height = options.size || "100%";
8127
+ element.style.minWidth = element.style.minHeight = "50px";
8128
+
8129
+ element.bgcolor = options.bgColor || LX.getThemeColor( "global-dark-background" );
8130
+ element.pointscolor = options.pointsColor || LX.getThemeColor( "global-selected-light" );
8131
+ element.linecolor = options.lineColor || "#555";
8132
+ element.value = value || [];
8133
+ element.xrange = options.xrange || [ 0, 1 ]; // min, max
8134
+ element.yrange = options.yrange || [ 0, 1 ]; // min, max
8135
+ element.defaulty = options.defaulty != null ? options.defaulty : 0.0;
8136
+ element.no_overlap = options.noOverlap || false;
8137
+ element.show_samples = options.showSamples || 0;
8138
+ element.allow_add_values = options.allowAddValues ?? true;
8139
+ element.draggable_x = options.draggableX ?? true;
8140
+ element.draggable_y = options.draggableY ?? true;
8141
+ element.smooth = (options.smooth && typeof( options.smooth ) == 'number' ? options.smooth : 0.3) || false;
8142
+ element.move_out = options.moveOutAction ?? LX.CURVE_MOVEOUT_DELETE;
8143
+
8144
+ this.element = element;
8145
+
8146
+ let canvas = document.createElement( "canvas" );
8147
+ canvas.width = canvas.height = options.size || 200;
8148
+ element.appendChild( canvas );
8149
+ this.canvas = canvas;
8150
+
8151
+ element.addEventListener( "mousedown", onmousedown );
8152
+
8153
+ element.getValueAt = function( x ) {
8154
+
8155
+ if( x < element.xrange[ 0 ] || x > element.xrange[ 1 ] )
8156
+ {
8157
+ return element.defaulty;
8158
+ }
8159
+
8160
+ var last = [ element.xrange[ 0 ], element.defaulty ];
8161
+ var f = 0;
8162
+ for( var i = 0; i < element.value.length; i += 1 )
8163
+ {
8164
+ var v = element.value[ i ];
8165
+ if( x == v[ 0 ] ) return v[ 1 ];
8166
+ if( x < v[ 0 ] )
8167
+ {
8168
+ f = ( x - last[ 0 ] ) / (v[ 0 ] - last[ 0 ]);
8169
+ return last[ 1 ] * ( 1 - f ) + v[ 1 ] * f;
8170
+ }
8171
+
8172
+ last = v;
8173
+ }
8174
+
8175
+ v = [ element.xrange[ 1 ], element.defaulty ];
8176
+ f = (x - last[ 0 ]) / (v[ 0 ] - last[ 0 ]);
8177
+ return last[ 1 ] * ( 1 - f ) + v[ 1 ] * f;
8178
+ }
8179
+
8180
+ element.resample = function( samples ) {
8181
+
8182
+ var r = [];
8183
+ var dx = (element.xrange[1] - element.xrange[ 0 ]) / samples;
8184
+ for(var i = element.xrange[0]; i <= element.xrange[1]; i += dx)
8185
+ {
8186
+ r.push( element.getValueAt(i) );
8187
+ }
8188
+ return r;
8189
+ }
8190
+
8191
+ element.addValue = function(v) {
8192
+
8193
+ for(var i = 0; i < element.value; i++) {
8194
+ var value = element.value[i];
8195
+ if(value[0] < v[0]) continue;
8196
+ element.value.splice(i,0,v);
8197
+ redraw();
8198
+ return;
8199
+ }
8200
+
8201
+ element.value.push(v);
8202
+ redraw();
8203
+ }
8204
+
8205
+ //value to canvas
8206
+ function convert(v, r) {
8207
+
8208
+ Math.pow(v[0],2)
8209
+ return [ canvas.width * ( v[0] - element.xrange[0])/ (element.xrange[1]),
8210
+ canvas.height * (v[1] - element.yrange[0])/ (element.yrange[1])];
8211
+ }
8212
+
8213
+ //canvas to value
8214
+ function unconvert(v) {
8215
+ return [(v[0] * element.xrange[1] / canvas.width + element.xrange[0]),
8216
+ (v[1] * element.yrange[1] / canvas.height + element.yrange[0])];
8217
+ }
8218
+
8219
+ var selected = -1;
8220
+
8221
+ element.redraw = function( o = {} ) {
8222
+
8223
+ if( o.value ) element.value = o.value;
8224
+ if( o.xrange ) element.xrange = o.xrange;
8225
+ if( o.yrange ) element.yrange = o.yrange;
8226
+ if( o.smooth ) element.smooth = o.smooth;
8227
+ var rect = canvas.parentElement.getBoundingClientRect();
8228
+ if( canvas.parentElement.parentElement ) rect = canvas.parentElement.parentElement.getBoundingClientRect();
8229
+ if( rect && canvas.width != rect.width && rect.width && rect.width < 1000 )
8230
+ {
8231
+ canvas.width = rect.width;
8232
+ }
8233
+
8234
+ var ctx = canvas.getContext( "2d" );
8235
+ ctx.setTransform( 1, 0, 0, 1, 0, 0 );
8236
+ ctx.translate( 0, canvas.height );
8237
+ ctx.scale( 1, -1 );
8238
+
8239
+ ctx.fillStyle = element.bgcolor;
8240
+ ctx.fillRect(0,0,canvas.width,canvas.height);
8241
+
8242
+ ctx.strokeStyle = element.linecolor;
8243
+ ctx.beginPath();
8244
+
8245
+ //draw line
8246
+ var pos = convert([ element.xrange[ 0 ],element.defaulty ]);
8247
+ ctx.moveTo( pos[ 0 ], pos[ 1 ] );
8248
+ let values = [pos[ 0 ], pos[ 1 ]];
8249
+
8250
+ for(var i in element.value) {
8251
+ var value = element.value[i];
8252
+ pos = convert(value);
8253
+ values.push(pos[ 0 ]);
8254
+ values.push(pos[ 1 ]);
8255
+
8256
+ }
8257
+
8258
+ pos = convert([ element.xrange[ 1 ], element.defaulty ]);
8259
+ values.push(pos[ 0 ]);
8260
+ values.push(pos[ 1 ]);
8261
+
8262
+ // Draw points
8263
+ const center = [0,0];
8264
+ pos = convert(center)
8265
+ ctx.fillStyle = "gray";
8266
+ ctx.beginPath();
8267
+ ctx.arc( pos[ 0 ], pos[ 1 ], 3, 0, Math.PI * 2);
8268
+ ctx.fill();
8269
+
8270
+ for( var i = 0; i < element.value.length; i += 1 ) {
8271
+ var value = element.value[ i ];
8272
+ pos = convert( value );
8273
+ if( selected == i )
8274
+ ctx.fillStyle = "white";
8275
+ else
8276
+ ctx.fillStyle = element.pointscolor;
8277
+ ctx.beginPath();
8278
+ ctx.arc( pos[ 0 ], pos[ 1 ], selected == i ? 4 : 3, 0, Math.PI * 2);
8279
+ ctx.fill();
8280
+ }
8281
+
8282
+ if(element.show_samples) {
8283
+ var samples = element.resample(element.show_samples);
8284
+ ctx.fillStyle = "#888";
8285
+ for(var i = 0; i < samples.length; i += 1)
8286
+ {
8287
+ var value = [ i * ((element.xrange[ 1 ] - element.xrange[ 0 ]) / element.show_samples) + element.xrange[ 0 ], samples[ i ] ];
8288
+ pos = convert(value);
8289
+ ctx.beginPath();
8290
+ ctx.arc( pos[ 0 ], pos[ 1 ], 2, 0, Math.PI * 2);
8291
+ ctx.fill();
8292
+ }
8293
+ }
8294
+ }
8295
+
8296
+ var last_mouse = [ 0, 0 ];
8297
+
8298
+ function onmousedown( e ) {
8299
+ document.addEventListener( "mousemove", onmousemove );
8300
+ document.addEventListener( "mouseup", onmouseup );
8301
+
8302
+ var rect = canvas.getBoundingClientRect();
8303
+ var mousex = e.clientX - rect.left;
8304
+ var mousey = e.clientY - rect.top;
8305
+
8306
+ selected = computeSelected( mousex, canvas.height - mousey );
8307
+
8308
+ if( e.button == LX.MOUSE_LEFT_CLICK && selected == -1 && element.allow_add_values ) {
8309
+ var v = unconvert([ mousex, canvas.height - mousey ]);
8310
+ element.value.push( v );
8311
+ sortValues();
8312
+ selected = element.value.indexOf( v );
8313
+ }
8314
+
8315
+ last_mouse = [ mousex, mousey ];
8316
+ element.redraw();
8317
+ e.preventDefault();
8318
+ e.stopPropagation();
8319
+ }
8320
+
8321
+ function onmousemove( e ) {
8322
+
8323
+ var rect = canvas.getBoundingClientRect();
8324
+ var mousex = e.clientX - rect.left;
8325
+ var mousey = e.clientY - rect.top;
8326
+
8327
+ if( mousex < 0 ) mousex = 0;
8328
+ else if( mousex > canvas.width ) mousex = canvas.width;
8329
+ if( mousey < 0 ) mousey = 0;
8330
+ else if( mousey > canvas.height ) mousey = canvas.height;
8331
+
8332
+ // Dragging to remove
8333
+ const currentMouseDiff = [ e.clientX - rect.left, e.clientY - rect.top ];
8334
+ if( selected != -1 && distance( currentMouseDiff, [ mousex, mousey ] ) > canvas.height * 0.5 )
8335
+ {
8336
+ if( element.move_out == LX.CURVE_MOVEOUT_DELETE)
8337
+ {
8338
+ element.value.splice( selected, 1 );
8339
+ }
8340
+ else
8341
+ {
8342
+ const d = [ currentMouseDiff[ 0 ] - mousex, currentMouseDiff[ 1 ] - mousey ];
8343
+ let value = element.value[ selected ];
8344
+ value[ 0 ] = ( d[ 0 ] == 0.0 ) ? value[ 0 ] : ( d[ 0 ] < 0.0 ? element.xrange[ 0 ] : element.xrange[ 1 ] );
8345
+ value[ 1 ] = ( d[ 1 ] == 0.0 ) ? value[ 1 ] : ( d[ 1 ] < 0.0 ? element.yrange[ 1 ] : element.yrange[ 0 ] );
8346
+ }
8347
+
8348
+ onmouseup( e );
8349
+ return;
8350
+ }
8351
+
8352
+ var dx = element.draggable_x ? last_mouse[ 0 ] - mousex : 0;
8353
+ var dy = element.draggable_y ? last_mouse[ 1 ] - mousey : 0;
8354
+ var delta = unconvert([ -dx, dy ]);
8355
+
8356
+ if( selected != -1 ) {
8357
+ var minx = element.xrange[ 0 ];
8358
+ var maxx = element.xrange[ 1 ];
8359
+
8360
+ if( element.no_overlap )
8361
+ {
8362
+ if( selected > 0) minx = element.value[ selected - 1 ][ 0 ];
8363
+ if( selected < ( element.value.length - 1 ) ) maxx = element.value[ selected + 1 ][ 0 ];
8364
+ }
8365
+
8366
+ var v = element.value[selected];
8367
+ v[ 0 ] += delta[ 0 ];
8368
+ v[ 1 ] += delta[ 1 ];
8369
+ if(v[ 0 ] < minx) v[ 0 ] = minx;
8370
+ else if(v[ 0 ] > maxx) v[ 0 ] = maxx;
8371
+ if(v[ 1 ] < element.yrange[ 0 ]) v[ 1 ] = element.yrange[ 0 ];
8372
+ else if(v[ 1 ] > element.yrange[ 1 ]) v[ 1 ] = element.yrange[ 1 ];
8373
+ }
8374
+
8375
+ sortValues();
8376
+ element.redraw();
8377
+ last_mouse[ 0 ] = mousex;
8378
+ last_mouse[ 1 ] = mousey;
8379
+ onchange( e );
8380
+
8381
+ e.preventDefault();
8382
+ e.stopPropagation();
8383
+ }
8384
+
8385
+ function onmouseup( e ) {
8386
+ selected = -1;
8387
+ element.redraw();
8388
+ document.removeEventListener("mousemove", onmousemove);
8389
+ document.removeEventListener("mouseup", onmouseup);
8390
+ onchange(e);
8391
+ e.preventDefault();
8392
+ e.stopPropagation();
8393
+ }
8394
+
8395
+ function onchange( e ) {
8396
+ if( options.callback )
8397
+ options.callback.call( element, element.value, e );
8398
+ }
8399
+
8400
+ function distance(a,b) { return Math.sqrt( Math.pow(b[0]-a[0],2) + Math.pow(b[1]-a[1],2) ); };
8401
+
8402
+ function computeSelected( x, y ) {
8403
+
8404
+ var minDistance = 100000;
8405
+ var maxDistance = 8; //pixels
8406
+ var selected = -1;
8407
+ for( var i = 0; i < element.value.length; i++ )
8408
+ {
8409
+ var value = element.value[ i ];
8410
+ var pos = convert( value );
8411
+ var dist = distance( [ x,y ], pos );
8412
+ if( dist < minDistance && dist < maxDistance )
8413
+ {
8414
+ minDistance = dist;
8415
+ selected = i;
8416
+ }
8417
+ }
8418
+ return selected;
8419
+ }
8420
+
8421
+ function sortValues() {
8422
+ var v = null;
8423
+ if( selected != -1 )
8424
+ {
8425
+ v = element.value[ selected ];
8426
+ }
8427
+ element.value.sort(function( a,b ) { return a[ 0 ] - b[ 0 ]; });
8428
+ if( v )
8429
+ {
8430
+ selected = element.value.indexOf( v );
8431
+ }
8432
+ }
8433
+
8434
+ element.redraw();
8435
+ return this;
8436
+ }
8437
+
8438
+ redraw( options = {} ) {
8439
+ this.element.redraw( options );
8440
+ }
8441
+ }
8442
+
8443
+ LX.Dial = Dial;
8444
+
7645
8445
  class AssetViewEvent {
7646
8446
 
7647
8447
  static NONE = 0;
@@ -7659,7 +8459,7 @@ class AssetViewEvent {
7659
8459
  this.value = value;
7660
8460
  this.multiple = false; // Multiple selection
7661
8461
  }
7662
-
8462
+
7663
8463
  string() {
7664
8464
  switch(this.type) {
7665
8465
  case AssetViewEvent.NONE: return "assetview_event_none";
@@ -7717,6 +8517,7 @@ class AssetView {
7717
8517
  this.skipPreview = options.skipPreview ?? false;
7718
8518
  this.useNativeTitle = options.useNativeTitle ?? false;
7719
8519
  this.onlyFolders = options.onlyFolders ?? true;
8520
+ this.allowMultipleSelection = options.allowMultipleSelection ?? false;
7720
8521
  this.previewActions = options.previewActions ?? [];
7721
8522
  this.contextMenu = options.contextMenu ?? [];
7722
8523
  this.onRefreshContent = options.onRefreshContent;
@@ -7734,7 +8535,7 @@ class AssetView {
7734
8535
  {
7735
8536
  [ contentArea, right ] = contentArea.split({ type: "horizontal", sizes: ["80%", "20%"]});
7736
8537
  }
7737
-
8538
+
7738
8539
  this.allowedTypes = options.allowedTypes || ["None", "Image", "Mesh", "Script", "JSON", "Clip"];
7739
8540
 
7740
8541
  this.prevData = [];
@@ -7752,7 +8553,7 @@ class AssetView {
7752
8553
  }
7753
8554
 
7754
8555
  this._createContentPanel( contentArea );
7755
-
8556
+
7756
8557
  // Create resource preview panel
7757
8558
  if( !this.skipPreview )
7758
8559
  {
@@ -7768,7 +8569,7 @@ class AssetView {
7768
8569
 
7769
8570
  this.prevData.length = 0;
7770
8571
  this.nextData.length = 0;
7771
-
8572
+
7772
8573
  this.data = data;
7773
8574
 
7774
8575
  this._processData( this.data, null );
@@ -7831,7 +8632,7 @@ class AssetView {
7831
8632
  */
7832
8633
 
7833
8634
  _updatePath( data ) {
7834
-
8635
+
7835
8636
  this.path.length = 0;
7836
8637
 
7837
8638
  const push_parents_id = i => {
@@ -7871,7 +8672,7 @@ class AssetView {
7871
8672
  }
7872
8673
 
7873
8674
  this.tree = this.leftPanel.addTree( "Content Browser", tree_data, {
7874
- // icons: tree_icons,
8675
+ // icons: tree_icons,
7875
8676
  filter: false,
7876
8677
  onlyFolders: this.onlyFolders,
7877
8678
  onevent: event => {
@@ -7881,7 +8682,7 @@ class AssetView {
7881
8682
 
7882
8683
  switch( event.type )
7883
8684
  {
7884
- case LX.TreeEvent.NODE_SELECTED:
8685
+ case LX.TreeEvent.NODE_SELECTED:
7885
8686
  if( !event.multiple )
7886
8687
  {
7887
8688
  this._enterFolder( node );
@@ -7896,13 +8697,13 @@ class AssetView {
7896
8697
  LX.emit("@on_folder_change", this.path.join('/'));
7897
8698
  }
7898
8699
  break;
7899
- case LX.TreeEvent.NODE_DRAGGED:
8700
+ case LX.TreeEvent.NODE_DRAGGED:
7900
8701
  node.folder = value;
7901
8702
  this._refreshContent();
7902
8703
  break;
7903
8704
  }
7904
8705
  },
7905
- });
8706
+ });
7906
8707
  }
7907
8708
 
7908
8709
  /**
@@ -8052,7 +8853,7 @@ class AssetView {
8052
8853
  this.content.innerHTML = "";
8053
8854
  this.content.className = (isContentLayout ? "lexassetscontent" : "lexassetscontent list");
8054
8855
  let that = this;
8055
-
8856
+
8056
8857
  const add_item = function(item) {
8057
8858
 
8058
8859
  const type = item.type.charAt( 0 ).toUpperCase() + item.type.slice( 1 );
@@ -8115,15 +8916,13 @@ class AssetView {
8115
8916
  itemEl.title = type + ": " + item.id;
8116
8917
  }
8117
8918
 
8118
- if( item.selected != undefined )
8919
+ if( that.allowMultipleSelection )
8119
8920
  {
8120
- let span = document.createElement('span');
8121
- span.className = "lexcheckbox";
8122
- let checkbox_input = document.createElement('input');
8123
- checkbox_input.type = "checkbox";
8124
- checkbox_input.className = "checkbox";
8125
- checkbox_input.checked = item.selected;
8126
- checkbox_input.addEventListener('change', ( e, v ) => {
8921
+ let checkbox = document.createElement( 'input' );
8922
+ checkbox.type = "checkbox";
8923
+ checkbox.className = "lexcheckbox";
8924
+ checkbox.checked = item.selected;
8925
+ checkbox.addEventListener('change', ( e, v ) => {
8127
8926
  item.selected = !item.selected;
8128
8927
  if( that.onevent )
8129
8928
  {
@@ -8133,10 +8932,9 @@ class AssetView {
8133
8932
  }
8134
8933
  e.stopPropagation();
8135
8934
  e.stopImmediatePropagation();
8136
- })
8137
- span.appendChild(checkbox_input);
8138
- itemEl.appendChild(span);
8139
-
8935
+ });
8936
+
8937
+ itemEl.appendChild( checkbox );
8140
8938
  }
8141
8939
 
8142
8940
  let title = document.createElement('span');
@@ -8161,7 +8959,7 @@ class AssetView {
8161
8959
  preview = document.createElement('svg');
8162
8960
  preview.className = "asset-file-preview";
8163
8961
  itemEl.appendChild(preview);
8164
-
8962
+
8165
8963
  let textEl = document.createElement('text');
8166
8964
  preview.appendChild(textEl);
8167
8965
  // If no extension, e.g. Clip, use the type...
@@ -8202,12 +9000,13 @@ class AssetView {
8202
9000
  }
8203
9001
 
8204
9002
  this.classList.add('selected');
9003
+ that.selectedItem = item;
8205
9004
 
8206
9005
  if( !that.skipPreview )
8207
9006
  {
8208
9007
  that._previewAsset( item );
8209
9008
  }
8210
- }
9009
+ }
8211
9010
  else if( isFolder )
8212
9011
  {
8213
9012
  that._enterFolder( item );
@@ -8229,7 +9028,7 @@ class AssetView {
8229
9028
 
8230
9029
  const multiple = that.content.querySelectorAll('.selected').length;
8231
9030
 
8232
- LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
9031
+ LX.addContextMenu( multiple > 1 ? (multiple + " selected") :
8233
9032
  isFolder ? item.id : item.type, e, m => {
8234
9033
  if( multiple <= 1 )
8235
9034
  {
@@ -8281,7 +9080,7 @@ class AssetView {
8281
9080
  LX.request({ url: item.path, dataType: 'blob', success: (f) => {
8282
9081
  item.bytesize = f.size;
8283
9082
  fr.readAsDataURL( f );
8284
- fr.onload = e => {
9083
+ fr.onload = e => {
8285
9084
  item.src = e.currentTarget.result; // This is a base64 string...
8286
9085
  item._path = item.path;
8287
9086
  delete item.path;
@@ -8334,14 +9133,14 @@ class AssetView {
8334
9133
  if( file.type == "folder" ) this.previewPanel.addText("Files", file.children ? file.children.length.toString() : "0", null, options);
8335
9134
 
8336
9135
  this.previewPanel.addSeparator();
8337
-
9136
+
8338
9137
  const previewActions = [...this.previewActions];
8339
9138
 
8340
9139
  if( !previewActions.length )
8341
9140
  {
8342
9141
  // By default
8343
9142
  previewActions.push({
8344
- name: 'Download',
9143
+ name: 'Download',
8345
9144
  callback: () => LX.downloadURL(file.src, file.id)
8346
9145
  });
8347
9146
  }
@@ -8369,8 +9168,8 @@ class AssetView {
8369
9168
  if(result) continue;
8370
9169
 
8371
9170
  fr.readAsDataURL( file );
8372
- fr.onload = e => {
8373
-
9171
+ fr.onload = e => {
9172
+
8374
9173
  let ext = file.name.substr(file.name.lastIndexOf('.') + 1).toLowerCase();
8375
9174
 
8376
9175
  let item = {
@@ -8385,12 +9184,12 @@ class AssetView {
8385
9184
  case 'png':
8386
9185
  case 'jpg':
8387
9186
  item.type = "image"; break;
8388
- case 'js':
8389
- case 'css':
9187
+ case 'js':
9188
+ case 'css':
8390
9189
  item.type = "script"; break;
8391
- case 'json':
9190
+ case 'json':
8392
9191
  item.type = "json"; break;
8393
- case 'obj':
9192
+ case 'obj':
8394
9193
  item.type = "mesh"; break;
8395
9194
  default:
8396
9195
  item.type = ext;
@@ -8399,7 +9198,7 @@ class AssetView {
8399
9198
  }
8400
9199
 
8401
9200
  this.currentData.push( item );
8402
-
9201
+
8403
9202
  if(i == (num_files - 1)) {
8404
9203
  this._refreshContent();
8405
9204
  if( !this.skipBrowser )
@@ -8488,7 +9287,7 @@ class AssetView {
8488
9287
  }
8489
9288
 
8490
9289
  LX.AssetView = AssetView;
8491
-
9290
+
8492
9291
  /*
8493
9292
  * Requests
8494
9293
  */
@@ -8496,7 +9295,7 @@ LX.AssetView = AssetView;
8496
9295
  Object.assign(LX, {
8497
9296
 
8498
9297
  /**
8499
- * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
9298
+ * Request file from url (it could be a binary, text, etc.). If you want a simplied version use
8500
9299
  * @method request
8501
9300
  * @param {Object} request object with all the parameters like data (for sending forms), dataType, success, error
8502
9301
  * @param {Function} on_complete
@@ -8616,7 +9415,7 @@ Object.assign(LX, {
8616
9415
  requestBinary(url, on_complete, on_error ) {
8617
9416
  return this.request({ url: url, dataType:"binary", success: on_complete, error: on_error });
8618
9417
  },
8619
-
9418
+
8620
9419
  /**
8621
9420
  * Request script and inserts it in the DOM
8622
9421
  * @method requireScript
@@ -8645,7 +9444,7 @@ Object.assign(LX, {
8645
9444
  script.src = url[i] + ( version ? "?version=" + version : "" );
8646
9445
  script.original_src = url[i];
8647
9446
  script.async = false;
8648
- script.onload = function(e) {
9447
+ script.onload = function(e) {
8649
9448
  total--;
8650
9449
  loaded_scripts.push(this);
8651
9450
  if(total)
@@ -8657,7 +9456,7 @@ Object.assign(LX, {
8657
9456
  on_complete( loaded_scripts );
8658
9457
  };
8659
9458
  if(on_error)
8660
- script.onerror = function(err) {
9459
+ script.onerror = function(err) {
8661
9460
  on_error(err, this.original_src, this.num );
8662
9461
  }
8663
9462
  document.getElementsByTagName('head')[0].appendChild(script);
@@ -8681,7 +9480,7 @@ Object.assign(LX, {
8681
9480
  {
8682
9481
  LX.request({ url: url, dataType: 'blob', success: (f) => {
8683
9482
  fr.readAsDataURL( f );
8684
- fr.onload = e => {
9483
+ fr.onload = e => {
8685
9484
  _download(e.currentTarget.result);
8686
9485
  };
8687
9486
  } });
@@ -8767,31 +9566,31 @@ LX.UTILS = {
8767
9566
  element.offsetHeight;
8768
9567
  },
8769
9568
  getControlPoints( x0, y0, x1, y1, x2, y2, t ) {
8770
-
9569
+
8771
9570
  // x0,y0,x1,y1 are the coordinates of the end (knot) pts of this segment
8772
9571
  // x2,y2 is the next knot -- not connected here but needed to calculate p2
8773
9572
  // p1 is the control point calculated here, from x1 back toward x0.
8774
- // p2 is the next control point, calculated here and returned to become the
9573
+ // p2 is the next control point, calculated here and returned to become the
8775
9574
  // next segment's p1.
8776
9575
  // t is the 'tension' which controls how far the control points spread.
8777
-
9576
+
8778
9577
  // Scaling factors: distances from this knot to the previous and following knots.
8779
9578
  var d01=Math.sqrt(Math.pow(x1-x0,2)+Math.pow(y1-y0,2));
8780
9579
  var d12=Math.sqrt(Math.pow(x2-x1,2)+Math.pow(y2-y1,2));
8781
-
9580
+
8782
9581
  var fa=t*d01/(d01+d12);
8783
9582
  var fb=t-fa;
8784
-
9583
+
8785
9584
  var p1x=x1+fa*(x0-x2);
8786
9585
  var p1y=y1+fa*(y0-y2);
8787
-
9586
+
8788
9587
  var p2x=x1-fb*(x0-x2);
8789
- var p2y=y1-fb*(y0-y2);
8790
-
9588
+ var p2y=y1-fb*(y0-y2);
9589
+
8791
9590
  return [p1x,p1y,p2x,p2y]
8792
9591
  },
8793
9592
  drawSpline( ctx, pts, t ) {
8794
-
9593
+
8795
9594
  ctx.save();
8796
9595
  var cp=[]; // array of control points, as x0,y0,x1,y1,...
8797
9596
  var n=pts.length;
@@ -8799,7 +9598,7 @@ LX.UTILS = {
8799
9598
  // Draw an open curve, not connected at the ends
8800
9599
  for(var i=0;i<n-4;i+=2) {
8801
9600
  cp=cp.concat(LX.UTILS.getControlPoints(pts[i],pts[i+1],pts[i+2],pts[i+3],pts[i+4],pts[i+5],t));
8802
- }
9601
+ }
8803
9602
 
8804
9603
  for(var i=2;i<pts.length-5;i+=2) {
8805
9604
  ctx.beginPath();
@@ -8815,13 +9614,13 @@ LX.UTILS = {
8815
9614
  ctx.quadraticCurveTo(cp[0],cp[1],pts[2],pts[3]);
8816
9615
  ctx.stroke();
8817
9616
  ctx.closePath();
8818
-
9617
+
8819
9618
  ctx.beginPath();
8820
9619
  ctx.moveTo(pts[n-2],pts[n-1]);
8821
9620
  ctx.quadraticCurveTo(cp[2*n-10],cp[2*n-9],pts[n-4],pts[n-3]);
8822
9621
  ctx.stroke();
8823
9622
  ctx.closePath();
8824
-
9623
+
8825
9624
  ctx.restore();
8826
9625
  }
8827
9626
  };