treste 2.4.8 → 2.4.9

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.
@@ -408,10 +408,73 @@ class StdAsync {
408
408
  exports.StdAsync = StdAsync;
409
409
  /**
410
410
  * ========================================
411
- * GUI Module - Interface Gráfica
411
+ * GUI Module - Interface Gráfica Desktop com Electron
412
412
  * ========================================
413
413
  */
414
+ // Variáveis globais para Electron
415
+ let electronModule = null;
416
+ let BrowserWindow = null;
417
+ let app = null;
418
+ function loadElectron() {
419
+ // Se já carregado e disponível, retornar true
420
+ if (electronModule && app && BrowserWindow) {
421
+ // Verificar se app.isReady() ou se está disponível
422
+ if (app && typeof app.isReady === 'function') {
423
+ return true;
424
+ }
425
+ }
426
+ try {
427
+ // Tentar carregar Electron
428
+ electronModule = require('electron');
429
+ if (electronModule) {
430
+ // Tentar acessar app e BrowserWindow
431
+ // Quando executado através do Node.js (não Electron main process),
432
+ // app e BrowserWindow podem não estar disponíveis diretamente
433
+ if (electronModule.app) {
434
+ app = electronModule.app;
435
+ }
436
+ if (electronModule.BrowserWindow) {
437
+ BrowserWindow = electronModule.BrowserWindow;
438
+ }
439
+ // Se temos Electron instalado mas app/BrowserWindow não estão disponíveis,
440
+ // ainda podemos tentar usá-los (pode funcionar em contexto Electron)
441
+ if (electronModule && electronModule.app && electronModule.BrowserWindow) {
442
+ app = electronModule.app;
443
+ BrowserWindow = electronModule.BrowserWindow;
444
+ return true;
445
+ }
446
+ // Se app ou BrowserWindow não estão disponíveis, retornar false
447
+ // Isso significa que não estamos no contexto Electron main process
448
+ return false;
449
+ }
450
+ }
451
+ catch (e) {
452
+ // Electron não instalado
453
+ return false;
454
+ }
455
+ return false;
456
+ }
414
457
  class StdGUI {
458
+ /**
459
+ * Initialize Electron app
460
+ */
461
+ static ensureAppReady() {
462
+ if (!loadElectron()) {
463
+ console.error('❌ Electron não está instalado. Para usar GUI desktop, instale: npm install electron');
464
+ return Promise.resolve(false);
465
+ }
466
+ if (this.appReady) {
467
+ return Promise.resolve(true);
468
+ }
469
+ if (app.isReady()) {
470
+ this.appReady = true;
471
+ return Promise.resolve(true);
472
+ }
473
+ return app.whenReady().then(() => {
474
+ this.appReady = true;
475
+ return true;
476
+ });
477
+ }
415
478
  /**
416
479
  * Create Terminal
417
480
  */
@@ -421,7 +484,6 @@ class StdGUI {
421
484
  console.clear();
422
485
  },
423
486
  printAt: (x, y, text) => {
424
- // Basic terminal positioning
425
487
  process.stdout.write(`\x1b[${y};${x}H${text}`);
426
488
  },
427
489
  getHeight: () => process.stdout.rows || 24,
@@ -429,51 +491,254 @@ class StdGUI {
429
491
  };
430
492
  }
431
493
  /**
432
- * Create Window (placeholder)
494
+ * Create Desktop Window with Electron
433
495
  */
434
496
  static createWindow(options) {
435
- console.log(`Creating window: ${options.title || 'Untitled'}`);
436
- return {
437
- show: () => console.log('Window shown'),
438
- hide: () => console.log('Window hidden'),
439
- close: () => console.log('Window closed'),
440
- addComponent: (component) => {
441
- console.log('Component added', component);
497
+ const title = options.title || options.título || 'Janela Trest';
498
+ const width = options.width || options.largura || 800;
499
+ const height = options.height || options.altura || 600;
500
+ const resizable = options.resizable !== false;
501
+ const center = options.center !== false;
502
+ const minWidth = options.minWidth || options.larguraMinima || undefined;
503
+ const minHeight = options.minHeight || options.alturaMinima || undefined;
504
+ const maxWidth = options.maxWidth || options.larguraMaxima || undefined;
505
+ const maxHeight = options.maxHeight || options.alturaMaxima || undefined;
506
+ const fullscreen = options.fullscreen || options.telaCheia || false;
507
+ const alwaysOnTop = options.alwaysOnTop || options.sempreNoTopo || false;
508
+ const winId = ++this.windowCounter;
509
+ let browserWindow = null;
510
+ // Check if Electron is available
511
+ if (!loadElectron()) {
512
+ return this.createFallbackWindow(options);
513
+ }
514
+ // Create window wrapper object immediately (window will be created when app is ready)
515
+ const windowWrapper = {
516
+ id: winId,
517
+ _pending: true,
518
+ _options: options,
519
+ show: () => {
520
+ if (browserWindow) {
521
+ browserWindow.show();
522
+ }
523
+ else if (windowWrapper._pending) {
524
+ windowWrapper._pendingShow = true;
525
+ }
526
+ },
527
+ hide: () => {
528
+ if (browserWindow) {
529
+ browserWindow.hide();
530
+ }
531
+ },
532
+ close: () => {
533
+ if (browserWindow) {
534
+ browserWindow.close();
535
+ this.windows.delete(winId);
536
+ }
537
+ },
538
+ loadHTML: (html) => {
539
+ if (browserWindow) {
540
+ browserWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(html)}`);
541
+ }
542
+ else {
543
+ windowWrapper._pendingHTML = html;
544
+ }
545
+ },
546
+ loadFile: (filePath) => {
547
+ if (browserWindow) {
548
+ browserWindow.loadFile(filePath);
549
+ }
550
+ else {
551
+ windowWrapper._pendingFile = filePath;
552
+ }
553
+ },
554
+ loadURL: (url) => {
555
+ if (browserWindow) {
556
+ browserWindow.loadURL(url);
557
+ }
558
+ else {
559
+ windowWrapper._pendingURL = url;
560
+ }
561
+ },
562
+ setTitle: (newTitle) => {
563
+ if (browserWindow) {
564
+ browserWindow.setTitle(newTitle);
565
+ }
566
+ else {
567
+ windowWrapper._pendingTitle = newTitle;
568
+ }
569
+ },
570
+ setSize: (w, h) => {
571
+ if (browserWindow) {
572
+ browserWindow.setSize(w, h);
573
+ }
574
+ },
575
+ setMinimumSize: (w, h) => {
576
+ if (browserWindow) {
577
+ browserWindow.setMinimumSize(w, h);
578
+ }
579
+ },
580
+ setMaximumSize: (w, h) => {
581
+ if (browserWindow) {
582
+ browserWindow.setMaximumSize(w, h);
583
+ }
584
+ },
585
+ minimize: () => {
586
+ if (browserWindow)
587
+ browserWindow.minimize();
588
+ },
589
+ maximize: () => {
590
+ if (browserWindow)
591
+ browserWindow.maximize();
592
+ },
593
+ restore: () => {
594
+ if (browserWindow)
595
+ browserWindow.restore();
442
596
  },
597
+ focus: () => {
598
+ if (browserWindow)
599
+ browserWindow.focus();
600
+ },
601
+ on: (event, callback) => {
602
+ if (browserWindow) {
603
+ browserWindow.on(event, callback);
604
+ }
605
+ },
606
+ webContents: null,
607
+ };
608
+ // Initialize app and create window in background
609
+ this.ensureAppReady().then((isReady) => {
610
+ if (!isReady) {
611
+ return;
612
+ }
613
+ try {
614
+ browserWindow = new BrowserWindow({
615
+ width: width,
616
+ height: height,
617
+ title: title,
618
+ resizable: resizable,
619
+ center: center,
620
+ minWidth: minWidth,
621
+ minHeight: minHeight,
622
+ maxWidth: maxWidth,
623
+ maxHeight: maxHeight,
624
+ fullscreen: fullscreen,
625
+ alwaysOnTop: alwaysOnTop,
626
+ webPreferences: {
627
+ nodeIntegration: true,
628
+ contextIsolation: false,
629
+ webSecurity: false,
630
+ },
631
+ show: false,
632
+ });
633
+ this.windows.set(winId, browserWindow);
634
+ windowWrapper._pending = false;
635
+ windowWrapper.webContents = browserWindow.webContents;
636
+ browserWindow.on('closed', () => {
637
+ this.windows.delete(winId);
638
+ browserWindow = null;
639
+ });
640
+ // Apply pending operations
641
+ if (windowWrapper._pendingHTML) {
642
+ browserWindow.loadURL(`data:text/html;charset=utf-8,${encodeURIComponent(windowWrapper._pendingHTML)}`);
643
+ }
644
+ else if (windowWrapper._pendingFile) {
645
+ browserWindow.loadFile(windowWrapper._pendingFile);
646
+ }
647
+ else if (windowWrapper._pendingURL) {
648
+ browserWindow.loadURL(windowWrapper._pendingURL);
649
+ }
650
+ if (windowWrapper._pendingTitle) {
651
+ browserWindow.setTitle(windowWrapper._pendingTitle);
652
+ }
653
+ if (windowWrapper._pendingShow) {
654
+ browserWindow.show();
655
+ }
656
+ }
657
+ catch (error) {
658
+ console.error('❌ Erro ao criar janela Electron:', error.message);
659
+ }
660
+ });
661
+ return windowWrapper;
662
+ }
663
+ /**
664
+ * Fallback window (quando Electron não está disponível)
665
+ */
666
+ static createFallbackWindow(options) {
667
+ console.warn('⚠️ Electron não disponível - usando modo fallback');
668
+ return {
669
+ id: ++this.windowCounter,
670
+ show: () => console.log('Window shown (fallback mode)'),
671
+ hide: () => console.log('Window hidden (fallback mode)'),
672
+ close: () => console.log('Window closed (fallback mode)'),
673
+ loadHTML: (html) => console.log('HTML loaded (fallback mode)'),
674
+ loadFile: (filePath) => console.log(`File loaded: ${filePath} (fallback mode)`),
675
+ loadURL: (url) => console.log(`URL loaded: ${url} (fallback mode)`),
676
+ setTitle: (title) => console.log(`Title set: ${title} (fallback mode)`),
677
+ setSize: () => { },
678
+ setMinimumSize: () => { },
679
+ setMaximumSize: () => { },
680
+ minimize: () => { },
681
+ maximize: () => { },
682
+ restore: () => { },
683
+ focus: () => { },
684
+ on: () => { },
685
+ webContents: null,
443
686
  };
444
687
  }
445
688
  /**
446
- * Create Button
689
+ * Create Button (HTML-based)
447
690
  */
448
691
  static createButton(text, onClick) {
449
692
  return {
450
- text,
451
- click: onClick,
452
- disable: () => console.log(`Button "${text}" disabled`),
453
- enable: () => console.log(`Button "${text}" enabled`),
693
+ type: 'button',
694
+ text: text,
695
+ onClick: onClick,
696
+ disabled: false,
697
+ disable: function () {
698
+ this.disabled = true;
699
+ },
700
+ enable: function () {
701
+ this.disabled = false;
702
+ },
703
+ setText: function (newText) {
704
+ this.text = newText;
705
+ },
454
706
  };
455
707
  }
456
708
  /**
457
- * Create Text Input
709
+ * Create Text Input (HTML-based)
458
710
  */
459
711
  static createText(placeholder, onChange) {
460
712
  return {
713
+ type: 'text',
714
+ placeholder: placeholder,
461
715
  value: '',
462
- change: onChange,
463
- focus: () => console.log('Text focused'),
464
- blur: () => console.log('Text blurred'),
716
+ onChange: onChange,
717
+ setValue: function (newValue) {
718
+ this.value = newValue;
719
+ },
720
+ focus: function () { },
721
+ blur: function () { },
465
722
  };
466
723
  }
467
724
  /**
468
- * Create List
725
+ * Create List (HTML-based)
469
726
  */
470
727
  static createList(data, onSelect) {
471
728
  return {
472
- data,
473
- select: onSelect,
474
- update: (newData) => {
475
- data = newData;
476
- console.log('List updated');
729
+ type: 'list',
730
+ data: data || [],
731
+ onSelect: onSelect,
732
+ update: function (newData) {
733
+ this.data = newData || [];
734
+ },
735
+ add: function (item) {
736
+ this.data.push(item);
737
+ },
738
+ remove: function (index) {
739
+ if (index >= 0 && index < this.data.length) {
740
+ this.data.splice(index, 1);
741
+ }
477
742
  },
478
743
  };
479
744
  }
@@ -482,14 +747,18 @@ class StdGUI {
482
747
  */
483
748
  static componentContainer(children) {
484
749
  return {
485
- children,
486
- add: (component) => {
487
- children.push(component);
750
+ type: 'container',
751
+ children: children || [],
752
+ add: function (component) {
753
+ this.children.push(component);
488
754
  },
489
- remove: (component) => {
490
- const index = children.indexOf(component);
755
+ remove: function (component) {
756
+ const index = this.children.indexOf(component);
491
757
  if (index > -1)
492
- children.splice(index, 1);
758
+ this.children.splice(index, 1);
759
+ },
760
+ clear: function () {
761
+ this.children = [];
493
762
  },
494
763
  };
495
764
  }
@@ -497,20 +766,426 @@ class StdGUI {
497
766
  * Component Label
498
767
  */
499
768
  static componentLabel(text) {
500
- return { text };
769
+ return {
770
+ type: 'label',
771
+ text: text,
772
+ setText: function (newText) {
773
+ this.text = newText;
774
+ },
775
+ };
501
776
  }
502
777
  /**
503
778
  * Component Image
504
779
  */
505
780
  static componentImage(src) {
506
781
  return {
507
- src,
508
- show: () => console.log(`Image shown: ${src}`),
509
- hide: () => console.log(`Image hidden: ${src}`),
782
+ type: 'image',
783
+ src: src,
784
+ visible: true,
785
+ show: function () {
786
+ this.visible = true;
787
+ },
788
+ hide: function () {
789
+ this.visible = false;
790
+ },
791
+ setSrc: function (newSrc) {
792
+ this.src = newSrc;
793
+ },
794
+ };
795
+ }
796
+ /**
797
+ * Helper: Render components to HTML
798
+ */
799
+ static renderComponentToHTML(component) {
800
+ if (!component)
801
+ return '';
802
+ switch (component.type) {
803
+ case 'button':
804
+ return `<button onclick="window.trestGuiClickHandler(${JSON.stringify(component.text)})" ${component.disabled ? 'disabled' : ''}>${component.text}</button>`;
805
+ case 'text':
806
+ return `<input type="text" placeholder="${component.placeholder || ''}" value="${component.value || ''}" onchange="window.trestGuiChangeHandler(this.value)" />`;
807
+ case 'label':
808
+ return `<label>${component.text || ''}</label>`;
809
+ case 'image':
810
+ if (!component.visible)
811
+ return '';
812
+ return `<img src="${component.src || ''}" alt="" />`;
813
+ case 'list':
814
+ const items = (component.data || []).map((item, index) => `<li onclick="window.trestGuiSelectHandler(${index})">${item}</li>`).join('');
815
+ return `<ul>${items}</ul>`;
816
+ case 'container':
817
+ const childrenHTML = (component.children || []).map((child) => this.renderComponentToHTML(child)).join('');
818
+ return `<div>${childrenHTML}</div>`;
819
+ default:
820
+ return '';
821
+ }
822
+ }
823
+ /**
824
+ * Keep app running (prevents Electron from closing)
825
+ */
826
+ static keepRunning() {
827
+ if (!loadElectron() || !app) {
828
+ console.warn('⚠️ Electron não disponível para manterRodando()');
829
+ return;
830
+ }
831
+ app.on('window-all-closed', () => {
832
+ // On macOS, apps typically stay active
833
+ if (process.platform !== 'darwin') {
834
+ // Keep running - don't quit automatically
835
+ }
836
+ });
837
+ }
838
+ /**
839
+ * Create Widget (base class for all GUI components)
840
+ * Allows creating GUI programmatically without HTML
841
+ */
842
+ static Widget(type, props = {}) {
843
+ return {
844
+ type: type,
845
+ props: props || {},
846
+ children: [],
847
+ id: `widget_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
848
+ setText: function (text) {
849
+ this.props.text = text;
850
+ this.update();
851
+ },
852
+ setVisible: function (visible) {
853
+ this.props.visible = visible !== false;
854
+ this.update();
855
+ },
856
+ setEnabled: function (enabled) {
857
+ this.props.enabled = enabled !== false;
858
+ this.update();
859
+ },
860
+ addChild: function (child) {
861
+ this.children.push(child);
862
+ this.update();
863
+ },
864
+ removeChild: function (child) {
865
+ const index = this.children.indexOf(child);
866
+ if (index > -1) {
867
+ this.children.splice(index, 1);
868
+ this.update();
869
+ }
870
+ },
871
+ update: function () {
872
+ // Update será implementado quando widget for adicionado a uma janela
873
+ if (this._parentWindow) {
874
+ this._parentWindow._updateGUI();
875
+ }
876
+ },
877
+ toHTML: function () {
878
+ return this._renderToHTML();
879
+ },
880
+ _renderToHTML: function () {
881
+ // Implementação base - será sobrescrita por widgets específicos
882
+ return '';
883
+ }
884
+ };
885
+ }
886
+ /**
887
+ * Create Button Widget (programmatic)
888
+ */
889
+ static Button(text, onClick) {
890
+ const widget = this.Widget('button', {
891
+ text: text || '',
892
+ onClick: onClick,
893
+ enabled: true,
894
+ visible: true,
895
+ style: {}
896
+ });
897
+ widget._renderToHTML = function () {
898
+ const style = this.props.style || {};
899
+ const styleStr = Object.keys(style).map(k => `${k}:${style[k]}`).join(';');
900
+ const disabled = this.props.enabled === false ? 'disabled' : '';
901
+ const onclick = this.props.onClick ? `onclick="window.trestGuiButtonClick('${this.id}')"` : '';
902
+ return `<button id="${this.id}" ${onclick} ${disabled} style="${styleStr}">${this.props.text || ''}</button>`;
903
+ };
904
+ return widget;
905
+ }
906
+ /**
907
+ * Create Label Widget (programmatic)
908
+ */
909
+ static Label(text) {
910
+ const widget = this.Widget('label', {
911
+ text: text || '',
912
+ visible: true,
913
+ style: {}
914
+ });
915
+ widget._renderToHTML = function () {
916
+ const style = this.props.style || {};
917
+ const styleStr = Object.keys(style).map(k => `${k}:${style[k]}`).join(';');
918
+ return `<label id="${this.id}" style="${styleStr}">${this.props.text || ''}</label>`;
919
+ };
920
+ return widget;
921
+ }
922
+ /**
923
+ * Create Input Widget (programmatic)
924
+ */
925
+ static Input(placeholder = '', onChange) {
926
+ const widget = this.Widget('input', {
927
+ placeholder: placeholder || '',
928
+ value: '',
929
+ onChange: onChange,
930
+ enabled: true,
931
+ visible: true,
932
+ type: 'text',
933
+ style: {}
934
+ });
935
+ widget.setValue = function (value) {
936
+ this.props.value = value;
937
+ this.update();
938
+ };
939
+ widget.getValue = function () {
940
+ return this.props.value || '';
941
+ };
942
+ widget._renderToHTML = function () {
943
+ const style = this.props.style || {};
944
+ const styleStr = Object.keys(style).map(k => `${k}:${style[k]}`).join(';');
945
+ const disabled = this.props.enabled === false ? 'disabled' : '';
946
+ const onchange = this.props.onChange ? `onchange="window.trestGuiInputChange('${this.id}', this.value)"` : '';
947
+ return `<input id="${this.id}" type="${this.props.type || 'text'}" placeholder="${this.props.placeholder || ''}" value="${this.props.value || ''}" ${onchange} ${disabled} style="${styleStr}">`;
948
+ };
949
+ return widget;
950
+ }
951
+ /**
952
+ * Create VBox Layout (vertical box - like PySide6)
953
+ */
954
+ static VBox(children = [], spacing = 0) {
955
+ const widget = this.Widget('vbox', {
956
+ spacing: spacing || 0,
957
+ alignment: 'top'
958
+ });
959
+ widget.children = children || [];
960
+ widget._renderToHTML = function () {
961
+ const spacing = this.props.spacing || 0;
962
+ const style = `display: flex; flex-direction: column; gap: ${spacing}px;`;
963
+ const childrenHTML = this.children.map((child) => {
964
+ if (child && typeof child.toHTML === 'function') {
965
+ return child.toHTML();
966
+ }
967
+ return typeof child === 'string' ? child : '';
968
+ }).join('');
969
+ return `<div id="${this.id}" style="${style}">${childrenHTML}</div>`;
970
+ };
971
+ return widget;
972
+ }
973
+ /**
974
+ * Create HBox Layout (horizontal box - like PySide6)
975
+ */
976
+ static HBox(children = [], spacing = 0) {
977
+ const widget = this.Widget('hbox', {
978
+ spacing: spacing || 0,
979
+ alignment: 'left'
980
+ });
981
+ widget.children = children || [];
982
+ widget._renderToHTML = function () {
983
+ const spacing = this.props.spacing || 0;
984
+ const style = `display: flex; flex-direction: row; gap: ${spacing}px;`;
985
+ const childrenHTML = this.children.map((child) => {
986
+ if (child && typeof child.toHTML === 'function') {
987
+ return child.toHTML();
988
+ }
989
+ return typeof child === 'string' ? child : '';
990
+ }).join('');
991
+ return `<div id="${this.id}" style="${style}">${childrenHTML}</div>`;
992
+ };
993
+ return widget;
994
+ }
995
+ /**
996
+ * Create Grid Layout (like PySide6)
997
+ */
998
+ static Grid(rows = 1, cols = 1, spacing = 0) {
999
+ const widget = this.Widget('grid', {
1000
+ rows: rows || 1,
1001
+ cols: cols || 1,
1002
+ spacing: spacing || 0
1003
+ });
1004
+ widget.children = [];
1005
+ widget._grid = Array(rows).fill(null).map(() => Array(cols).fill(null));
1006
+ widget.addWidget = function (child, row, col, rowSpan = 1, colSpan = 1) {
1007
+ if (row >= 0 && row < this.props.rows && col >= 0 && col < this.props.cols) {
1008
+ this.children.push({ widget: child, row, col, rowSpan, colSpan });
1009
+ this._grid[row][col] = child;
1010
+ this.update();
1011
+ }
1012
+ };
1013
+ widget._renderToHTML = function () {
1014
+ const spacing = this.props.spacing || 0;
1015
+ const style = `display: grid; grid-template-rows: repeat(${this.props.rows}, 1fr); grid-template-columns: repeat(${this.props.cols}, 1fr); gap: ${spacing}px;`;
1016
+ // Renderizar widgets nos lugares corretos do grid
1017
+ const gridItems = Array(this.props.rows * this.props.cols).fill('<div></div>');
1018
+ this.children.forEach((item) => {
1019
+ const index = item.row * this.props.cols + item.col;
1020
+ const rowSpan = item.rowSpan || 1;
1021
+ const colSpan = item.colSpan || 1;
1022
+ const gridArea = `${item.row + 1} / ${item.col + 1} / ${item.row + rowSpan + 1} / ${item.col + colSpan + 1}`;
1023
+ const childHTML = item.widget && typeof item.widget.toHTML === 'function'
1024
+ ? item.widget.toHTML()
1025
+ : '';
1026
+ gridItems[index] = `<div style="grid-area: ${gridArea}">${childHTML}</div>`;
1027
+ });
1028
+ return `<div id="${this.id}" style="${style}">${gridItems.join('')}</div>`;
1029
+ };
1030
+ return widget;
1031
+ }
1032
+ /**
1033
+ * Create Programmatic Window (without HTML dependency)
1034
+ * This allows creating windows and adding widgets programmatically
1035
+ */
1036
+ static createProgrammaticWindow(options) {
1037
+ const title = options.title || options.título || 'Janela Trest';
1038
+ const width = options.width || options.largura || 800;
1039
+ const height = options.height || options.altura || 600;
1040
+ // Create window normally
1041
+ const window = this.createWindow(options);
1042
+ // Add programmatic methods
1043
+ window._widgets = [];
1044
+ window._layout = null;
1045
+ window._useProgrammatic = true;
1046
+ window.setLayout = function (layout) {
1047
+ this._layout = layout;
1048
+ if (layout && layout._parentWindow) {
1049
+ layout._parentWindow = this;
1050
+ }
1051
+ this._updateGUI();
1052
+ };
1053
+ window.addWidget = function (widget) {
1054
+ this._widgets.push(widget);
1055
+ if (widget && widget._parentWindow) {
1056
+ widget._parentWindow = this;
1057
+ }
1058
+ this._updateGUI();
1059
+ };
1060
+ window.removeWidget = function (widget) {
1061
+ const index = this._widgets.indexOf(widget);
1062
+ if (index > -1) {
1063
+ this._widgets.splice(index, 1);
1064
+ this._updateGUI();
1065
+ }
1066
+ };
1067
+ window.clear = function () {
1068
+ this._widgets = [];
1069
+ this._layout = null;
1070
+ this._updateGUI();
1071
+ };
1072
+ window._updateGUI = function () {
1073
+ // Gerar HTML automaticamente dos widgets
1074
+ let contentHTML = '';
1075
+ if (this._layout) {
1076
+ // Se há um layout, renderizar o layout
1077
+ if (this._layout.toHTML) {
1078
+ contentHTML = this._layout.toHTML();
1079
+ }
1080
+ }
1081
+ else {
1082
+ // Senão, renderizar widgets diretamente
1083
+ contentHTML = this._widgets.map((w) => {
1084
+ if (w && typeof w.toHTML === 'function') {
1085
+ return w.toHTML();
1086
+ }
1087
+ return '';
1088
+ }).join('');
1089
+ }
1090
+ // Criar HTML completo
1091
+ const html = `<!DOCTYPE html>
1092
+ <html lang="pt-BR">
1093
+ <head>
1094
+ <meta charset="UTF-8">
1095
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
1096
+ <title>${title}</title>
1097
+ <style>
1098
+ * {
1099
+ margin: 0;
1100
+ padding: 0;
1101
+ box-sizing: border-box;
1102
+ }
1103
+
1104
+ body {
1105
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
1106
+ padding: 20px;
1107
+ background: #f5f5f5;
1108
+ }
1109
+
1110
+ .container {
1111
+ background: white;
1112
+ border-radius: 8px;
1113
+ padding: 20px;
1114
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
1115
+ min-height: calc(100vh - 40px);
1116
+ }
1117
+
1118
+ button {
1119
+ padding: 10px 20px;
1120
+ border: none;
1121
+ border-radius: 4px;
1122
+ background: #007bff;
1123
+ color: white;
1124
+ cursor: pointer;
1125
+ font-size: 14px;
1126
+ transition: background 0.3s;
1127
+ }
1128
+
1129
+ button:hover:not(:disabled) {
1130
+ background: #0056b3;
1131
+ }
1132
+
1133
+ button:disabled {
1134
+ background: #ccc;
1135
+ cursor: not-allowed;
1136
+ }
1137
+
1138
+ input {
1139
+ padding: 10px;
1140
+ border: 1px solid #ddd;
1141
+ border-radius: 4px;
1142
+ font-size: 14px;
1143
+ width: 100%;
1144
+ }
1145
+
1146
+ input:focus {
1147
+ outline: none;
1148
+ border-color: #007bff;
1149
+ }
1150
+
1151
+ label {
1152
+ display: block;
1153
+ margin-bottom: 5px;
1154
+ font-weight: 500;
1155
+ color: #333;
1156
+ }
1157
+ </style>
1158
+ </head>
1159
+ <body>
1160
+ <div class="container">
1161
+ ${contentHTML}
1162
+ </div>
1163
+
1164
+ <script>
1165
+ // Handlers para widgets programáticos
1166
+ window.trestGuiButtonClick = function(widgetId) {
1167
+ // Callback será implementado quando widget for criado
1168
+ console.log('Button clicked:', widgetId);
1169
+ };
1170
+
1171
+ window.trestGuiInputChange = function(widgetId, value) {
1172
+ console.log('Input changed:', widgetId, value);
1173
+ };
1174
+ </script>
1175
+ </body>
1176
+ </html>`;
1177
+ // Atualizar janela com HTML gerado
1178
+ if (this.loadHTML) {
1179
+ this.loadHTML(html);
1180
+ }
510
1181
  };
1182
+ return window;
511
1183
  }
512
1184
  }
513
1185
  exports.StdGUI = StdGUI;
1186
+ StdGUI.windows = new Map();
1187
+ StdGUI.windowCounter = 0;
1188
+ StdGUI.appReady = false;
514
1189
  /**
515
1190
  * ========================================
516
1191
  * Database Module - SQLite