├── .gitignore ├── LICENSE ├── README.md ├── demos ├── ascdemo.js ├── dlgdemo.js ├── scrdemo.js └── wdgdemo.js ├── dojs.ini ├── images ├── js.png └── screen1.png ├── jiyuai.bat ├── jsconfig.json ├── main.js ├── minimal.js ├── run.sh └── ui ├── fonts.js ├── res ├── fontlist.js └── scrollb.png ├── uiapp.js ├── utils.js ├── widgets ├── button.js ├── chckbx.js ├── dialog.js ├── group.js ├── image.js ├── menu.js ├── menubar.js ├── scrllbr.js ├── text.js ├── tinput.js ├── widget.js └── window.js └── window ├── about.js └── debug.js /.gitignore: -------------------------------------------------------------------------------- 1 | dosbox.conf 2 | JSLOG.TXT 3 | local_cacert.pem 4 | *.DJS 5 | crash.js 6 | symify.exe 7 | libc++.dll 8 | NE2000.COM 9 | SDL2.dll 10 | SDL2_net.dll 11 | *.dll 12 | *.DXE 13 | COPYING.txt 14 | dosbox_with_debugger.exe 15 | README.txt 16 | doc/manual.txt 17 | doc/video.txt 18 | default.config 19 | _resources 20 | *.exe 21 | WATTCP.CFG 22 | JSBOOT.ZIP 23 | CWSDPMI.EXE 24 | cacert.pem 25 | capture 26 | .vscode 27 | run.sh -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2023 Gilliaard Damme 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JiYuAi a small UI kit for DOjS 2 | A small UI kit to create simple UI apps 3 | 4 | ![Screenshot](./images/screen1.png) 5 | 6 | Look in minimal.js for minimal usage 7 | 8 | ## How to setup? 9 | Just download version 1.10 of DOjS from https://github.com/SuperIlu/DOjS/releases/tag/v1.100 10 | And extract all files to this folder and run jiyuai from DOS or Dosbox 11 | 12 | ### Using dosbox 13 | The app is tested in with [Dosbox-staging](https://dosbox-staging.github.io/). 14 | 15 | You can use the following configuration to run the JiYuAi: 16 | ``` 17 | [dosbox] 18 | memsize=32 19 | 20 | [cpu] 21 | cputype=pentium_slow 22 | cycles=max 23 | 24 | [autoexec] 25 | mount c . 26 | c: 27 | jiuai.bat 28 | ``` 29 | 30 | ## TODO 31 | * [ ] Maybe make UIApp a singelton object. (There is only 1 app.) 32 | * [ ] Create textarea field 33 | * [ ] only update parts of the screen that are needed. 34 | * [ ] rename children to nodes (more in line with htmldom) 35 | * [ ] beter event handling for mouse (like mouseIn, mouseOut, mouseOver) 36 | * [ ] make a generic event handler system. 37 | 38 | ## CHANGELOG 39 | 40 | ### v0.0.2 41 | * Added checkbox widget 42 | * Added group widget 43 | * Added image widget 44 | * Give buttons rounded corners 45 | * Give buttons a pressed in state 46 | * Make window ascci table demo smaller 47 | 48 | ### v0.0.1 49 | * First version 50 | -------------------------------------------------------------------------------- /demos/ascdemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Ascii table demo. 3 | * 4 | * Basic demo to show ascii table from 1 to 255 5 | */ 6 | function AsciiTableDemo() { 7 | UIWindow.call(this, 'ascciDemo', 'Ascii table') 8 | this.destroyOnClose = true; 9 | } 10 | 11 | AsciiTableDemo.prototype = Object.create(UIWindow.prototype); 12 | AsciiTableDemo.constructor = AsciiTableDemo; 13 | 14 | AsciiTableDemo.prototype.init = function() { 15 | var widgetOffsetY = 30 16 | var widgetOffsetX = 10 17 | var maxWidgetHeight = 0 18 | var itemCounter = 0; 19 | 20 | for (var a = 1; a <= 255; a++) { 21 | if (a >= 128 && a <= 160) { 22 | // continue; 23 | } 24 | 25 | this.addChildren(new UIText(widgetOffsetX, widgetOffsetY, a + ' ' + String.fromCharCode(a))) 26 | widgetOffsetY = widgetOffsetY + 12 27 | itemCounter++ 28 | if (itemCounter > 25) { 29 | widgetOffsetY = 30 30 | widgetOffsetX = widgetOffsetX + 50 31 | itemCounter = 0; 32 | } 33 | if (widgetOffsetY > maxWidgetHeight) { 34 | maxWidgetHeight = widgetOffsetY 35 | } 36 | } 37 | 38 | this.setSize(widgetOffsetX + 40, maxWidgetHeight + 22); 39 | this.setPosition(-1, -1); 40 | } 41 | 42 | exports.__VERSION__ = 1 43 | exports.AsciiTableDemo = AsciiTableDemo; 44 | -------------------------------------------------------------------------------- /demos/dlgdemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Dialog demo 3 | */ 4 | function DialogDemo() { 5 | UIWindow.call(this, 'dialogDemo', 'Dialog demo') 6 | this.setSize(320, 150); 7 | this.setPosition(-1, -1); 8 | this.destroyOnClose = true; 9 | } 10 | 11 | DialogDemo.prototype = Object.create(UIWindow.prototype); 12 | DialogDemo.constructor = DialogDemo; 13 | 14 | DialogDemo.prototype.init = function() { 15 | 16 | this.addChildren(new UIText(10, 36, 'Title')) 17 | this.titleInput = new UITextInput('Dialog title', 80, 30, 230, 20) 18 | this.addChildren(this.titleInput); 19 | 20 | this.addChildren(new UIText(10, 66, 'Description')) 21 | this.descriptionInput = new UITextInput('Dialog description', 80, 60, 230, 20) 22 | this.addChildren(this.descriptionInput); 23 | 24 | this.showAlertButton = new UIButton(10, 90, 'Show alert'); 25 | this.showAlertButton.onClick = this.showAlertDialog.bind(this); 26 | this.addChildren(this.showAlertButton) 27 | 28 | this.showConfirmButton = new UIButton(140, 90, 'Show confirm'); 29 | this.showConfirmButton.onClick = this.showConfirmDialog.bind(this); 30 | this.addChildren(this.showConfirmButton) 31 | 32 | this.multiLineDialogButton = new UIButton(10, 120, 'Show multi-line'); 33 | this.multiLineDialogButton.onClick = function() { 34 | UIDialog.createAlert( 35 | this.app, 36 | 'A multiline alert dialog with multiple lines.\nThe lines are from different sizes\nThis is third line to render.\nThe last line you can now close this message. :)' 37 | ); 38 | } 39 | this.addChildren(this.multiLineDialogButton); 40 | } 41 | 42 | /** 43 | * Show alert dialog 44 | */ 45 | DialogDemo.prototype.showAlertDialog = function() { 46 | UIDialog.createAlert( 47 | this.app, 48 | this.descriptionInput.value, 49 | this.titleInput.value 50 | ) 51 | } 52 | 53 | /** 54 | * Show confirm dialog 55 | */ 56 | DialogDemo.prototype.showConfirmDialog = function() { 57 | UIDialog.createConfirm( 58 | this.app, 59 | this.descriptionInput.value, 60 | this.titleInput.value, 61 | function (status) { 62 | UIDialog.createAlert( 63 | this.app, 64 | status ? 'You clicked OK' : 'You clicked Cancel' , 65 | 'Result' 66 | );40 67 | } 68 | ) 69 | } 70 | 71 | exports.__VERSION__ = 1 72 | exports.DialogDemo = DialogDemo 73 | -------------------------------------------------------------------------------- /demos/scrdemo.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Basic scrollbar demo 3 | */ 4 | function ScrollbarDemo() { 5 | UIWindow.call(this, 'scrollbarDemo', 'Scrollbar demo') 6 | this.setSize(300, 300) 7 | this.destroyOnClose = true; 8 | } 9 | 10 | ScrollbarDemo.prototype = Object.create(UIWindow.prototype); 11 | ScrollbarDemo.constructor = ScrollbarDemo; 12 | 13 | ScrollbarDemo.prototype.init = function() { 14 | this.vscroll = new UIScrollBar(this.w - 16, 20, 16, this.h - 36) // , 0, 10, 5) 15 | this.vscroll.onChange = this._updateStatusLabel.bind(this); 16 | this.addChildren(this.vscroll); 17 | 18 | this.hscroll = new UIScrollBar(0, this.h - 16, this.w - 16, 16) // , 0, 10, 5) 19 | this.hscroll.onChange = this._updateStatusLabel.bind(this); 20 | this.addChildren(this.hscroll); 21 | 22 | this.vScrollValue = new UIText(74, 30, '-1'); 23 | this.hScrollValue = new UIText(74, 40, '-1'); 24 | this.addChildren(new UIText(10, 30, 'Vscroll:')); 25 | this.addChildren(new UIText(10, 40, 'Hscroll:')); 26 | this.addChildren(this.vScrollValue); 27 | this.addChildren(this.hScrollValue); 28 | 29 | this._updateStatusLabel(); 30 | } 31 | 32 | /** 33 | * Update the labels of scrollbar position. 34 | */ 35 | ScrollbarDemo.prototype._updateStatusLabel = function() { 36 | this.vScrollValue.setText(this.vscroll.value); 37 | this.hScrollValue.setText(this.hscroll.value); 38 | } 39 | 40 | exports.__VERSION__ = 1 41 | exports.ScrollbarDemo = ScrollbarDemo 42 | -------------------------------------------------------------------------------- /demos/wdgdemo.js: -------------------------------------------------------------------------------- 1 | function WidgetsDemo() { 2 | UIWindow.call(this, 'widgetsDemo', 'Component/widget showcase') 3 | this.setSize(SizeX()- 10, SizeY() - 30) 4 | this.setPosition(-1, 25) 5 | this.destroyOnClose = true; 6 | this.menu = [ 7 | { 8 | label: 'Menu item', 9 | children: [ 10 | { 11 | label: 'You can have multiple menu items attached to a window' 12 | }, 13 | { 14 | label: '-' 15 | }, 16 | { 17 | label: 'The can be separated by a line if you want' 18 | } 19 | ] 20 | }, 21 | { 22 | label: 'Second menu item', 23 | children: [ 24 | { 25 | label: 'Second menu item' 26 | }, 27 | ] 28 | } 29 | ] 30 | } 31 | 32 | 33 | WidgetsDemo.prototype = Object.create(UIWindow.prototype); 34 | WidgetsDemo.constructor = WidgetsDemo; 35 | 36 | 37 | WidgetsDemo.prototype.init = function() { 38 | this.addChildren(new UIText(10, 30, 'This is a showcase of all supported widgets')); 39 | this.addChildren(new UIButton(10, 50, 'Example Button')); 40 | this.addChildren(new UITextInput('Example text input', 10, 80, 200)); 41 | this.addChildren(new UIScrollBar(10, 110, 200, 20)); 42 | this.addChildren(new UIScrollBar(10, 130, 20, 100)); 43 | this.addChildren(new UICheckbox(10, 240, 'Checkbox example 1')); 44 | this.addChildren(new UICheckbox(10, 260, 'Checkbox example 2', true)); 45 | 46 | var menu = new UIMenu( 47 | this.app, 48 | [ 49 | { 50 | label: 'Menu item test' 51 | }, 52 | { 53 | label: '-' 54 | }, 55 | { 56 | label: 'menu item 2' 57 | } 58 | ], 59 | 220, 60 | 50 61 | ); 62 | this.addChildren(menu); 63 | 64 | var group = new UIGroup('Group name', 40, 130, 200, 100) 65 | this.addChildren(group); 66 | group.addChildren(new UIText(10, 20, 'A widget inside a group')); 67 | group.addChildren(new UIButton(10, 40, 'Example Button')) 68 | 69 | this.addChildren(new UIImage(new Bitmap('./images/js.png'), 10, 300)) 70 | } 71 | 72 | exports.__VERSION__ = 1; 73 | exports.WidgetsDemo = WidgetsDemo; 74 | -------------------------------------------------------------------------------- /dojs.ini: -------------------------------------------------------------------------------- 1 | ; DOjS will search for dojs.ini in the current directory. 2 | ; This optional file can be used to provide command line parameters without using the command line. 3 | ; all boolean options ignore the value, it is just important they have a value. 4 | ; lines starting with a semicolon (";") are treated as comments and ignored. 5 | 6 | ; The name of the section is ignored when DOjS parses this file. We just use [dojs] for now 7 | [dojs] 8 | 9 | ; Do not invoke the editor, just run the script. 10 | ; r = true 11 | 12 | ; Use 50-line mode in the editor. 13 | ; l = true 14 | 15 | ; Screen width: 320 or 640, Default: 640. 16 | ; w = 640 17 | ; w = 320 18 | 19 | ; Bit per pixel:8, 16, 24, 32 20 | ; b = 8 21 | ; b = 16 22 | ; b = 24 23 | ; b = 32 24 | 25 | ; No wave sound. 26 | ; s = true 27 | 28 | ; No FM sound. 29 | f = true 30 | 31 | ; Disable alpha (speeds up rendering). 32 | a = true 33 | 34 | ; Allow raw disk write (CAUTION!). 35 | ; x = true 36 | 37 | ; Disable TCP-stack. 38 | t = true 39 | 40 | ; Disable JSLOG.TXT. 41 | ; n = true 42 | 43 | ; Redirect JSLOG.TXT to . 44 | ; j = logname.TXT 45 | 46 | ; which script to load: 47 | ; script = examples\boxlines.js 48 | -------------------------------------------------------------------------------- /images/js.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dutchtux3000/jiyuai/4b6f4ddc4360f33c9ebd4749d1545fbe85fd44b8/images/js.png -------------------------------------------------------------------------------- /images/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dutchtux3000/jiyuai/4b6f4ddc4360f33c9ebd4749d1545fbe85fd44b8/images/screen1.png -------------------------------------------------------------------------------- /jiyuai.bat: -------------------------------------------------------------------------------- 1 | rescan 2 | dojs -r main.js -------------------------------------------------------------------------------- /jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "none", 4 | "target": "es5" 5 | }, 6 | "exclude": [] 7 | } -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | var inputStr = ''; 2 | Include('./ui/uiapp.js'); 3 | Include('./demos/dlgdemo.js'); 4 | Include('./demos/scrdemo.js'); 5 | Include('./demos/ascdemo.js'); 6 | Include('./demos/wdgdemo.js'); 7 | 8 | 9 | function Setup() { 10 | inputStr = '' 11 | SetFramerate(60); 12 | MouseShowCursor(true); 13 | MouseSetCursorMode(1); 14 | //SetExitKey(KEY.Code.KEY_F12); 15 | SetExitKey(KEY.Code.NO_KEY); 16 | 17 | // setup screen 18 | app = new UIApp(); 19 | app.showFps = false; 20 | app.showMemStats = false; 21 | app.enableDebugWindow = true; 22 | app.showLastDrawTick = false; 23 | app.init(); 24 | 25 | /** 26 | * Demos that the user can test/see 27 | */ 28 | var demoExamples = [ 29 | { 30 | label: 'Component/widget showcase', 31 | keyCode: 7170, 32 | keyLabel: 'Ctrl+1', 33 | onClick: function() { 34 | if (app.doesWindowExistsById('widgetsDemo')) { 35 | app.showWindow('widgetsDemo') 36 | } else { 37 | app.addWindow(new WidgetsDemo()); 38 | } 39 | } 40 | }, 41 | { 42 | label: 'Text input example', 43 | keyCode: 7426, 44 | keyLabel: 'Ctrl+2', 45 | onClick: function() { 46 | app.showWindow('window2') 47 | } 48 | }, 49 | { 50 | label: 'Ascii table', 51 | keyCode: 7682, 52 | keyLabel: 'Ctrl+3', 53 | onClick: function() { 54 | if (app.doesWindowExistsById('ascciDemo')) { 55 | app.showWindow('ascciDemo') 56 | } else { 57 | app.addWindow(new AsciiTableDemo()); 58 | } 59 | } 60 | }, 61 | { 62 | label: 'Dialog demo', 63 | keyCode: 7938, 64 | keyLabel: 'Ctrl+4', 65 | onClick: function() { 66 | if (app.doesWindowExistsById('dialogDemo')) { 67 | app.showWindow('dialogDemo') 68 | } else { 69 | app.addWindow(new DialogDemo()); 70 | } 71 | } 72 | }, 73 | { 74 | label: 'Scrollbar demo', 75 | keyCode: 8194, 76 | keyLabel: 'Ctrl+5', 77 | onClick: function() { 78 | if (app.doesWindowExistsById('scrollbarDemo')) { 79 | app.showWindow('scrollbarDemo') 80 | } else { 81 | app.addWindow(new ScrollbarDemo()); 82 | } 83 | } 84 | }, 85 | { 86 | label: 'About dialog', 87 | onClick: function() { 88 | app.showAbout() 89 | } 90 | } 91 | ] 92 | 93 | /** 94 | * Build demo window 95 | */ 96 | window1 = new UIWindow('window1', 'Example widgets') 97 | window1.isClosable = false 98 | window1.menu = [ 99 | { 100 | label: 'Examples', 101 | children: demoExamples, 102 | }, 103 | ] 104 | window1.addChildren(new UIText(10, 30, 'Here are list of of demos')) 105 | 106 | 107 | var buttonOffsetY = 50; 108 | demoExamples.forEach(function (item) { 109 | button = new UIButton(10, buttonOffsetY, item.label); 110 | button.setSize(230, 24); 111 | button.onClick = item.onClick 112 | buttonOffsetY = buttonOffsetY + 34 113 | window1.addChildren(button) 114 | }) 115 | 116 | window1.setSize(250, buttonOffsetY); 117 | window1.setPosition(-1, -1) 118 | 119 | app.addWindow(window1); 120 | 121 | /** 122 | * Create input example 123 | */ 124 | window2 = new UIWindow('window2', 'Text input example') 125 | window2.setSize(300, 100); 126 | window2.setPosition(-1, -1); 127 | window2.setVisible(false); 128 | window2.menu = []; 129 | 130 | // window2.addChildren(new UITextInput('Input value', 10, 30, 280, 20)) 131 | window2.addChildren(new UITextInput('Here are list of supported widgets/controls', 10, 30, 280, 20)) 132 | // window2.addChildren(new UITextInput('', 10, 30, 280, 20)) 133 | window2.addChildren(new UIButton(10, 60, 'Focus switch test')) 134 | app.addWindow(window2); 135 | 136 | // just do some garbage collecting if needed 137 | Gc(); 138 | } 139 | 140 | function Loop() { 141 | app.loop(); 142 | 143 | // Some debug code. 144 | // TextXY(0, SizeY() - 10, "fps:" + GetFramerate(), EGA.WHITE); 145 | // TextXY(0, SizeY() - 20, "memory:" + JSON.stringify(MemoryInfo()), EGA.WHITE); 146 | // TextXY(0, SizeY() - 30, "inputStr:" + inputStr, EGA.WHITE); 147 | } 148 | 149 | function Input(event) { 150 | 151 | var newEvent = JSON.parse(JSON.stringify(event)); 152 | newEvent.keyCode = event.key >> 8; 153 | inputStr = JSON.stringify(newEvent); 154 | 155 | // forcegarbage collecting if needed 156 | if (event.key === 1792) { // ctrl + alt + g 157 | Gc() 158 | } 159 | 160 | app.input(event); 161 | } 162 | -------------------------------------------------------------------------------- /minimal.js: -------------------------------------------------------------------------------- 1 | Include('./ui/uiapp.js'); 2 | 3 | function Setup() { 4 | inputStr = '' 5 | SetFramerate(60); 6 | MouseShowCursor(true); 7 | MouseSetCursorMode(1); 8 | SetExitKey(KEY.Code.KEY_F12); 9 | 10 | // setup screen 11 | app = new UIApp(); 12 | app.init(); 13 | 14 | // create basic window 15 | window1 = new UIWindow('example', 'Example window') 16 | window1.isClosable = false 17 | 18 | app.addWindow(window1); 19 | } 20 | 21 | function Loop() { 22 | app.loop(); 23 | } 24 | 25 | function Input(event) { 26 | app.input(event); 27 | } 28 | -------------------------------------------------------------------------------- /run.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | # flatpak run com.dosbox_x.DOSBox-X -conf ./dosbox.conf 3 | # flatpak run com.dosbox.DOSBox -conf ./dosbox.conf 4 | flatpak run io.github.dosbox-staging -conf ./dosbox.conf -------------------------------------------------------------------------------- /ui/fonts.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UI font manager loading. keep everything in one place. 3 | */ 4 | function UIFonts() { 5 | // this.fonts = Require('./ui/res/fontlist.js'); 6 | this.loadedFonts = {} 7 | } 8 | 9 | /** 10 | * Load font base on string from JSBOOT.ZIP * 11 | * @param {string} name 12 | */ 13 | UIFonts.prototype.loadFont = function(name) { 14 | this.loadedFonts[name] = new Font(JSBOOTPATH + 'fonts/' + name + '.fnt'); 15 | } 16 | 17 | /** 18 | * Get font object from given name 19 | * 20 | * @param {string} name 21 | * @returns {Font} 22 | */ 23 | UIFonts.prototype.getFont = function(name) { 24 | if (!this.loadedFonts[name]) { 25 | this.loadFont(name); 26 | } 27 | 28 | return this.loadedFonts[name]; 29 | } 30 | 31 | exports._VERSION_ = 1 32 | exports.UIFonts = UIFonts; -------------------------------------------------------------------------------- /ui/res/fontlist.js: -------------------------------------------------------------------------------- 1 | /** 2 | * list of all fonts that are in the JSBOOT.ZIP file. 3 | */ 4 | exports._VERSION_ = 1; 5 | exports.FONT_LIST = [ 6 | "anti10", 7 | "arti10al", 8 | "arti10", 9 | "arti10se", 10 | "arts09", 11 | "asim09", 12 | "brig09", 13 | "cant10", 14 | "cant10sc", 15 | "char11b", 16 | "char11bi", 17 | "char11", 18 | "char11i", 19 | "char14b", 20 | "char14bi", 21 | "char14", 22 | "char14i", 23 | "char16b", 24 | "char16bi", 25 | "char16", 26 | "char16i", 27 | "char18b", 28 | "char18bi", 29 | "char18", 30 | "char18i", 31 | "char23b", 32 | "char23bi", 33 | "char23", 34 | "char23i", 35 | "char30b", 36 | "char30bi", 37 | "char30", 38 | "char30i", 39 | "char40b", 40 | "char40bi", 41 | "char40", 42 | "char40i", 43 | "cntd09", 44 | "comp09al", 45 | "comp09", 46 | "comp11", 47 | "cour11b", 48 | "cour11bi", 49 | "cour11", 50 | "cour11i", 51 | "cour12b", 52 | "cour12bi", 53 | "cour12", 54 | "cour12i", 55 | "cour14b", 56 | "cour14bi", 57 | "cour14", 58 | "cour14i", 59 | "cour16b", 60 | "cour16bi", 61 | "cour16", 62 | "cour16i", 63 | "cour20b", 64 | "cour20bi", 65 | "cour20", 66 | "cour20i", 67 | "cour25b", 68 | "cour25bi", 69 | "cour25", 70 | "cour25i", 71 | "cour34b", 72 | "cour34bi", 73 | "cour34", 74 | "cour34i", 75 | "esch10", 76 | "eurt12", 77 | "feyn10", 78 | "gaga10", 79 | "gaga10i", 80 | "gaga10sc", 81 | "gamo05", 82 | "gamo06", 83 | "gamo07b", 84 | "gamo07bi", 85 | "gamo07", 86 | "gamo07i", 87 | "gamo07sc", 88 | "gamo08b", 89 | "gamo08bi", 90 | "gamo08", 91 | "gamo08i", 92 | "gamo08sc", 93 | "gamo08so", 94 | "gamo09ba", 95 | "gamo09b", 96 | "gamo09bi", 97 | "gamo09bs", 98 | "gamo09", 99 | "gamo09i", 100 | "gamo09is", 101 | "gamo09sa", 102 | "helv11b", 103 | "helv11bi", 104 | "helv11", 105 | "helv11i", 106 | "helv13b", 107 | "helv13bi", 108 | "helv13", 109 | "helv13i", 110 | "helv15b", 111 | "helv15bi", 112 | "helv15", 113 | "helv15i", 114 | "helv17b", 115 | "helv17bi", 116 | "helv17", 117 | "helv17i", 118 | "helv22b", 119 | "helv22bi", 120 | "helv22", 121 | "helv22i", 122 | "helv29b", 123 | "helv29bi", 124 | "helv29", 125 | "helv29i", 126 | "helv38b", 127 | "helv38bi", 128 | "helv38", 129 | "helv38i", 130 | "holl11", 131 | "juli10", 132 | "kand09al", 133 | "kand09", 134 | "lomo08", 135 | "lomo10", 136 | "lomv08", 137 | "lomv10", 138 | "lucb11b", 139 | "lucb11bi", 140 | "lucb11", 141 | "lucb11i", 142 | "lucb12b", 143 | "lucb12bi", 144 | "lucb12", 145 | "lucb12i", 146 | "lucb15b", 147 | "lucb15bi", 148 | "lucb15", 149 | "lucb15i", 150 | "lucb17b", 151 | "lucb17bi", 152 | "lucb17", 153 | "lucb17i", 154 | "lucb21b", 155 | "lucb21bi", 156 | "lucb21", 157 | "lucb21i", 158 | "lucb27b", 159 | "lucb27bi", 160 | "lucb27", 161 | "lucb27i", 162 | "lucb40b", 163 | "lucb40bi", 164 | "lucb40", 165 | "lucb40i", 166 | "lucs11b", 167 | "lucs11bi", 168 | "lucs11", 169 | "lucs11i", 170 | "lucs12b", 171 | "lucs12bi", 172 | "lucs12", 173 | "lucs12i", 174 | "lucs15b", 175 | "lucs15bi", 176 | "lucs15", 177 | "lucs15i", 178 | "lucs17b", 179 | "lucs17bi", 180 | "lucs17", 181 | "lucs17i", 182 | "lucs21b", 183 | "lucs21bi", 184 | "lucs21", 185 | "lucs21i", 186 | "lucs27b", 187 | "lucs27bi", 188 | "lucs27", 189 | "lucs27i", 190 | "lucs40b", 191 | "lucs40bi", 192 | "lucs40", 193 | "lucs40i", 194 | "luct10b", 195 | "luct10", 196 | "luct11b", 197 | "luct11", 198 | "luct13b", 199 | "luct13", 200 | "luct15b", 201 | "luct15", 202 | "luct19b", 203 | "luct19", 204 | "luct27b", 205 | "luct27", 206 | "luct38b", 207 | "luct38", 208 | "ludt10b", 209 | "ludt10", 210 | "ncen11b", 211 | "ncen11bi", 212 | "ncen11", 213 | "ncen11i", 214 | "ncen13b", 215 | "ncen13bi", 216 | "ncen13", 217 | "ncen13i", 218 | "ncen15b", 219 | "ncen15bi", 220 | "ncen15", 221 | "ncen15i", 222 | "ncen18b", 223 | "ncen18bi", 224 | "ncen18", 225 | "ncen18i", 226 | "ncen22b", 227 | "ncen22bi", 228 | "ncen22", 229 | "ncen22i", 230 | "ncen29b", 231 | "ncen29bi", 232 | "ncen29", 233 | "ncen29i", 234 | "ncen40b", 235 | "ncen40bi", 236 | "ncen40", 237 | "ncen40i", 238 | "nexu08", 239 | "noxi08", 240 | "ocr10", 241 | "pc6x14", 242 | "pc6x8", 243 | "pc8x14", 244 | "pc8x14t", 245 | "pc8x16", 246 | "pc8x8", 247 | "pc8x8t", 248 | "shin08", 249 | "smoo09", 250 | "symb11", 251 | "symb14", 252 | "symb16", 253 | "symb20", 254 | "symb25", 255 | "symb32", 256 | "symb34", 257 | "term10", 258 | "term10sc", 259 | "term16", 260 | "term16so", 261 | "term20", 262 | "term20so", 263 | "thin09", 264 | "tixu08a", 265 | "tixu08", 266 | "tms11b", 267 | "tms11bi", 268 | "tms11", 269 | "tms11i", 270 | "tms13b", 271 | "tms13bi", 272 | "tms13", 273 | "tms13i", 274 | "tms15b", 275 | "tms15bi", 276 | "tms15", 277 | "tms15i", 278 | "tms18b", 279 | "tms18bi", 280 | "tms18", 281 | "tms18i", 282 | "tms22b", 283 | "tms22bi", 284 | "tms22", 285 | "tms22i", 286 | "tms29b", 287 | "tms29bi", 288 | "tms29", 289 | "tms29i", 290 | "tms38b", 291 | "tms38bi", 292 | "tms38", 293 | "tms38i", 294 | "trac11", 295 | "unir08", 296 | "unir08ko", 297 | "unir08so", 298 | "unir09", 299 | "unir09i", 300 | "unir09is", 301 | "unir09ko", 302 | "unir09so", 303 | "univ05b", 304 | "univ05bi", 305 | "univ05", 306 | "univ05i", 307 | "univ05sc", 308 | "univ07b", 309 | "univ07bi", 310 | "univ07", 311 | "univ07i", 312 | "univ09b", 313 | "univ09bi", 314 | "univ09", 315 | "univ09i", 316 | "univ09w", 317 | "univ10b", 318 | "univ10bi", 319 | "univ10", 320 | "univ10i", 321 | "univ12bb", 322 | "univ12b", 323 | "univ12bi", 324 | "univ12", 325 | "univ14b", 326 | "univ14", 327 | "univ18", 328 | "unva05b", 329 | "unva05", 330 | "unva05sc", 331 | "unva06b", 332 | "unva06", 333 | "unva08b", 334 | "unva08", 335 | "unva14b", 336 | "unva14bi", 337 | "unvs10b", 338 | "unvs10", 339 | "vizv06b", 340 | "vizv06", 341 | "xm10x17b", 342 | "xm10x17", 343 | "xm10x20b", 344 | "xm10x20", 345 | "xm11x19b", 346 | "xm12x15", 347 | "xm12x20b", 348 | "xm12x20", 349 | "xm12x23", 350 | "xm12x24", 351 | "xm14x26", 352 | "xm16x25b", 353 | "xm16x25", 354 | "xm16x25i", 355 | "xm4x5", 356 | "xm4x6", 357 | "xm5x10", 358 | "xm5x6", 359 | "xm5x8", 360 | "xm6x10b", 361 | "xm6x10", 362 | "xm6x12b", 363 | "xm6x12", 364 | "xm6x12i", 365 | "xm6x13b", 366 | "xm6x13", 367 | "xm6x16", 368 | "xm6x6", 369 | "xm6x8", 370 | "xm6x9", 371 | "xm7x10", 372 | "xm7x12", 373 | "xm7x13b", 374 | "xm7x13", 375 | "xm7x14", 376 | "xm7x17", 377 | "xm7x8", 378 | "xm8x10b", 379 | "xm8x10", 380 | "xm8x12b", 381 | "xm8x12", 382 | "xm8x13b", 383 | "xm8x13", 384 | "xm8x14b", 385 | "xm8x14", 386 | "xm8x15b", 387 | "xm8x15", 388 | "xm8x16b", 389 | "xm8x16", 390 | "xm8x16i", 391 | "xm8x19", 392 | "xm8x8b", 393 | "xm8x8", 394 | "xm8x8i", 395 | "xm9x15b", 396 | "xm9x15", 397 | "xm9x17", 398 | "zin09", 399 | "zin09t" 400 | ] -------------------------------------------------------------------------------- /ui/res/scrollb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dutchtux3000/jiyuai/4b6f4ddc4360f33c9ebd4749d1545fbe85fd44b8/ui/res/scrollb.png -------------------------------------------------------------------------------- /ui/uiapp.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2023 Gilliaard Damme 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 20 | SOFTWARE. 21 | */ 22 | Include('./ui/utils.js'); 23 | Include('./ui/fonts.js'); 24 | Include('./ui/widgets/widget.js'); 25 | 26 | Include('./ui/widgets/menubar.js'); 27 | Include('./ui/widgets/menu.js'); 28 | Include('./ui/widgets/window.js'); 29 | 30 | // widgets 31 | Include('./ui/widgets/text.js'); 32 | Include('./ui/widgets/button.js'); 33 | Include('./ui/widgets/tinput.js'); 34 | Include('./ui/widgets/scrllbr.js'); 35 | Include('./ui/widgets/group.js'); 36 | Include('./ui/widgets/chckbx.js'); 37 | Include('./ui/widgets/image.js'); 38 | 39 | // window 40 | Include('./ui/widgets/dialog.js'); 41 | Include('./ui/window/about.js'); 42 | Include('./ui/window/debug.js'); 43 | 44 | // library loading 45 | LoadLibrary("png") 46 | 47 | /** 48 | * UI app object handling all rendering/event handling of the application 49 | */ 50 | function UIApp() { 51 | this.showFps = false 52 | this.showLastDrawTick = false 53 | this.showMemStats = false; 54 | this.background = Color(1,130,129, 255) 55 | this.needsRedraw = true; // first time needs to draw. 56 | this.applicationMenuSymbol = '@' 57 | this.applicationMenu = []; // Application menu that is always used. 58 | 59 | this.currentWindowMenu = []; 60 | this.menuBar = new UIMenuBar(this); 61 | this._windows = []; 62 | 63 | this._windowIsMoving = false 64 | this._windowOffsetX = 0; 65 | this._windowOffsetY = 0; 66 | this._windowMoveX = 0; 67 | this._windowMoveY = 0; 68 | 69 | this._prevMouseButtons = 0; 70 | this._prevMouseTick = 0; 71 | this._prevMouseX = 0; 72 | this._prevMouseY = 0; 73 | 74 | this.enableDebugWindow = false; 75 | 76 | this.applicationInfo = { 77 | name: 'Application name', 78 | version: '0.0.1', 79 | copyright: 'Created by DutchTux 2023', 80 | license: 'Unlicensed' 81 | } 82 | 83 | this.fonts = new UIFonts(); 84 | } 85 | 86 | /** 87 | * Init app after creating. 88 | */ 89 | UIApp.prototype.init = function () { 90 | // init 91 | this.menuBar.app = this; 92 | this.applicationMenu = [ 93 | { 94 | label: 'About', 95 | onClick: function () { 96 | this.showAbout() 97 | }.bind(this) 98 | }, 99 | { 100 | label: '-' 101 | }, 102 | { 103 | label: 'Quit', 104 | keyCode: 4352, 105 | keyLabel: 'Alt+Q', 106 | onClick: function () { 107 | Stop() 108 | } 109 | } 110 | ]; 111 | // Application menu that is always used. 112 | if (this.enableDebugWindow) { 113 | this.debugWindow = new DebugWindow(); 114 | this.debugWindow.setVisible(false); 115 | this.addWindow(this.debugWindow); 116 | } 117 | this.rebuildMenu(); 118 | } 119 | 120 | /** 121 | * Rebuild the menubar based on active window adn the applicationMenu variable. 122 | * It also creates a menu Window at the end so you can switch from window by selecting in the menu 123 | */ 124 | UIApp.prototype.rebuildMenu = function () { 125 | var activeWindow = this.getActiveWindow() 126 | 127 | this.menuBar.setMenu( 128 | [ 129 | { 130 | label: this.applicationMenuSymbol, 131 | children: this.applicationMenu 132 | } 133 | ].concat( 134 | this.currentWindowMenu, 135 | [ 136 | { 137 | label: 'Window', 138 | children: [ 139 | { 140 | label: 'Close window', 141 | // keyCode: 5911, 142 | keyLabel: 'Ctrl+W', 143 | isDisabled: !(activeWindow && activeWindow.isClosable), 144 | onClick: function () { 145 | this.hideActiveWindow() 146 | }.bind(this) 147 | }, 148 | { 149 | label: '-' 150 | }, 151 | ].concat( 152 | this._windows. 153 | filter(function (item) { 154 | return item.isVisible === true 155 | }) 156 | .map(function (item) { 157 | return { 158 | label: ((item.isActive) ? String.fromCharCode(7) + ' ' : ' ') + item.title, 159 | isChecked: item.isActive, 160 | onClick: function () { 161 | this.setWindowActiveById(item.id) 162 | }.bind(this) 163 | } 164 | }.bind(this)) 165 | ) 166 | } 167 | ] 168 | ) 169 | ); 170 | } 171 | 172 | /** 173 | * Applcation loop handling. 174 | * 175 | * Go through all window that reloaded and call the _handleloop function. 176 | */ 177 | UIApp.prototype.loop = function() { 178 | // loop through all widget and call the loop function 179 | var tick = MsecTime(); 180 | // loop through all window and call the loop function if it exists. 181 | this._windows.forEach(function (item) { 182 | if (item._handleLoop) { 183 | item._handleLoop(tick) 184 | } 185 | }) 186 | 187 | // draw screen; 188 | this.draw(); 189 | } 190 | 191 | /** 192 | * Handling drawing screen if needed. 193 | * 194 | * It only redraws when needed. 195 | * It also renders some debug information if needed. 196 | * 197 | */ 198 | UIApp.prototype.draw = function () { 199 | if (this.needsRedraw == true) { 200 | 201 | MouseShowCursor(false); 202 | ClearScreen(app.background); 203 | this._windows.forEach(function (windowItem) { 204 | if (windowItem.isVisible) { 205 | windowItem.draw(); 206 | } 207 | }) 208 | 209 | var activeWindow = this.getActiveWindow(); 210 | if ((activeWindow && activeWindow.isModal !== true) || (activeWindow === undefined)) { 211 | this.menuBar.draw(); 212 | } 213 | 214 | this.drawWindowMoveFrame() 215 | this.needsRedraw = false; 216 | 217 | // render last draw tick 218 | if (this.showLastDrawTick) { 219 | var lastDrawStr = 'Last draw tick:' + MsecTime(); 220 | var lastDrawX = SizeX() - (Utils.calcTextWidth(lastDrawStr) + 16) 221 | FilledBox(lastDrawX, 6, SizeX(), 14, EGA.WHITE); 222 | TextXY(lastDrawX, 6, lastDrawStr, EGA.BLACK, EGA.BLUE); 223 | } 224 | 225 | MouseShowCursor(true); 226 | } 227 | 228 | if (this.showFps || this.showMemStats) { 229 | var statStr = '' 230 | 231 | // show FPS 232 | if (this.showFps) { 233 | statStr += 'FPS:' + GetFramerate() + ' '; 234 | } 235 | 236 | // show mem stats 237 | if (this.showMemStats) { 238 | var memoryInfo = MemoryInfo() 239 | // statStr += 'Mem:' + Utils.humanFileSize(memoryInfo.total - memoryInfo.remaining) + '/' + Utils.humanFileSize(memoryInfo.total) 240 | statStr += 'Mem:' + (memoryInfo.total - memoryInfo.remaining) + '/' + (memoryInfo.total) 241 | } 242 | 243 | var statX = SizeX() - (Utils.calcTextWidth(statStr) + 16) 244 | FilledBox(statX, 6, SizeX(), 14, EGA.WHITE); 245 | TextXY(statX, 6, statStr, EGA.BLACK, EGA.BLUE); 246 | } 247 | } 248 | 249 | /** 250 | * Draws the window move frame of the current window that use is moving. 251 | */ 252 | UIApp.prototype.drawWindowMoveFrame = function () { 253 | var activeWindow = this.getActiveWindow(); 254 | if (!activeWindow || !this._windowIsMoving) { 255 | return; 256 | } 257 | 258 | var frameRect = Utils.createRectObj(this._windowMoveX, this._windowMoveY, activeWindow.w, activeWindow.h); 259 | Box(frameRect.x, frameRect.y, frameRect.w, frameRect.h, EGA.DARK_GRAY); 260 | Box(frameRect.x, frameRect.y, frameRect.w, frameRect.y + 20, EGA.DARK_GRAY); 261 | } 262 | 263 | /** 264 | * Do all keyboard/mouse handling of the applciation. think of handling menu shortcuts and window shortcuts. 265 | * Send the first to the menu and then through the active window if needed. 266 | * 267 | * @param {*} event 268 | * @returns 269 | */ 270 | UIApp.prototype.input = function (event) { 271 | var activeWindow = this.getActiveWindow(); 272 | // key press handle 273 | if (event.key >= 0) { 274 | // var key = event.key & 0xff 275 | var key = event.key 276 | var keyCode = key >> 8; 277 | var char = String.fromCharCode(key & 0xFF); 278 | 279 | // send to menubar 280 | var stopPropagation = this.menuBar.onKeyPress(key, keyCode, char) 281 | if (stopPropagation === true) { 282 | return; 283 | } 284 | 285 | // send key press to window 286 | if (activeWindow && !this.menuBar.isActive && activeWindow.onKeyPress) { 287 | activeWindow._handleInput(key, keyCode, char); 288 | } 289 | } 290 | 291 | // mouse handle 292 | // check menu 293 | var mouseX = event.x 294 | var mouseY = event.y 295 | var mouseButtons = event.buttons 296 | var isLeftClicked = (mouseButtons === 0 && this._prevMouseButtons === 1); 297 | 298 | var menuHandled = false 299 | if ((activeWindow && activeWindow.isModal !== true) || (activeWindow === undefined)) { 300 | menuHandled = (this._windowIsMoving === false) ? 301 | this.menuBar._handleMouseInput(mouseButtons, mouseX, mouseY, isLeftClicked) 302 | : false 303 | if (!menuHandled) { 304 | this.menuBar.handleMenuShortCode(key) 305 | } 306 | } 307 | 308 | if (!menuHandled) { 309 | this._inputMouseHandle(mouseButtons, mouseX, mouseY, isLeftClicked) 310 | } 311 | 312 | if (this.enableDebugWindow && keyCode === KEY.Code.KEY_F12) { 313 | if (this.debugWindow && this.debugWindow.isVisible) { 314 | this.hideWindow('debugWindow'); 315 | } else { 316 | this.showWindow('debugWindow'); 317 | } 318 | } 319 | 320 | this._prevMouseButtons = mouseButtons 321 | this._prevMouseTick = event.ticks 322 | this._prevMouseX = mouseX 323 | this._prevMouseY = mouseY 324 | } 325 | 326 | /** 327 | * Mouse handling handling. 328 | * 329 | * @param {number} mouseButtons 330 | * @param {number} mouseX 331 | * @param {number} mouseY 332 | * @param {bool} isLeftClicked 333 | */ 334 | UIApp.prototype._inputMouseHandle = function (mouseButtons, mouseX, mouseY, isLeftClicked) { 335 | var activeWindow = this.getActiveWindow(); 336 | 337 | if (this._windowIsMoving === true) { 338 | if (mouseButtons === 0) { 339 | this._windowIsMoving = false; 340 | var newY = mouseY - this._windowOffsetY 341 | if (newY < 20) { 342 | newY = 20 343 | } 344 | 345 | activeWindow.setPosition(mouseX - this._windowOffsetX, newY); 346 | return; 347 | } else { 348 | this._windowMoveX = mouseX - this._windowOffsetX 349 | this._windowMoveY = mouseY - this._windowOffsetY 350 | this.reRender(); 351 | return 352 | } 353 | } 354 | 355 | // check if a window needs to be actived 356 | if (activeWindow && activeWindow.rectTest(mouseX, mouseY)) { 357 | // check if going to drag the window 358 | if (activeWindow.showTitlebar === true && mouseButtons === 1 && this._prevMouseButtons == 0 && activeWindow.titlebarHitTest(mouseX, mouseY)) { 359 | this._windowIsMoving = true 360 | this._windowOffsetX = mouseX - activeWindow.x 361 | this._windowOffsetY = mouseY - activeWindow.y 362 | 363 | this._windowMoveX = mouseX - this._windowOffsetX 364 | this._windowMoveY = mouseY - this._windowOffsetY 365 | this.reRender(); 366 | return; 367 | } 368 | 369 | activeWindow._handleMouseInput(mouseButtons, mouseX, mouseY, isLeftClicked); 370 | return; 371 | } 372 | 373 | if (isLeftClicked && activeWindow && activeWindow.isModal !== true) { 374 | var foundHits = this._windows 375 | .filter(function (child) { 376 | return child.isVisible === true && child.rectTest(mouseX, mouseY) 377 | }); 378 | 379 | if (foundHits.length > 0) { 380 | // active window 381 | this.setWindowActiveById(foundHits[foundHits.length - 1].id) 382 | } 383 | } 384 | } 385 | 386 | /** 387 | * Shows the about window. 388 | */ 389 | UIApp.prototype.showAbout = function () { 390 | if (this.doesWindowExistsById('aboutWindow')) { 391 | this.showWindow('aboutWindow'); 392 | } else { 393 | this.addWindow(new AboutWindow()); 394 | } 395 | } 396 | 397 | /** 398 | * Add window to window stack and set active if is visible. 399 | * 400 | * @param {UIWindow} window 401 | */ 402 | UIApp.prototype.addWindow = function (window) { 403 | this._windows.push(window); 404 | window.setApp(this); 405 | window.init(); 406 | if (window.isVisible) { 407 | this.setWindowActiveById(window.id) 408 | } 409 | } 410 | 411 | /** 412 | * Set window active by id 413 | * 414 | * @param {string} id 415 | */ 416 | UIApp.prototype.setWindowActiveById = function (id) { 417 | activeIndex = -1 418 | this._windows.forEach(function (windowItem, index) { 419 | // old window is active. mark for redraw 420 | if (windowItem.isActive === true && (id !== windowItem.id)) { 421 | windowItem.reDraw(); 422 | } 423 | 424 | // mark window if it is active. 425 | windowItem.isActive = (id === windowItem.id) 426 | if (windowItem.isActive) { 427 | this.currentWindowMenu = windowItem.menu 428 | activeIndex = index 429 | } 430 | }.bind(this)); 431 | 432 | if (activeIndex >= 0) { 433 | this._windows.push(this._windows.splice(activeIndex, 1)[0]); 434 | this._windows[this._windows.length - 1].reDraw(); 435 | if (this._windows[this._windows.length - 1].onFocus) { 436 | this._windows[this._windows.length - 1].onFocus() 437 | } 438 | } 439 | 440 | this.rebuildMenu(); 441 | this.reRender(); 442 | } 443 | 444 | /** 445 | * Show the window visible and active bij id 446 | * 447 | * @param {string} id 448 | */ 449 | UIApp.prototype.showWindow = function (id) { 450 | var index = this.getWindowIndexById(id); 451 | if (index >= 0) { 452 | this._windows[index].setVisible(true); 453 | } 454 | this.setWindowActiveById(id); 455 | } 456 | 457 | /** 458 | * Returns active window that has current focus/active 459 | * 460 | * @returns {UIWindow} 461 | */ 462 | UIApp.prototype.getActiveWindow = function () { 463 | var activeWindow = this._windows.filter(function (item) { return item.isActive === true }); 464 | return activeWindow.length > 0 ? activeWindow[0] : undefined; 465 | } 466 | 467 | /** 468 | * Get index of window by id 469 | * 470 | * @param {string} id 471 | * @returns {number} 472 | */ 473 | UIApp.prototype.getWindowIndexById = function (id) { 474 | var foundIndex = -1; 475 | this._windows.forEach(function (item, index) { 476 | if (item.id === id) { 477 | foundIndex = index 478 | } 479 | }) 480 | 481 | return foundIndex; 482 | } 483 | 484 | /** 485 | * Check if a window exists by id 486 | * 487 | * @param {string} id 488 | * @returns {bool} 489 | */ 490 | UIApp.prototype.doesWindowExistsById = function (id) { 491 | return (this.getWindowIndexById(id) >= 0) 492 | } 493 | 494 | /** 495 | * Hide window by id 496 | * 497 | * @param {string} id 498 | */ 499 | UIApp.prototype.hideWindow = function (id) { 500 | var windowIndex = this.getWindowIndexById(id); 501 | if (windowIndex >= 0) { 502 | this._windows[windowIndex].setVisible(false); 503 | this.reRender(); 504 | var inactiveWindow = this._windows.filter(function (item) { return item.isVisible === true }) 505 | if (inactiveWindow.length > 0) { 506 | var newWindow = inactiveWindow.pop(); 507 | this.setWindowActiveById(newWindow.id); 508 | } 509 | } 510 | this.rebuildMenu(); 511 | } 512 | 513 | /** 514 | * Hide active window 515 | */ 516 | UIApp.prototype.hideActiveWindow = function () { 517 | var activeWindow = this.getActiveWindow(); 518 | if (activeWindow && activeWindow.isClosable) { 519 | activeWindow.close(); 520 | } 521 | } 522 | 523 | /** 524 | * Removes a window by id from the stack. 525 | * It automaticly active the most last window in the stack. 526 | * 527 | * @param {string} id 528 | */ 529 | UIApp.prototype.removeWindowById = function(id) { 530 | var windowIndex = this.getWindowIndexById(id); 531 | var activeWindow = this.getActiveWindow(); 532 | 533 | if (windowIndex >= 0) { 534 | this._windows.splice(windowIndex, 1) 535 | this.reRender(); 536 | } 537 | 538 | if (activeWindow.id === id) { 539 | var inactiveWindow = this._windows.filter(function (item) { return item.isVisible === true }) 540 | if (inactiveWindow.length > 0) { 541 | var newWindow = inactiveWindow.pop(); 542 | this.setWindowActiveById(newWindow.id); 543 | } 544 | } 545 | } 546 | 547 | /** 548 | * Get the most recent mouse state as a object 549 | * 550 | * @returns {buttons: number, tick: number, x: number, y: number} 551 | */ 552 | UIApp.prototype.getMouseState = function() { 553 | return { 554 | buttons: this._prevMouseButtons, 555 | tick: this._prevMouseTick, 556 | x: this._prevMouseX, 557 | y: this._prevMouseY 558 | } 559 | } 560 | 561 | /** 562 | * Mark the UI to be rendered. 563 | */ 564 | UIApp.prototype.reRender = function () { 565 | this.needsRedraw = true 566 | } 567 | 568 | /** 569 | * Shortcut function to get a font 570 | * 571 | * @param {string} name 572 | * @returns 573 | */ 574 | UIApp.prototype.getFont = function(name) { 575 | return this.fonts.getFont(name); 576 | } 577 | 578 | exports.__VERSION__ = 1; 579 | exports.UIApp = UIApp; 580 | 581 | -------------------------------------------------------------------------------- /ui/utils.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Calculate width of given text 3 | * 4 | * @param {string} text 5 | * @returns {number} 6 | */ 7 | function calcTextWidth(text) { 8 | return text.length * 8 9 | } 10 | 11 | /** 12 | * 13 | * @param {number} testX 14 | * @param {number} testY 15 | * @param {number} x 16 | * @param {number} y 17 | * @param {number} w 18 | * @param {number} h 19 | * @returns {bool} 20 | */ 21 | function hitTest(testX, testY, x, y, w, h) { 22 | return (testX >= x && testX <= w) && (testY >= y && testY <= h) 23 | } 24 | 25 | /** 26 | * Check if the text x and y are in the given rect 27 | * 28 | * @param {number} testX 29 | * @param {number} testY 30 | * @param {x: number, y: number, w: number, h: number} rect 31 | * @returns {bool} 32 | */ 33 | function hitTestRect(testX, testY, rect) { 34 | return hitTest(testX, testY, rect.x, rect.y, rect.w, rect.h) 35 | } 36 | 37 | /** 38 | * Create react object from params 39 | * 40 | * @param {number} x 41 | * @param {number} y 42 | * @param {number} w 43 | * @param {number} h 44 | * @returns {x: number, y: number, w: number, h: number} 45 | */ 46 | function createRectObj(x, y, w, h) { 47 | return { 48 | x: x, 49 | y: y, 50 | w: x + w, 51 | h: y + h 52 | } 53 | } 54 | 55 | /** 56 | * Format bytes as human-readable text. 57 | * 58 | * @param bytes Number of bytes. 59 | * @param siParam True to use metric (SI) units, aka powers of 1000. False to use 60 | * binary (IEC), aka powers of 1024. 61 | * @param dpParam Number of decimal places to display. 62 | * 63 | * @return Formatted string. 64 | */ 65 | function humanFileSize(bytes, siParam, dpParam) { 66 | var si = siParam ? siParam : false; 67 | var dp = dpParam ? dpParam : 1; 68 | var thresh = si ? 1000 : 1024; 69 | 70 | if (Math.abs(bytes) < thresh) { 71 | return bytes + ' B'; 72 | } 73 | 74 | var units = si ? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'] : ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB']; 75 | var u = -1; 76 | var r = Math.pow(10, dp); 77 | do { 78 | bytes /= thresh; 79 | ++u; 80 | } while (Math.round(Math.abs(bytes) * r) / r >= thresh && u < units.length - 1); 81 | 82 | return bytes.toFixed(dp) + ' ' + units[u]; 83 | } 84 | 85 | /** 86 | * 87 | * @param {number} x1 88 | * @param {number} y1 89 | * @param {number} x2 90 | * @param {number} y2 91 | * @param {Color} c 92 | * @param {number} r 93 | */ 94 | function BoxWithRadius(x1, y1, x2, y2, c, r) { 95 | 96 | Line(x1 + r, y1, x2 - r, y1, c) // top 97 | Line(x2, y1 + r, x2, y2 - r, c) // right 98 | Line(x1 + r, y2, x2 - r, y2, c) // bottom 99 | Line(x1, y1 + r, x1, y2 - r, c) // left 100 | 101 | // create corners 102 | CircleArc(x1 + r, y1 + r, r, 64, 128, c) // top left 103 | CircleArc(x2 - r, y1 + r, r, 0, 64, c) // top right 104 | CircleArc(x2 - r, y2 - r, r, 192, 256, c) // bottom right 105 | CircleArc(x1 + r, y2 - r, r, 128, 192, c) // bottom left 106 | } 107 | 108 | function FilledBoxWithRadius(x1, y1, x2, y2, c, r) { 109 | 110 | // left right 111 | FilledBox(x1, y1 + r, x2, y2 - r, c); 112 | 113 | // top 114 | FilledBox(x1 + r, y1, x2 - r, y1 + r, c); 115 | 116 | // bottom 117 | FilledBox(x1 + r, y2 - r, x2 - r, y2, c); 118 | 119 | // create corners 120 | FilledCircle(x1 + r, y1 + r, r, c) // top left 121 | FilledCircle(x2 - r, y1 + r, r, c) // top right 122 | FilledCircle(x2 - r, y2 - r, r, c) // bottom right 123 | FilledCircle(x1 + r, y2 - r, r, c) // bottom left 124 | } 125 | 126 | exports.__VERSION__ = 1; 127 | exports.Utils = { 128 | calcTextWidth: calcTextWidth, 129 | hitTest: hitTest, 130 | hitTestRect: hitTestRect, 131 | createRectObj: createRectObj, 132 | humanFileSize: humanFileSize 133 | }; 134 | exports.BoxWithRadius = BoxWithRadius; 135 | exports.FilledBoxWithRadius = FilledBoxWithRadius; -------------------------------------------------------------------------------- /ui/widgets/button.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UITButton widget 3 | * 4 | * @param {number} x 5 | * @param {number} y 6 | * @param {string} label 7 | */ 8 | function UIButton(x, y, label) { 9 | UIWidget.call(this); 10 | this.setPosition(x, y) 11 | this.setSize(Utils.calcTextWidth(label) + 32, 24) 12 | this.label = label 13 | this.hasFocus = false 14 | this._canHaveFocus = true 15 | this._isPressed = false 16 | this._hasSizeSet = false 17 | this.fontName = 'pc8x8' 18 | } 19 | 20 | UIButton.prototype = Object.create(UIWidget.prototype); 21 | UIButton.prototype.constructor = UIButton; 22 | 23 | /** 24 | * Handle mouse input 25 | * 26 | * @param {number} mouseButtons 27 | * @param {number} mouseX 28 | * @param {number} mouseY 29 | * @param {bool} isLeftClicked 30 | */ 31 | UIButton.prototype._handleMouseInput = function(mouseButtons, mouseX, mouseY, isLeftClicked) { 32 | if (mouseButtons === 1 && this.hasFocus === false) { 33 | if (this.parent && this.parent._clearFocusFromWidgets) { 34 | this.parent._clearFocusFromWidgets(); 35 | } 36 | this.hasFocus = true 37 | } 38 | 39 | this._checkPressState(mouseX, mouseY, mouseButtons); 40 | 41 | UIWidget.prototype._handleMouseInput.call(this, mouseButtons, mouseX, mouseY, isLeftClicked); 42 | } 43 | 44 | /** 45 | * Draw button 46 | */ 47 | UIButton.prototype.draw = function() { 48 | var font = this.app.getFont(this.fontName); 49 | if (!this._hasSizeSet) { 50 | this.setSize(font.StringWidth(this.label) + 32, 24); 51 | } 52 | 53 | var radius = this.h / 2; 54 | radius = radius < 0 ? 8: radius; 55 | 56 | var rect = this.getDrawRect(this.x, this.y, this.w, this.h) 57 | var rectXCenter = rect.x + (this.w / 2) 58 | var rectYCenter = rect.y + (this.h / 2) 59 | 60 | 61 | FilledBoxWithRadius(rect.x, rect.y, rect.w, rect.h, this._isPressed ? Color(242, 140, 40, 255) : EGA.WHITE, radius); 62 | if (!this._isPressed) { 63 | BoxWithRadius(rect.x, rect.y, rect.w, rect.h, EGA.BLACK, radius); 64 | } 65 | font.DrawStringCenter(rectXCenter, rectYCenter - 4, this.label, this._isPressed ? EGA.WHITE : EGA.BLACK, NO_COLOR) 66 | 67 | if (this.hasFocus) { 68 | // Box(rect.x + 2, rect.y + 2, rect.w - 2, rect.h - 2, EGA.BLACK); 69 | BoxWithRadius(rect.x + 2, rect.y + 2, rect.w - 2, rect.h - 2, this._isPressed ? EGA.WHITE : EGA.BLACK, radius); 70 | } 71 | } 72 | 73 | /** 74 | * Set size of button 75 | * 76 | * @param {number} w 77 | * @param {number} h 78 | */ 79 | UIButton.prototype.setSize = function(w, h) { 80 | this._hasSizeSet = true; 81 | 82 | UIWidget.prototype.setSize.call(this, w, h); 83 | } 84 | 85 | UIButton.prototype._handleLoop = function(tick) { 86 | if(!this.app._windowIsMoving) { 87 | var mouseState = this.app.getMouseState(); 88 | this._checkPressState(mouseState.x, mouseState.y, mouseState.buttons); 89 | } 90 | } 91 | 92 | UIButton.prototype._checkPressState = function(x, y, buttons) { 93 | var isPressed = (this.rectTest(x, y) && buttons === 1) 94 | if (isPressed !== this._isPressed) { 95 | this._isPressed = isPressed; 96 | this.reDraw(); 97 | } 98 | } 99 | 100 | exports.__VERSION__ = 1; 101 | exports.UIButton = UIButton; -------------------------------------------------------------------------------- /ui/widgets/chckbx.js: -------------------------------------------------------------------------------- 1 | function UICheckbox(x, y, label, isChecked) { 2 | UIWidget.call(this); 3 | this.setPosition(x, y); 4 | this.setSize(200, 20); 5 | this.label = label 6 | this.fontName = 'pc6x8'; 7 | this.isChecked = isChecked === undefined ? false : isChecked; 8 | this._canHaveFocus = true 9 | } 10 | 11 | UICheckbox.prototype = Object.create(UIWidget.prototype); 12 | UICheckbox.prototype.constructor = UICheckbox; 13 | 14 | /** 15 | * Draw checkbox 16 | */ 17 | UICheckbox.prototype.draw = function() { 18 | var drawPos = this.getDrawRect(this.x, this.y, this.w, this.h); 19 | var font = this.app.getFont(this.fontName); 20 | 21 | // draw checkbox 22 | var checkboxRect = this._checkboxRect(); 23 | Box(checkboxRect.x, checkboxRect.y, checkboxRect.w, checkboxRect.h, EGA.BLACK); 24 | 25 | if (this.hasFocus) { 26 | Line(checkboxRect.x + 1, checkboxRect.h + 1, checkboxRect.w + 1, checkboxRect.h + 1, EGA.DARK_GREY) 27 | Line(checkboxRect.w + 1, checkboxRect.y + 1, checkboxRect.w + 1, checkboxRect.h, EGA.DARK_GREY) 28 | } 29 | 30 | if (this.isChecked) { 31 | Line(checkboxRect.x + 2, checkboxRect.y + 2, checkboxRect.w - 2, checkboxRect.h - 2, EGA.BLACK) 32 | Line(checkboxRect.w - 2, checkboxRect.y + 2, checkboxRect.x + 2, checkboxRect.h - 2, EGA.BLACK) 33 | } 34 | 35 | font.DrawStringLeft(checkboxRect.w + 6, drawPos.y + 4, this.label, EGA.BLACK, NO_COLOR); 36 | } 37 | 38 | /** 39 | * Handle mouse input 40 | * 41 | * @param {number} mouseButtons 42 | * @param {number} mouseX 43 | * @param {number} mouseY 44 | * @param {bool} isLeftClicked 45 | */ 46 | UICheckbox.prototype._handleMouseInput = function(mouseButtons, mouseX, mouseY, isLeftClicked) { 47 | if (mouseButtons === 1 && this.hasFocus === false) { 48 | if (this.parent && this.parent._clearFocusFromWidgets) { 49 | this.parent._clearFocusFromWidgets(); 50 | } 51 | this.hasFocus = true 52 | this.reDraw(); 53 | } 54 | 55 | UIWidget.prototype._handleMouseInput.call(this, mouseButtons, mouseX, mouseY, isLeftClicked); 56 | } 57 | 58 | UICheckbox.prototype.onClick = function() { 59 | this.setChecked(!this.isChecked); 60 | } 61 | 62 | UICheckbox.prototype._checkboxRect = function() { 63 | var rect = this.getDrawRect(this.x, this.y); 64 | return { 65 | x: rect.x + 2, 66 | y: rect.y + 2, 67 | w: rect.x + 12, 68 | h: rect.y + 12 69 | } 70 | } 71 | 72 | UICheckbox.prototype._handleMouseClick = function(x, y) { 73 | this.onClick(); 74 | } 75 | 76 | UICheckbox.prototype.setChecked = function(value) { 77 | this.isChecked = value; 78 | this.onChecked(value); 79 | this.reDraw(); 80 | } 81 | 82 | UICheckbox.prototype.onChecked = function(value) { 83 | 84 | } 85 | 86 | 87 | 88 | exports.__VERSION__ = 1; 89 | exports.UICheckbox = UICheckbox; -------------------------------------------------------------------------------- /ui/widgets/dialog.js: -------------------------------------------------------------------------------- 1 | var dialogCounter = 0 2 | var DIALOG_PADDING_X = 10; 3 | var DIALOG_PADDING_Y = 10; 4 | /** 5 | * Create dialogs with buttons 6 | * 7 | * @param {string} title 8 | * @param {string} description 9 | * @param {*[]} buttons 10 | */ 11 | function UIDialog(title, description, buttons) { 12 | dialogCounter++; 13 | UIWindow.call(this, 'dialog' + dialogCounter, title) 14 | this.description = description; 15 | this.buttons = buttons 16 | this.isModal = true 17 | this.showTitlebar = false; 18 | this.destroyOnClose = true; 19 | 20 | if (this.buttons.length > 1) { 21 | this.isClosable = false; 22 | } 23 | } 24 | 25 | UIDialog.prototype = Object.create(UIWindow.prototype); 26 | UIDialog.constructor = UIDialog; 27 | 28 | /** 29 | * Init dialog 30 | */ 31 | UIDialog.prototype.init = function() { 32 | var font = this.app.getFont('pc6x8'); 33 | 34 | var descriptionLineSplit = this.description.split('\n'); 35 | var minWidth = this.buttons.length * 90 36 | var labelMaxWidth = this.showTitlebar ? font.StringWidth(this.title) + 40 : minWidth + (DIALOG_PADDING_Y * 2); 37 | var labelPosY = this.showTitlebar === true ? 20 : 0; 38 | descriptionLineSplit.forEach(function (line) { 39 | var textWidth = font.StringWidth(line); 40 | if (textWidth > labelMaxWidth) { 41 | labelMaxWidth = textWidth; 42 | } 43 | var uiLabel = new UIText(DIALOG_PADDING_X, DIALOG_PADDING_Y + labelPosY, line); 44 | 45 | this.addChildren(uiLabel); 46 | labelPosY = labelPosY + 10; 47 | }.bind(this)); 48 | labelPosY = labelPosY + 10; 49 | 50 | labelMaxWidth = (labelMaxWidth < minWidth) ? minWidth : labelMaxWidth; 51 | 52 | var buttonOffsetX = labelMaxWidth + DIALOG_PADDING_X; 53 | this.buttons.reverse().forEach(function (btn) { 54 | var button = new UIButton(0, 0, btn.label) 55 | button.hasFocus = btn.hasFocus; 56 | button.onClick = function() { 57 | if (btn.onClick) { 58 | btn.onClick(); 59 | } 60 | this.close(); 61 | }.bind(this); 62 | button.setSize((button.w < 75) ? 75 : button.w, button.h) 63 | button.setPosition(buttonOffsetX - button.w, DIALOG_PADDING_Y + labelPosY); 64 | this.addChildren(button); 65 | buttonOffsetX = buttonOffsetX - (button.w + 10) 66 | }.bind(this)) 67 | 68 | this.setSize(labelMaxWidth + (DIALOG_PADDING_X * 2), 20 + (DIALOG_PADDING_Y * 2) + labelPosY) 69 | this.setPosition(-1, 80); 70 | } 71 | 72 | /** 73 | * Function to create alert dialog 74 | * 75 | * @param {UIApp} app 76 | * @param {string} description 77 | * @param {string} title 78 | * @param {Function} callBack 79 | */ 80 | UIDialog.createAlert = function(app, description, title, callBack) { 81 | var dialog = new UIDialog( 82 | title ? title : 'Alert', 83 | description, 84 | [ 85 | { 86 | label: 'OK', 87 | hasFocus: true, 88 | onClick: callBack 89 | } 90 | ] 91 | ); 92 | 93 | app.addWindow(dialog) 94 | dialog.reDraw(); 95 | } 96 | 97 | /** 98 | * Create confirm dialog 99 | * 100 | * @param {UIApp} app 101 | * @param {string} description 102 | * @param {string} title 103 | * @param {Function} callBack 104 | */ 105 | UIDialog.createConfirm = function(app, description, title, callBack) { 106 | var dialog = new UIDialog( 107 | title ? title : 'Confirm', 108 | description, 109 | [ 110 | { 111 | label: 'OK', 112 | hasFocus: true, 113 | onClick: function () { 114 | callBack(true); 115 | }, 116 | }, 117 | { 118 | label: 'Cancel', 119 | hasFocus: false, 120 | onClick: function () { 121 | callBack(false); 122 | }, 123 | } 124 | ] 125 | ); 126 | 127 | app.addWindow(dialog) 128 | dialog.reDraw(); 129 | } 130 | 131 | exports.__VERSION__ = 1; 132 | exports.UIDialog = UIDialog; -------------------------------------------------------------------------------- /ui/widgets/group.js: -------------------------------------------------------------------------------- 1 | function UIGroup(label, x, y, w, h) { 2 | UIWidget.call(this); 3 | this.label = label; 4 | this.setPosition(x, y); 5 | this.setSize(w, h); 6 | this.fontName = 'pc6x8' 7 | } 8 | 9 | UIGroup.prototype = Object.create(UIWidget.prototype); 10 | UIGroup.prototype.constructor = UIGroup; 11 | 12 | UIGroup.prototype.draw = function() { 13 | var rect = this.getDrawRect(this.x, this.y, this.w, this.h); 14 | var font = this.app.getFont(this.fontName); 15 | var labelWidth = font.StringWidth(this.label) + 8; 16 | 17 | BoxWithRadius(rect.x, rect.y + 6, rect.w, rect.h, EGA.BLACK, 4); 18 | FilledBox(rect.x + 10, rect.y, rect.x + labelWidth + 4, rect.y + 10, EGA.WHITE); 19 | 20 | font.DrawStringLeft(rect.x + 12, rect.y + 2, this.label, EGA.BLACK, EGA.WHITE); 21 | 22 | // render the children 23 | this._renderChildren(); 24 | } 25 | 26 | exports.__VERSION__ = 1; 27 | exports.UIGroup = UIGroup; -------------------------------------------------------------------------------- /ui/widgets/image.js: -------------------------------------------------------------------------------- 1 | function UIImage(image, x, y) { 2 | UIWidget.call(this); 3 | this.image = image; 4 | this.setPosition(x, y) 5 | // this.setSize(image.width. image.height); 6 | } 7 | 8 | UIImage.prototype = Object.create(UIWidget.prototype); 9 | UIImage.prototype.constructor = UIImage; 10 | 11 | UIImage.prototype.draw = function() { 12 | var rect = this.getDrawRect(this.x, this.y, this.w, this.h) 13 | this.image.Draw(rect.x, rect.y); 14 | } 15 | 16 | exports.__VERSION__ = 1; 17 | exports.UIImage = UIImage; -------------------------------------------------------------------------------- /ui/widgets/menu.js: -------------------------------------------------------------------------------- 1 | var MENUITEM_SELECTED_COLOR = Color(242, 140, 40, 255) //EGA.BLACK 2 | 3 | /** 4 | * UIMenu widget 5 | * 6 | * @param {UIApp} app 7 | * @param {*} items 8 | * @param {number} x 9 | * @param {number} y 10 | */ 11 | function UIMenu(app, items, x, y) { 12 | UIWidget.call(this); 13 | this.x = x 14 | this.y = y 15 | this.w = 16 16 | this.h = 16 17 | this.items = items; 18 | this.app = app 19 | this.hasBeenLoaded = false 20 | this.selectedIndex = -1; 21 | this.onMenuItemSelected = undefined 22 | this.menuRects = [] 23 | this.fontName = 'pc8x8' 24 | } 25 | 26 | UIMenu.prototype = Object.create(UIWidget.prototype); 27 | UIMenu.prototype.constructor = UIMenu; 28 | 29 | /** 30 | * Calculate menu size based on items 31 | */ 32 | UIMenu.prototype.calculateSize = function () { 33 | var font = this.app.getFont(this.fontName); 34 | var maxWidth = 16; 35 | var maxHeight = 0; 36 | 37 | this.items.forEach(function (item) { 38 | var menuItemWidth = font.StringWidth(item.label) + 39 | font.StringWidth(item.keyLabel ? ' ' + item.keyLabel : '') 40 | + 16; 41 | 42 | if (menuItemWidth > maxWidth) { 43 | maxWidth = menuItemWidth; 44 | } 45 | if (item.label !== '-') { 46 | maxHeight = maxHeight + 20; 47 | } 48 | }); 49 | 50 | var menuOffsetY = 0 51 | this.menuRects = this.items.map(function (item) { 52 | if (item.label !== '-') { 53 | 54 | if (item.label !== '-') { 55 | var retValue = Utils.createRectObj(0, menuOffsetY, maxWidth, 20) 56 | menuOffsetY = menuOffsetY + 20; 57 | 58 | return retValue; 59 | } else { 60 | return undefined 61 | } 62 | } 63 | }) 64 | 65 | if (maxHeight === 0) { 66 | maxHeight = 16 67 | } 68 | 69 | this.setSize(maxWidth, maxHeight); 70 | this.hasBeenLoaded = true 71 | } 72 | 73 | /** 74 | * Draw menu 75 | */ 76 | UIMenu.prototype.draw = function () { 77 | if (!this.hasBeenLoaded) { 78 | this.calculateSize(); 79 | } 80 | var font = this.app.getFont(this.fontName); 81 | //var relRect = this.getRelativeRect(this.x, this.y, this.w, this.h); 82 | var rect = this.getDrawRect(this.x, this.y, this.w, this.h); 83 | 84 | FilledBox(rect.x, rect.y, rect.w, rect.h, EGA.WHITE); 85 | Box(rect.x, rect.y, rect.w, rect.h, EGA.BLACK); 86 | Line(rect.x + 2, rect.h + 1, rect.w + 1, rect.h + 1, EGA.DARK_GREY) 87 | Line(rect.w + 1, rect.y + 3, rect.w + 1, rect.h, EGA.DARK_GREY) 88 | 89 | menuOffsetY = this.y 90 | menuOffsetX = this.x 91 | 92 | this.items.forEach(function (item, index) { 93 | textColor = item.isDisabled ? EGA.DARK_GRAY : EGA.BLACK 94 | if (item.label === '-') { 95 | Line(menuOffsetX, menuOffsetY, menuOffsetX + this.w, menuOffsetY, EGA.BLACK) 96 | } else { 97 | if (this.selectedIndex === index) { 98 | textColor = item.isDisabled ? EGA.LIGHT_GRAY : EGA.WHITE 99 | FilledBox(menuOffsetX + 1, menuOffsetY +1, (menuOffsetX + this.w) -1, menuOffsetY + 19, MENUITEM_SELECTED_COLOR); 100 | } 101 | font.DrawStringLeft(menuOffsetX + 8, menuOffsetY + 6, item.label, textColor, NO_COLOR) 102 | if (item.keyLabel) { 103 | font.DrawStringRight((menuOffsetX + (this.w - 8)), menuOffsetY + 6, item.keyLabel, textColor, NO_COLOR) 104 | } 105 | menuOffsetY = menuOffsetY + 20 106 | } 107 | }.bind(this)); 108 | } 109 | 110 | /** 111 | * Keyboard handling 112 | * 113 | * @param {number} key 114 | * @param {number} keyCode 115 | * @returns 116 | */ 117 | UIMenu.prototype.onKeyPress = function (key, keyCode) { 118 | var retValue = false 119 | var currentMenuItemIndex = this.selectedIndex; 120 | 121 | switch (keyCode) { 122 | case KEY.Code.KEY_UP: 123 | currentMenuItemIndex = this.getNewMenuIndex(currentMenuItemIndex, -1) 124 | break; 125 | 126 | case KEY.Code.KEY_DOWN: 127 | currentMenuItemIndex = this.getNewMenuIndex(currentMenuItemIndex, 1) 128 | break; 129 | 130 | case KEY.Code.KEY_ENTER: 131 | case KEY.Code.KEY_ENTER_PAD: 132 | this._handleCurrentSelectedItem(currentMenuItemIndex); 133 | retValue = true 134 | break; 135 | } 136 | 137 | if (currentMenuItemIndex !== this.selectedIndex) { 138 | this.selectedIndex = currentMenuItemIndex 139 | this.reDraw(); 140 | } 141 | 142 | return retValue; 143 | } 144 | 145 | /** 146 | * Hanlde mouse input 147 | * 148 | * @param {number} buttons 149 | * @param {number} x 150 | * @param {number} y 151 | * @param {bool} isLeftClicked 152 | */ 153 | UIMenu.prototype._handleMouseInput = function (buttons, x, y, isLeftClicked) { 154 | if (buttons === 1 || isLeftClicked) { 155 | var relRect = this.getRelativeRect(this.x, this.y, this.w, this.h); 156 | var newSelectedIndex = 0; 157 | 158 | this.menuRects.forEach(function (item, index) { 159 | if (item !== undefined) { 160 | if (Utils.hitTest(x, y, relRect.x + item.x, relRect.y + item.y, relRect.x + item.w, relRect.y + item.h)) { 161 | newSelectedIndex = index 162 | } 163 | } 164 | }) 165 | 166 | if (newSelectedIndex !== this.selectedIndex) { 167 | this.selectedIndex = newSelectedIndex; 168 | this.reDraw(); 169 | } 170 | 171 | if (isLeftClicked) { 172 | this._handleCurrentSelectedItem(this.selectedIndex); 173 | } 174 | } 175 | } 176 | 177 | /** 178 | * Determine next menu item that can be selected 179 | * 180 | * @param {number} currentIndex 181 | * @param {number} amount -1 go up, 1 go down 182 | * @returns 183 | */ 184 | UIMenu.prototype.getNewMenuIndex = function (currentIndex, amount) { 185 | var newIndex = currentIndex + amount 186 | if (amount < 0) { 187 | newIndex = (newIndex < 0) ? (this.items.length - 1) : newIndex 188 | } 189 | if (amount > 0) { 190 | newIndex = (newIndex >= this.items.length) ? 0 : newIndex 191 | } 192 | 193 | if (this.items[newIndex] && this.items[newIndex].label === '-') { 194 | newIndex = this.getNewMenuIndex(newIndex, amount) 195 | } 196 | 197 | return newIndex; 198 | } 199 | /** 200 | * Handle current menu item 201 | * 202 | * @param {number} menuIndex 203 | * @returns 204 | */ 205 | UIMenu.prototype._handleCurrentSelectedItem = function (menuIndex) { 206 | if (this.items[menuIndex]) { 207 | if (this.items[menuIndex].isDisabled) { 208 | return; 209 | } 210 | if (this.items[menuIndex].onClick) { 211 | this.items[menuIndex].onClick(); 212 | } 213 | } 214 | 215 | if (this.onMenuItemSelected) { 216 | this.onMenuItemSelected(this); 217 | } 218 | } 219 | 220 | exports.__VERSION__ = 1 221 | exports.UIMenu = UIMenu; -------------------------------------------------------------------------------- /ui/widgets/menubar.js: -------------------------------------------------------------------------------- 1 | var MENUITEM_SELECTED_COLOR = Color(242, 140, 40, 255) //EGA.BLACK 2 | var MENU_TOGGLE_KEY = KEY.Code.KEY_F10 3 | var MENU_CLOSE_KEY = 15131 // escape 4 | /** 5 | * UI menu bar 6 | * 7 | * @param {UIApp} app 8 | */ 9 | function UIMenuBar(app) { 10 | this.applicationMenuSymbol = '@'; 11 | this.currentMenuOpen = -1; 12 | this.items = []; 13 | this.isActive = false; 14 | this.selectedMenuItem = 0 15 | this.app = app; 16 | this.currentActiveMenu = undefined; 17 | this.x = 0 18 | this.y = 0 19 | this.w = SizeX() 20 | this.h = 20 21 | this.menuRects = [] 22 | this.fontName = 'pc8x8' 23 | } 24 | 25 | UIMenuBar.prototype = Object.create(UIWidget.prototype); 26 | UIMenuBar.prototype.constructor = UIMenuBar; 27 | 28 | /** 29 | * Draw menubar 30 | */ 31 | UIMenuBar.prototype.draw = function () { 32 | var font = this.app.getFont(this.fontName); 33 | FilledBox(0, 0, SizeX(), 20, EGA.WHITE); 34 | Line(0, 20, SizeX(), 20, EGA.BLACK); 35 | 36 | menuActiveOffsetX = 0 37 | this.menuRects.forEach(function (item, index) { 38 | textColor = index === 0 ? Color(242, 140, 40, 255) : EGA.BLACK; 39 | if (this.isActive === true && index === this.selectedMenuItem) { 40 | textColor = EGA.WHITE; 41 | FilledBox(item.x, item.y, item.w, item.h - 1, MENUITEM_SELECTED_COLOR); 42 | menuActiveOffsetX = item.x; 43 | } 44 | font.DrawStringLeft(item.x + 8, item.y + 6, this.items[index].label, textColor, NO_COLOR) 45 | }.bind(this)); 46 | 47 | if (this.isActive && this.currentActiveMenu) { 48 | this.currentActiveMenu.x = menuActiveOffsetX; 49 | this.currentActiveMenu.draw(); 50 | } 51 | } 52 | 53 | /** 54 | * Handle keyboard input 55 | * 56 | * @param {number} key 57 | * @param {number} keyCode 58 | * @returns 59 | */ 60 | UIMenuBar.prototype.onKeyPress = function (key, keyCode) { 61 | var retValue = false 62 | // check if menubar needs to be activated 63 | if (keyCode === MENU_TOGGLE_KEY) { 64 | this.toggleMenu(); 65 | if (this.isActive) { 66 | retValue = true 67 | } 68 | } 69 | 70 | if (!this.isActive) { 71 | return false; 72 | } 73 | var currentMenubarItemIndex = this.selectedMenuItem; 74 | 75 | switch (keyCode) { 76 | case KEY.Code.KEY_LEFT: 77 | currentMenubarItemIndex--; 78 | currentMenubarItemIndex = (currentMenubarItemIndex < 0) ? (this.items.length - 1) : currentMenubarItemIndex 79 | break; 80 | 81 | case KEY.Code.KEY_RIGHT: 82 | currentMenubarItemIndex++; 83 | currentMenubarItemIndex = (currentMenubarItemIndex >= this.items.length) ? 0 : currentMenubarItemIndex 84 | break; 85 | } 86 | 87 | if (key === MENU_CLOSE_KEY) { // escape 88 | this.closeMenu(); 89 | } 90 | 91 | if (this.selectedMenuItem !== currentMenubarItemIndex) { 92 | this.selectedMenuItem = currentMenubarItemIndex; 93 | this.createMenuFromSelected(); 94 | this.reDraw(); 95 | retValue = true 96 | } 97 | 98 | if (this.currentActiveMenu) { 99 | retValue = this.currentActiveMenu.onKeyPress(key, keyCode); 100 | } 101 | 102 | return retValue 103 | } 104 | 105 | /** 106 | * Handle keyboard shortcuts in menu 107 | * 108 | * @param {*} keyCode 109 | * @returns 110 | */ 111 | UIMenuBar.prototype.handleMenuShortCode = function(keyCode) { 112 | var retValue = false; 113 | 114 | this.items.forEach(function (item) { 115 | if (item.isDisabled === true || retValue === true) { 116 | return; 117 | } 118 | item.children.forEach(function (subItem) { 119 | if (subItem.isDisabled === true || retValue === true) { 120 | return; 121 | } 122 | if (subItem.keyCode !== undefined && subItem.keyCode === keyCode && subItem.onClick) { 123 | subItem.onClick(); 124 | retValue = true; 125 | return; 126 | } 127 | }); 128 | }); 129 | 130 | // input is handled close the menu..isModal 131 | if (retValue === true) { 132 | this.closeMenu(); 133 | } 134 | 135 | return retValue; 136 | } 137 | /** 138 | * Create UIMenu object from the selected menu 139 | */ 140 | UIMenuBar.prototype.createMenuFromSelected = function () { 141 | var currentMenubarItemIndex = this.selectedMenuItem; 142 | var currentMenu = this.items[currentMenubarItemIndex] 143 | if (currentMenubarItemIndex >= 0 && currentMenu && currentMenu.children) { 144 | this.currentActiveMenu = new UIMenu(this.app, currentMenu.children, 0, 20); 145 | this.currentActiveMenu.onMenuItemSelected = function () { 146 | this.closeMenu(); 147 | }.bind(this) 148 | } else { 149 | this.currentActiveMenu = undefined 150 | } 151 | this.reDraw(); 152 | } 153 | 154 | /** 155 | * Close menu 156 | */ 157 | UIMenuBar.prototype.closeMenu = function () { 158 | this.isActive = false; 159 | this.currentActiveMenu = undefined; 160 | this.selectedMenuItem = 0 161 | this.createMenuFromSelected(); 162 | } 163 | 164 | /** 165 | * Open menu 166 | */ 167 | UIMenuBar.prototype.openMenu = function () { 168 | this.isActive = true; 169 | this.createMenuFromSelected(); 170 | } 171 | 172 | /** 173 | * Toggle menu to show or hide. 174 | */ 175 | UIMenuBar.prototype.toggleMenu = function () { 176 | if (!this.isActive) { 177 | this.openMenu(); 178 | } else { 179 | this.closeMenu(); 180 | } 181 | } 182 | /** 183 | * Set menu items 184 | * 185 | * @param {*} items 186 | */ 187 | UIMenuBar.prototype.setMenu = function (items) { 188 | this.items = items; 189 | var font = this.app.getFont(this.fontName); 190 | 191 | // calculate menu items 192 | menuOffsetX = 0 193 | this.menuRects = items.map(function (item) { 194 | var menuItemWidth = font.StringWidth(item.label); 195 | var menuRect = Utils.createRectObj(menuOffsetX, 0, menuItemWidth + 16, 20) 196 | menuOffsetX = menuRect.w 197 | return menuRect; 198 | }); 199 | } 200 | 201 | /** 202 | * Handle mouse input 203 | * 204 | * @param {number} buttons 205 | * @param {number} x 206 | * @param {number} y 207 | * @param {number} isLeftClicked 208 | * @returns 209 | */ 210 | UIMenuBar.prototype._handleMouseInput = function (buttons, x, y, isLeftClicked) { 211 | // if (buttons === 1) { 212 | if (buttons === 1 || isLeftClicked) { 213 | if (!this.isActive && this.rectTest(x, y)) { 214 | this._handleMouseMenuBarSelect(x, y); 215 | this.openMenu(); 216 | return true; 217 | } 218 | 219 | if (this.isActive) { 220 | if (this._handleMouseMenuBarSelect(x, y)) { 221 | return true; 222 | } 223 | 224 | if (this.currentActiveMenu && this.currentActiveMenu.rectTest(x, y)) { 225 | this.currentActiveMenu._handleMouseInput(buttons, x, y, isLeftClicked); 226 | return true; 227 | } 228 | 229 | if (!this.rectTest(x, y)) { 230 | this.closeMenu(); 231 | return true; 232 | } 233 | } 234 | } 235 | 236 | return false; 237 | } 238 | 239 | /** 240 | * Handle mouse input to select menu item 241 | * 242 | * @param {number} x 243 | * @param {number} y 244 | * @returns 245 | */ 246 | UIMenuBar.prototype._handleMouseMenuBarSelect = function (x, y) { 247 | var newIndex = this.selectedMenuItem; 248 | 249 | // check which menu items should be opened. 250 | this.menuRects.forEach(function (item, index) { 251 | if (Utils.hitTest(x, y, item.x, item.y, item.w, item.h)) { 252 | newIndex = index 253 | } 254 | }) 255 | 256 | if (newIndex !== this.selectedMenuItem) { 257 | this.selectedMenuItem = newIndex 258 | this.createMenuFromSelected(); 259 | return true; 260 | } 261 | 262 | return false; 263 | } 264 | 265 | exports.__VERSION__ = 1 266 | exports.UIMenuBar = UIMenuBar; -------------------------------------------------------------------------------- /ui/widgets/scrllbr.js: -------------------------------------------------------------------------------- 1 | var SCROLLBAR_ORIENTATION_HORIZONTAL = 'horizontal'; 2 | var SCROLLBAR_ORIENTATION_VERTICAL = 'vertical'; 3 | var SCROLLBAR_SIZE = 16; 4 | var SCROLLBAR_BUTTON_SIZE = 16; 5 | 6 | /** 7 | * Arrows of scrollbar 8 | */ 9 | var SCROLLBAR_ARROW_FILENAME = "./ui/res/scrollb.png"; 10 | var SCROLLBAR_ARROW_BITMAP = undefined 11 | 12 | /** 13 | * Which button is pressed on the scrollbar 14 | */ 15 | var SCROLLBAR_BUTTON_STATE = { 16 | NONE: undefined, 17 | MIN_BUTTON: 'min', 18 | PLUS_BUTTON: 'plus', 19 | SCROLL: 'scroll', 20 | } 21 | 22 | /** 23 | * UI scrollbar widget 24 | * 25 | * @param {number} x 26 | * @param {number} y 27 | * @param {number} w 28 | * @param {number} h 29 | * @param {number} min 30 | * @param {number} max 31 | * @param {number} value 32 | */ 33 | function UIScrollBar(x, y, w, h, min, max, value) { 34 | UIWidget.call(this); 35 | this.orientation = SCROLLBAR_ORIENTATION_VERTICAL; 36 | this.min = min ? min : 0; 37 | this.max = max ? max : 100; 38 | this.value = value ? value : 0; 39 | this.step = 1 40 | 41 | this._isMoveScroll = false 42 | this._state = SCROLLBAR_BUTTON_STATE.NONE; 43 | 44 | this.setPosition(x, y); 45 | this.setSize(w, h); 46 | 47 | // load arrow images if not done yet. 48 | if (SCROLLBAR_ARROW_BITMAP === undefined) { 49 | SCROLLBAR_ARROW_BITMAP = new Bitmap(SCROLLBAR_ARROW_FILENAME) 50 | } 51 | } 52 | 53 | UIScrollBar.prototype = Object.create(UIWidget.prototype); 54 | UIScrollBar.prototype.constructor = UIScrollBar; 55 | 56 | /** 57 | * Set size of scrollbasr 58 | * 59 | * @param {number} w 60 | * @param {number} h 61 | */ 62 | UIScrollBar.prototype.setSize = function(w, h) { 63 | this.w = w; 64 | this.h = h; 65 | 66 | this._determineScrollbarType(); 67 | UIWidget.prototype.setSize.call(this, this.w, this.h); 68 | } 69 | 70 | /** 71 | * Set min value 72 | * 73 | * @param {number} min 74 | */ 75 | UIScrollBar.prototype.setMin = function(min) { 76 | this.min = min 77 | this.reDraw(); 78 | } 79 | 80 | /** 81 | * Set max value 82 | * 83 | * @param {number} max 84 | */ 85 | UIScrollBar.prototype.setMax = function(max) { 86 | this.max = max 87 | this.reDraw(); 88 | } 89 | 90 | /** 91 | * Set value 92 | * 93 | * @param {number} value 94 | */ 95 | UIScrollBar.prototype.setValue = function(value) { 96 | this.value = value 97 | if (this.value < this.min) { 98 | this.value = this.min; 99 | } 100 | if (this.value > this.max) { 101 | this.value = this.max; 102 | } 103 | this.onChange(this.value); 104 | this.reDraw(); 105 | } 106 | 107 | /** 108 | * Set step of when clicking the arrow buttons 109 | * 110 | * @param {number} step 111 | */ 112 | UIScrollBar.prototype.setStep = function(step) { 113 | this.step = step; 114 | } 115 | 116 | 117 | /** 118 | * On change event of the scrollbar 119 | * 120 | * @param {number} value 121 | */ 122 | UIScrollBar.prototype.onChange = function(value) { 123 | 124 | } 125 | 126 | /** 127 | * Scrollbar loop 128 | * 129 | * @param {number} tick 130 | */ 131 | UIScrollBar.prototype._handleLoop = function(tick) { 132 | var mouseState = this.app.getMouseState(); 133 | 134 | if (this._state !== SCROLLBAR_BUTTON_STATE.NONE) { 135 | if (!this.rectTest(mouseState.x, mouseState.y)) { 136 | this._resetScroll(); 137 | return; 138 | } 139 | 140 | switch (mouseState.buttons) { 141 | case 0: 142 | this._resetScroll(); 143 | break; 144 | case 1: 145 | if (!this.rectTest(mouseState.x, mouseState.y)) { 146 | this._resetScroll(); 147 | } else { 148 | this._handleScrollButton(mouseState.x, mouseState.y); 149 | } 150 | break; 151 | } 152 | } 153 | } 154 | 155 | /** 156 | * Determine the scrollbar type by checking width and height 157 | */ 158 | UIScrollBar.prototype._determineScrollbarType = function() { 159 | if (this.w > this.h) { 160 | this.orientation = SCROLLBAR_ORIENTATION_HORIZONTAL; 161 | this.h = SCROLLBAR_SIZE; 162 | } else { 163 | this.orientation = SCROLLBAR_ORIENTATION_VERTICAL; 164 | this.w = SCROLLBAR_SIZE; 165 | } 166 | } 167 | 168 | /** 169 | * Draw scrollbar 170 | */ 171 | UIScrollBar.prototype.draw = function() { 172 | var relRect = this.getDrawRect(this.x, this.y, this.w, this.h); 173 | var minBtnRect = this._getMinButtonRect(true); 174 | var plusBtnRect = this._getPlusButtonRect(true); 175 | var valueBtnRect = this._getValueButtonRect(true); 176 | 177 | var minImgX = (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) ? 32 : 0 178 | var minImgY = (this._state === SCROLLBAR_BUTTON_STATE.MIN_BUTTON) ? 16 : 0 179 | 180 | var plusImgX = (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) ? 48 : 16 181 | var plusImgY = (this._state === SCROLLBAR_BUTTON_STATE.PLUS_BUTTON) ? 16 : 0 182 | 183 | FilledBox(relRect.x, relRect.y, relRect.w, relRect.h, EGA.LIGHT_GREY); 184 | Box(relRect.x, relRect.y, relRect.w, relRect.h, EGA.BLACK); 185 | 186 | // min button 187 | SCROLLBAR_ARROW_BITMAP.DrawAdvanced(minImgX, minImgY, 16, 16, minBtnRect.x, minBtnRect.y, 16, 16) 188 | Box(minBtnRect.x, minBtnRect.y, minBtnRect.w, minBtnRect.h, EGA.BLACK); 189 | 190 | // plus button 191 | SCROLLBAR_ARROW_BITMAP.DrawAdvanced(plusImgX, plusImgY, 16, 16, plusBtnRect.x, plusBtnRect.y, 16, 16) 192 | Box(plusBtnRect.x, plusBtnRect.y, plusBtnRect.w, plusBtnRect.h, EGA.BLACK); 193 | 194 | // value rect 195 | FilledBox(valueBtnRect.x, valueBtnRect.y, valueBtnRect.w, valueBtnRect.h, EGA.WHITE); 196 | Box(valueBtnRect.x, valueBtnRect.y, valueBtnRect.w, valueBtnRect.h, EGA.BLACK); 197 | } 198 | 199 | /** 200 | * Get the rect for up/left arrow 201 | * 202 | * @param {boolean} forDraw get rect for drawing 203 | * @returns { x: number, y: number, w: number, h:number } 204 | */ 205 | UIScrollBar.prototype._getMinButtonRect = function(forDraw) { 206 | var relRect = (forDraw === true) ? this.getDrawRect(this.x, this.y, this.w, this.h) : this.getRelativeRect(this.x, this.y, this.w, this.h); 207 | if (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) { 208 | return { 209 | x: relRect.x, 210 | y: relRect.y, 211 | w: relRect.x + SCROLLBAR_BUTTON_SIZE, 212 | h: relRect.h, 213 | } 214 | } else { 215 | return { 216 | x: relRect.x, 217 | y: relRect.y, 218 | w: relRect.w, 219 | h: relRect.y + SCROLLBAR_BUTTON_SIZE, 220 | } 221 | } 222 | } 223 | 224 | /** 225 | * Get the rect for down/right arrow 226 | * 227 | * @param {boolean} forDraw get rect for drawing 228 | * @returns { x: number, y: number, w: number, h:number } 229 | */ 230 | UIScrollBar.prototype._getPlusButtonRect = function(forDraw) { 231 | var relRect = (forDraw === true) ? this.getDrawRect(this.x, this.y, this.w, this.h) : this.getRelativeRect(this.x, this.y, this.w, this.h); 232 | if (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) { 233 | return { 234 | x: relRect.w - SCROLLBAR_BUTTON_SIZE, 235 | y: relRect.y, 236 | w: relRect.w, 237 | h: relRect.h, 238 | } 239 | } else { 240 | return { 241 | x: relRect.x, 242 | y: relRect.h - SCROLLBAR_BUTTON_SIZE, 243 | w: relRect.w, 244 | h: relRect.h, 245 | } 246 | } 247 | } 248 | 249 | /** 250 | * Get the rect for scroll position 251 | * 252 | * @param {boolean} forDraw get rect for drawing 253 | * @returns { x: number, y: number, w: number, h:number } 254 | */ 255 | UIScrollBar.prototype._getValueButtonRect = function(forDraw) { 256 | var relRect = (forDraw === true) ? this.getDrawRect(this.x, this.y, this.w, this.h) : this.getRelativeRect(this.x, this.y, this.w, this.h); 257 | var pxVal = this._pixelPerUnit() 258 | var valuePos = (this.value * pxVal); 259 | var valueRectOffset = (SCROLLBAR_BUTTON_SIZE / 2) 260 | var scrollOffset = SCROLLBAR_BUTTON_SIZE + valueRectOffset 261 | var scrollPos = scrollOffset + valuePos 262 | 263 | if (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) { 264 | return { 265 | x: relRect.x + scrollPos - valueRectOffset, 266 | y: relRect.y, 267 | w: relRect.x + scrollPos + valueRectOffset, 268 | h: relRect.h, 269 | } 270 | } else { 271 | return { 272 | x: relRect.x, 273 | y: relRect.y + scrollPos - valueRectOffset, 274 | w: relRect.w, 275 | h: relRect.y + scrollPos + valueRectOffset, 276 | } 277 | } 278 | } 279 | 280 | /** 281 | * Handle mouse input 282 | * 283 | * @param {number} mouseButtons 284 | * @param {number} mouseX 285 | * @param {number} mouseY 286 | * @param {bool} isLeftClicked 287 | */ 288 | UIScrollBar.prototype._handleMouseInput = function(mouseButtons, mouseX, mouseY, isLeftClicked) { 289 | if (mouseButtons === 1 && this.hasFocus === false) { 290 | if (this.parent && this.parent._clearFocusFromWidgets) { 291 | this.parent._clearFocusFromWidgets(); 292 | } 293 | this.hasFocus = true 294 | } 295 | 296 | var minBtnRect = this._getMinButtonRect(); 297 | var valueBtnRect = this._getValueButtonRect(); 298 | 299 | // check arrow buttons 300 | if (mouseButtons === 1) { 301 | if (this._isMoveScroll) { 302 | var mouseAbsPos = (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) ? 303 | mouseX - minBtnRect.w - (SCROLLBAR_BUTTON_SIZE / 2) : 304 | mouseY - minBtnRect.h - (SCROLLBAR_BUTTON_SIZE / 2) 305 | var mouseVal = mouseAbsPos / this._pixelPerUnit(); 306 | 307 | this.setValue(Math.round(mouseVal)); 308 | this._state = SCROLLBAR_BUTTON_STATE.SCROLL; 309 | return; 310 | } 311 | 312 | if (this._state === SCROLLBAR_BUTTON_STATE.NONE) { 313 | this._handleScrollButton(mouseX, mouseY); 314 | 315 | if (Utils.hitTestRect(mouseX, mouseY, valueBtnRect)) { 316 | this._isMoveScroll = true; 317 | this._state = SCROLLBAR_BUTTON_STATE.SCROLL; 318 | return; 319 | } 320 | return; 321 | } 322 | } 323 | 324 | if (mouseButtons === 0 && this._state !== SCROLLBAR_BUTTON_STATE.NONE) { 325 | this._resetScroll(); 326 | } 327 | } 328 | 329 | 330 | /** 331 | * Handle buttons of mouse 332 | * 333 | * @param {number} mouseX 334 | * @param {number} mouseY 335 | */ 336 | UIScrollBar.prototype._handleScrollButton = function(mouseX, mouseY) { 337 | var minBtnRect = this._getMinButtonRect(); 338 | var plusBtnRect = this._getPlusButtonRect(); 339 | 340 | if (Utils.hitTestRect(mouseX, mouseY, minBtnRect)) { 341 | this.setValue(this.value - this.step); 342 | this._state = SCROLLBAR_BUTTON_STATE.MIN_BUTTON; 343 | return; 344 | } 345 | 346 | if (Utils.hitTestRect(mouseX, mouseY, plusBtnRect)) { 347 | this.setValue(this.value + this.step); 348 | this._state = SCROLLBAR_BUTTON_STATE.PLUS_BUTTON; 349 | return; 350 | } 351 | } 352 | 353 | UIScrollBar.prototype._pixelPerUnit = function() { 354 | var rectSize = (this.orientation === SCROLLBAR_ORIENTATION_HORIZONTAL) ? this.w : this.h 355 | return (rectSize - SCROLLBAR_BUTTON_SIZE - (SCROLLBAR_BUTTON_SIZE * 2)) / (this.max - this.min) 356 | } 357 | 358 | UIScrollBar.prototype._resetScroll = function() { 359 | this._isMoveScroll = false; 360 | this._state = SCROLLBAR_BUTTON_STATE.NONE; 361 | this.reDraw(); 362 | } 363 | 364 | exports.__VERSION__ = 1 365 | exports.UIScrollBar = UIScrollBar 366 | -------------------------------------------------------------------------------- /ui/widgets/text.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UI Text - simple draw text to the screen at a specific position 3 | * 4 | * @param {number} x 5 | * @param {number} y 6 | * @param {number} text 7 | */ 8 | function UIText(x, y, text) { 9 | UIWidget.call(this); 10 | this.x = x; 11 | this.y = y; 12 | this.text = text; 13 | this.forecolor = EGA.BLACK; 14 | this.backgroundColor = NO_COLOR; 15 | this.fontName = 'pc6x8' 16 | } 17 | 18 | UIText.prototype = Object.create(UIWidget.prototype); 19 | UIText.prototype.constructor = UIText; 20 | 21 | /** 22 | * Draw text 23 | */ 24 | UIText.prototype.draw = function() { 25 | var drawPos = this.getDrawPosition(this.x, this.y); 26 | var font = this.app.getFont(this.fontName); 27 | font.DrawStringLeft(drawPos.x, drawPos.y, this.text, this.forecolor, this.backgroundColor); 28 | } 29 | 30 | /** 31 | * Set text 32 | * 33 | * @param {string} text 34 | */ 35 | UIText.prototype.setText = function(text) { 36 | this.text = text; 37 | this.reDraw(); 38 | } 39 | 40 | /** 41 | * Set the font used to render the text 42 | * 43 | * @param {string} name 44 | */ 45 | UIText.prototype.setFontName = function(name) { 46 | this.fontName = name; 47 | this.reDraw(); 48 | } 49 | 50 | exports.__VERSION__ = 1; 51 | exports.UIText = UIText; -------------------------------------------------------------------------------- /ui/widgets/tinput.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UI text input widget. 3 | * 4 | * TODO: 5 | * [ ] add multiline support 6 | * [ ] better hanlding of scrolling of text 7 | * 8 | * 9 | * @param {string} value 10 | * @param {number} x 11 | * @param {number} y 12 | * @param {number} w 13 | * @param {number} h 14 | */ 15 | function UITextInput(value, x, y, w, h) { 16 | UIWidget.call(this); 17 | this.value = value; 18 | this.setPosition(x, y) 19 | this.setSize(w ? w : 200, h ? h : 20) 20 | this._canHaveFocus = true 21 | this.hasFocus = false 22 | this.textPaddingX = 5 23 | this._carrotPosition = 0; 24 | this._textOffsetRender = 0; 25 | this.fontName = 'pc8x8' 26 | this._textOffsetCalculated = false 27 | 28 | Debug(JSON.stringify(String)); 29 | } 30 | 31 | UITextInput.prototype = Object.create(UIWidget.prototype); 32 | UITextInput.prototype.constructor = UITextInput; 33 | 34 | /** 35 | * Draw widget 36 | */ 37 | UITextInput.prototype.draw = function () { 38 | var rect = this.getDrawRect(this.x, this.y, this.w, this.h) 39 | var font = this.app.getFont(this.fontName); 40 | var TextPosY = rect.y + 6 41 | 42 | if (!this._textOffsetCalculated) { 43 | this._recalcTextOffset(); 44 | } 45 | 46 | FilledBox(rect.x, rect.y, rect.w, rect.h, EGA.WHITE); 47 | Box(rect.x, rect.y, rect.w, rect.h, EGA.BLACK); 48 | 49 | var renderValue = this.value 50 | var maxCharsInput = (this.w - (this.textPaddingX * 2)) / 8; 51 | 52 | if (renderValue.length > maxCharsInput) { 53 | renderValue = renderValue.slice(this._textOffsetRender, this._textOffsetRender + maxCharsInput); 54 | } 55 | 56 | // render text 57 | font.DrawStringLeft(rect.x + this.textPaddingX, TextPosY, renderValue, EGA.BLACK, NO_COLOR); 58 | 59 | 60 | if (this.hasFocus) { 61 | var carrotColor = EGA.DARK_GREY 62 | 63 | // focus shadow 64 | Line(rect.x + 1, rect.h + 1, rect.w + 1, rect.h + 1, EGA.DARK_GREY) 65 | Line(rect.w + 1, rect.y + 1, rect.w + 1, rect.h, EGA.DARK_GREY) 66 | 67 | // set carrot position 68 | var carrotPositionX = this.textPaddingX + this._carrotPosition * 8; 69 | if (this._textOffsetRender > 0) { 70 | carrotPositionX = carrotPositionX - (this._textOffsetRender * 8) 71 | } 72 | Line(rect.x + carrotPositionX, TextPosY - 1, rect.x + carrotPositionX, TextPosY + 8, carrotColor); 73 | 74 | Line(rect.x + carrotPositionX - 2, TextPosY - 3, rect.x + carrotPositionX - 1, TextPosY - 2, carrotColor); 75 | Line(rect.x + carrotPositionX + 2, TextPosY - 3, rect.x + carrotPositionX + 1, TextPosY - 2, carrotColor); 76 | 77 | Line(rect.x + carrotPositionX - 2, TextPosY + 10, rect.x + carrotPositionX - 1, TextPosY + 9, carrotColor); 78 | Line(rect.x + carrotPositionX + 2, TextPosY + 10, rect.x + carrotPositionX + 1, TextPosY + 9, carrotColor); 79 | } 80 | } 81 | 82 | /** 83 | * Handle mouse input. so if you click position of the text it will set the cursor to it. 84 | * 85 | * @param {number} mouseButtons 86 | * @param {number} mouseX 87 | * @param {number} mouseY 88 | * @param {bool} isLeftClicked 89 | */ 90 | UITextInput.prototype._handleMouseInput = function (mouseButtons, mouseX, mouseY, isLeftClicked) { 91 | if (mouseButtons === 1 && this.hasFocus === false) { 92 | if (this.parent && this.parent._clearFocusFromWidgets) { 93 | this.parent._clearFocusFromWidgets(); 94 | } 95 | 96 | this.hasFocus = true 97 | } 98 | 99 | if (isLeftClicked) { 100 | var rect = this.getRelativeRect(this.x, this.y, this.w, this.h) 101 | var relativeMouseX = mouseX - rect.x; 102 | 103 | if (this._textOffsetRender > 0) { 104 | this.setCarrotPosition(this._textOffsetRender + parseInt((relativeMouseX - this.textPaddingX) / 8)); 105 | } else { 106 | this.setCarrotPosition(parseInt((relativeMouseX - this.textPaddingX) / 8)); 107 | } 108 | } 109 | 110 | UIWidget.prototype._handleMouseInput.call(this, mouseButtons, mouseX, mouseY, isLeftClicked); 111 | } 112 | /** 113 | * Handles the keyboard input. 114 | * 115 | * @param {*} key 116 | * @param {*} keyCode 117 | * @param {*} char 118 | */ 119 | UITextInput.prototype._handleInput = function (key, keyCode, char) { 120 | switch (keyCode) { 121 | case KEY.Code.KEY_END: 122 | this.setCarrotPosition(this.value.length); 123 | break; 124 | case KEY.Code.KEY_HOME: 125 | this.setCarrotPosition(0); 126 | break; 127 | case KEY.Code.KEY_DOWN: 128 | case KEY.Code.KEY_LEFT: 129 | this.setCarrotPosition(this._carrotPosition - 1); 130 | break; 131 | case KEY.Code.KEY_UP: 132 | case KEY.Code.KEY_RIGHT: 133 | this.setCarrotPosition(this._carrotPosition + 1); 134 | break; 135 | case KEY.Code.KEY_BACKSPACE: 136 | this._removeCharsFromValue(this._carrotPosition - 1) 137 | break; 138 | case KEY.Code.KEY_DEL: 139 | this._removeCharsFromValue(this._carrotPosition) 140 | break; 141 | case KEY.Code.KEY_TAB: // Ignore TAB for now 142 | case KEY.Code.KEY_ENTER: // Ignore TAB for now 143 | break; 144 | default: 145 | if (key >= CharCode(" ")) { 146 | this._addCharAtCarrotPosition(char); 147 | } 148 | break; 149 | } 150 | 151 | UIWidget.prototype._handleInput.call(this, key, keyCode, char) 152 | } 153 | 154 | /** 155 | * Sets value 156 | * 157 | * @param {string} value 158 | */ 159 | UITextInput.prototype.setValue = function (value) { 160 | this.value = value; 161 | this._recalcTextOffset(); 162 | 163 | this.reDraw(); 164 | } 165 | 166 | /** 167 | * Set carrot position of widget 168 | * 169 | * @param {number} position 170 | */ 171 | UITextInput.prototype.setCarrotPosition = function (position) { 172 | var newPosition = position; 173 | 174 | if (newPosition > this.value.length) { 175 | newPosition = this.value.length 176 | } 177 | 178 | if (newPosition < 0) { 179 | newPosition = 0; 180 | } 181 | 182 | this._carrotPosition = newPosition; 183 | this._recalcTextOffset(); 184 | 185 | this.reDraw(); 186 | } 187 | /** 188 | * Add a character at the carrot position 189 | * 190 | * @param {string} char 191 | */ 192 | UITextInput.prototype._addCharAtCarrotPosition = function (char) { 193 | var oldValue = this.value; 194 | var newValue = oldValue.substring(0, this._carrotPosition) + char + oldValue.substring(this._carrotPosition); 195 | 196 | this.setValue(newValue); 197 | this.setCarrotPosition(this._carrotPosition + 1); 198 | } 199 | 200 | /** 201 | * Remove a char from position 202 | * 203 | * @param {number} position 204 | */ 205 | UITextInput.prototype._removeCharsFromValue = function (position) { 206 | if ((position > this.value.length) || (position <= -1)) { 207 | return; 208 | } 209 | var newValue = this.value.slice(0, position) + this.value.slice(position + 1); 210 | this.setValue(newValue); 211 | this.setCarrotPosition(position); 212 | } 213 | 214 | /** 215 | * Recalculate the offset of the text that is going to be redrawn. 216 | * 217 | * @returns 218 | */ 219 | UITextInput.prototype._recalcTextOffset = function () { 220 | this._textOffsetRender = 0 221 | if (!this.app) { 222 | return; 223 | } 224 | 225 | var font = this.app.getFont(this.fontName); 226 | var textWidth = font.StringWidth(this.value); 227 | var maxCharsInput = (this.w - (this.textPaddingX * 2)) / 8; 228 | var maxOffset = this.value.length - maxCharsInput 229 | if (textWidth > this.w - (this.textPaddingX * 2)) { 230 | this._textOffsetRender = this._carrotPosition - (maxCharsInput / 2); 231 | } 232 | 233 | if (this._textOffsetRender < 0) { 234 | this._textOffsetRender = 0; 235 | } 236 | 237 | if (this._textOffsetRender > maxOffset) { 238 | this._textOffsetRender = maxOffset; 239 | } 240 | 241 | this._textOffsetCalculated = true; 242 | } 243 | 244 | 245 | exports.__VERSION__ = 1; 246 | exports.UITextInput = UITextInput; -------------------------------------------------------------------------------- /ui/widgets/widget.js: -------------------------------------------------------------------------------- 1 | /** 2 | * UI widget component 3 | * 4 | * The base widget where all other widgets are based on. 5 | */ 6 | function UIWidget() { 7 | this.x = 0; 8 | this.y = 0; 9 | this.w = 0; 10 | this.h = 0; 11 | this.children = []; 12 | this.app = undefined 13 | this.parent = undefined; 14 | this.isActive = false; 15 | this.isVisible = true; 16 | this._canHaveFocus = false; 17 | this._calculatedRelativePosition = { x: this.x, y: this.y }; 18 | this._needsDraw = false; 19 | this.hasOwnDrawBuffer = false; 20 | } 21 | 22 | /** 23 | * Draw widget 24 | */ 25 | UIWidget.prototype.draw = function() { 26 | this._renderChildren(); 27 | } 28 | 29 | /** 30 | * Set size of widget 31 | * 32 | * @param {number} w 33 | * @param {number} h 34 | */ 35 | UIWidget.prototype.setSize = function(w, h) { 36 | this.w = w; 37 | this.h = h; 38 | this.reDraw(); 39 | } 40 | 41 | /** 42 | * Set position of widget 43 | * 44 | * @param {number} x 45 | * @param {number} y 46 | */ 47 | UIWidget.prototype.setPosition = function(x, y) { 48 | this.x = x; 49 | this.y = y; 50 | this.reDraw(); 51 | } 52 | 53 | /** 54 | * Set widget visible state 55 | * 56 | * @param {bool} isVisible 57 | */ 58 | UIWidget.prototype.setVisible = function(isVisible) { 59 | this.isVisible = isVisible 60 | } 61 | 62 | 63 | /** 64 | * Add child to widget 65 | * 66 | * @param {UIWidget} child 67 | */ 68 | UIWidget.prototype.addChildren = function(child) { 69 | child.app = this.app; 70 | child.parent = this; 71 | this.children.push(child); 72 | this.reDraw(); 73 | } 74 | 75 | /** 76 | * Remove all children from the widget 77 | */ 78 | UIWidget.prototype.clearChildren = function() { 79 | this.children = []; 80 | this.reDraw(); 81 | } 82 | 83 | /** 84 | * Get relative position of widget that will be drawn to the screen. 85 | * 86 | * @param {number} x 87 | * @param {number} y 88 | * @returns {x: number, y: number} 89 | */ 90 | UIWidget.prototype.getRelativePosition = function(x, y) { 91 | if (this.parent === undefined) { 92 | return { x: x, y: y} 93 | } 94 | 95 | return this.parent.getRelativePosition(this.parent.x + x, this.parent.y + y) 96 | } 97 | 98 | /** 99 | * Get relative rect of widget that will be rendered. 100 | * @param {number} x 101 | * @param {number} y 102 | * @param {number} w 103 | * @param {number} h 104 | * @returns {x: number, y: number, w: number, h: number} 105 | */ 106 | UIWidget.prototype.getRelativeRect = function(x, y, w, h) { 107 | var relativePos = this.getRelativePosition(x, y); 108 | 109 | return { 110 | x: relativePos.x, 111 | y: relativePos.y, 112 | w: relativePos.x + w, 113 | h: relativePos.y + h, 114 | } 115 | } 116 | 117 | /** 118 | * Get drawing position of the widget 119 | * 120 | * @param {number} x 121 | * @param {number} y 122 | * @returns {x: number, y: number} 123 | */ 124 | UIWidget.prototype.getDrawPosition = function(x, y) { 125 | if ((this.parent === undefined) || (this.parent.hasOwnDrawBuffer)) { 126 | return { x: x, y: y} 127 | } 128 | 129 | return this.parent.getDrawPosition(this.parent.x + x, this.parent.y + y) 130 | } 131 | 132 | /** 133 | * Get drawing rect of the widget. 134 | * 135 | * @param {number} x 136 | * @param {number} y 137 | * @param {number} w 138 | * @param {number} h 139 | * @returns {x: number, y: number, w: number, h: number} 140 | */ 141 | UIWidget.prototype.getDrawRect = function(x, y, w, h) { 142 | var drawPos = this.getDrawPosition(x, y); 143 | 144 | return { 145 | x: drawPos.x, 146 | y: drawPos.y, 147 | w: drawPos.x + w, 148 | h: drawPos.y + h, 149 | } 150 | } 151 | 152 | 153 | /** 154 | * Call draw function of all children. 155 | */ 156 | UIWidget.prototype._renderChildren = function() { 157 | this.children.forEach(function (child) { 158 | if (child.isVisible) { 159 | child.draw(); 160 | child._needsDraw = false; 161 | 162 | } 163 | }.bind(this)) 164 | } 165 | 166 | /** 167 | * Hanlde key input of widget 168 | * 169 | * @param {number} key 170 | * @param {number} keyCode 171 | * @param {string} char 172 | */ 173 | UIWidget.prototype._handleInput = function(key, keyCode, char) { 174 | this.onKeyPress(key, keyCode, char); 175 | } 176 | 177 | /** 178 | * Handle mouse input of widget 179 | * It will send mouse input to it children if mouse is hit. 180 | * 181 | * @param {number} buttons 182 | * @param {number} x 183 | * @param {number} y 184 | * @param {bool} isLeftClicked 185 | */ 186 | UIWidget.prototype._handleMouseInput = function(buttons, x, y, isLeftClicked) { 187 | 188 | var childHasClicked = false 189 | if (this.children && this.children.length > 0) { 190 | this.children.forEach(function (child) { 191 | if (child.rectTest(x, y) === true) { 192 | child._handleMouseInput(buttons, x, y, isLeftClicked); 193 | childHasClicked = true 194 | } 195 | }); 196 | } 197 | 198 | // no child click then click the widget it self. 199 | if (childHasClicked === false) { 200 | if (isLeftClicked && this._handleMouseClick) { 201 | this._handleMouseClick(x, y); 202 | } 203 | } 204 | } 205 | 206 | /** 207 | * Handle loop of widget and send throught is children. 208 | * 209 | * @param {number} tick 210 | */ 211 | UIWidget.prototype._handleLoop = function(tick) { 212 | if (this.onLoop) { 213 | this.onLoop(tick) 214 | } 215 | 216 | this.children.forEach(function (child) { 217 | if (child._handleLoop) { 218 | child._handleLoop(tick); 219 | } 220 | }); 221 | } 222 | 223 | /** 224 | * Handle mouse click of widget 225 | * 226 | * @param {number} x 227 | * @param {number} y 228 | */ 229 | UIWidget.prototype._handleMouseClick = function (x, y) { 230 | if (this.onClick) { 231 | this.onClick(x, y) 232 | } 233 | } 234 | 235 | /** 236 | * Check if a position is within the Widget position 237 | * 238 | * @param {number} x 239 | * @param {number} y 240 | * @returns 241 | */ 242 | UIWidget.prototype.rectTest = function (x, y) { 243 | var relativePos = this.getRelativeRect(this.x, this.y, this.w, this.h); 244 | return Utils.hitTest(x, y, relativePos.x, relativePos.y, relativePos.w, relativePos.h) 245 | } 246 | 247 | // events 248 | UIWidget.prototype.onKeyPress = function(key, keyCode, char) { 249 | 250 | } 251 | 252 | UIWidget.prototype.onClick = function (x, y) { 253 | 254 | } 255 | 256 | UIWidget.prototype.onFocus = function() { 257 | this.reDraw(); 258 | } 259 | 260 | UIWidget.prototype.onBlur = function() { 261 | this.reDraw(); 262 | } 263 | 264 | UIWidget.prototype.onLoop = function(tick) { 265 | } 266 | 267 | /** 268 | * Set UIAPP to the widget and it's children. 269 | */ 270 | UIWidget.prototype.setApp = function(app) { 271 | this.app = app; 272 | this.children.forEach(function (child) { 273 | child.app = app; 274 | if (child.setApp) { 275 | child.setApp(app); 276 | } 277 | }) 278 | } 279 | 280 | /** 281 | * Mark to widget to be redraw for the next time. 282 | */ 283 | UIWidget.prototype.reDraw = function() { 284 | this._needsDraw = true; 285 | 286 | if (this.parent !== undefined) { 287 | this.parent.reDraw(); 288 | } 289 | 290 | if (this.app) { 291 | this.app.reRender(); 292 | } 293 | } 294 | 295 | exports.__VERSION__ = 1; 296 | exports.UIWidget = UIWidget; -------------------------------------------------------------------------------- /ui/widgets/window.js: -------------------------------------------------------------------------------- 1 | var KEY_CLOSE_WINDOW = 5911 // ctrl + w 2 | var KEY_NEXT_WIDGET = 16393 // tab 3 | var KEY_PREV_WIDGET = 16511 // ctrl + tab 4 | var KEY_CLICK_WIDGET = 17165 // enter 5 | var KEY_CLICK_WIDGET_ALT = 23309 // enter keypad 6 | 7 | /** 8 | * UI Window object 9 | * 10 | * @param {string} id 11 | * @param {string} title 12 | */ 13 | function UIWindow(id, title) { 14 | UIWidget.call(this); 15 | 16 | this.id = id 17 | 18 | this.title = title 19 | this.menu = []; 20 | this.isClosable = true; 21 | this.isModal = false; 22 | this.showTitlebar = true; 23 | this.destroyOnClose = false; 24 | this.onlyWhenInFocus = true; 25 | 26 | // private props 27 | this.hasOwnDrawBuffer = true; 28 | this._isCloseDown = false; 29 | this._titlebarFont = 'pc8x8' 30 | this._menuBarFont = 'pc8x8' 31 | this._windowBuffer = undefined; 32 | 33 | this.setSize(SizeX() - (24 * 2), this.h = SizeY() - (24 * 2) - 20) 34 | this.setPosition(24, 44); 35 | } 36 | 37 | UIWindow.prototype = Object.create(UIWidget.prototype); 38 | UIWindow.prototype.constructor = UIWindow; 39 | 40 | /** 41 | * called when the window is added to the UIApp object 42 | */ 43 | UIWindow.prototype.init = function() { 44 | } 45 | 46 | /** 47 | * Draw the window to the screen 48 | */ 49 | UIWindow.prototype.draw = function() { 50 | if (!this._windowBuffer) { 51 | this._windowBuffer = new Bitmap(this.w + 1, this.h + 1, EGA.WHITE); 52 | } 53 | 54 | // set render target for the widgets. 55 | if (this._needsDraw === true) { 56 | 57 | SetRenderBitmap(this._windowBuffer); 58 | 59 | FilledBox(0, 0, this.w, this.h, EGA.WHITE); 60 | Box(0, 0, this.w, this.h, EGA.BLACK); 61 | 62 | // draw children 63 | this._renderChildren(); 64 | 65 | // draw titlebar 66 | if (this.showTitlebar) { 67 | this.drawTitlebar(); 68 | } 69 | 70 | SetRenderBitmap(null); 71 | 72 | // reset redraw 73 | this._needsDraw = false; 74 | } 75 | 76 | // draw buffer to screen 77 | if ((this.x <= 0) || (this.y <= 0)) { 78 | var offsetX = (this.x <= 0) ? Math.abs(this.x) : 0 79 | var offsetY = (this.y <= 0) ? Math.abs(this.y) : 0 80 | this._windowBuffer.DrawAdvanced(offsetX, offsetY, (this.w + 1) - offsetX, (this.h + 1) - offsetY, (this.x + offsetX), (this.y + offsetY), (this.w + 1) - offsetX, (this.h + 1) - offsetY) 81 | } else { 82 | this._windowBuffer.Draw(this.x, this.y); 83 | } 84 | 85 | // draw shadow 86 | if (this.isActive) { 87 | // bottom 88 | Line(this.x + 2, this.y + this.h + 1, this.x + this.w + 1, this.y + this.h + 1, EGA.DARK_GREY) 89 | 90 | // right 91 | Line(this.x + this.w + 1, this.y + 2, this.x + this.w + 1, this.y + this.h, EGA.DARK_GREY) 92 | } 93 | 94 | 95 | if (this.isModal) { 96 | this.drawMenubarTitle(); 97 | } 98 | 99 | } 100 | 101 | /** 102 | * Draw titlebar 103 | */ 104 | UIWindow.prototype.drawTitlebar = function () { 105 | Box(0, 0, this.w, 20, EGA.BLACK); 106 | 107 | // close button 108 | if (this.isClosable) { 109 | Box(4, 4, 16, 16, EGA.BLACK); 110 | if (this._isCloseDown) { 111 | Line(7, 7, 13, 13, EGA.BLACK) 112 | Line(13, 7, 7, 13, EGA.BLACK) 113 | } 114 | } 115 | 116 | // title 117 | var font = this.app.getFont(this._titlebarFont); 118 | var titleWidth = font.StringWidth(this.title); 119 | font.DrawStringLeft(24, 6, this.title, EGA.BLACK, NO_COLOR) 120 | 121 | // var lines = [6, 10, 14] 122 | var lines = [6, 8, 10, 12, 14] 123 | if (this.isActive) { 124 | lines.forEach(function (lineY) { 125 | Line(32 + titleWidth, lineY, this.w - 6, lineY, EGA.BLACK) 126 | }.bind(this)); 127 | } 128 | } 129 | 130 | /** 131 | * Draw titlebar to position of the menu bar 132 | */ 133 | UIWindow.prototype.drawMenubarTitle = function() { 134 | var font = this.app.getFont(this._menuBarFont); 135 | FilledBox(0, 0, SizeX(), 20, EGA.WHITE); 136 | Line(0, 20, SizeX(), 20, EGA.BLACK); 137 | font.DrawStringCenter((SizeX() / 2), 6, this.title, EGA.BLACK, NO_COLOR) 138 | } 139 | 140 | /** 141 | * Check if position is in rect of the titlebar 142 | * It excludes the close button if it not clossable. 143 | * 144 | * @param {number} x 145 | * @param {number} y 146 | * @returns {bool} 147 | */ 148 | UIWindow.prototype.titlebarHitTest = function(x, y) { 149 | var titlebarRect = Utils.createRectObj(this.x, this.y, this.w, 20) 150 | // check if close is not selected. 151 | if (this.isClosable && Utils.hitTest(x, y, this.x + 4, this.y + 4, this.x + 16, this.y + 16)) { 152 | return false; 153 | } 154 | 155 | return Utils.hitTestRect(x, y, titlebarRect) 156 | } 157 | 158 | 159 | /** 160 | * Handles the keyboard input of the window. 161 | * 162 | * @param {number} key 163 | * @param {number} keyCode 164 | * @param {string} char 165 | */ 166 | UIWindow.prototype._handleInput = function(key, keyCode, char) { 167 | switch (key) { 168 | case KEY_CLOSE_WINDOW: 169 | if (this.isClosable) { 170 | this.close(); 171 | } 172 | break; 173 | case KEY_NEXT_WIDGET: 174 | this._setNextChildFocus(1); 175 | break; 176 | 177 | case KEY_PREV_WIDGET: 178 | this._setNextChildFocus(-1); 179 | break; 180 | case KEY_CLICK_WIDGET: 181 | case KEY_CLICK_WIDGET_ALT: 182 | this.children.forEach(function (child) { 183 | if (child.hasFocus) { 184 | child.onClick(); 185 | } 186 | }); 187 | break; 188 | } 189 | var stopPropagation = false 190 | this.children.forEach(function (child) { 191 | if (child.hasFocus) { 192 | stopPropagation = child._handleInput(key, keyCode, char) 193 | } 194 | }) 195 | 196 | if (stopPropagation !== true) { 197 | this.onKeyPress(key, keyCode); 198 | } 199 | } 200 | 201 | /** 202 | * Handles mouse input of the window. 203 | * 204 | * @param {number} buttons 205 | * @param {number} x 206 | * @param {number} y 207 | * @param {bool} isLeftClicked 208 | */ 209 | UIWindow.prototype._handleMouseInput = function(buttons, x, y, isLeftClicked) { 210 | if (this.isClosable) { 211 | var lastCloseDown = this._isCloseDown 212 | this._isCloseDown = false 213 | 214 | if (Utils.hitTest(x, y, this.x + 4, this.y + 4, this.x + 16, this.y + 16)) { 215 | if (buttons === 1) { 216 | this._isCloseDown = true; 217 | } 218 | if (isLeftClicked) { 219 | this._isCloseDown = false; 220 | this.close(); 221 | } 222 | } 223 | 224 | if (lastCloseDown !== this._isCloseDown) { 225 | this.reDraw(); 226 | } 227 | } 228 | 229 | UIWidget.prototype._handleMouseInput.call(this, buttons, x, y, isLeftClicked); 230 | } 231 | 232 | /** 233 | * Set the next available child in the window focused 234 | * 235 | * @param {number} direction 236 | */ 237 | UIWindow.prototype._setNextChildFocus = function(direction) { 238 | // get current focused element 239 | var currentFocus = -1; 240 | this.children.forEach(function (item, index) { 241 | if (item.hasFocus === true) { 242 | currentFocus = index; 243 | } 244 | }); 245 | 246 | currentFocus = this._getNextFocusableChildIndex(currentFocus, direction); 247 | 248 | // set focus on new element 249 | this.children.forEach(function (item, index) { 250 | if (index === currentFocus) { 251 | item.hasFocus = true 252 | } else { 253 | item.hasFocus = false 254 | } 255 | }); 256 | 257 | this.reDraw(); 258 | } 259 | 260 | /** 261 | * Clears focus of all widgets 262 | */ 263 | UIWindow.prototype._clearFocusFromWidgets = function () { 264 | this.children.forEach(function (item) { 265 | item.hasFocus = false 266 | }); 267 | 268 | this.reDraw(); 269 | } 270 | 271 | /** 272 | * Get the next index of the child that can has focus 273 | * 274 | * @param {number} startIndex 275 | * @param {number} direction -1 is up, 1 is down 276 | * @returns 277 | */ 278 | UIWindow.prototype._getNextFocusableChildIndex = function (startIndex, direction) { 279 | var lookUpIndex = startIndex + direction 280 | 281 | if (lookUpIndex > this.children.length - 1) { 282 | lookUpIndex = 0 283 | } 284 | if (lookUpIndex < 0) { 285 | lookUpIndex = this.children.length - 1 286 | } 287 | 288 | if (this.children[lookUpIndex] && this.children[lookUpIndex]._canHaveFocus == true) { 289 | return lookUpIndex; 290 | } 291 | 292 | return this._getNextFocusableChildIndex(lookUpIndex, direction); 293 | } 294 | 295 | /** 296 | * handle the closing of the window 297 | * 298 | * @returns 299 | */ 300 | UIWindow.prototype.close = function() { 301 | var contClose = this.onClose(); 302 | 303 | if (contClose === false) { 304 | return; 305 | } 306 | 307 | if (this.destroyOnClose) { 308 | this.app.removeWindowById(this.id); 309 | } else { 310 | this.app.hideWindow(this.id); 311 | } 312 | } 313 | 314 | /** 315 | * Handle showing the window 316 | */ 317 | UIWindow.prototype.show = function() { 318 | if (this.onShow) { 319 | this.onShow(); 320 | } 321 | } 322 | 323 | /** 324 | * onClose event 325 | */ 326 | UIWindow.prototype.onClose = function() { 327 | 328 | } 329 | 330 | /** 331 | * onshow Event 332 | */ 333 | UIWindow.prototype.onShow = function() { 334 | 335 | } 336 | 337 | /** 338 | * Handle window loop[ 339 | * ] 340 | * @param {number} tick 341 | * @returns 342 | */ 343 | UIWindow.prototype._handleLoop = function(tick) { 344 | if (!this.onlyWhenInFocus) { 345 | return; 346 | } 347 | 348 | UIWidget.prototype._handleLoop.call(this, tick) 349 | } 350 | 351 | /** 352 | * Get position for drawing widgets inside the window 353 | * 354 | * @param {number} x 355 | * @param {number} y 356 | * @returns 357 | */ 358 | UIWindow.prototype.getDrawPosition = function(x, y) { 359 | return { x: x, y: y } 360 | } 361 | 362 | /** 363 | * Set size of window 364 | * 365 | * @param {number} w 366 | * @param {number} h 367 | */ 368 | UIWindow.prototype.setSize = function(w, h) { 369 | this.w = w; 370 | this.h = h; 371 | this._windowBuffer = undefined; // clear window buffer from 372 | this.reDraw(); 373 | } 374 | 375 | /** 376 | * Set the window visible 377 | * 378 | * @param {*} isVisible 379 | */ 380 | UIWindow.prototype.setVisible = function(isVisible) { 381 | this.isVisible = isVisible; 382 | 383 | // clear out window buffer if visible is set to false (Frees memory) 384 | if (isVisible === false) { 385 | this._windowBuffer = undefined; 386 | } else { 387 | this.reDraw(); 388 | } 389 | } 390 | 391 | /** 392 | * Set position of window 393 | * 394 | * @param {number} x 395 | * @param {number} y 396 | */ 397 | UIWindow.prototype.setPosition = function(x, y) { 398 | this.x = x === -1 ? (SizeX() / 2) - (this.w / 2) : x; 399 | this.y = y === -1 ? (SizeY() / 2) - (this.h / 2) : y; 400 | 401 | if (this.app) { 402 | this.app.reRender(); 403 | } 404 | } 405 | 406 | exports.__VERSION__ = 1; 407 | exports.UIWindow = UIWindow; 408 | -------------------------------------------------------------------------------- /ui/window/about.js: -------------------------------------------------------------------------------- 1 | /** 2 | * About program dialog 3 | */ 4 | function AboutWindow() { 5 | UIWindow.call(this, 'aboutWindow', 'About program') 6 | this.setSize(300, 148); 7 | this.setPosition(-1, 96); 8 | this.destroyOnClose = true; 9 | 10 | this.showHumanReadableMemory = true 11 | } 12 | 13 | AboutWindow.prototype = Object.create(UIWindow.prototype); 14 | AboutWindow.constructor = AboutWindow; 15 | 16 | /** 17 | * Init window 18 | */ 19 | AboutWindow.prototype.init = function() { 20 | var appInfo = this.app.applicationInfo 21 | 22 | this.title = 'About ' + appInfo.name 23 | var appTitle = new UIText(24, 30, appInfo.name + ' ' + appInfo.version) 24 | appTitle.setFontName('pc8x16') 25 | this.addChildren(appTitle); 26 | this.addChildren(new UIText(24, 50, appInfo.copyright)); 27 | this.addChildren(new UIText(24, 60, appInfo.license)); 28 | 29 | this.memoryUsedLabel = new UIText(24, 80, 'Memory used:') 30 | this.memoryRemainingLabel = new UIText(24, 90, 'Memory used:') 31 | this.memoryAvailable = new UIText(24, 100, 'Memory total:') 32 | this.addChildren(this.memoryUsedLabel); 33 | this.addChildren(this.memoryRemainingLabel); 34 | this.addChildren(this.memoryAvailable); 35 | 36 | var button = new UIButton((this.w / 2) - (70 /2), this.h - 32, 'Close'); 37 | button.setSize(75, 24); 38 | button.hasFocus = true; 39 | button.onClick = function () { 40 | this.close(); 41 | }.bind(this) 42 | this.addChildren(button) 43 | } 44 | 45 | /** 46 | * On focus 47 | */ 48 | AboutWindow.prototype.onFocus = function() { 49 | this.updateMemoryInfo(); 50 | } 51 | 52 | /** 53 | * Keyboard handling 54 | * 55 | * @param {number} key 56 | * @param {number} keyCode 57 | * @param {string} char 58 | */ 59 | AboutWindow.prototype.onKeyPress = function(key, keyCode, char) { 60 | if (keyCode === KEY.Code.KEY_M) { 61 | this.showHumanReadableMemory = !this.showHumanReadableMemory; 62 | this.updateMemoryInfo(); 63 | } 64 | } 65 | /** 66 | * Update memory information 67 | */ 68 | AboutWindow.prototype.updateMemoryInfo = function() { 69 | // update memory information 70 | var memoryInfo = MemoryInfo() 71 | 72 | if (this.showHumanReadableMemory) { 73 | this.memoryUsedLabel.setText('Memory used: ' + Utils.humanFileSize(memoryInfo.total - memoryInfo.remaining)); 74 | this.memoryRemainingLabel.setText('Memory remaining: ' + Utils.humanFileSize(memoryInfo.remaining)); 75 | this.memoryAvailable.setText('Memory total: ' + Utils.humanFileSize(memoryInfo.total)); 76 | } else { 77 | this.memoryUsedLabel.setText('Memory used: ' + (memoryInfo.total - memoryInfo.remaining)); 78 | this.memoryRemainingLabel.setText('Memory remaining: ' + memoryInfo.remaining); 79 | this.memoryAvailable.setText('Memory total: ' + memoryInfo.total); 80 | } 81 | } 82 | 83 | exports.__VERSION__ = 1 84 | exports.AboutWindow = AboutWindow 85 | -------------------------------------------------------------------------------- /ui/window/debug.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Debug window 3 | * For now you van set values to keep on screen 4 | */ 5 | function DebugWindow() { 6 | UIWindow.call(this, 'debugWindow', 'Debug') 7 | this.setSize(200, 200); 8 | this.setPosition(SizeX() - 210, SizeY() - 210) 9 | 10 | this.watchValues = []; 11 | } 12 | 13 | DebugWindow.prototype = Object.create(UIWindow.prototype); 14 | DebugWindow.constructor = DebugWindow; 15 | 16 | /** 17 | * Init 18 | */ 19 | DebugWindow.prototype.init = function() { 20 | this.watchValueHolder = new UIGroup('Value watcher', 10, 30, this.w - 20, this.h - 40); 21 | this.addChildren(this.watchValueHolder); 22 | this._updateWatchValueUI(); 23 | } 24 | 25 | /** 26 | * Update widgets of values that is being watched. 27 | */ 28 | DebugWindow.prototype._updateWatchValueUI = function() { 29 | this.watchValueHolder.clearChildren(); 30 | var offsetY = 14 31 | this.watchValues.forEach(function (item) { 32 | this.watchValueHolder.addChildren(new UIText(4, offsetY, item.key + ' : ' + item.value)); 33 | offsetY = offsetY + 10 34 | }.bind(this)); 35 | } 36 | 37 | /** 38 | * Add value to watch 39 | * 40 | * @param {string} key 41 | * @param {string|number} value 42 | */ 43 | DebugWindow.prototype.addValueWatch = function(key, value) { 44 | var item = { 45 | key: key, 46 | value: value 47 | } 48 | 49 | var keyIndex = -1 50 | this.watchValues.forEach(function(item, index) { 51 | if (item.key === key) { 52 | keyIndex = index 53 | } 54 | }); 55 | 56 | if (keyIndex >= 0) { 57 | this.watchValues[keyIndex] = item; 58 | } else { 59 | this.watchValues.push(item); 60 | } 61 | 62 | this._updateWatchValueUI() 63 | } 64 | 65 | exports.__VERSION__ = 1 66 | exports.DebugWindow = DebugWindow; 67 | --------------------------------------------------------------------------------