├── app-consts.js ├── .babelrc ├── .babelrc.dev ├── hat.ogg ├── bass.ogg ├── kick.ogg ├── piano.ogg ├── ride.ogg ├── snare.ogg ├── thumb.png ├── impulse.mp3 ├── screen.png ├── Yodel_Sound_Effect.mp3 ├── simpleui-v04-trix.png ├── src ├── images │ └── mana16.png ├── simpleui_consts.js ├── index.js ├── simpleui_framepool.js ├── throttle.js ├── bmfont.js ├── simpleui_ex_slider2d.js ├── simpleui_ex_scroll.js ├── simpleui_ex_gradient.js ├── simpleui_ex_linestar.js ├── simpleui_ex_panel.js ├── simpleui_ex_gridfont.js ├── debounce.js ├── simpleui_driver_pixi_webgl.js ├── simpleui_app_plasma.js ├── simpleui_driver_html5_canvas.js ├── simpleui_drawing.js └── simpleui_app_demo.js ├── _android-debug.txt ├── index.html ├── .babelrc.prod ├── .eslintrc.js ├── mongoose.conf ├── m_v8.js ├── polyfill_raf.js ├── readme.md ├── chromium-debug.bat ├── package.json ├── _notes.txt ├── webpack.config.js ├── index-canvas.html ├── _setup_notes.txt └── index-webgl-pixi.html /app-consts.js: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | } -------------------------------------------------------------------------------- /.babelrc.dev: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | } -------------------------------------------------------------------------------- /hat.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/hat.ogg -------------------------------------------------------------------------------- /bass.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/bass.ogg -------------------------------------------------------------------------------- /kick.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/kick.ogg -------------------------------------------------------------------------------- /piano.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/piano.ogg -------------------------------------------------------------------------------- /ride.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/ride.ogg -------------------------------------------------------------------------------- /snare.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/snare.ogg -------------------------------------------------------------------------------- /thumb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/thumb.png -------------------------------------------------------------------------------- /impulse.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/impulse.mp3 -------------------------------------------------------------------------------- /screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/screen.png -------------------------------------------------------------------------------- /Yodel_Sound_Effect.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/Yodel_Sound_Effect.mp3 -------------------------------------------------------------------------------- /simpleui-v04-trix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/simpleui-v04-trix.png -------------------------------------------------------------------------------- /src/images/mana16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/remzmike/javascript-simpleui/HEAD/src/images/mana16.png -------------------------------------------------------------------------------- /_android-debug.txt: -------------------------------------------------------------------------------- 1 | C:\android-platform-tools>adb kill-server 2 | 3 | C:\android-platform-tools>adb start-server 4 | * daemon not running; starting now at tcp:5037 5 | * daemon started successfully 6 | 7 | C:\android-platform-tools>adb devices 8 | List of devices attached 9 | 92011d5628032433 device 10 | 11 | --- 12 | 13 | Now you should be able to connect with Desktop Chrome's dev tools. -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 13 | 14 | 15 |

index-canvas.html

16 |

index-webgl-pixi.html

17 | 18 | -------------------------------------------------------------------------------- /.babelrc.prod: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["@babel/preset-env"], 3 | "plugins": [ 4 | "closure-elimination", 5 | [ 6 | "groundskeeper-willie", 7 | { 8 | "removeConsole": true, 9 | "removeDebugger": true, 10 | "removePragma": true 11 | } 12 | ], 13 | "module:faster.js", 14 | "babel-plugin-loop-optimizer", 15 | "minify-constant-folding", 16 | "tailcall-optimization", 17 | [ 18 | "transform-named-imports", 19 | { 20 | "webpackConfig": "./webpack.config.js", 21 | "webpackConfigIndex": 0 22 | } 23 | ] 24 | ] 25 | 26 | } -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "browser": true 4 | }, 5 | "extends": "eslint:recommended", 6 | "parserOptions": { 7 | "ecmaVersion": 6, 8 | "sourceType": "module" 9 | }, 10 | "rules": { 11 | "comma-dangle": "off", // dumb 12 | "no-console": "off", // dumb 13 | "quotes": "off", // dumb 14 | "indent": [ 15 | "error", 16 | 4 17 | ], 18 | "linebreak-style": [ 19 | "error", 20 | "windows" 21 | ], 22 | "semi": [ 23 | "error", 24 | "always" 25 | ] 26 | } 27 | }; -------------------------------------------------------------------------------- /src/simpleui_consts.js: -------------------------------------------------------------------------------- 1 | // todo: later: const namespace import or something i guess ( "namespaced constants" probably eventually too ) 2 | 3 | // drawstate 4 | export const _Hovered = 0 | 0; 5 | export const _Held = 1 | 0; 6 | 7 | // layout modes 8 | export const _none = 0 | 0; 9 | export const _vertical = 1 | 0; 10 | export const _horizontal = 2 | 0; 11 | // later: 'grid', 'columns', 'rows' 12 | 13 | // colors 14 | export const _r = 0 | 0; 15 | export const _g = 1 | 0; 16 | export const _b = 2 | 0; 17 | export const _a = 3 | 0; 18 | 19 | // mouse buttons 20 | export const _left = 0 | 0; 21 | export const _middle = 1 | 0; 22 | export const _right = 2 | 0; 23 | 24 | // gridfont & gridfont_letter 25 | export const _complete = 0 | 0; 26 | export const _reset_complete = 1 | 0; 27 | export const _segment = 2 | 0; 28 | export const _partial = 3 | 0; -------------------------------------------------------------------------------- /mongoose.conf: -------------------------------------------------------------------------------- 1 | # Mongoose web server configuration file. 2 | # For detailed description of every option, visit 3 | # https://github.com/cesanta/mongoose 4 | # Lines starting with '#' and empty lines are ignored. 5 | # To make a change, remove leading '#', modify option's value, 6 | # save this file and then restart Mongoose. 7 | 8 | # ip_acl 9 | # access_log_file 10 | # auth_domain mydomain.com 11 | # cgi_interpreter 12 | # cgi_pattern **.cgi$|**.pl$|**.php$ 13 | # debug_level 0 14 | document_root H:\javascript-simpleui 15 | # enable_dir_listing yes 16 | # error_log_file 17 | # extra_headers 18 | extra_mime_types .mjs=application/javascript 19 | # global_auth_file 20 | # hide_files_patterns 21 | # hexdump_file 22 | # index_files index.html,index.htm,index.shtml,index.cgi,index.php 23 | # listening_port 8080 24 | # ssi_pattern **.shtml$|**.shtm$ 25 | # ssl_certificate 26 | # ssl_key 27 | # ssl_ca_certificate 28 | # start_browser yes 29 | # url_rewrites 30 | -------------------------------------------------------------------------------- /m_v8.js: -------------------------------------------------------------------------------- 1 | // webpack cannot pack this because it contains natives syntax 2 | // the webpack parser (acorn) does not support natives syntax (eg. %IsSmi) 3 | // 4 | // i think it was the natives calls crashing the tab, not browser es modules, ainf 5 | 6 | const m_v8_enabled = false; 7 | 8 | const m_v8 = { 9 | IsSmi: (x) => { 10 | if (!m_v8_enabled) return true; 11 | 12 | if (x != null && x != undefined && typeof(x) == 'number') { // idk, maybe prevents crashes 13 | //return %IsSmi(x); 14 | } else { 15 | return false; 16 | } 17 | }, 18 | IsValidSmi: (x) => { 19 | if (!m_v8_enabled) return true; 20 | 21 | if (x != null && x != undefined && typeof(x) == 'number') { // idk, maybe prevents crashes 22 | //return %IsValidSmi(x); 23 | } else { 24 | return false; 25 | } 26 | }, 27 | // 28 | assert_smi: (x) => { 29 | if (!m_v8_enabled) return true; 30 | 31 | console.assert(m_v8.IsSmi(x)); 32 | console.assert(m_v8.IsValidSmi(x)); 33 | } 34 | }; 35 | 36 | -------------------------------------------------------------------------------- /polyfill_raf.js: -------------------------------------------------------------------------------- 1 | // polyfill for raf 2 | (function() { 3 | var lastTime = 0; 4 | var vendors = ['ms', 'moz', 'webkit', 'o']; 5 | for(var x = 0; x < vendors.length && !window.requestAnimationFrame; ++x) { 6 | window.requestAnimationFrame = window[vendors[x]+'RequestAnimationFrame']; 7 | window.cancelAnimationFrame = window[vendors[x]+'CancelAnimationFrame'] 8 | || window[vendors[x]+'CancelRequestAnimationFrame']; 9 | } 10 | 11 | if (!window.requestAnimationFrame) 12 | window.requestAnimationFrame = function(callback, element) { 13 | var currTime = performance.now(); // kk 14 | var timeToCall = Math.max(0, 16 - (currTime - lastTime)); 15 | var id = window.setTimeout(function() { callback(currTime + timeToCall); }, 16 | timeToCall); 17 | lastTime = currTime + timeToCall; 18 | return id; 19 | }; 20 | 21 | if (!window.cancelAnimationFrame) 22 | window.cancelAnimationFrame = function(id) { 23 | clearTimeout(id); 24 | }; 25 | }()); 26 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ([demo](https://remzmike.github.io/simpleui/)) 2 | 3 | ![screenshot](simpleui-v04-trix.png) 4 | 5 | * simpleui - v00 - 12/8/2016 10:19:01 AM - preview release 6 | * simpleui - v01 - 9/16/2018 1:03 PM - added webgl support 7 | * simpleui - v02 - 11/10/2018 4:14 PM - broad refactoring 8 | * simpleui - v03 - 11/17/2018 5:32 PM - broad refactoring 9 | 10 | ## About 11 | 12 | This is an "immediate-mode ui", which basically means ui components are functions. 13 | 14 | This is useful because it changes the way gui applications are written and extended. 15 | 16 | It is a work in progress, written in a straight-line style for easy experimentation. 17 | 18 | ## History 19 | 20 | I wrote this twice in C#, then ported to lua, then ported to this javascript. 21 | 22 | Where has this library been used? 23 | 24 | * XNA (C#) (2008) 25 | * Mono GTK [cairo] (C#) (2012) 26 | * leaguebot (lua) (2014) 27 | * love2d (lua) (2015) 28 | * html canvas (javascript) (2016) 29 | * html webgl [pixi] (javascript) (2018) 30 | 31 | ## Todo 32 | 33 | * refactor / semantic compression 34 | * nested stack auto id's 35 | * dom renderer (no canvas) 36 | -------------------------------------------------------------------------------- /chromium-debug.bat: -------------------------------------------------------------------------------- 1 | rem chrome_cmd.exe --no-sandbox --js-flags="--trace-opt --trace-deopt --allow-natives-syntax --redirect-code-traces" 2 | rem C:\Chromium\bin\chrome_cmd.exe --js-flags="--trace-deopt --allow-natives-syntax --redirect-code-traces" 3 | rem the above crashes often for some reason (redirect code traces) 4 | rem the below does not 5 | rem C:\Chromium\bin\chrome_cmd.exe --js-flags="--trace-opt --trace-deopt --allow-natives-syntax" 6 | rem changing to gui to see if things show in debugview 7 | rem for some reason --no-sandbox helps debugview? 8 | rem C:\Chromium\bin\chrome_gui.exe --no-sandbox --js-flags="--trace-deopt --allow-natives-syntax --redirect-code-traces" 9 | 10 | rem C:\Chromium\bin\chrome_gui.exe --no-sandbox --js-flags="--code-comments --trace-deopt --allow-natives-syntax --redirect-code-traces" 11 | rem --remote-debugging-port=9222 12 | C:\Chromium\bin\chrome_gui.exe --no-sandbox --js-flags="--code-comments --trace-deopt --allow-natives-syntax --redirect-code-traces --remote-debugging-port=9222" 13 | 14 | rem C:\Chromium\bin\chrome_cmd.exe --js-flags="--trace-deopt --allow-natives-syntax" 15 | rem C:\Chromium\bin\chrome_cmd.exe --js-flags="--allow-natives-syntax" -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import * as m_simpleui from './simpleui.js'; 2 | import * as m_simpleui_drawing from './simpleui_drawing.js'; 3 | import * as m_simpleui_consts from './simpleui_consts.js'; 4 | import { do_gridfont } from './simpleui_ex_gridfont.js'; 5 | import { do_panel_begin, do_panel_end } from './simpleui_ex_panel.js'; 6 | import { do_scroll_begin, do_scroll_end, do_scroll_item_begin, do_scroll_item_end } from './simpleui_ex_scroll.js'; 7 | import { do_gradient_stroke_edit } from './simpleui_ex_gradient.js'; 8 | import { do_linestar_edit } from './simpleui_ex_linestar.js'; 9 | import { do_slider2d } from './simpleui_ex_slider2d.js'; 10 | import { do_app_demo, do_background_anim, do_sidepanel } from './simpleui_app_demo.js'; 11 | import { do_app_audio } from './simpleui_app_audio1.js'; 12 | import { do_app_audio2 } from './simpleui_app_audio2.js'; 13 | import { do_app_plasma } from './simpleui_app_plasma.js'; 14 | 15 | export { 16 | m_simpleui, 17 | m_simpleui_drawing, 18 | m_simpleui_consts, 19 | do_gridfont, 20 | do_panel_begin, do_panel_end, 21 | do_scroll_begin, do_scroll_end, do_scroll_item_begin, do_scroll_item_end, 22 | do_gradient_stroke_edit, 23 | do_linestar_edit, 24 | do_slider2d, 25 | do_app_demo, do_background_anim, do_sidepanel, 26 | do_app_audio, 27 | do_app_audio2, 28 | do_app_plasma 29 | }; -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rzmk-simpleui", 3 | "version": "0.4.0", 4 | "description": "([demo](https://remzmike.github.io/simpleui/))", 5 | "//main": "node_main.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "//build-babel": "babel src -d public", 9 | "build": "webpack --config webpack.config.js", 10 | "watch": "webpack --watch", 11 | "dev": "webpack-dev-server --open-page dev.html", 12 | "chromium": "chromium-debug.bat" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/remzmike/javascript-simpleui.git" 17 | }, 18 | "keywords": [], 19 | "author": "rzmk", 20 | "license": "ISC", 21 | "bugs": { 22 | "url": "https://github.com/remzmike/javascript-simpleui/issues" 23 | }, 24 | "homepage": "https://github.com/remzmike/javascript-simpleui#readme", 25 | "devDependencies": { 26 | "@babel/cli": "^7.1.2", 27 | "@babel/core": "^7.1.2", 28 | "@babel/preset-env": "^7.1.0", 29 | "babel-loader": "^8.0.4", 30 | "babel-plugin-closure-elimination": "^1.3.0", 31 | "babel-plugin-groundskeeper-willie": "^1.3.2", 32 | "babel-plugin-minify-constant-folding": "^0.5.0", 33 | "babel-plugin-tailcall-optimization": "^1.0.13", 34 | "babel-plugin-transform-named-imports": "^2.0.0", 35 | "babel-types": "^6.26.0", 36 | "faster.js": "^1.1.0", 37 | "url-loader": "^1.1.2", 38 | "webpack": "^4.22.0", 39 | "webpack-cli": "^3.1.2", 40 | "webpack-dev-server": "^3.1.9" 41 | }, 42 | "dependencies": { 43 | "babel-plugin-loop-optimizer": "^1.4.1", 44 | "eslint": "^5.11.1" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /_notes.txt: -------------------------------------------------------------------------------- 1 | // C:\Users\Eowilson\Downloads\mutrix 2 | // C:\Users\Eowilson\Downloads\love-0.8.0-win-x64 3 | // c:\mutrix.png 4 | 5 | [links] 6 | https://www.sitepoint.com/es6-babel-webpack/ 7 | https://github.com/babel/awesome-babel 8 | https://flaviocopes.com/es-modules/ 9 | https://hacks.mozilla.org/2018/03/es-modules-a-cartoon-deep-dive/ 10 | 11 | CODE SAMPLES of jit optimization scenarios 12 | https://developer.mozilla.org/en-US/docs/Mozilla/Projects/SpiderMonkey/JIT_Optimization_Strategies 13 | 14 | -- 15 | 16 | 17 | i think driver should handle batching 18 | so internal still just does normal box/text calls 19 | and driver decides what to do... batch or immediate 20 | 21 | if driver decides to batch (which im not doing yet) 22 | then i will need a way to tell the driver when to draw 23 | or... 24 | 25 | 26 | driver is really "driver" and 27 | it will do the LOOP... 28 | 29 | this might be interesting... 30 | because it makes more reusable? 31 | 32 | so then simpleui is legit a library that gets used 33 | by say a pixi app or a canvas app or an app written to be both and use simpleui 34 | and the app decides how to deal with "frame rate" (0.1 fps to 100+ fps) 35 | 36 | 37 | that tangent is crazy 38 | 39 | 40 | -- 41 | 42 | babel opt 43 | 44 | 1) 45 | https://github.com/codemix/babel-plugin-closure-elimination 46 | npm install --save-dev babel-plugin-closure-elimination 47 | { 48 | "plugins": ["closure-elimination"] 49 | } 50 | 51 | 52 | 2) 53 | https://github.com/betaorbust/babel-plugin-groundskeeper-willie 54 | npm install --save-dev babel-plugin-groundskeeper-willie 55 | 56 | 3) 57 | https://github.com/vzhou842/faster.js 58 | npm install --save-dev faster.js 59 | "plugins":"module:faster.js" 60 | 61 | 4) 62 | https://github.com/vihanb/babel-plugin-loop-optimizer 63 | npm install babel-plugin-loop-optimizer 64 | "plugins": ["babel-plugin-loop-optimizer"] -------------------------------------------------------------------------------- /src/simpleui_framepool.js: -------------------------------------------------------------------------------- 1 | // a pool that can clear itself every frame 2 | // does not need to support releasing mid-frame 3 | 4 | function FramePool(creator, max) { 5 | console.assert(max > 0); 6 | this.creator = creator; 7 | this.max = max; 8 | this.index = 0; // points to first unused item 9 | this.items = [creator()]; // jit hint 10 | for (let i = 1; i < max; i++) { 11 | this.items.push(creator()); 12 | } 13 | } 14 | 15 | FramePool.prototype.acquire = function () { 16 | // add 1k slots if full 17 | if (this.index >= this.items.length) { 18 | //console.info('[growing framepool]', this.max, this.max + 1000); 19 | this.max = this.max + 1000; 20 | for (let i = 0; i < 1000; i++) { 21 | this.items.push(this.creator()); 22 | } 23 | } 24 | this.index++; 25 | return this.items[this.index-1]; 26 | }; 27 | 28 | FramePool.prototype.release_all = function () { 29 | // releasing all == moving the index cursor 30 | this.index = 0; 31 | }; 32 | 33 | // usage 34 | /* 35 | if (true) { 36 | function Rectangle(x, y, w, h) { 37 | return { 38 | x: x, 39 | y: y, 40 | w: w, 41 | h: h, 42 | } 43 | } 44 | 45 | function RectangleDefault() { 46 | return Rectangle(0, 0, 200, 20); 47 | } 48 | 49 | function RectanglePooled(x, y, w, h) { 50 | const o = rectangle_pool.acquire(); 51 | o.x = x; 52 | o.y = y; 53 | o.w = w; 54 | o.h = h; 55 | return o; 56 | } 57 | 58 | const rectangle_pool = new FramePool(RectangleDefault, 2000); 59 | 60 | for (let i=0; i<5000; i++) { 61 | RectanglePooled(12, 13, i, i); 62 | } 63 | 64 | console.log(rectangle_pool.items[4999]); 65 | 66 | rectangle_pool.release_all(); 67 | }*/ 68 | 69 | export { 70 | FramePool 71 | }; -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | 2 | // . o ( js module decisions ) o . 3 | // 4 | // 1) legacy "put me in a global" modules 5 | // 6 | // generally undesirable 7 | // 8 | // 2) es modules (2018 november) 9 | // 10 | // es module support in chrome 70 often results in crashed tabs 11 | // the dev tools immediately detach and there is no error message 12 | // the syntax is also more limited and quirky than the same syntax with webpack 13 | // 14 | // 3) webpack modules 15 | // 16 | // cannot support --allow-natives-syntax due to acorn js parser not allowing %-prefixed functions (standard js parse) 17 | // but webpack modules are still what i should use if i don't want in-browser (read:buggy) modules 18 | // 19 | // best design is to probably just use a separate legacy (global namespace) module to put %IsSmi into like v8.IsSmi or whatever 20 | // 21 | // 4) other 22 | // 23 | // maybe rollup or something, but webpack is the king so I should try to make it work 24 | // 25 | // conclusion: use webpack and non-bundled global library for v8 native calls (m_v8.js) 26 | 27 | // . o ( should i use babel ) o . 28 | // 29 | // i do not need it for language features! (webpack gives me modules) 30 | // i DO need it for performance, IF THERE IS ANY 31 | // some people say babel makes it harder for the jit 32 | // other people say it makes it easier 33 | // https://codemix.com/blog/why-babel-matters/ 34 | // 35 | // i can probably switch it on/off from time to time to compare 36 | // 37 | // conclusion: currently unknown, hopefully some interesting babel plugins for me, but we'll see 38 | 39 | const path = require('path'); 40 | 41 | module.exports = { 42 | // mode default is "production" 43 | mode: 'development', 44 | entry: "./src/index.js", 45 | output: { 46 | path: path.resolve(__dirname, "dist"), 47 | filename: "simpleui-bundle.js", 48 | libraryTarget: 'window' 49 | }, 50 | module: { 51 | rules: [ 52 | { 53 | test: /\.js$/, 54 | exclude: /(node_modules)/, 55 | loader: "babel-loader", 56 | }, 57 | { 58 | test: /\.(png|jp(e*)g|svg)$/, 59 | use: [{ 60 | loader: 'url-loader', 61 | options: { 62 | limit: 8000, 63 | name: 'images/[hash]-[name].[ext]' 64 | } 65 | }] 66 | } 67 | ] 68 | } 69 | }; -------------------------------------------------------------------------------- /src/throttle.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | import debounce from './debounce.js' 3 | 4 | /** 5 | * Creates a throttled function that only invokes `func` at most once per 6 | * every `wait` milliseconds (or once per browser frame). The throttled function 7 | * comes with a `cancel` method to cancel delayed `func` invocations and a 8 | * `flush` method to immediately invoke them. Provide `options` to indicate 9 | * whether `func` should be invoked on the leading and/or trailing edge of the 10 | * `wait` timeout. The `func` is invoked with the last arguments provided to the 11 | * throttled function. Subsequent calls to the throttled function return the 12 | * result of the last `func` invocation. 13 | * 14 | * **Note:** If `leading` and `trailing` options are `true`, `func` is 15 | * invoked on the trailing edge of the timeout only if the throttled function 16 | * is invoked more than once during the `wait` timeout. 17 | * 18 | * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred 19 | * until the next tick, similar to `setTimeout` with a timeout of `0`. 20 | * 21 | * If `wait` is omitted in an environment with `requestAnimationFrame`, `func` 22 | * invocation will be deferred until the next frame is drawn (typically about 23 | * 16ms). 24 | * 25 | * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) 26 | * for details over the differences between `throttle` and `debounce`. 27 | * 28 | * @since 0.1.0 29 | * @category Function 30 | * @param {Function} func The function to throttle. 31 | * @param {number} [wait=0] 32 | * The number of milliseconds to throttle invocations to; if omitted, 33 | * `requestAnimationFrame` is used (if available). 34 | * @param {Object} [options={}] The options object. 35 | * @param {boolean} [options.leading=true] 36 | * Specify invoking on the leading edge of the timeout. 37 | * @param {boolean} [options.trailing=true] 38 | * Specify invoking on the trailing edge of the timeout. 39 | * @returns {Function} Returns the new throttled function. 40 | * @example 41 | * 42 | * // Avoid excessively updating the position while scrolling. 43 | * jQuery(window).on('scroll', throttle(updatePosition, 100)) 44 | * 45 | * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes. 46 | * const throttled = throttle(renewToken, 300000, { 'trailing': false }) 47 | * jQuery(element).on('click', throttled) 48 | * 49 | * // Cancel the trailing throttled invocation. 50 | * jQuery(window).on('popstate', throttled.cancel) 51 | */ 52 | function throttle(func, wait, options) { 53 | let leading = true 54 | let trailing = true 55 | 56 | if (typeof func != 'function') { 57 | throw new TypeError('Expected a function') 58 | } 59 | if (options) { 60 | leading = 'leading' in options ? !!options.leading : leading 61 | trailing = 'trailing' in options ? !!options.trailing : trailing 62 | } 63 | return debounce(func, wait, { 64 | 'leading': leading, 65 | 'maxWait': wait, 66 | 'trailing': trailing 67 | }) 68 | } 69 | 70 | export default throttle -------------------------------------------------------------------------------- /src/bmfont.js: -------------------------------------------------------------------------------- 1 | // from https://www.npmjs.com/package/load-bmfont 2 | 3 | // one export 4 | export function parseBMFontAscii(data) { 5 | if (!data) 6 | throw new Error('no data provided') 7 | data = data.toString().trim() 8 | 9 | var output = { 10 | pages: [], 11 | chars: [], 12 | kernings: [] 13 | } 14 | 15 | var lines = data.split(/\r\n?|\n/g) 16 | 17 | if (lines.length === 0) 18 | throw new Error('no data in BMFont file') 19 | 20 | for (var i = 0; i < lines.length; i++) { 21 | var lineData = splitLine(lines[i], i) 22 | if (!lineData) //skip empty lines 23 | continue 24 | 25 | if (lineData.key === 'page') { 26 | if (typeof lineData.data.id !== 'number') 27 | throw new Error('malformed file at line ' + i + ' -- needs page id=N') 28 | if (typeof lineData.data.file !== 'string') 29 | throw new Error('malformed file at line ' + i + ' -- needs page file="path"') 30 | output.pages[lineData.data.id] = lineData.data.file 31 | } else if (lineData.key === 'chars' || lineData.key === 'kernings') { 32 | //... do nothing for these two ... 33 | } else if (lineData.key === 'char') { 34 | output.chars.push(lineData.data) 35 | } else if (lineData.key === 'kerning') { 36 | output.kernings.push(lineData.data) 37 | } else { 38 | output[lineData.key] = lineData.data 39 | } 40 | } 41 | 42 | return output 43 | } 44 | 45 | function splitLine(line, idx) { 46 | line = line.replace(/\t+/g, ' ').trim() 47 | if (!line) 48 | return null 49 | 50 | var space = line.indexOf(' ') 51 | if (space === -1) 52 | throw new Error("no named row at line " + idx) 53 | 54 | var key = line.substring(0, space) 55 | 56 | line = line.substring(space + 1) 57 | //clear "letter" field as it is non-standard and 58 | //requires additional complexity to parse " / = symbols 59 | line = line.replace(/letter=[\'\"]\S+[\'\"]/gi, '') 60 | line = line.split("=") 61 | line = line.map(function(str) { 62 | return str.trim().match((/(".*?"|[^"\s]+)+(?=\s*|\s*$)/g)) 63 | }) 64 | 65 | var data = [] 66 | for (var i = 0; i < line.length; i++) { 67 | var dt = line[i] 68 | if (i === 0) { 69 | data.push({ 70 | key: dt[0], 71 | data: "" 72 | }) 73 | } else if (i === line.length - 1) { 74 | data[data.length - 1].data = parseData(dt[0]) 75 | } else { 76 | data[data.length - 1].data = parseData(dt[0]) 77 | data.push({ 78 | key: dt[1], 79 | data: "" 80 | }) 81 | } 82 | } 83 | 84 | var out = { 85 | key: key, 86 | data: {} 87 | } 88 | 89 | data.forEach(function(v) { 90 | out.data[v.key] = v.data; 91 | }) 92 | 93 | return out 94 | } 95 | 96 | function parseData(data) { 97 | if (!data || data.length === 0) 98 | return "" 99 | 100 | if (data.indexOf('"') === 0 || data.indexOf("'") === 0) 101 | return data.substring(1, data.length - 1) 102 | if (data.indexOf(',') !== -1) 103 | return parseIntList(data) 104 | return parseInt(data, 10) 105 | } 106 | 107 | function parseIntList(data) { 108 | return data.split(',').map(function(val) { 109 | return parseInt(val, 10) 110 | }) 111 | } -------------------------------------------------------------------------------- /src/simpleui_ex_slider2d.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | 5 | const _none = consts._none; 6 | //const _vertical = consts._vertical; 7 | //const _horizontal = consts._horizontal; 8 | 9 | const RectangleP = ui.RectangleP; 10 | const ColorP = ui.ColorP; 11 | 12 | function do_slider2d(uiid, local_rect, min, max, point_value) { 13 | const rect = ui.layout_translated(local_rect); 14 | 15 | const min_x = 0 | min; 16 | const min_y = 0 | min; 17 | const max_x = 0 | max; 18 | const max_y = 0 | max; 19 | const rect_w_half = 0 | rect.w / 2; 20 | const rect_h_half = 0 | rect.h / 2; 21 | const grab_size = 20; 22 | const grab_size_half = 0 | grab_size / 2; 23 | 24 | // this component deals with 2 different coordinate spaces 25 | // 1. value scale (min to max) : caller input/output 26 | const value_x_range = max_x - min_x; 27 | const value_y_range = max_y - min_y; 28 | const value_x = 0 | Math.min(max_x, Math.max(min_x, point_value.x)); 29 | const value_y = 0 | Math.min(max_y, Math.max(min_y, point_value.y)); 30 | // 2. local scale (0 to rect.w/h) : screen/draw/ui 31 | const local_x_range = rect.w; 32 | const local_y_range = rect.h; 33 | let local_x = 0 | ((value_x - min_x) / value_x_range) * local_x_range; 34 | let local_y = 0 | ((value_y - min_y) / value_y_range) * local_y_range; 35 | 36 | let changed = 0 | false; 37 | 38 | const layout = ui.layout_push(_none); 39 | { 40 | // background 41 | // this background block is probably a separate component. (active-rectangle) [explore functional (hoc vs wrap vs idk)] 42 | // ......................... 43 | const bg_uiid = uiid + '-bg'; 44 | uidraw.rectangle_soft(rect, uidraw.normal_back); 45 | const rect1 = uidraw.rectangle_erode(rect, 1); 46 | uidraw.rectangle_soft(rect1, uidraw.normal_face); 47 | ui.add_hotspot(bg_uiid, rect); 48 | 49 | // draw grid 50 | const stroke_color = ui.make_css_color(ColorP(255, 255, 255, 100)); 51 | uidraw.push_strokestyle(stroke_color); 52 | uidraw.line(layout.x + 1, layout.y + rect_h_half, layout.x + rect.w - 1, layout.y + rect_h_half); 53 | uidraw.line(layout.x + rect_w_half, layout.y + 1, layout.x + rect_w_half, layout.y + rect.h - 1); 54 | uidraw.pop_strokestyle(); 55 | 56 | let rel_x; // in local scale 57 | let rel_y; 58 | 59 | if (ui.state.item_went_down == bg_uiid || ui.state.item_held == bg_uiid) { 60 | rel_x = 0 | ui.driver.GetCursorX() - layout.x; 61 | rel_y = 0 | ui.driver.GetCursorY() - layout.y; 62 | changed = 0 | true; 63 | local_x = 0 | Math.min(rect.w, Math.max(0, rel_x)); 64 | local_y = 0 | Math.min(rect.h, Math.max(0, rel_y)); 65 | } 66 | // ......................... 67 | 68 | // i want a handle so that selecting near edges of background is easier 69 | /*_ = ui.handle(uiid_pt1, handle1_rect, local_x, local_y); 70 | if (_.changed) { 71 | rel_x = _.x1; 72 | rel_y = _.y1; 73 | changed = 0 | changed | _.changed; 74 | }*/ 75 | 76 | // handle 77 | const handle1_rect = RectangleP(local_x - grab_size_half, local_y - grab_size_half, grab_size, grab_size); 78 | 79 | // circle indicator 80 | if (changed && ui.state.item_held == bg_uiid) { 81 | const handle1_face = ui.layout_translated(uidraw.rectangle_erode(handle1_rect, 4)); 82 | uidraw.circle(handle1_face, uidraw.raised_accent); 83 | } else { 84 | const handle1_face = ui.layout_translated(uidraw.rectangle_erode(handle1_rect, 6)); 85 | uidraw.circle(handle1_face, uidraw.raised_face); 86 | } 87 | 88 | } 89 | ui.layout_pop(); 90 | ui.layout_increment(rect); 91 | 92 | const result_x = min_x + (local_x / rect.w) * value_x_range; 93 | const result_y = min_y + (local_y / rect.h) * value_y_range; 94 | 95 | let state = ui.get_state(uiid); 96 | if (!state) { 97 | state = ui.set_state(uiid, {changed: 0 | false, x1: 0 | 0, y1: 0 | 0}); 98 | } 99 | state.changed = 0 | changed; 100 | state.x1 = 0 | result_x; // nolive probably should be .x not .x1 101 | state.y1 = 0 | result_y; 102 | return state; 103 | 104 | /*return [ 105 | 0 | changed, 106 | 0 | result_x, 107 | 0 | result_y 108 | ];*/ 109 | } 110 | 111 | export { 112 | do_slider2d 113 | }; -------------------------------------------------------------------------------- /src/simpleui_ex_scroll.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | 5 | const _none = consts._none; 6 | const _vertical = consts._vertical; 7 | const _horizontal = consts._horizontal; 8 | 9 | const debug = 0 | false; 10 | /* 11 | todo:later: the scroll item hotspot isn't being clipped (might be a problem later...) 12 | if i dont fix it properly, then it might be possible to fix by placing a dead hotspot above the overflow and below any later widgets/hotspots 13 | */ 14 | 15 | const RectangleP = ui.RectangleP; 16 | 17 | function do_scroll_begin(uiid, local_rect, row_height, item_count) { 18 | const rect = ui.layout_translated(local_rect); 19 | let state = ui.get_state(uiid); 20 | if (!state) { 21 | state = ui.set_state(uiid, { 22 | 'scroll_value': 0 | 0, 23 | 'rect': rect, 24 | 'row_height': 0 | row_height, 25 | 'item_count': 0 | item_count, 26 | 'mod': null, 27 | }); 28 | } 29 | 30 | ui.layout_push(_none); 31 | 32 | // horizontal layout = [vertical layout, slider] 33 | ui.layout_push(_horizontal); 34 | 35 | const mod = state.scroll_value % state.row_height; 36 | state.mod = mod; 37 | 38 | // vertical layout = [item1, item2, ...] 39 | ui.layout_push(_vertical, 0, rect.x, rect.y - mod); // mod handles item offset in view 40 | //ui.layout_push(_vertical, 0, rect.x, rect.y); 41 | 42 | state.first_visible_index = 0 | Math.floor(state.scroll_value / state.row_height); 43 | const max_visible = 0 | Math.ceil(rect.h / state.row_height) + 1; 44 | state.last_visible_index = 0 | Math.min(item_count, state.first_visible_index + max_visible); 45 | 46 | let rect2 = uidraw.rectangle_dilate(rect, 1); 47 | uidraw.rectangle(rect2, uidraw.panel_color); 48 | 49 | uidraw.begin_clip(rect); 50 | 51 | state.rect = rect; // this is equivalent to prop-changed-so-update-state-mirror in vue/react/etc 52 | 53 | return state; 54 | } 55 | 56 | function do_scroll_end(uiid) { 57 | var state = ui.get_state(uiid); 58 | let item_count = state.item_count; 59 | let row_height = state.row_height; 60 | let rect = state.rect; 61 | 62 | uidraw.end_clip(); 63 | 64 | ui.layout_pop(); // vert 65 | 66 | // smi truncate is floor 67 | const max_items_shown = 0 | rect.h / row_height; 68 | 69 | if (item_count > max_items_shown) { 70 | var slider_max = item_count * row_height - rect.h; 71 | let _ = ui.vslider(uiid + '-vslider', RectangleP(-20, 0, 20, rect.h), 0, slider_max, state.scroll_value, ''); 72 | if (_.changed) { 73 | state.scroll_value = _.value; 74 | } 75 | } 76 | 77 | if (debug) { 78 | ui.layout_push(_vertical); 79 | ui.label(state.scroll_value+'', RectangleP(0,0,50,20)); 80 | ui.label(state.mod+'', RectangleP(0,0,50,20)); 81 | ui.layout_pop(); 82 | } 83 | 84 | if (state.last_visible_index == state.item_count) { 85 | // pass (is this a bug) and/or (is there a better way?) 86 | } else { 87 | ui.layout_peek().maxh -= row_height; 88 | } 89 | ui.layout_pop(); // horz 90 | 91 | ui.layout_pop(); // none 92 | 93 | ui.layout_increment(rect); 94 | 95 | if (debug) { 96 | uidraw.rectangle_outline(rect, ui.ColorP(255,0,0,255)); 97 | } 98 | } 99 | 100 | // todo: messy, latest feature 101 | function do_scroll_item_begin(scroll_uiid, i) { 102 | //var layout_parent = ui.layout_peek(); 103 | //console.log(layout_parent); 104 | //var layout = ui.layout_push(_vertical); 105 | var scroll = ui.get_state(scroll_uiid); 106 | scroll.translate_y = i * scroll.row_height; 107 | scroll.widget_y1 = scroll.rect.y + scroll.translate_y - scroll.scroll_value; 108 | scroll.widget_y2 = scroll.widget_y1 + scroll.row_height; 109 | 110 | //const peek = ui.layout_peek(); 111 | 112 | // im emulating the layout here, because i know it is vertical... (TODO: LATER: FIXERINO) 113 | //peek.y -= scroll.partial_item; // this handles the offset of every item... 114 | //console.log(layout); 115 | //} 116 | } 117 | 118 | function do_scroll_item_end(scroll_uiid) { 119 | // debug draws 120 | if (false) { 121 | const scroll = ui.get_state(scroll_uiid); 122 | uidraw.rectangle(RectangleP(scroll.rect.x, scroll.widget_y1, 200, 2), ui.ColorP(51, 102, 102, 200)); 123 | uidraw.rectangle(RectangleP(scroll.rect.x, scroll.widget_y2 - 1, 200, 1), ui.ColorP(102, 0, 0, 200)); 124 | } 125 | } 126 | 127 | 128 | export { 129 | do_scroll_begin, do_scroll_end, 130 | do_scroll_item_begin, do_scroll_item_end 131 | }; -------------------------------------------------------------------------------- /src/simpleui_ex_gradient.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import { Color, make_css_color } from './simpleui.js'; 3 | import * as uidraw from './simpleui_drawing.js'; 4 | import * as consts from './simpleui_consts.js'; 5 | 6 | const _none = consts._none; 7 | //const _vertical = consts._vertical; 8 | //const _horizontal = consts._horizontal; 9 | 10 | const RectangleP = ui.RectangleP; 11 | const ColorP = ui.ColorP; 12 | 13 | let css_line_color2; 14 | let css_line_color1; 15 | 16 | function initialize() { 17 | css_line_color2 = make_css_color(Color(255, 255, 255, 255)); 18 | css_line_color1 = make_css_color(Color(0, 0, 0, 255)); 19 | } 20 | 21 | function do_gradient_stroke_edit(uiid, min, max, x1, y1, x2, y2) { 22 | let _; 23 | 24 | const min_x = 0 | min; 25 | const min_y = 0 | min; 26 | const max_x = 0 | max; 27 | const max_y = 0 | max; 28 | const dim_w = max_x - min_x; 29 | const dim_h = max_y - min_y; 30 | const dim_w_half = 0 | (dim_w / 2); 31 | const dim_h_half = 0 | (dim_h / 2); 32 | const dim_w_quarter = 0 | (dim_w / 4); 33 | const dim_h_quarter = 0 | (dim_h / 4); 34 | const grab_dim = 20; 35 | const grab_dim_half = 0 | grab_dim / 2; 36 | 37 | let changed = 0 | false; 38 | let result_x1 = 0 | x1; 39 | let result_y1 = 0 | y1; 40 | let result_x2 = 0 | x2; 41 | let result_y2 = 0 | y2; 42 | 43 | const layout = ui.layout_push(_none); 44 | { 45 | const p1_x = x1 - min_x; 46 | const p1_y = y1 - min_y; 47 | const p2_x = x2 - min_x; 48 | const p2_y = y2 - min_y; 49 | 50 | // background 51 | uidraw.rectangle3d(RectangleP(layout.x, layout.y, dim_w, dim_h), uidraw.normal_face); 52 | 53 | // grid lines 54 | const stroke_color = make_css_color(ColorP(255, 255, 255, 255)); 55 | uidraw.push_strokestyle(stroke_color); 56 | uidraw.line(layout.x + dim_w_half, layout.y, layout.x + dim_w_half, layout.y + dim_h); 57 | uidraw.line(layout.x, layout.y + dim_h_half, layout.x + dim_w, layout.y + dim_h_half); 58 | uidraw.pop_strokestyle(); 59 | 60 | // gradient swatch 61 | uidraw.rectangle3d(RectangleP(layout.x + dim_w_quarter, layout.y + dim_h_quarter, dim_w_half, dim_h_half), uidraw.accent); 62 | 63 | // line between points 1 64 | // ---> 65 | const handle1_rect = RectangleP(p1_x - grab_dim_half, p1_y - grab_dim_half, grab_dim, grab_dim); 66 | const handle2_rect = RectangleP(p2_x - grab_dim_half, p2_y - grab_dim_half, grab_dim, grab_dim); 67 | 68 | const dx = handle2_rect.x - handle1_rect.x; 69 | const dy = handle2_rect.y - handle1_rect.y; 70 | 71 | const radians = Math.atan2(dx, dy); 72 | const pt = ui.angled_norm_line(radians, grab_dim_half); 73 | 74 | uidraw.push_linewidth(3); 75 | uidraw.push_strokestyle(css_line_color1); 76 | uidraw.line(layout.x + p1_x + pt.x, layout.y + p1_y + pt.y, layout.x + p2_x - pt.x, layout.y + p2_y - pt.y); 77 | uidraw.pop_strokestyle(); 78 | uidraw.pop_linewidth(); 79 | // line between points 2 80 | uidraw.push_linedash([2,2]); 81 | uidraw.push_linewidth(2); 82 | uidraw.push_strokestyle(css_line_color2); 83 | uidraw.line(layout.x + p1_x + pt.x, layout.y + p1_y + pt.y, layout.x + p2_x - pt.x, layout.y + p2_y - pt.y); 84 | uidraw.pop_strokestyle(); 85 | uidraw.pop_linewidth(); 86 | uidraw.pop_linedash(); 87 | // <--- 88 | 89 | const uiid_pt1 = uiid + '-pt1'; 90 | _ = ui.reticle(uiid_pt1, handle1_rect, p1_x, p1_y); 91 | if (_.changed) { 92 | changed = 0 | changed | _.changed; 93 | result_x1 = 0 | Math.min(max_x, Math.max(min_x, _.x1 + min_x)); 94 | result_y1 = 0 | Math.min(max_y, Math.max(min_y, _.y1 + min_y)); 95 | } 96 | 97 | const uiid_pt2 = uiid + '-pt2'; 98 | _ = ui.reticle(uiid_pt2, handle2_rect, p2_x, p2_y); 99 | if (_.changed) { 100 | changed = 0 | changed | _.changed; 101 | result_x2 = 0 | Math.min(max_x, Math.max(min_x, _.x1 + min_x)); 102 | result_y2 = 0 | Math.min(max_y, Math.max(min_y, _.y1 + min_y)); 103 | } 104 | } 105 | ui.layout_pop(); 106 | ui.layout_increment2(dim_w, 0); 107 | 108 | let state = ui.get_state(uiid); 109 | if (!state) { 110 | state = ui.set_state(uiid, {changed: 0 | false, x1: 0 | x1, y1: 0 | y1, x2: 0 | x2, y2: 0 | y2}); 111 | } 112 | state.changed = 0 | changed; 113 | state.x1 = 0 | result_x1; 114 | state.y1 = 0 | result_y1; 115 | state.x2 = 0 | result_x2; 116 | state.y2 = 0 | result_y2; 117 | return state; 118 | 119 | /*return [ 120 | 0 | changed, 121 | 0 | result_x1, 122 | 0 | result_y1, 123 | 0 | result_x2, 124 | 0 | result_y2 125 | ];*/ 126 | } 127 | 128 | export { 129 | do_gradient_stroke_edit, 130 | initialize 131 | }; -------------------------------------------------------------------------------- /index-canvas.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | remzmike - simpleui demo 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 89 | 90 | 149 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /src/simpleui_ex_linestar.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | import { do_slider2d } from './simpleui_ex_slider2d.js'; 5 | 6 | const _none = consts._none; 7 | const _vertical = consts._vertical; 8 | const _horizontal = consts._horizontal; 9 | 10 | const PointP = ui.PointP; 11 | const RectangleP = ui.RectangleP; 12 | const ColorP = ui.ColorP; 13 | 14 | const ends = []; 15 | 16 | /* 17 | this function seems to show an interesting performance issue with simpleui > 0.3.1 18 | 19 | with new deferred commands rendering, performance chokes after a high number of commands here 20 | 21 | maybe the commands could be made smaller: 22 | eg. (beginpath, 1, move_to, 4,x1,y1,x2,y2, line_to, 4,x3,y3,x4,y4) // 14 elements 23 | to. (path, 8, x1, y1, x2, y2, x3, y3, x4, y4) // 10 elements 24 | (it's a significant percentage but probably not enough for me) 25 | 26 | i think this is happening because some stack/buffer limit is reached and jit deopts 27 | 28 | giant commands buffer = bad performance 29 | 30 | immediate rendering benefits from extra closeness to cpu 31 | 32 | you should be able to choose deferred or immediate rendering 33 | not just deferred, not just immediate 34 | although if i have to pick one i should probably go immediate 35 | 36 | i think... support both, eventually, but probably default to immediate for a long while 37 | 38 | BUT, still use a uidraw layer of indirection (not directly calling on context) 39 | AND, still use push_/pop_ uidraw api instead of sets 40 | */ 41 | function draw_star(uiid, segments, joints, webs, rings) { 42 | 43 | ends.length = 0; 44 | 45 | let incr = Math.PI * 2 / segments; 46 | 47 | for (let i = 0; i < segments; i++) { 48 | let rad = i * incr; 49 | let pt = ui.angled_norm_line(rad, 200); 50 | ends.push(pt); 51 | } 52 | 53 | const peek = ui.layout_peek(); 54 | uidraw.begin_path(); 55 | const stroke_color = ui.make_css_color(ColorP(200, 220, 200, 127)); 56 | uidraw.push_strokestyle(stroke_color); 57 | 58 | // webs 59 | webs = Math.min(webs, joints); 60 | if (webs > 0) { 61 | for (let i0 = 0; i0 < ends.length; i0++) { 62 | let pt0 = ends[i0]; 63 | let x0 = pt0.x; 64 | let y0 = pt0.y; 65 | // begin 66 | let i1 = (i0 + 1) % ends.length; 67 | let pt1 = ends[i1]; 68 | let x1 = pt1.x; 69 | let y1 = pt1.y; 70 | for (let j = 0; j < webs; j++) { 71 | let p0 = (j + 1) / joints; 72 | uidraw.move_to(peek.x + x0 * p0, peek.y + y0 * p0); 73 | uidraw.line_to(peek.x + x1 * p0, peek.y + y1 * p0); 74 | //console.log(x0*p0, y0*p0, x1*p0, y1*p0); 75 | } 76 | } 77 | } 78 | 79 | // rings 80 | /* 81 | agg::ellipse e; 82 | for (int j=0;j e_stroke(e); 87 | 88 | agg::conv_transform< 89 | agg::ellipse, 90 | agg::trans_affine> 91 | trans_e(e, matrix); 92 | 93 | agg::conv_stroke< 94 | agg::conv_transform< 95 | agg::ellipse, 96 | agg::trans_affine>> 97 | e_stroke(trans_e); 98 | 99 | ras.add_path(e_stroke); 100 | //path->move_to(0, p0); 101 | //path->arc_rel(0, 0, 4, false, false, p0, p0); 102 | //path->arc_rel(p0, p0, 4, true, true, p0,p0); 103 | //cr.move_to(p,0) 104 | //cr.arc(0, 0, p, 0, 2 * math.pi) 105 | } 106 | */ 107 | 108 | // star 109 | for (let i0 = 0; i0 < ends.length; i0++) { 110 | let pt0 = ends[i0]; 111 | let x0 = pt0.x; 112 | let y0 = pt0.y; 113 | // begin 114 | let i1 = (i0 + 1) % ends.length; 115 | let pt1 = ends[i1]; 116 | let x1 = pt1.x; 117 | let y1 = pt1.y; 118 | for (let j = 0; j < joints; j++) { 119 | let p0 = (j + 1) / joints; 120 | let p1 = 1 - p0; 121 | uidraw.move_to(peek.x + x0 * p0, peek.y + y0 * p0); 122 | uidraw.line_to(peek.x + x1 * p1, peek.y + y1 * p1); 123 | } 124 | } 125 | 126 | uidraw.stroke(); 127 | uidraw.pop_strokestyle(); 128 | } 129 | 130 | // idea:: 131 | // linestar_edit = make_linestar_edit(13, 12, 5); 132 | // linestar_edit(uiid); 133 | function do_linestar_edit(uiid, segments, joints, webs) { 134 | let _; 135 | const rings = 19; 136 | 137 | const vertical = ui.layout_push(_vertical); 138 | const bg_rect = ui.layout_translated(RectangleP(0, 0, 400, 400)); 139 | uidraw.rectangle_soft(bg_rect, uidraw.normal_back); 140 | ui.layout_increment2(400, 400); 141 | 142 | ui.layout_push(_none, 0, vertical.x + 200, vertical.y - 200); 143 | draw_star( 144 | uiid, 145 | segments, 146 | joints, 147 | webs, 148 | rings 149 | ); 150 | ui.layout_pop(); 151 | 152 | let changed = 0 | false; 153 | const rect = RectangleP(0, 0, 200, 20); 154 | 155 | _ = do_slider2d(uiid + '-slider2d', RectangleP(0, 0, 400, 200), 3, 20, PointP(segments, joints)); 156 | if (_.changed) { 157 | segments = _.x1; 158 | joints = _.y1; 159 | changed = 0 | true; 160 | } 161 | 162 | ui.layout_push(_horizontal); 163 | _ = ui.slider(uiid + '-slider-segments', rect, 0, 80, segments, ''); 164 | if (_.changed) { 165 | segments = _.value; 166 | changed = 0 | true; 167 | } 168 | 169 | ui.label('segments', rect); 170 | ui.layout_pop(); 171 | 172 | ui.layout_push(_horizontal); 173 | _ = ui.slider(uiid + '-slider-joints', rect, 0, 80, joints, ''); 174 | if (_.changed) { 175 | joints = _.value; 176 | changed = 0 | true; 177 | } 178 | ui.label('joints', rect); 179 | ui.layout_pop(); 180 | 181 | ui.layout_push(_horizontal); 182 | _ = ui.slider(uiid + '-slider-webs', rect, 0, 80, webs, ''); 183 | if (_.changed) { 184 | webs = _.value; 185 | changed = 0 | true; 186 | } 187 | 188 | ui.label('webs', rect); 189 | ui.layout_pop(); 190 | 191 | ui.layout_pop(); // vertical 192 | 193 | let state = ui.get_state(uiid); 194 | if (!state) { 195 | state = ui.set_state(uiid, {changed: 0 | false, segments: 0 | segments, joints: 0 | joints, webs: 0 | webs}); 196 | } 197 | state.changed = 0 | changed; 198 | state.segments = 0 | segments; 199 | state.joints = 0 | joints; 200 | state.webs = 0 | webs; 201 | return state; 202 | 203 | /*return [ 204 | 0 | changed, 205 | 0 | segments, 206 | 0 | joints, 207 | 0 | webs 208 | ];*/ 209 | } 210 | 211 | export { 212 | do_linestar_edit 213 | }; -------------------------------------------------------------------------------- /src/simpleui_ex_panel.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | 5 | const _none = consts._none; 6 | const _vertical = consts._vertical; 7 | const _horizontal = consts._horizontal; 8 | 9 | const ColorP = ui.ColorP; 10 | const Rectangle = ui.Rectangle; 11 | const RectangleP = ui.RectangleP; 12 | 13 | // how does panel module know when it's a new frame? 14 | // need to know new frame so i can reset the collapsed item count to 0 15 | 16 | // these are set in _begin and checked in _end 17 | // it's important to handle events at end of _end 18 | let _button; 19 | let _handle; 20 | 21 | const panel_pad = 0 | 20; 22 | const bar_height = 0 | 24; 23 | const debug_draw = 0 | false; 24 | const text_width = 0 | 10; // hax 25 | const text_ox = 0 | (bar_height / 2 - text_width / 2); 26 | const text_oy = 0 | (text_ox / 2); 27 | 28 | function do_collapsed(uiid, rect, state) { 29 | let collapsed_x = 0 | 200 + ui.state.collapsed_panel_index * 224; 30 | let collapsed_y = 0 | 1; 31 | 32 | // handle is positioned left 1 pixel so border overlaps with collapse button, so width needs +1 33 | const handle_w = 0 | 200 + 1; // this 200 needs to be configureable 34 | const glyph = 'v'; 35 | let x = rect.x; 36 | let y = rect.y; 37 | 38 | // collapsed layout 39 | ui.layout_push(_none, 0, collapsed_x, collapsed_y); 40 | { 41 | // draw background bar before first collapsed panel 42 | if (ui.state.collapsed_panel_index == 0) { 43 | uidraw.rectangle(RectangleP(200, 0, canvas.width - 200, 25), uidraw.panel_color); 44 | } 45 | 46 | const uiid_handle = uiid + '-handle'; 47 | _button = ui.checkbutton(uiid + '-button', glyph, RectangleP(0, 0, bar_height, bar_height), state.expanded, text_ox, text_oy); 48 | 49 | const hrect = RectangleP(bar_height - 1, 0, handle_w, bar_height); 50 | _handle = ui.handle(uiid_handle, hrect, x, y); 51 | 52 | ui.label(uiid, RectangleP(bar_height + 10, 0, handle_w + panel_pad, bar_height)); 53 | 54 | } 55 | ui.layout_pop(); 56 | 57 | ui.state.collapsed_panel_index++; 58 | } 59 | 60 | function do_expanded(uiid, rect, state) { 61 | 62 | ui.layout_push(_none); // breakout of whatever layout we might be in (absolute positioning) 63 | 64 | let x = 0 | rect.x; 65 | let y = 0 | rect.y; 66 | 67 | // todo: later: push_id('handle'); 68 | // or: next_id('-handle'); etc. 69 | 70 | let bar_layout = ui.layout_push(_horizontal, -1, x, y); // app.panel_layout_padding 71 | { 72 | // background 73 | let back_rect = RectangleP(bar_layout.x, bar_layout.y, rect.w + panel_pad * 2, rect.h + panel_pad * 2 + bar_height); 74 | let back_rect1 = uidraw.rectangle_erode(back_rect, 1); 75 | uidraw.rectangle_soft(back_rect, uidraw.normal_back); 76 | uidraw.rectangle_soft(back_rect1, uidraw.panel_color); 77 | 78 | //let rect1 = uidraw.rectangle_panel_pad(rect, panel_pad); 79 | ui.add_hotspot(uiid + '-bg-hotspot', back_rect); 80 | 81 | _button = ui.checkbutton(uiid + '-button', '-', RectangleP(0, 0, bar_height, bar_height), state.expanded, text_ox, text_oy); 82 | 83 | const handle_w = 0 | (rect.w + panel_pad * 2 - bar_height) + 1; 84 | const handle_rect = RectangleP(0, 0, handle_w, bar_height); 85 | const uiid_handle = uiid + '-handle'; 86 | _handle = ui.handle(uiid_handle, handle_rect, x, y); 87 | 88 | //ui.label(uiid, Rectangle(bar_height + 10, 0, handle_w + panel_pad, bar_height)); 89 | const label_rect = RectangleP(x + bar_height + 8, y, handle_w + panel_pad, bar_height); 90 | uidraw.label(uiid, uidraw.vertical_center_text(label_rect), ColorP(255, 255, 255, 255)); 91 | } 92 | ui.layout_pop(); // bar_layout 93 | 94 | const padding = 2; // app.panel_layout_padding 95 | ui.layout_push(_vertical, padding, x + panel_pad, y + bar_height + panel_pad); // content layout 96 | 97 | if (debug_draw) { 98 | const debug_color = ColorP(0, 200, 200, 127); 99 | const debug_color2 = ColorP(200, 200, 0, 127); 100 | 101 | // draw xy indicator 102 | uidraw.rectangle(RectangleP(x - 4, y - 4, 8, 8), debug_color2); 103 | 104 | // draw layout origin 105 | let layout = ui.layout_peek(); 106 | let absolute_rect = RectangleP(layout.x - 2, layout.y - 2, 4, 4); 107 | uidraw.rectangle(absolute_rect, debug_color); 108 | 109 | //ui.label('first xy', Rectangle(0, 0, 100, 40), debug_color); 110 | } 111 | 112 | } 113 | 114 | function do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded) { 115 | console.assert(first_x != null, 'do_panel_begin: first_x required'); 116 | console.assert(first_y != null, 'do_panel_begin: first_y required'); 117 | //if (first_visible == null) first_visible = true; 118 | if (first_expanded == null) first_expanded = true; 119 | 120 | //uidraw.label('#' + ui.state.collapsed_panel_index, Point(200, 20*ui.state.collapsed_panel_index), Color(255,255,255,255)); 121 | 122 | let state = ui.get_state(uiid); 123 | if (!state) { 124 | state = ui.set_state(uiid, { 125 | 'uiid': uiid, 126 | 'rect': Rectangle(first_x, first_y, 1, 1), 127 | 'visible': 0 | true, //first_visible, 128 | 'expanded': 0 | first_expanded 129 | }); 130 | } 131 | 132 | if (state.expanded) { 133 | do_expanded(uiid, state.rect, state); 134 | } else { 135 | do_collapsed(uiid, state.rect, state); 136 | } 137 | 138 | return state; 139 | } 140 | 141 | function do_panel_end(uiid) { 142 | const state = ui.get_state(uiid); 143 | 144 | if (state.expanded) { 145 | const peek = ui.layout_peek(); 146 | ui.layout_pop(); // content layout 147 | 148 | if (state.visible) { 149 | console.assert(state); // should have been created in _begin 150 | state.rect.w = 0 | peek.totalw; // idk: nolive, panels are jank cuz i was bad, fixing 151 | state.rect.h = 0 | peek.totalh; 152 | // this means one frame of latency between content updates and panel autosizing 153 | } 154 | 155 | ui.layout_pop(); // none layout 156 | } 157 | 158 | // state changes last 159 | if (state.expanded) { 160 | if (_button.changed) { 161 | state.expanded = 0 | false; 162 | } 163 | if (_handle.changed) { 164 | state.rect.x = 0 | _handle.x1; 165 | state.rect.y = 0 | _handle.y1; 166 | } 167 | } else { 168 | if (_button.changed) { 169 | state.expanded = 0 | true; 170 | } 171 | if (_handle.changed) { 172 | // if they click collapsed handle, panel expands 173 | state.expanded = 0 | true; 174 | // cancel item_held when they do this... *shrug* (todo:later:architecture) 175 | const uiid_handle = uiid + '-handle'; 176 | if (ui.state.item_held == uiid_handle) { 177 | ui.state.item_held = null; 178 | } 179 | } 180 | } 181 | } 182 | 183 | export { 184 | do_panel_begin, 185 | do_panel_end 186 | }; -------------------------------------------------------------------------------- /src/simpleui_ex_gridfont.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | 4 | const Color = ui.Color; 5 | 6 | const _gridfonts = { 7 | 'hint-four': { 8 | [' ']: [], 9 | a: [[0, 2, 1, 2], [1, 2, 2, 3], [2, 3, 1, 4], [1, 4, 0, 3], [0, 3, 1, 3]], 10 | b: [[0, 0, 0, 1], [0, 1, 0, 2], [0, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 3], [2, 3, 1, 2]], 11 | c: [[1, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 4]], 12 | d: [[2, 0, 2, 1], [2, 1, 2, 2], [2, 2, 2, 3], [2, 3, 1, 4], [1, 4, 0, 3], [0, 3, 1, 2]], 13 | e: [[1, 3, 2, 3], [2, 3, 1, 2], [1, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 4]], 14 | f: [[2, 0, 1, 1], [1, 1, 1, 2], [1, 2, 1, 3], [1, 3, 1, 4], [0, 2, 1, 2]], 15 | g: [[1, 4, 0, 3], [0, 3, 1, 2], [1, 2, 2, 3], [2, 3, 2, 4], [2, 4, 2, 5], [2, 5, 1, 6]], 16 | h: [[0, 0, 0, 1], [0, 1, 0, 2], [0, 2, 0, 3], [0, 3, 1, 2], [1, 2, 2, 3], [2, 3, 2, 4]], 17 | i: [[1, 2, 1, 3], [1, 3, 1, 4], [1, 1, 2, 0]], 18 | j: [[1, 2, 1, 3], [1, 3, 1, 4], [1, 4, 1, 5], [1, 5, 0, 6], [1, 1, 2, 0]], 19 | k: [[0, 0, 0, 1], [0, 1, 0, 2], [0, 2, 0, 3], [0, 3, 1, 3], [1, 3, 2, 2], [1, 3, 2, 4]], 20 | l: [[1, 0, 1, 1], [1, 1, 1, 2], [1, 2, 1, 3], [1, 3, 2, 4]], 21 | m: [[0, 4, 0, 3], [0, 3, 1, 2], [1, 2, 2, 3], [2, 3, 2, 4], [1, 3, 1, 4]], 22 | n: [[0, 4, 0, 3], [0, 3, 1, 2], [1, 2, 2, 3], [2, 3, 2, 4]], 23 | o: [[0, 2, 1, 2], [1, 2, 2, 3], [2, 3, 1, 4], [1, 4, 0, 3]], 24 | p: [[0, 6, 0, 5], [0, 5, 0, 4], [0, 4, 0, 3], [0, 3, 1, 2], [1, 2, 2, 3], [2, 3, 1, 4]], 25 | q: [[2, 6, 2, 5], [2, 5, 2, 4], [2, 4, 2, 3], [2, 3, 1, 2], [1, 2, 0, 3], [0, 3, 1, 4]], 26 | r: [[0, 4, 0, 3], [0, 3, 1, 2], [1, 2, 2, 2]], 27 | s: [[1, 2, 0, 3], [0, 3, 1, 3], [1, 3, 2, 3], [2, 3, 1, 4], [1, 4, 0, 4]], 28 | t: [[1, 1, 1, 2], [1, 2, 1, 3], [1, 3, 1, 4], [1, 4, 2, 3], [0, 2, 1, 2]], 29 | u: [[0, 2, 0, 3], [0, 3, 0, 4], [0, 4, 1, 4], [1, 4, 2, 3], [2, 3, 2, 2]], 30 | v: [[0, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 3], [2, 3, 2, 2]], 31 | w: [[0, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 3], [2, 3, 2, 2], [1, 2, 1, 3]], 32 | x: [[0, 2, 1, 3], [1, 3, 2, 4], [2, 2, 1, 3], [1, 3, 0, 4]], 33 | y: [[0, 2, 0, 3], [0, 3, 1, 4], [1, 4, 2, 3], [2, 3, 2, 4], [2, 4, 2, 5], [2, 5, 1, 6]], 34 | z: [[1, 2, 2, 2], [2, 2, 1, 3], [1, 3, 0, 4], [0, 4, 1, 4], [1, 4, 2, 4]], 35 | } 36 | }; 37 | 38 | const _f = _gridfonts['hint-four']; 39 | // list of segments, each segment is a standalone stroke from x1,y1 to x2,y2 40 | 41 | const _gridfont_chars_a = 'abcdefghijklmnopqrstuvwxyz '.split(''); 42 | const _gridfont_chars = {}; 43 | 44 | for (let i = 0; i < _gridfont_chars_a.length; i++) { 45 | _gridfont_chars[_gridfont_chars_a[i]] = 0 | true; 46 | } 47 | 48 | let _gridfont_gradient; 49 | 50 | function initialize() { 51 | if (ui.driver.config.has_drawbox_gradient) { 52 | _gridfont_gradient = ui.driver.CreateDrawboxGradient( 53 | ui.driver.GetContext(), 54 | 400, 400, 55 | 1000, 1000, 56 | Color(0x00, 0xB5, 0xE3, 255), 57 | Color(255, 0, 255, 255) 58 | ); 59 | } 60 | } 61 | 62 | // http://cogsci.indiana.edu/gridfonts.html 63 | // see also: hershey fonts: http://sol.gfxile.net/hershey/fontprev.html 64 | function do_gridfont(uiid, s, name, x, y, scale, reset) { 65 | let a = s.split(''); 66 | let complete = 0 | true; 67 | let reset_complete = 0 | true; 68 | 69 | uidraw.push_linewidth(1.5); 70 | uidraw.push_strokestyle(_gridfont_gradient); 71 | 72 | uidraw.begin_path(); 73 | 74 | for (let i = 0; i < a.length; i++) { 75 | let letter = a[i]; 76 | if (!_gridfont_chars[letter]) { 77 | letter = ' '; 78 | } 79 | const x_letter = 0 | (x + i * (scale * 3)); 80 | const _ = do_gridfont_letter(uiid + '-letter-' + i, name, x_letter, y, letter, scale, reset); 81 | complete = complete && _.complete; 82 | reset_complete = reset_complete && _.reset_complete; 83 | } 84 | 85 | uidraw.stroke(); 86 | 87 | uidraw.pop_strokestyle(); 88 | uidraw.pop_linewidth(); 89 | 90 | let state = ui.get_state(uiid); 91 | if (!state) { 92 | state = ui.set_state(uiid, {complete: 0 | false, reset_complete: 0 | false}); 93 | } 94 | state.complete = 0 | complete; 95 | state.reset_complete = 0 | reset_complete; 96 | return state; 97 | 98 | //return [complete, reset_complete]; 99 | } 100 | 101 | function do_gridfont_letter(uiid, name, x, y, letter, scale, reset) { 102 | console.assert(name); 103 | console.assert(letter); 104 | console.assert(reset != null); 105 | console.assert(reset != undefined); 106 | 107 | let state = ui.get_state(uiid); 108 | if (!state) { 109 | state = ui.set_state(uiid, { 110 | complete: 0 | false, 111 | reset_complete: 0 | false, 112 | segment: 0 | 1, 113 | partial: 0 | 0 114 | }); 115 | } 116 | 117 | const fl = _f[letter]; // font letter 118 | 119 | const segment = state.segment; 120 | let partial = state.partial; 121 | 122 | // draw lines up to segment count 123 | // sometimes segment is > fl.length due to prev frame 124 | if (segment <= fl.length) { 125 | for (let i = 0; i < segment; i++) { 126 | let a1 = 0 | fl[i][0]; 127 | let b1 = 0 | fl[i][1]; 128 | let a2 = 0 | fl[i][2]; 129 | let b2 = 0 | fl[i][3]; 130 | let x1 = 0 | (x + (a1 * scale)); 131 | let y1 = 0 | (y + (b1 * scale)); 132 | let x2 = 0 | (x + (a2 * scale)); 133 | let y2 = 0 | (y + (b2 * scale)); 134 | uidraw.move_to(x1, y1); 135 | uidraw.line_to(x2, y2); 136 | } 137 | } 138 | // now draw current line... over multiple frames... 139 | // when complete then increment segment... 140 | if (segment < fl.length) { 141 | let i = segment; 142 | let a1 = 0 | fl[i][0]; 143 | let b1 = 0 | fl[i][1]; 144 | let a2 = 0 | fl[i][2]; 145 | let b2 = 0 | fl[i][3]; 146 | let x1 = 0 | (x + (a1 * scale)); 147 | let y1 = 0 | (y + (b1 * scale)); 148 | let x2 = 0 | (x + (a2 * scale)); 149 | let y2 = 0 | (y + (b2 * scale)); 150 | let dx = 0 | (x2 - x1); 151 | let dy = 0 | (y2 - y1); 152 | 153 | let p1 = 0 | (x1 + dx * partial / 10); 154 | let p2 = 0 | (y1 + dy * partial / 10); 155 | uidraw.move_to(x1, y1); 156 | uidraw.line_to(p1, p2); 157 | 158 | // i promoted partial from float to int, so new partial 1 == old partial 0.1 159 | // this means some of the logic changes from 1 to 10, and i divide by 10 for local calcs 160 | // the goal is to avoid float, avoid deopt, avoid heap 161 | state.partial = 0 | (state.partial + 1); //0.033 * 3; 162 | partial = state.partial; // blah. 163 | 164 | if (partial >= 10) { 165 | /*let leftover = partial - 10; 166 | if (leftover > 0) { 167 | // later: leftovers! :-) 168 | }*/ 169 | state.partial = 0 | 0; 170 | state.segment = 0 | (state.segment + 1); 171 | } 172 | } 173 | 174 | // keeping these out of an if block to avoid weird jit deopt 175 | state.segment = 0 | reset ? 1 : state.segment; 176 | state.partial = 0 | reset ? 0 : state.partial; 177 | state.reset_complete = 0 | reset ? true : state.reset_complete; 178 | state.complete = 0 | (segment >= fl.length); 179 | 180 | return state; 181 | } 182 | 183 | export { 184 | do_gridfont, 185 | initialize 186 | }; -------------------------------------------------------------------------------- /src/debounce.js: -------------------------------------------------------------------------------- 1 | // a klonkin wrapper for lodash's debounce function 2 | // converted to an node module 3 | 4 | /** Used as the `TypeError` message for "Functions" methods. */ 5 | var FUNC_ERROR_TEXT = 'Expected a function'; 6 | 7 | /** Used as references for various `Number` constants. */ 8 | var NAN = 0 / 0; 9 | 10 | /** `Object#toString` result references. */ 11 | var symbolTag = '[object Symbol]'; 12 | 13 | /** Used to match leading and trailing whitespace. */ 14 | var reTrim = /^\s+|\s+$/g; 15 | 16 | /** Used to detect bad signed hexadecimal string values. */ 17 | var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; 18 | 19 | /** Used to detect binary string values. */ 20 | var reIsBinary = /^0b[01]+$/i; 21 | 22 | /** Used to detect octal string values. */ 23 | var reIsOctal = /^0o[0-7]+$/i; 24 | 25 | /** Built-in method references without a dependency on `root`. */ 26 | var freeParseInt = parseInt; 27 | 28 | /** Detect free variable `global` from Node.js. */ 29 | var freeGlobal = typeof global == 'object' && global && global.Object === Object && global; 30 | 31 | /** Detect free variable `self`. */ 32 | var freeSelf = typeof self == 'object' && self && self.Object === Object && self; 33 | 34 | /** Used as a reference to the global object. */ 35 | var root = freeGlobal || freeSelf || Function('return this')(); 36 | 37 | /** Used for built-in method references. */ 38 | var objectProto = Object.prototype; 39 | 40 | /** 41 | * Used to resolve the 42 | * [`toStringTag`](http://ecma-international.org/ecma-262/7.0/#sec-object.prototype.tostring) 43 | * of values. 44 | */ 45 | var objectToString = objectProto.toString; 46 | 47 | /* Built-in method references for those with the same name as other `lodash` methods. */ 48 | var nativeMax = Math.max, 49 | nativeMin = Math.min; 50 | 51 | /** 52 | * Gets the timestamp of the number of milliseconds that have elapsed since 53 | * the Unix epoch (1 January 1970 00:00:00 UTC). 54 | * 55 | * @static 56 | * @memberOf _ 57 | * @since 2.4.0 58 | * @category Date 59 | * @returns {number} Returns the timestamp. 60 | * @example 61 | * 62 | * _.defer(function(stamp) { 63 | * console.log(_.now() - stamp); 64 | * }, _.now()); 65 | * // => Logs the number of milliseconds it took for the deferred invocation. 66 | */ 67 | var now = function () { 68 | return root.Date.now(); 69 | }; 70 | 71 | /** 72 | * Creates a debounced function that delays invoking `func` until after `wait` 73 | * milliseconds have elapsed since the last time the debounced function was 74 | * invoked. The debounced function comes with a `cancel` method to cancel 75 | * delayed `func` invocations and a `flush` method to immediately invoke them. 76 | * Provide `options` to indicate whether `func` should be invoked on the 77 | * leading and/or trailing edge of the `wait` timeout. The `func` is invoked 78 | * with the last arguments provided to the debounced function. Subsequent 79 | * calls to the debounced function return the result of the last `func` 80 | * invocation. 81 | * 82 | * **Note:** If `leading` and `trailing` options are `true`, `func` is 83 | * invoked on the trailing edge of the timeout only if the debounced function 84 | * is invoked more than once during the `wait` timeout. 85 | * 86 | * If `wait` is `0` and `leading` is `false`, `func` invocation is deferred 87 | * until to the next tick, similar to `setTimeout` with a timeout of `0`. 88 | * 89 | * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/) 90 | * for details over the differences between `_.debounce` and `_.throttle`. 91 | * 92 | * @static 93 | * @memberOf _ 94 | * @since 0.1.0 95 | * @category Function 96 | * @param {Function} func The function to debounce. 97 | * @param {number} [wait=0] The number of milliseconds to delay. 98 | * @param {Object} [options={}] The options object. 99 | * @param {boolean} [options.leading=false] 100 | * Specify invoking on the leading edge of the timeout. 101 | * @param {number} [options.maxWait] 102 | * The maximum time `func` is allowed to be delayed before it's invoked. 103 | * @param {boolean} [options.trailing=true] 104 | * Specify invoking on the trailing edge of the timeout. 105 | * @returns {Function} Returns the new debounced function. 106 | * @example 107 | * 108 | * // Avoid costly calculations while the window size is in flux. 109 | * jQuery(window).on('resize', _.debounce(calculateLayout, 150)); 110 | * 111 | * // Invoke `sendMail` when clicked, debouncing subsequent calls. 112 | * jQuery(element).on('click', _.debounce(sendMail, 300, { 113 | * 'leading': true, 114 | * 'trailing': false 115 | * })); 116 | * 117 | * // Ensure `batchLog` is invoked once after 1 second of debounced calls. 118 | * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 }); 119 | * var source = new EventSource('/stream'); 120 | * jQuery(source).on('message', debounced); 121 | * 122 | * // Cancel the trailing debounced invocation. 123 | * jQuery(window).on('popstate', debounced.cancel); 124 | */ 125 | function debounce(func, wait, options) { 126 | var lastArgs, 127 | lastThis, 128 | maxWait, 129 | result, 130 | timerId, 131 | lastCallTime, 132 | lastInvokeTime = 0, 133 | leading = false, 134 | maxing = false, 135 | trailing = true; 136 | 137 | if (typeof func != 'function') { 138 | throw new TypeError(FUNC_ERROR_TEXT); 139 | } 140 | wait = wait || 0; 141 | if (options) { 142 | leading = !!options.leading; 143 | maxing = 'maxWait' in options; 144 | maxWait = maxing ? nativeMax(+(options.maxWait) || 0, wait) : maxWait; 145 | trailing = 'trailing' in options ? !!options.trailing : trailing; 146 | } 147 | 148 | function invokeFunc(time) { 149 | var args = lastArgs, 150 | thisArg = lastThis; 151 | 152 | lastArgs = lastThis = undefined; 153 | lastInvokeTime = time; 154 | result = func.apply(thisArg, args); 155 | return result; 156 | } 157 | 158 | function leadingEdge(time) { 159 | // Reset any `maxWait` timer. 160 | lastInvokeTime = time; 161 | // Start the timer for the trailing edge. 162 | timerId = setTimeout(timerExpired, wait); 163 | // Invoke the leading edge. 164 | return leading ? invokeFunc(time) : result; 165 | } 166 | 167 | function remainingWait(time) { 168 | var timeSinceLastCall = time - lastCallTime, 169 | timeSinceLastInvoke = time - lastInvokeTime, 170 | result = wait - timeSinceLastCall; 171 | 172 | return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result; 173 | } 174 | 175 | function shouldInvoke(time) { 176 | var timeSinceLastCall = time - lastCallTime, 177 | timeSinceLastInvoke = time - lastInvokeTime; 178 | 179 | // Either this is the first call, activity has stopped and we're at the 180 | // trailing edge, the system time has gone backwards and we're treating 181 | // it as the trailing edge, or we've hit the `maxWait` limit. 182 | return (lastCallTime === undefined || (timeSinceLastCall >= wait) || 183 | (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait)); 184 | } 185 | 186 | function timerExpired() { 187 | var time = now(); 188 | if (shouldInvoke(time)) { 189 | return trailingEdge(time); 190 | } 191 | // Restart the timer. 192 | timerId = setTimeout(timerExpired, remainingWait(time)); 193 | } 194 | 195 | function trailingEdge(time) { 196 | timerId = undefined; 197 | 198 | // Only invoke if we have `lastArgs` which means `func` has been 199 | // debounced at least once. 200 | if (trailing && lastArgs) { 201 | return invokeFunc(time); 202 | } 203 | lastArgs = lastThis = undefined; 204 | return result; 205 | } 206 | 207 | function cancel() { 208 | if (timerId !== undefined) { 209 | clearTimeout(timerId); 210 | } 211 | lastInvokeTime = 0; 212 | lastArgs = lastCallTime = lastThis = timerId = undefined; 213 | } 214 | 215 | function flush() { 216 | return timerId === undefined ? result : trailingEdge(now()); 217 | } 218 | 219 | function debounced() { 220 | var time = now(), 221 | isInvoking = shouldInvoke(time); 222 | 223 | lastArgs = arguments; 224 | lastThis = this; 225 | lastCallTime = time; 226 | 227 | if (isInvoking) { 228 | if (timerId === undefined) { 229 | return leadingEdge(lastCallTime); 230 | } 231 | if (maxing) { 232 | // Handle invocations in a tight loop. 233 | timerId = setTimeout(timerExpired, wait); 234 | return invokeFunc(lastCallTime); 235 | } 236 | } 237 | if (timerId === undefined) { 238 | timerId = setTimeout(timerExpired, wait); 239 | } 240 | return result; 241 | } 242 | debounced.cancel = cancel; 243 | debounced.flush = flush; 244 | return debounced; 245 | } 246 | 247 | export default debounce; -------------------------------------------------------------------------------- /_setup_notes.txt: -------------------------------------------------------------------------------- 1 | [setup notes] 2 | 3 | following this (#10 google result, #1 quite bad, 2-9 not broad enough) 4 | https://www.sitepoint.com/es6-babel-webpack/ 5 | but making modifications (eg. dont override default browserslist) 6 | 7 | npm init -y 8 | npm install babel-cli babel-preset-env --save-dev 9 | 10 | modify package.json 11 | 12 | "scripts": { 13 | "build": "babel src -d public" 14 | }, 15 | 16 | create .babelrc 17 | 18 | { 19 | "presets": [ 20 | [ 21 | "env", 22 | { 23 | } 24 | ] 25 | ] 26 | } 27 | 28 | NOTE: don't change targets 29 | https://github.com/browserslist/browserslist#best-practices 30 | https://github.com/browserslist/browserslist/pull/254/commits/560100ce358bf9aaf6b1f43a0e020399c1b 31 | ... 32 | 33 | made a sample file (eg. leftpad.js from original article) 34 | 35 | ran this and verified the output in public/src/ 36 | 37 | npm run build 38 | 39 | created src/js/index.js 40 | 41 | import leftPad from './leftpad'; 42 | 43 | const serNos = [6934, 23111, 23114, 1001, 211161]; 44 | const strSNos = serNos.map(sn => leftPad(sn, 8, '0')); 45 | console.log(strSNos); 46 | 47 | do build again and verified output 48 | 49 | npm run build 50 | 51 | install webpack local 52 | 53 | npm install webpack webpack-cli --save-dev 54 | 55 | add this to package.json "scripts" section 56 | 57 | 58 | "build": "webpack --config webpack.config.js" 59 | 60 | renamed existing babel build to "build-babel" 61 | 62 | "build-babel": "babel src -d public", 63 | "build": "webpack --config webpack.config.js", 64 | 65 | 66 | create webpack.config.js 67 | 68 | const path = require("path"); 69 | 70 | module.exports = { 71 | mode: 'development', 72 | entry: "./src/js/index.js", 73 | output: { 74 | path: path.resolve(__dirname, "public"), 75 | filename: "bundle.js" 76 | } 77 | }; 78 | 79 | delete old public/js dir and run build again 80 | 81 | npm run build 82 | 83 | it creates public/bundle.js now 84 | 85 | add transpiling support (ie. babel core and babel loader (webpack) support) 86 | 87 | npm install babel-loader babel-core --save-dev 88 | 89 | modify webpack.config.js adding module section after output section 90 | 91 | module: { 92 | rules: [ 93 | { 94 | test: /\.js$/, 95 | exclude: /(node_modules)/, 96 | use: { 97 | loader: "babel-loader", 98 | options: { 99 | presets: ["babel-preset-env"] 100 | } 101 | } 102 | } 103 | ] 104 | } 105 | 106 | then build again 107 | 108 | npm run build 109 | 110 | gives me an error, maybe got some wrong version, babel docs show this 111 | 112 | npm install --save-dev babel-loader @babel/core 113 | 114 | ok so i exec the above command and it changes package.json devDependencies 115 | then i guess i need to uninstall the old key "babel-core" 116 | 117 | npm uninstall babel-core 118 | 119 | which modifies package.json devDependencies again as now expected 120 | 121 | and i run build again and get a crazy error: 122 | 123 | TypeError: Cannot read property 'bindings' of null 124 | 125 | googling finds some github thread talking about babel 6 vs 7 compat problems 126 | which makes complete sense based on the above ~20 lines 127 | so, yeah... blagh, i gotta figure out what @ means in npm packages 128 | 129 | oh the @ is some kind of official scoping thing 130 | 131 | https://docs.npmjs.com/getting-started/scoped-packages 132 | 133 | like i would literally have to pay money to publish scoped packages under @remzmike/my-library 134 | but i can publish packages named remzmike/my-library for free/normal? 135 | 136 | googling : "should i use babel 6 or 7" 137 | (answer: inconclusive, gonna just try to make 7 work) 138 | 139 | doing this: 140 | 141 | npm uninstall babel-preset-env 142 | npm install @babel/preset-env --save-dev 143 | 144 | 145 | changing .babelrc to: 146 | 147 | { 148 | "presets": ["@babel/preset-env"] 149 | } 150 | 151 | build still fails, same error 152 | 153 | in webpack.config.js, change this line: 154 | presets: ["babel-preset-env"] 155 | to this: 156 | presets: ["@babel/preset-env"] 157 | 158 | then `npm run build` again AND IT FINALLY WORKS 159 | 160 | now creating html file, bundle.html 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | Webpack & Babel Demonstration 169 | 170 | 171 |
172 |

Parts List

173 |
    174 |
    175 | 176 | 177 | 178 | 179 | modify src/js/index.js to be: 180 | 181 | import leftPad from './leftpad'; 182 | 183 | const serNos = [6934, 23111, 23114, 1001, 211161]; 184 | const partEl = document.getElementById('part-list'); 185 | const strList = serNos.reduce( 186 | (acc, element) => acc += `
  1. ${leftPad(element, 8, '0')}
  2. `, '' 187 | ); 188 | 189 | partEl.innerHTML = strList; 190 | 191 | derk, the bundle file is at /public/bundle.js and html says public/js/bundle.js 192 | 193 | so, modify the html so it says /public/bundle.js 194 | 195 | and now visit bundle.html in browser 196 | 197 | and it's a webpack javascript error wee hoo, we killin this 198 | 199 | ...intermission... 200 | 201 | looking at dependency versions 202 | 203 | webpack 4.22 is latest, now reading webpack docs 204 | 205 | changing webpack.config.js "public" output path to "dist" since i like that and know it 206 | leaving the package.json "build-babel" to use a dir called "public" 207 | actually just gonna comment that one for now... 208 | also changed bundle.html to point to dist instead of public 209 | 210 | @babel/core 7.1.2 confirmed as latest 211 | @babel/preset-env 7.1.0 confirmed as latest 212 | babel-cli 6.26 is latest but @babel/cli is 7.1.2 213 | i think this is a 6 vs 7 indicator (if not coincidence) 214 | gonna uninstall this babel-cli i guess... and install the other 215 | 216 | npm install @babel/cli --save-dev 217 | 218 | newly added @babel/cli 7.1.2 confirmed as latest 219 | 220 | babel-loader 8.04 confirmed as latest for babel(v7) 221 | 222 | "This README is for babel-loader v8 + Babel v7 Check the 7.x branch for docs with Babel v6" 223 | so it looks like the babel-loader versions are one higher than the version of babel, BLECH 224 | 225 | webpack-cli 3.1.2 confirmed as latest 226 | 227 | building again fails... but i think i found the problem... 228 | 229 | npm run build 230 | 231 | holy shit oops, leftpad.js didn't have export, adding this to function def 232 | 233 | export default, so it becomes: 234 | export default function leftPad(str, len, ch) { 235 | 236 | build again 237 | 238 | npm run build 239 | 240 | FINALLY! the leftpad sample works 241 | 242 | add watch support, in package.json scripts section 243 | 244 | "watch": "webpack --watch" 245 | 246 | then `npm run watch` to verify, and ctrl-c cancel 247 | 248 | now setup dev server for magic refresh 249 | 250 | npm install webpack-dev-server --save-dev 251 | 252 | add this script... (consider rename for later) 253 | 254 | "start": "webpack --watch & webpack-dev-server --open-page 'webpack-dev-server'" 255 | (with no stupid trailing comma) 256 | ... ermm that's a unix &, so... 257 | changing the line to: 258 | "dev": "webpack-dev-server --open-page bundle.html" 259 | 260 | and run it 261 | 262 | npm run dev 263 | 264 | error in browser 265 | 266 | "Uncaught Error: Cannot find module 'fs'" 267 | 268 | googling... 269 | 270 | i guess i'll just read more webpack info about the "module" section in webpack.config.js 271 | 272 | seems fine, will try to verify babel-loader rule 273 | 274 | this page shows babel 6 vs babel 7 package combos: 275 | https://www.npmjs.com/package/babel-loader 276 | -- 277 | webpack 4.x | babel-loader 8.x | babel 7.x 278 | npm install -D babel-loader @babel/core @babel/preset-env webpack 279 | vs. 280 | 281 | webpack 4.x | babel-loader 7.x | babel 6.x 282 | npm install -D babel-loader@7 babel-core babel-preset-env webpack 283 | 284 | verified that i am using the right packages for babel 7 (babel-loader 8) 285 | ok, so .babelrc can be used to set presets (and other config) instead of as module.rules[i].use.options section in webpack.config.js 286 | 287 | so i got rid of the rules[0].use section and added a rules[0].loader section 288 | 289 | loader: "babel-loader", 290 | 291 | and i verified .babelrc 292 | 293 | `npm run dev` again and it's the same 'fs' browser error 294 | 295 | Uncaught Error: Cannot find module 'fs' 296 | (it's being thrown from Server.js but appears hardcoded) 297 | 298 | at this point i don't know where the code is that throws this error 299 | is it some magicompiled thing called Server.js on the client? 300 | so i guess let's just ignore a web server for now 301 | since this code should work without one anyway 302 | it's flippin simpleui after all, not a server app 303 | 304 | same error when visiting via file:/// 305 | 306 | why is webpack-dev-server code in my bundle anyway? 307 | i mean, im not supposed to have an fs module in the browser anyway am i??? 308 | 309 | i mean, i guess read some setup info on webpack-dev-server now (nevermind) 310 | 311 | SOLVED, forgot to re-build somewhere along the line since i wasn't running watch 312 | 313 | npm run build 314 | 315 | verified it works, by visiting file:/// 316 | 317 | now just use 318 | 319 | npm run watch 320 | 321 | and it will generate what i need... should be done now right? 322 | dare i try to do this again? maybe next time i need to i just refine this doc 323 | 324 | end @ 4:25 PM 10/21/2018 -------------------------------------------------------------------------------- /src/simpleui_driver_pixi_webgl.js: -------------------------------------------------------------------------------- 1 | // this driver is outdated 2 | import * as ui from './simpleui.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | import { parseBMFontAscii } from './bmfont.js'; 5 | import bmfont_definition_mana16 from './bmfont_definition_mana16.js'; 6 | import images_mana16 from './images/mana16.png'; 7 | 8 | const _r = consts._r; 9 | const _g = consts._g; 10 | const _b = consts._b; 11 | const _left = consts._left; 12 | 13 | const bmfont_mana16 = parseBMFontAscii(bmfont_definition_mana16); 14 | const bmfont_mana16_img = new Image(512, 81); 15 | bmfont_mana16_img.src = images_mana16; 16 | 17 | let _mouse_pos = [0 | 0, 0 | 0]; 18 | 19 | function on_mouse_move(evt) { 20 | let rect = canvas.getBoundingClientRect(); 21 | _mouse_pos.x = 0 | (evt.clientX - rect.left); 22 | _mouse_pos.y = 0 | (evt.clientY - rect.top); 23 | } 24 | 25 | function on_mouse_down(evt) { 26 | let x = evt.clientX; 27 | let y = evt.clientY; 28 | let button = evt.button; 29 | ui.on_mousepressed(x, y, button); 30 | } 31 | 32 | function on_mouse_up(evt) { 33 | let x = evt.clientX; 34 | let y = evt.clientY; 35 | let button = evt.button; 36 | ui.on_mousereleased(x, y, button); 37 | } 38 | 39 | // meh 40 | function on_touch_start(evt) { 41 | let x = evt.clientX; 42 | let y = evt.clientY; 43 | ui.on_mousepressed(x, y, _left); 44 | } 45 | 46 | function on_touch_end(evt) { 47 | let x = evt.clientX; 48 | let y = evt.clientX; 49 | ui.on_mousereleased(x, y, _left); 50 | } 51 | 52 | /* -------------------------------------------------------------------------- */ 53 | 54 | function GetCursorX() { 55 | return 0 | _mouse_pos.x; 56 | } 57 | 58 | function GetCursorY() { 59 | return 0 | _mouse_pos.y; 60 | } 61 | 62 | function GetFontSize() { 63 | return 0 | 14; 64 | } 65 | 66 | let fonts = [ 67 | { name: 'sans-serif', size: 14, line_size: 14 }, 68 | ]; 69 | function DrawText_Stroke(text, x, y, color) { 70 | let fontsize = GetFontSize(); 71 | let font = fonts[0]; 72 | context.font = font.size + "px '" + font.name + "'"; 73 | if (color == null) { 74 | color = ui.Color(255, 255, 255, 255); 75 | } 76 | context.fillStyle = ui.make_css_color(color); 77 | let yoffset = fontsize - 2; 78 | context.fillText(text, x, y + yoffset); 79 | } 80 | 81 | function DrawText_Bitmap(text, x, y, color) { 82 | for (let i = 0; i < text.length; i++) { 83 | let idx = text.charCodeAt(i); 84 | let def = bmfont_mana16.chars[idx - 31]; 85 | 86 | // replace unknown chars with ? 87 | if (!def) { 88 | idx = "?".charCodeAt(0); 89 | def = bmfont_mana16.chars[idx - 31]; 90 | } 91 | 92 | /*context.drawImage( 93 | bmfont_mana16_img, 94 | def.x, def.y, def.width, def.height, 95 | x + def.xoffset, y + def.yoffset, def.width, def.height 96 | );*/ 97 | x += def.xadvance; 98 | } 99 | } 100 | 101 | const _pixi_text_objects = {}; 102 | 103 | function DrawText_PixiText(text, x, y, color) { 104 | let int_color = color[_r] << 16 | color[_g] << 8 | color[_b]; 105 | const key = `${text}:${int_color};${x}x${y}`; 106 | let o = _pixi_text_objects[key]; 107 | if (!o) { 108 | o = new PIXI.Text( 109 | text, 110 | { fontFamily: 'Arial', fontSize: 14, fill: int_color } 111 | ); 112 | _pixi_text_objects[key] = o; 113 | } 114 | o.x = x; 115 | o.y = y; 116 | pixi_app.stage.addChild(o); 117 | } 118 | 119 | function DrawText_Original(text, x, y, color) { // 10-12 ms ff 120 | if (ui.config.drawtext_bitmap) { 121 | DrawText_Bitmap(text, x, y, color); 122 | } else { 123 | DrawText_Stroke(text, x, y, color); 124 | } 125 | } 126 | 127 | function DrawBoxInternal(rect, color, soft) { 128 | 129 | const x = rect.x; 130 | const y = rect.y; 131 | const width = rect.w; 132 | const height = rect.h; 133 | 134 | //if (color) { 135 | const rgb = color[_r] << 16 | color[_g] << 8 | color[_b] << 0; 136 | context.beginFill(rgb); //, color.a/255); 137 | //graphics.beginFill(0xFF3300); 138 | //graphics.lineStyle(4, 0xffd900, 1); 139 | 140 | //} 141 | 142 | /* 143 | // test texture resampling from white pixel in font 144 | context.drawImage( 145 | bmfont_mana16_img, 146 | 2, 2, 1, 1, 147 | rect.x, rect.y, rect.w, rect.h, 148 | );*/ 149 | 150 | if (soft) { 151 | const lines = 1; // adjustor 152 | context.fillRect(x, y + lines, width, height - (lines * 2)); // mid 153 | 154 | //for (let i=1; i<=lines; i++) { 155 | 156 | // faster than making a path, in pixi anyway 157 | const i = 1; 158 | const top_x1 = x + i; 159 | const top_y1 = y + (lines - i); 160 | const top_w = width - (i * 2); 161 | context.fillRect(top_x1, top_y1, top_w, 1); //top 162 | const bot_x1 = x + i; 163 | const bot_y1 = y + height - 1 - (lines - i); 164 | const bot_w = width - (i * 2); 165 | context.fillRect(bot_x1, bot_y1, bot_w, 1); //bot 166 | 167 | } else { 168 | context.fillRect(x, y, width, height); 169 | } 170 | 171 | let use_gradient = ui.config.drawbox_gradient_enable; 172 | 173 | if (use_gradient) { 174 | let is_size_excluded = width > 200 || height > 200; // || (width<20 && height<20); 175 | if (is_size_excluded) { 176 | use_gradient = false; 177 | } 178 | } 179 | const disabled_for_gl_port = true; 180 | if (!disabled_for_gl_port && use_gradient) { 181 | context.translate(x, y); // for gradient 182 | context.fillStyle = ui.config.drawbox_gradient; 183 | context.fillRect(1, 1, width - 2, height - 2); 184 | context.translate(-x, -y); // this appears to be faster than wrapping save/restore :-> 185 | } 186 | 187 | } 188 | 189 | function DrawBox(rect, color) { 190 | return DrawBoxInternal(rect, color, 0 | false); 191 | } 192 | 193 | function DrawRoundedBox(rect, color) { 194 | return DrawBoxInternal(rect, color, 0 | true); 195 | } 196 | 197 | const DrawCircle = DrawBox; 198 | 199 | function DrawLine(x1, y1, x2, y2) { 200 | x1 = 0 | x1; 201 | y1 = 0 | y1; 202 | x2 = 0 | x2; 203 | y2 = 0 | y2; 204 | context.lineStyle(1, 0xFFFFFF, 1, 0); 205 | context.moveTo(x1, y1); 206 | context.lineTo(x2, y2); 207 | } 208 | 209 | const DrawText = DrawText_PixiText; 210 | //const DrawText = DrawText_Bitmap; 211 | 212 | // -------------------------------------------------------- // 213 | 214 | let canvas; 215 | let context; 216 | let pixi_app; 217 | 218 | function initialize(canvasId) { 219 | // {LINEAR: 0 (default), NEAREST: 1} 220 | PIXI.settings.SCALE_MODE = PIXI.SCALE_MODES.LINEAR; // doesnt really matter wrt antialiasing 221 | 222 | console.log('window.devicePixelRatio', window.devicePixelRatio); 223 | 224 | pixi_app = new PIXI.Application({ 225 | width: 256, 226 | height: 256, 227 | antialias: false, // enables extremely weak antialiasing 228 | forceFXAA: false, 229 | transparent: true, 230 | resolution: 1, //window.devicePixelRatio, 231 | }); 232 | pixi_app.renderer.roundPixels = true; 233 | console.log(pixi_app.renderer); 234 | 235 | canvas = pixi_app.view; 236 | document.body.appendChild(canvas); 237 | 238 | context = new PIXI.Graphics(); 239 | 240 | context.fillRect = function (x, y, w, h) { 241 | context.lineStyle(0, 0xffffff, 1, 0); 242 | context.drawRect(x, y, w, h); 243 | }; 244 | context.save = function () { }; 245 | context.restore = function () { }; 246 | context.clip = function () { }; 247 | context.rect = function () { }; 248 | context.translate = function () { }; 249 | context.stroke = function () { }; 250 | context.beginPath = function () { }; 251 | context.setLineDash = function () { }; 252 | 253 | //pixi_app.stage.addChild(graphics); 254 | 255 | canvas.addEventListener('mousemove', on_mouse_move, false); 256 | // touch move? (NO!) 257 | 258 | canvas.addEventListener('mousedown', on_mouse_down, false); 259 | canvas.addEventListener('touchstart', on_touch_start, { capture: false, passive: true }); 260 | 261 | canvas.addEventListener('mouseup', on_mouse_up, false); 262 | canvas.addEventListener('touchend', on_touch_end, false); 263 | } 264 | 265 | function GetClientWidth() { 266 | return 0 | canvas.width; 267 | } 268 | 269 | function GetClientHeight() { 270 | return 0 | canvas.height; 271 | } 272 | 273 | function GetContext() { 274 | return context; 275 | } 276 | 277 | function GetCanvas() { 278 | return canvas; 279 | } 280 | 281 | function UpdateSize() { 282 | let w = window.innerWidth - app.canvas_size_hack; 283 | let h = window.innerHeight - app.canvas_size_hack; 284 | pixi_app.renderer.resize(w, h); 285 | } 286 | 287 | function FrameClear() { 288 | let stage = pixi_app.stage; 289 | for (var i = stage.children.length - 1; i >= 0; i--) { stage.removeChild(stage.children[i]); } 290 | context.clear(); 291 | stage.addChild(context); 292 | } 293 | 294 | const config = { 295 | has_drawbox_gradient: 0 | false, 296 | }; 297 | 298 | // https://stackoverflow.com/a/36673184 299 | function IsTouchDevice() { 300 | return (navigator.maxTouchPoints || 'ontouchstart' in document.documentElement); 301 | } 302 | 303 | export { 304 | initialize, 305 | config, 306 | DrawBox, 307 | DrawRoundedBox, 308 | DrawText, 309 | DrawLine, 310 | DrawCircle, 311 | GetCursorX, 312 | GetCursorY, 313 | GetFontSize, 314 | GetClientHeight, 315 | GetClientWidth, 316 | GetContext, 317 | GetCanvas, 318 | UpdateSize, 319 | //CreateDrawboxGradient, 320 | FrameClear, 321 | IsTouchDevice, 322 | }; -------------------------------------------------------------------------------- /src/simpleui_app_plasma.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | import { do_panel_begin, do_panel_end } from './simpleui_ex_panel.js'; 5 | 6 | const _r = consts._r; 7 | const _g = consts._g; 8 | const _b = consts._b; 9 | const _a = consts._a; 10 | const _none = consts._none; 11 | const _vertical = consts._vertical; 12 | const _horizontal = consts._horizontal; 13 | 14 | const ColorP = ui.ColorP; 15 | const PointP = ui.PointP; 16 | const RectangleP = ui.RectangleP; 17 | 18 | function sum(a) { 19 | let result = 0|0; 20 | for (let i = 0; i < a.length; i++) { 21 | let v = a[i]; 22 | result = result + v; 23 | } 24 | return result; 25 | } 26 | console.assert(sum([1, 2, 3]) == 6); 27 | 28 | const _sincos_none = 0; 29 | const _sincos_sin = 1; 30 | const _sincos_cos = 2; 31 | 32 | let _plasma_image; 33 | let _plasma_data; 34 | let _plasma_time = 0; 35 | let _plasma_step = 0; 36 | 37 | let _plasma_r_div = 59; 38 | let _plasma_g_div = 59; 39 | let _plasma_b_div = 64; 40 | let _plasma_r_sincos = _sincos_sin; 41 | let _plasma_g_sincos = _sincos_cos; 42 | let _plasma_b_sincos = _sincos_sin; 43 | let _plasma_r_invert = true; 44 | let _plasma_g_invert = false; 45 | let _plasma_b_invert = true; 46 | 47 | let _plasma_zoom; 48 | let _plasma_zoom_slider; 49 | let _plasma_mult; 50 | let _plasma_mult_slider; 51 | let _plasma_step_interval = 4; 52 | 53 | set_plasma_zoom(213); 54 | set_plasma_mult(120); 55 | 56 | let _plasma_palette; 57 | 58 | function plasma_init() 59 | { 60 | let r, g, b; 61 | let r_enabled = true; 62 | let g_enabled = true; 63 | let b_enabled = true; 64 | 65 | _plasma_palette = []; 66 | 67 | let r_sincos; 68 | if (_plasma_r_sincos == _sincos_sin) { 69 | r_sincos = Math.sin; 70 | } else if (_plasma_r_sincos == _sincos_cos) { 71 | r_sincos = Math.cos; 72 | } else { 73 | r_enabled = false; 74 | } 75 | 76 | let g_sincos; 77 | if (_plasma_g_sincos == _sincos_sin) { 78 | g_sincos = Math.sin; 79 | } else if (_plasma_g_sincos == _sincos_cos) { 80 | g_sincos = Math.cos; 81 | } else { 82 | g_enabled = false; 83 | } 84 | 85 | let b_sincos; 86 | if (_plasma_b_sincos == _sincos_sin) { 87 | b_sincos = Math.sin; 88 | } else if (_plasma_b_sincos == _sincos_cos) { 89 | b_sincos = Math.cos; 90 | } else { 91 | b_enabled = false; 92 | } 93 | 94 | for (let i = 0; i < 256; i++) 95 | { 96 | // original: eg. r = sin2byte( Math.cos(Math.PI * i / 128) ); 97 | if (r_enabled) { 98 | r = sin2byte( r_sincos(Math.PI * i / _plasma_r_div) ); 99 | if (_plasma_r_invert) { 100 | r = 255 - r; 101 | } 102 | } else { 103 | r = 0; 104 | } 105 | 106 | if (g_enabled) { 107 | g = 255 - sin2byte( g_sincos(Math.PI * i / _plasma_g_div) ); 108 | if (_plasma_g_invert) { 109 | g = 255 - g; 110 | } 111 | } else { 112 | g = 0; 113 | } 114 | 115 | if (b_enabled) { 116 | b = 255 - sin2byte( b_sincos(Math.PI * i / _plasma_b_div) ); 117 | if (_plasma_b_invert) { 118 | b = 255 - b; 119 | } 120 | } else { 121 | b = 0; 122 | } 123 | 124 | _plasma_palette.push([r, g, b]); 125 | } 126 | } 127 | 128 | plasma_init(); 129 | 130 | function sin2byte(v) 131 | { 132 | return Math.trunc((v + 1) * 255 / 2); 133 | } 134 | 135 | function distance(x1, y1, x2, y2) 136 | { 137 | var dx = x2 - x1; 138 | var dy = y2 - y1; 139 | var c = dx * dx + dy * dy; 140 | return Math.sqrt(c); 141 | } 142 | 143 | // slider values normalized to 0-1000, and represent a percent along the range of possible values 144 | function set_plasma_zoom(value) { 145 | _plasma_zoom_slider = value; 146 | const percent = _plasma_zoom_slider / 1000; 147 | _plasma_zoom = 1 + 99 * percent; // [1.0 - 100.0] 148 | } 149 | function set_plasma_mult(value) { 150 | _plasma_mult_slider = value; 151 | const percent = _plasma_mult_slider / 1000; 152 | _plasma_mult = percent * 2; // [0.0 - 2.0] 153 | } 154 | 155 | function get_color(x, y, t) 156 | { 157 | const v1 = Math.sin(x * _plasma_mult / _plasma_zoom + t); 158 | const v2 = Math.sin( distance(x, y, 128, 128) / _plasma_zoom); 159 | const v = sin2byte( (v1 + v2) / 2 ); 160 | return _plasma_palette[v]; 161 | } 162 | 163 | function do_app_plasma() { 164 | let expanded = !ui.driver.IsTouchDevice(); 165 | 166 | const row_x0 = 222; 167 | const row_y0 = 47; 168 | do_plasma_panel('plasma panel', row_x0, row_y0, true, expanded); 169 | } 170 | 171 | function do_plasma_panel(uiid, first_x, first_y, first_visible, first_expanded) { 172 | let _; 173 | const dim_x = 512; 174 | const dim_y = 512 + 20 + 4 + 2 + 1 + 1; 175 | 176 | if (_plasma_image == null) { 177 | _plasma_image = ui.context.createImageData(dim_x, dim_y); 178 | _plasma_data = _plasma_image.data; 179 | } 180 | 181 | let panel = do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded); 182 | 183 | if (panel.expanded) { 184 | const horizontal = ui.layout_push(_horizontal); 185 | 186 | _plasma_step++; 187 | 188 | if (_plasma_step % _plasma_step_interval == 0) { // perf: update every x-th frame 189 | _plasma_time = _plasma_time % 0x7fffffff; 190 | _plasma_time++; 191 | let color; 192 | for (let x = 0; x < dim_x; x++) { 193 | for (let y = 0; y < dim_y; y++) { 194 | color = get_color(x, y, _plasma_time / 60); 195 | const i = 0 | x * 4 + y * dim_x * 4; 196 | _plasma_data[i + 0] = 0 | color[0]; //x; 197 | _plasma_data[i + 1] = 0 | color[1]; //y; 198 | _plasma_data[i + 2] = 0 | color[2]; //-y; 199 | _plasma_data[i + 3] = 0 | 255; 200 | } 201 | } 202 | } 203 | 204 | ui.context.putImageData(_plasma_image, horizontal.x, horizontal.y); 205 | ui.layout_increment2(dim_x + 20, dim_y); 206 | 207 | const vertical = ui.layout_push(_vertical); 208 | 209 | // todo: inset on a darker padded rect? 210 | 211 | ui.layout_push(_horizontal); 212 | { 213 | ui.label('zoom: ' + _plasma_zoom_slider, Rectangle(0, 0, 100, 20)); 214 | _ = ui.slider(uiid + '-zoom-slider', Rectangle(0, 0, 200, 20), 0, 1000, _plasma_zoom_slider, ''); 215 | if (_.changed) { 216 | set_plasma_zoom(_.value); 217 | } 218 | } 219 | ui.layout_pop(); 220 | 221 | ui.layout_push(_horizontal) 222 | { 223 | ui.label('mult: ' + _plasma_mult_slider, Rectangle(0, 0, 100, 20)); 224 | _ = ui.slider(uiid + '-mult-slider', Rectangle(0, 0, 200, 20), 0, 1000, _plasma_mult_slider, ''); 225 | if (_.changed) { 226 | set_plasma_mult(_.value); 227 | } 228 | } 229 | ui.layout_pop(); 230 | 231 | ui.layout_increment2(0, 4); 232 | 233 | _ = do_slider2d(uiid + '-slider2d', RectangleP(0, 0, 300, 200), 0, 1000, PointP(_plasma_zoom_slider, _plasma_mult_slider)); 234 | if (_.changed) { 235 | set_plasma_zoom(_.x1); 236 | set_plasma_mult(_.y1); 237 | } 238 | 239 | ui.layout_increment2(0, 4); 240 | 241 | ui.layout_push(_horizontal); 242 | { 243 | ui.label('r invert', RectangleP(0,0,100,20)); 244 | _ = ui.checkbox(uiid + '-checkbox-r-invert', RectangleP(0,0,20,20), _plasma_r_invert); 245 | if (_.changed) { 246 | _plasma_r_invert = _.value; 247 | plasma_init(); 248 | } 249 | } 250 | ui.layout_pop(); 251 | 252 | ui.layout_push(_horizontal); 253 | { 254 | ui.label('g invert', RectangleP(0,0,100,20)); 255 | _ = ui.checkbox(uiid + '-checkbox-g-invert', RectangleP(0,0,20,20), _plasma_g_invert); 256 | if (_.changed) { 257 | _plasma_g_invert = _.value; 258 | plasma_init(); 259 | } 260 | } 261 | ui.layout_pop(); 262 | 263 | ui.layout_push(_horizontal); 264 | { 265 | ui.label('b invert', RectangleP(0,0,100,20)); 266 | _ = ui.checkbox(uiid + '-checkbox-b-invert', RectangleP(0,0,20,20), _plasma_b_invert); 267 | if (_.changed) { 268 | _plasma_b_invert = _.value; 269 | plasma_init(); 270 | } 271 | } 272 | ui.layout_pop(); 273 | 274 | ui.layout_push(_horizontal); 275 | { 276 | ui.label('r div', RectangleP(0,0,100,20)); 277 | _ = ui.slider(uiid + '-slider-r-div', RectangleP(0,0,200,20), 2, 360, _plasma_r_div, ''); 278 | if (_.changed) { 279 | _plasma_r_div = _.value; 280 | plasma_init(); 281 | } 282 | } 283 | ui.layout_pop(); 284 | 285 | ui.layout_push(_horizontal); 286 | { 287 | ui.label('g div', RectangleP(0,0,100,20)); 288 | _ = ui.slider(uiid + '-slider-g-div', RectangleP(0,0,200,20), 2, 360, _plasma_g_div, ''); 289 | if (_.changed) { 290 | _plasma_g_div = _.value; 291 | plasma_init(); 292 | } 293 | } 294 | ui.layout_pop(); 295 | 296 | ui.layout_push(_horizontal); 297 | { 298 | ui.label('b div', RectangleP(0,0,100,20)); 299 | _ = ui.slider(uiid + '-slider-b-div', RectangleP(0,0,200,20), 2, 360, _plasma_b_div, ''); 300 | if (_.changed) { 301 | _plasma_b_div = _.value; 302 | plasma_init(); 303 | } 304 | } 305 | ui.layout_pop(); 306 | 307 | ui.layout_increment2(0, 4); 308 | _ = ui.button(uiid + '-button-randomize', 'randomize', Rectangle(0,0,300,40)); 309 | if (_.clicked) { 310 | // zoom: 0-1000 311 | const random_zoom = Math.trunc( Math.random() * 1000 ); 312 | set_plasma_zoom(random_zoom); 313 | // mult: 0-1000 314 | const random_mult = Math.trunc( Math.random() * 1000 ); 315 | set_plasma_mult(random_mult); 316 | // step interval: 1-8 (not gonna randomize this) 317 | // rgb sincos: 0-2 318 | //_plasma_r_sincos = randomInt(0,2); 319 | //_plasma_g_sincos = randomInt(0,2); 320 | //_plasma_b_sincos = randomInt(0,2); 321 | // rgb invert: 0-1 322 | _plasma_r_invert = Math.random() < 0.5 ? true : false; 323 | _plasma_g_invert = Math.random() < 0.5 ? true : false; 324 | _plasma_b_invert = Math.random() < 0.5 ? true : false; 325 | // rgb div: 2-360 326 | _plasma_r_div = randomInt(2,360); 327 | _plasma_g_div = randomInt(2,360); 328 | _plasma_b_div = randomInt(2,360); 329 | plasma_init(); 330 | } 331 | 332 | // --- 333 | 334 | ui.layout_increment2(0, 4); 335 | ui.hline(300, 1, uidraw.normal_face); 336 | ui.layout_increment2(0, 4); 337 | 338 | ui.layout_push(_horizontal) 339 | { 340 | ui.label('step interval', RectangleP(0,0,100,20)); 341 | _ = ui.slider(uiid + '-step-slider', Rectangle(0, 0, 200, 20), 1, 8, _plasma_step_interval, ''); 342 | if (_.changed) { 343 | _plasma_step_interval = _.value; 344 | } 345 | } 346 | ui.layout_pop(); 347 | 348 | _ = do_slider_sincos(uiid + '-slider-sincos-r', 'r', _plasma_r_sincos); 349 | if (_.changed) { 350 | _plasma_r_sincos = _.value; 351 | console.log('_plasma_r_sincos', _.value); 352 | plasma_init(); 353 | } 354 | 355 | _ = do_slider_sincos(uiid + '-slider-sincos-g', 'g', _plasma_g_sincos); 356 | if (_.changed) { 357 | _plasma_g_sincos = _.value; 358 | console.log('_plasma_g_sincos', _.value); 359 | plasma_init(); 360 | } 361 | 362 | _ = do_slider_sincos(uiid + '-slider-sincos-b', 'b', _plasma_b_sincos); 363 | if (_.changed) { 364 | _plasma_b_sincos = _.value; 365 | console.log('_plasma_b_sincos', _.value); 366 | plasma_init(); 367 | } 368 | 369 | ui.layout_pop(); // vertical 370 | 371 | ui.layout_pop(); // horizontal 372 | 373 | } // panel.expanded 374 | 375 | do_panel_end(uiid); 376 | return panel; 377 | } 378 | 379 | function do_slider_sincos(uiid, key, value) { 380 | let _; 381 | 382 | let state = ui.get_state(uiid); 383 | if (!state) { 384 | state = ui.set_state(uiid, { 385 | changed: 0 | false, 386 | value: 0 | value 387 | }); 388 | } 389 | 390 | ui.layout_push(_horizontal) 391 | { 392 | ui.label(key + ' sin/cos', RectangleP(0,0,100,20)); 393 | _ = ui.slider(uiid + '-slider', Rectangle(0, 0, 200, 20), 0, 2, value, ''); 394 | if (_.changed) { 395 | state.changed = true; 396 | state.value = _.value; 397 | } 398 | } 399 | ui.layout_pop(); 400 | 401 | return state; 402 | } // do_slider_sincos 403 | 404 | function randomInt(a, b) { 405 | const delta = b - a; 406 | const rand = Math.floor(Math.random() * delta); 407 | return a + rand; 408 | } 409 | 410 | export { 411 | do_app_plasma 412 | }; -------------------------------------------------------------------------------- /src/simpleui_driver_html5_canvas.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | import { parseBMFontAscii } from './bmfont.js'; 5 | import bmfont_definition_mana16 from './bmfont_definition_mana16.js'; 6 | import images_mana16 from './images/mana16.png'; 7 | 8 | const bmfont_mana16 = parseBMFontAscii(bmfont_definition_mana16); 9 | const bmfont_mana16_img = new Image(512, 81); 10 | bmfont_mana16_img.src = images_mana16; 11 | 12 | const _r = consts._r; 13 | const _g = consts._g; 14 | const _b = consts._b; 15 | const _a = consts._a; 16 | 17 | // these are set in initialize 18 | let context; 19 | let canvas; 20 | 21 | let _mouse_pos = ui.Point(0 | -1, 0 | -1); // -1 to put it out of bounds... idk 22 | 23 | function on_mouse_move(evt) { 24 | _mouse_pos.x = 0 | (evt.offsetX); 25 | _mouse_pos.y = 0 | (evt.offsetY); 26 | } 27 | 28 | function on_mouse_down(evt) { 29 | ui.on_mousepressed(evt.clientX, evt.clientY, evt.button); 30 | } 31 | 32 | function on_mouse_up(evt) { 33 | ui.on_mousereleased(evt.clientX, evt.clientY, evt.button); 34 | } 35 | 36 | // meh 37 | function on_touch_start(evt) { 38 | ui.on_mousepressed(evt.clientX, evt.clientY, consts._left); 39 | } 40 | 41 | function on_touch_end(evt) { 42 | ui.on_mousereleased(evt.clientX, evt.clientY, consts._left); 43 | } 44 | 45 | /* -------------------------------------------------------------------------- */ 46 | 47 | function GetCursorX() { 48 | return 0 | _mouse_pos.x; 49 | } 50 | 51 | function GetCursorY() { 52 | return 0 | _mouse_pos.y; 53 | } 54 | 55 | function GetFontSize() { 56 | return 0 | 14; 57 | } 58 | 59 | function DrawText_Stroke(text, x, y, color) { 60 | let fontsize = GetFontSize(); 61 | if (color == null) { // todo: lose this kind of overloading for perf reasons 62 | color = ui.Color(255, 255, 255, 255); 63 | } 64 | context.font = "16px Arial"; 65 | context.fillStyle = ui.make_css_color(color); 66 | let yoffset = fontsize; 67 | context.fillText(text, x, y + yoffset); 68 | } 69 | 70 | function DrawText_Bitmap(text, x, y, color) { 71 | for (let i = 0; i < text.length; i++) { 72 | let idx = text.charCodeAt(i); 73 | let def = bmfont_mana16.chars[idx - 31]; 74 | 75 | // replace unknown chars with ? 76 | if (!def) { 77 | idx = "?".charCodeAt(0); 78 | def = bmfont_mana16.chars[idx - 31]; 79 | } 80 | 81 | context.drawImage( 82 | bmfont_mana16_img, 83 | def.x, def.y, def.width, def.height, 84 | x + def.xoffset, y + def.yoffset, def.width, def.height 85 | ); 86 | x += def.xadvance; 87 | } 88 | } 89 | 90 | // new idea, cache bitmaps instead of worrying about batching 91 | let _drawtext_cache = {}; 92 | function DrawText_Cached(text, x, y, color) { 93 | 94 | // draw normal if it's a number? 95 | //return DrawText_Original(text, x, y, color); 96 | //return; 97 | 98 | let key = text; 99 | if (!(key in _drawtext_cache)) { 100 | 101 | let prev_context = context; 102 | let prev_canvas = canvas; 103 | 104 | let cv = document.createElement('canvas'); 105 | 106 | let ctx = cv.getContext('2d'); 107 | cv.width = text.length * 10; 108 | cv.height = 20; 109 | //document.body.appendChild(cv); 110 | let o = { 'canvas': cv, 'context': ctx }; 111 | 112 | // draw into custom 113 | canvas = o.canvas; 114 | context = o.context; 115 | { 116 | DrawText_Bitmap(text, 0, 0, color); 117 | } 118 | context = prev_context; 119 | canvas = prev_canvas; 120 | 121 | _drawtext_cache[key] = o; 122 | } 123 | 124 | // now just draw the cached canvas onto screen canvas 125 | context.drawImage(_drawtext_cache[key].canvas, x, y); 126 | 127 | } 128 | 129 | function DrawText_Dynamic(text, x, y, color) { // 10-12 ms ff 130 | if (ui.config.drawtext_bitmap) { 131 | if (true) { 132 | DrawText_Cached(text + '', x, y, color); 133 | } else { 134 | DrawText_Bitmap(text + '', x, y, color); 135 | } 136 | } else { 137 | DrawText_Stroke(text + '', x, y, color); 138 | } 139 | } 140 | let DrawText = DrawText_Dynamic; // cached works now, and does increase performance. 141 | 142 | //let _gradient_bitmap; 143 | //let _gradient_bitmap_requested = 0 | false; 144 | function _DrawBoxGradient(x, y, w, h) { 145 | //let use_gradient = 0 | ui.config.drawbox_gradient_enable; 146 | //if (!use_gradient) return; 147 | 148 | //if (_gradient_bitmap) { 149 | //if ( 0 | w > 200 || h > 200) { 150 | //use_gradient = false; 151 | //} else { 152 | /*context.drawImage( // sllllooooooooooooooooooooooow 153 | _gradient_bitmap, 154 | 1, 1, w - 2, h - 2, 155 | x + 1, y + 1, w - 2, h - 2, 156 | );*/ 157 | context.translate(0 | x, 0 | y); // slow 158 | context.fillStyle = ui.config.drawbox_gradient; // slow 159 | context.fillRect(0 | 1, 0 | 1, 0 | w - 2, 0 | h - 2); // fast 160 | context.translate(0 | -x, 0 | -y); // slow 161 | // } 162 | /*} else { 163 | if (_gradient_bitmap_requested) { 164 | // pass 165 | } else { 166 | const grad_canvas = new OffscreenCanvas(200, 200); 167 | const grad_context = grad_canvas.getContext('2d'); 168 | grad_context.fillStyle = ui.config.drawbox_gradient; 169 | //grad_context.fillRect(1,1,w-2,h-2); // cant do this for texture, needs to happen before this call 170 | grad_context.fillRect(0,0,w,h); 171 | createImageBitmap(grad_canvas).then(function(bmp) { 172 | _gradient_bitmap = bmp; 173 | }); 174 | _gradient_bitmap_requested = 0 | true; 175 | } 176 | }*/ 177 | } 178 | 179 | function DrawBox(rect, color) { 180 | context.fillStyle = ui.make_css_color(color); 181 | context.fillRect(rect.x, rect.y, rect.w, rect.h); 182 | } 183 | 184 | function DrawBox3d(rect, color) { 185 | DrawBox(rect, color); 186 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 187 | } 188 | 189 | function DrawBoxOutline(rect, color) { 190 | context.strokeStyle = ui.make_css_color(color); 191 | if (context.lineWidth % 2 == 0) { 192 | context.strokeRect(rect.x, rect.y, rect.w, rect.h); 193 | } else { 194 | context.strokeRect(rect.x - 0.5, rect.y - 0.5, rect.w, rect.h); 195 | } 196 | 197 | 198 | } 199 | 200 | function DrawBoxSoft(rect, color) { 201 | let x = 0 | rect.x; // aliases like this dont seem to be a perf issue 202 | let y = 0 | rect.y; 203 | let w = 0 | rect.w; 204 | let h = 0 | rect.h; 205 | 206 | context.fillStyle = ui.make_css_color(color); 207 | context.fillRect(x, y + 1, w, h - (1 * 2)); // mid 208 | 209 | // top 210 | context.fillRect( 211 | x + 1, 212 | y, 213 | w - (1 * 2), 214 | 1 215 | ); 216 | // bot 217 | context.fillRect( 218 | x + 1, 219 | y + h - 1, 220 | w - (1 * 2), 221 | 1 222 | ); 223 | } 224 | 225 | function DrawBox3dSoft(rect, color) { 226 | DrawBoxSoft(rect, color); 227 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 228 | } 229 | 230 | function DrawBoxSoftRight(rect, color) { 231 | let x = 0 | rect.x; 232 | let y = 0 | rect.y; 233 | let w = 0 | rect.w; 234 | let h = 0 | rect.h; 235 | 236 | context.fillStyle = ui.make_css_color(color); 237 | 238 | const lines = 1; // adjustor 239 | context.fillRect(x, y + lines, w, h - (lines * 2)); // mid 240 | 241 | // faster than making a path, in pixi anyway 242 | const i = 1; 243 | const top_x1 = x + i - 1; 244 | const top_y1 = y + (lines - i); 245 | const top_w = w - (i * 2) + 1; 246 | context.fillRect(top_x1, top_y1, top_w, 1); //top 247 | const bot_x1 = x + i - 1; 248 | const bot_y1 = y + h - 1 - (lines - i); 249 | const bot_w = w - (i * 2) + 1; 250 | context.fillRect(bot_x1, bot_y1, bot_w, 1); //bot 251 | } 252 | 253 | function DrawBox3dSoftRight(rect, color) { 254 | DrawBoxSoftRight(rect, color); 255 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 256 | } 257 | 258 | function DrawBoxSoftLeft(rect, color) { 259 | let x = 0 | rect.x; 260 | let y = 0 | rect.y; 261 | let w = 0 | rect.w; 262 | let h = 0 | rect.h; 263 | 264 | context.fillStyle = ui.make_css_color(color); 265 | 266 | const lines = 1; // adjustor 267 | context.fillRect(x, y + lines, w, h - (lines * 2)); // mid 268 | 269 | // faster than making a path, in pixi anyway 270 | const i = 1; 271 | const top_x1 = x + i - 0; 272 | const top_y1 = y + (lines - i); 273 | const top_w = w - (i * 2) + 1; 274 | context.fillRect(top_x1, top_y1, top_w, 1); //top 275 | const bot_x1 = x + i - 0; 276 | const bot_y1 = y + h - 1 - (lines - i); 277 | const bot_w = w - (i * 2) + 1; 278 | context.fillRect(bot_x1, bot_y1, bot_w, 1); //bot 279 | } 280 | 281 | function DrawBox3dSoftLeft(rect, color) { 282 | DrawBoxSoftLeft(rect, color); 283 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 284 | } 285 | 286 | function DrawBoxSoftTop(rect, color) { 287 | let x = 0 | rect.x; 288 | let y = 0 | rect.y; 289 | let w = 0 | rect.w; 290 | let h = 0 | rect.h; 291 | 292 | context.fillStyle = ui.make_css_color(color); 293 | 294 | const lines = 1; // adjustor 295 | context.fillRect(x, y + lines, w, h - (lines * 2)); // mid 296 | 297 | // faster than making a path, in pixi anyway 298 | const i = 1; 299 | const top_x1 = x + i; 300 | const top_y1 = y + (lines - i); 301 | const top_w = w - (i * 2); 302 | context.fillRect(top_x1, top_y1, top_w, 1); //top 303 | const bot_x1 = x + i - 1; 304 | const bot_y1 = y + h - 1 - (lines - i); 305 | const bot_w = w - (i * 2) + 2; 306 | context.fillRect(bot_x1, bot_y1, bot_w, 1); //bot 307 | } 308 | 309 | function DrawBox3dSoftTop(rect, color) { 310 | DrawBoxSoftTop(rect, color); 311 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 312 | } 313 | 314 | function DrawBoxSoftBottom(rect, color) { 315 | let x = 0 | rect.x; 316 | let y = 0 | rect.y; 317 | let w = 0 | rect.w; 318 | let h = 0 | rect.h; 319 | 320 | context.fillStyle = ui.make_css_color(color); 321 | 322 | const lines = 1; // adjustor 323 | context.fillRect(x, y + lines, w, h - (lines * 2)); // mid 324 | 325 | // faster than making a path, in pixi anyway 326 | const i = 1; 327 | const top_x1 = x + i - 1; 328 | const top_y1 = y + (lines - i); 329 | const top_w = w - (i * 2) + 2; 330 | context.fillRect(top_x1, top_y1, top_w, 1); //top 331 | const bot_x1 = x + i; 332 | const bot_y1 = y + h - 1 - (lines - i); 333 | const bot_w = w - (i * 2); 334 | context.fillRect(bot_x1, bot_y1, bot_w, 1); //bot 335 | } 336 | 337 | function DrawBox3dSoftBottom(rect, color) { 338 | DrawBoxSoftBottom(rect, color); 339 | _DrawBoxGradient(0 | rect.x, 0 | rect.y, 0 | rect.w, 0 | rect.h); 340 | } 341 | 342 | function DrawCircle(rect, color) { 343 | const radius = 0 | rect.w / 2; 344 | const cx = 0 | rect.x + radius; 345 | const cy = 0 | rect.y + radius; 346 | 347 | context.beginPath(); 348 | context.fillStyle = ui.make_css_color(color); 349 | context.arc(cx, cy, radius, 0, 2 * Math.PI, false); 350 | context.fill(); 351 | } 352 | 353 | function DrawCircleOutline(rect, color) { 354 | const radius = 0 | rect.w / 2; 355 | const cx = 0 | rect.x + radius; 356 | const cy = 0 | rect.y + radius; 357 | 358 | context.beginPath(); 359 | context.strokeStyle = ui.make_css_color(color); 360 | context.arc(cx, cy, radius, 0, 2 * Math.PI, false); 361 | context.stroke(); 362 | } 363 | 364 | function DrawLine(x1, y1, x2, y2) { 365 | x1 = 0 | x1; 366 | y1 = 0 | y1; 367 | x2 = 0 | x2; 368 | y2 = 0 | y2; 369 | context.beginPath(); 370 | context.moveTo(x1, y1); 371 | context.lineTo(x2, y2); 372 | context.stroke(); 373 | } 374 | 375 | /* */ 376 | 377 | function initialize(canvas_id) { 378 | canvas = document.getElementById(canvas_id); 379 | console.assert(canvas); 380 | context = canvas.getContext('2d', { alpha: false }); 381 | 382 | // i wanted to draw aliased/jagged lines on html5 canvas, but it's not possible (except manually) 383 | // 384 | // imageSmoothingEnabled applies to pattern fills and drawImage, it does not affect general anti-aliasing. 385 | 386 | canvas.addEventListener('mousemove', on_mouse_move, false); 387 | // touch move? (NO!) 388 | 389 | canvas.addEventListener('mousedown', on_mouse_down, false); 390 | canvas.addEventListener('touchstart', on_touch_start, { capture: false, passive: true }); 391 | 392 | canvas.addEventListener('mouseup', on_mouse_up, false); 393 | canvas.addEventListener('touchend', on_touch_end, false); 394 | 395 | // disable default canvas right click 396 | canvas.addEventListener("contextmenu", function (e) { e.preventDefault(); e.stopPropagation(); return false; }, true); 397 | 398 | ui.config.drawbox_gradient = CreateDrawboxGradient( 399 | context, 400 | uidraw.box_gradient.x1, uidraw.box_gradient.y1, 401 | uidraw.box_gradient.x2, uidraw.box_gradient.y2, 402 | uidraw.box_gradient.color_stop1, 403 | uidraw.box_gradient.color_stop2 404 | ); 405 | } 406 | 407 | function GetClientWidth() { 408 | return 0 | canvas.width; 409 | } 410 | 411 | function GetClientHeight() { 412 | return 0 | canvas.height; 413 | } 414 | 415 | function GetContext() { 416 | return context; 417 | } 418 | 419 | function GetCanvas() { 420 | return canvas; 421 | } 422 | 423 | function UpdateSize() { 424 | // todo: move canvas_size_hack into this driver, expose options object with it 425 | canvas.width = window.innerWidth - app.canvas_size_hack; 426 | canvas.height = window.innerHeight - app.canvas_size_hack; 427 | } 428 | 429 | function CreateDrawboxGradient(context, x1, y1, x2, y2, input_color1, input_color2) { 430 | let color1; 431 | let color2; 432 | // ok, so firefox renders gradients WILDLY differently, something different with alpha 433 | if (ui.is_browser_gecko()) { 434 | color1 = Color( 435 | input_color1[_r], 436 | input_color1[_g], 437 | input_color1[_b], 438 | 0 | input_color1[_a] / 2 439 | ); 440 | color2 = Color( 441 | input_color2[_r], 442 | input_color2[_g], 443 | input_color2[_b], 444 | 0 | input_color2[_a] / 2 445 | ); 446 | } else { 447 | color1 = input_color1; 448 | color2 = input_color2; 449 | } 450 | 451 | console.assert(context); 452 | let grd = context.createLinearGradient(x1, y1, x2, y2); 453 | grd.addColorStop(0.0, ui.make_css_color(color1)); 454 | grd.addColorStop(1.0, ui.make_css_color(color2)); 455 | return grd; 456 | } 457 | 458 | function FrameClear() { 459 | uidraw.push_fillstyle(ui.make_css_color(uidraw.bg_color)); 460 | context.fillRect(0, 0, canvas.width, canvas.height); 461 | uidraw.pop_fillstyle(); 462 | /*context.beginPath(); 463 | context.fillStyle = make_css_color(uidraw.bg_color); 464 | context.rect(0, 0, canvas.width, canvas.height); 465 | context.fill(); 466 | context.closePath();*/ 467 | } 468 | 469 | function SetStrokeStyle(value) { 470 | context.strokeStyle = value; 471 | } 472 | 473 | function SetFillStyle(value) { 474 | context.fillStyle = value; 475 | } 476 | 477 | function SetLineWidth(value) { 478 | context.lineWidth = value; 479 | } 480 | 481 | function SetLineDash(value) { 482 | context.setLineDash(value); 483 | } 484 | 485 | function Stroke() { 486 | context.stroke(); 487 | } 488 | 489 | function BeginPath() { 490 | context.beginPath(); 491 | } 492 | 493 | function MoveTo(x, y) { 494 | context.moveTo(x, y); 495 | } 496 | 497 | function LineTo(x, y) { 498 | context.lineTo(x, y); 499 | } 500 | 501 | function BeginClip(rect) { 502 | context.save(); 503 | context.beginPath(); 504 | context.rect(rect.x, rect.y, rect.w, rect.h); 505 | context.clip(); 506 | } 507 | 508 | function EndClip() { 509 | context.restore(); 510 | } 511 | 512 | const config = { 513 | has_drawbox_gradient: 0 | true, 514 | }; 515 | 516 | function ResetDrawTextCache() { 517 | _drawtext_cache = {}; 518 | } 519 | 520 | // https://stackoverflow.com/a/36673184 521 | function IsTouchDevice() { 522 | return (navigator.maxTouchPoints || 'ontouchstart' in document.documentElement); 523 | } 524 | 525 | export { 526 | initialize, 527 | config, 528 | DrawBox, 529 | DrawBox3d, 530 | DrawBoxOutline, 531 | DrawBoxSoft, 532 | DrawBoxSoftRight, 533 | DrawBoxSoftLeft, 534 | DrawBoxSoftTop, 535 | DrawBoxSoftBottom, 536 | DrawBox3dSoft, 537 | DrawBox3dSoftRight, 538 | DrawBox3dSoftLeft, 539 | DrawBox3dSoftTop, 540 | DrawBox3dSoftBottom, 541 | DrawText, 542 | DrawLine, 543 | DrawCircle, 544 | DrawCircleOutline, 545 | GetCursorX, 546 | GetCursorY, 547 | GetFontSize, 548 | GetClientHeight, 549 | GetClientWidth, 550 | GetContext, 551 | GetCanvas, 552 | UpdateSize, 553 | CreateDrawboxGradient, 554 | FrameClear, 555 | SetStrokeStyle, 556 | SetFillStyle, 557 | SetLineWidth, 558 | SetLineDash, 559 | Stroke, 560 | BeginPath, 561 | MoveTo, 562 | LineTo, 563 | BeginClip, 564 | EndClip, 565 | ResetDrawTextCache, 566 | IsTouchDevice 567 | }; -------------------------------------------------------------------------------- /src/simpleui_drawing.js: -------------------------------------------------------------------------------- 1 | import * as m_simpleui from './simpleui.js'; 2 | import { Rectangle, RectangleP, Color, ColorP, Point, PointP } from './simpleui.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | // later: these will be in a var/obj/namespace here, so i can switch to other drivers 5 | import { 6 | DrawBox, 7 | DrawBox3d, 8 | DrawBoxOutline, 9 | DrawBoxSoft, 10 | DrawBoxSoftRight, 11 | DrawBoxSoftLeft, 12 | DrawBoxSoftTop, 13 | DrawBoxSoftBottom, 14 | DrawBox3dSoft, 15 | DrawBox3dSoftRight, 16 | DrawBox3dSoftLeft, 17 | DrawBox3dSoftTop, 18 | DrawBox3dSoftBottom, 19 | DrawText, 20 | DrawLine, 21 | DrawCircle, 22 | DrawCircleOutline, 23 | GetCursorX, 24 | GetCursorY, 25 | GetFontSize, 26 | SetStrokeStyle, 27 | SetFillStyle, 28 | SetLineWidth, 29 | SetLineDash, 30 | Stroke, 31 | BeginPath, 32 | MoveTo, 33 | LineTo, 34 | BeginClip, 35 | EndClip, 36 | } from './simpleui_driver_html5_canvas.js'; 37 | 38 | let commands = []; 39 | 40 | const _Hovered = consts._Hovered; 41 | const _Held = consts._Held; 42 | 43 | let round = Math.round; 44 | 45 | export const default_text_color = Color(255, 255, 255, 255); 46 | export const default_line_color = Color(255, 255, 255, 255); 47 | export const default_fill_color = Color(0, 0, 0, 255); 48 | 49 | export const accent = Color(120, 180, 140, 127); 50 | export const bg_color = Color(91 - 15, 98 - 15, 96 - 15, 255); 51 | export const panel_color = Color(46, 46, 46, 255); 52 | 53 | export const normal_back = Color(36, 36, 36, 255); 54 | export const normal_face = Color(72, 72, 72, 255); 55 | export const activating_face = Color(0, 204, 123, 0 | 255 * 0.8); 56 | 57 | export const raised_face = Color(180 - 10, 180 + 9 - 10, 180 - 3 - 10, 255); 58 | export const raised_accent = Color(250, 255, 240, 255); 59 | 60 | export const focus_back = normal_back; 61 | export const focus_face = normal_face; 62 | 63 | export const font_size = GetFontSize(); 64 | 65 | export const color_white = Color(255, 255, 255, 255); 66 | export const color_black = Color(0, 0, 0, 255); 67 | 68 | export const box_gradient = { 69 | x1: 80, 70 | y1: 22, 71 | x2: 85, 72 | y2: -32, 73 | color_stop1: Color(0, 0, 0, 50), // was 38 74 | color_stop2: Color(255, 255, 255, 144) 75 | }; 76 | 77 | // > no-alpha debug mode 78 | if (false) { 79 | accent = Color(64, 89, 89, 255); 80 | activating_face = accent; 81 | } 82 | 83 | // center 2 rectangles on each other, return x & y offsets 84 | function get_centered_offsets(rect1, rect2) { 85 | let ox = round((rect1.w - rect2.w) / 2); 86 | let oy = round((rect1.h - rect2.h) / 2); 87 | return [0 | ox, 0 | oy]; 88 | } 89 | 90 | function get_vertical_centered_text_offset(h) { 91 | let height_delta = 0 | h - font_size; 92 | let y_offset = 0 | (height_delta / 2); 93 | return y_offset; 94 | } 95 | // get_center_offset(rect.y, rect.h) 96 | 97 | function vertical_center_text(rect) { 98 | const y_offset = get_vertical_centered_text_offset(rect.h); 99 | let result = PointP(0 | rect.x, 0 | rect.y + y_offset); 100 | return result; 101 | } 102 | 103 | /*function rectangle_center(rect) { 104 | return Point(rect.x + rect.w / 2, rect.y + rect.h / 2); 105 | }*/ 106 | 107 | /*function rectangle_offset(rect, offset) { 108 | return RectangleP(0 | rect.x + offset, 0 | rect.y + offset, 0 | rect.w, 0 | rect.h); 109 | }*/ 110 | 111 | /*function rectangle_offset_xy(rect, offset_x, offset_y) { 112 | return RectangleP(0 | rect.x + offset_x, 0 | rect.y + offset_y, 0 | rect.w, 0 | rect.h); 113 | }*/ 114 | 115 | function rectangle_erode(rect, amount) { 116 | return RectangleP(0 | rect.x + amount, 0 | rect.y + amount, 0 | rect.w - amount * 2, 0 | rect.h - amount * 2); 117 | } 118 | 119 | function rectangle_dilate(rect, amount) { 120 | return rectangle_erode(rect, -amount); 121 | } 122 | 123 | /*function rectangle_underline(rect, size) { 124 | return Rectangle(rect.x, rect.y+rect.h-size, rect.w, size); 125 | }*/ 126 | 127 | function point_translate(pt, x, y) { 128 | pt.x = 0 | (pt.x + x); 129 | pt.y = 0 | (pt.y + y); 130 | return pt; 131 | } 132 | 133 | function draw_text(text, x, y, color) { 134 | if (m_simpleui.config.drawtext_enable) { 135 | DrawText(text, x, y, color); 136 | //commands.push(DrawText, 4, text, x, y, color); 137 | } 138 | } 139 | 140 | const draw_rectangle = DrawBox; 141 | const draw_rectangle3d = DrawBox3d; 142 | // 143 | const draw_rectangle_outline = DrawBoxOutline; 144 | // 145 | const draw_rectangle_soft = DrawBoxSoft; 146 | const draw_rectangle_soft_right = DrawBoxSoftRight; 147 | const draw_rectangle_soft_left = DrawBoxSoftLeft; 148 | const draw_rectangle_soft_top = DrawBoxSoftTop; 149 | const draw_rectangle_soft_bottom = DrawBoxSoftBottom; 150 | // 151 | const draw_rectangle3d_soft = DrawBox3dSoft; 152 | const draw_rectangle3d_soft_right = DrawBox3dSoftRight; 153 | const draw_rectangle3d_soft_left = DrawBox3dSoftLeft; 154 | const draw_rectangle3d_soft_top = DrawBox3dSoftTop; 155 | const draw_rectangle3d_soft_bottom = DrawBox3dSoftBottom; 156 | // 157 | const draw_circle = DrawCircle; 158 | const draw_circle_outline = DrawCircleOutline; 159 | const draw_line = DrawLine; 160 | 161 | function draw_label(text, rect, color) { 162 | draw_text(text, 0 | rect.x, 0 | rect.y, color); 163 | } 164 | 165 | function button(text, rect) { 166 | let rect1 = rectangle_erode(rect, 1); 167 | draw_rectangle3d_soft(rect, normal_back); 168 | draw_rectangle3d_soft(rect1, normal_face); 169 | // todo? draw_rectangle3d_soft_erode(rect, normal_face, 1); 170 | let text_pos = point_translate(vertical_center_text(rect1), 5, 0); 171 | draw_label(text, text_pos); 172 | //draw_label 173 | } 174 | 175 | function button_held(text, rect) { 176 | let rect1 = rectangle_erode(rect, 1); 177 | draw_rectangle3d_soft(rect, normal_back); 178 | draw_rectangle3d_soft(rect, activating_face); 179 | let text_pos = point_translate(vertical_center_text(rect1), 5, 0); 180 | text_pos.y = text_pos.y + 1; 181 | draw_label(text, text_pos); 182 | } 183 | 184 | function button_hovered(text, rect) { 185 | let rect1 = rectangle_erode(rect, 1); 186 | draw_rectangle3d_soft(rect, normal_back); 187 | draw_rectangle3d_soft(rect1, accent); 188 | let text_pos = point_translate(vertical_center_text(rect1), 5, 0); 189 | draw_label(text, text_pos); 190 | } 191 | 192 | function checkbox(rect, value) { 193 | draw_rectangle3d(rect, normal_back); 194 | draw_rectangle3d(rectangle_erode(rect, 1), normal_face); 195 | if (value) { 196 | draw_rectangle3d(rectangle_erode(rect, 4), color_white); 197 | } 198 | } 199 | 200 | function checkbox_held(rect, value) { 201 | draw_rectangle3d(rect, normal_back); 202 | draw_rectangle3d(rectangle_erode(rect, 1), activating_face); 203 | if (value) { 204 | draw_rectangle3d(rectangle_erode(rect, 4), color_white); 205 | } else { 206 | draw_rectangle3d(rectangle_erode(rect, 4), ColorP(255, 255, 255, 127)); 207 | } 208 | } 209 | 210 | function checkbox_hovered(rect, value) { 211 | draw_rectangle3d(rect, normal_back); 212 | draw_rectangle3d(rectangle_erode(rect, 1), accent); 213 | if (value) { 214 | draw_rectangle3d(rectangle_erode(rect, 4), color_white); 215 | } 216 | } 217 | 218 | function progressbar(rect, max, value) { 219 | let rect2 = rectangle_erode(rect, 2); 220 | draw_rectangle3d(rect, normal_back); 221 | const progw = 0 | (rect2.w * (value / max)); 222 | let progrect = RectangleP(rect2.x, rect2.y, progw, rect2.h); 223 | draw_rectangle3d_soft(progrect, accent); 224 | } 225 | 226 | function draw_slider(uiid, rect, state, min, max, value, handle_label) { 227 | console.assert(handle_label != null); 228 | 229 | const range = 0 | (max - min); 230 | const rel_value = 0 | (value - min); 231 | const value_percent = (rel_value / range); 232 | 233 | const rect1 = rectangle_erode(rect, 1); 234 | const rect2 = rectangle_erode(rect, 2); 235 | 236 | let progw = 0 | (rect2.w * value_percent); 237 | let progrect = RectangleP(rect2.x, rect2.y, progw, rect2.h); 238 | 239 | // handle 240 | let handledim = 0 | rect1.h; 241 | let handlew = handledim / 4; 242 | let handlepos = 0 | ((rect1.w - handlew) * value_percent + handledim / 2); 243 | const rectx = 0 | (rect1.x + handlepos - handledim / 2); 244 | const recty = 0 | (rect1.y); 245 | let hrect = RectangleP(rectx, recty + 1, handlew, handledim - 2); 246 | 247 | draw_rectangle3d(rect, normal_back); 248 | draw_rectangle3d_soft(progrect, accent); 249 | 250 | if (state[_Held]) { 251 | draw_rectangle3d(hrect, activating_face); 252 | } else if (state[_Hovered]) { 253 | draw_rectangle3d(hrect, raised_accent); 254 | } else { // normal 255 | draw_rectangle3d(hrect, raised_face); 256 | } 257 | if (handle_label) { 258 | const textx = 0 | (rect.x + rect.w - 16); 259 | const texty = 0 | (hrect.y + hrect.h / 2 - 8); 260 | draw_text(handle_label, textx, texty, color_white); 261 | } 262 | } 263 | 264 | function draw_vslider(uiid, rect, state, min, max, value, handle_label) { 265 | /*m_v8.assert_smi(rect.x); 266 | m_v8.assert_smi(rect.y); 267 | m_v8.assert_smi(rect.w); 268 | m_v8.assert_smi(rect.h); 269 | m_v8.assert_smi(state[_Hovered]); 270 | m_v8.assert_smi(state[_Held]); 271 | m_v8.assert_smi(min); 272 | m_v8.assert_smi(max); 273 | m_v8.assert_smi(value);*/ 274 | console.assert(handle_label != null); 275 | console.assert(handle_label != undefined); 276 | 277 | const range = 0 | (max - min); 278 | const rel_value = 0 | (value - min); 279 | const value_percent = (rel_value / range); 280 | 281 | const rect1 = rectangle_erode(rect, 1); 282 | const rect2 = rectangle_erode(rect, 2); 283 | 284 | let progh = 0 | (rect2.h * value_percent); 285 | let progrect = RectangleP(rect2.x, rect2.y, rect2.w, progh); 286 | 287 | // handle 288 | let handledim = 0 | rect1.w; 289 | let handleh = 0 | handledim / 4; 290 | let handlepos = 0 | ((rect1.h - handleh) * value_percent + handledim / 2); 291 | const rectx = 0 | rect1.x; 292 | const recty = 0 | (rect1.y + handlepos - handledim / 2); 293 | let hrect = RectangleP(rectx + 1, recty, handledim - 2, handleh); 294 | 295 | draw_rectangle3d(rect, normal_back); 296 | draw_rectangle3d_soft(progrect, accent); 297 | 298 | if (state[_Held]) { 299 | draw_rectangle3d(hrect, activating_face); 300 | } else if (state[_Hovered]) { 301 | draw_rectangle3d(hrect, raised_accent); 302 | } else { // normal 303 | draw_rectangle3d(hrect, raised_face); 304 | } 305 | if (handle_label) { 306 | const textx = 0 | (hrect.x + hrect.w / 2 - 5); 307 | const texty = 0 | (rect.y + rect.h - 16); 308 | draw_text(handle_label, textx, texty, color_white); 309 | } 310 | } 311 | 312 | function draw_checkbutton(text, rect, state, value, text_offset_x, text_offset_y) { 313 | draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, draw_rectangle3d); 314 | } 315 | 316 | function draw_checkbutton_soft_right(text, rect, state, value, text_offset_x, text_offset_y) { 317 | draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, draw_rectangle3d_soft_right); 318 | } 319 | 320 | function draw_checkbutton_soft_left(text, rect, state, value, text_offset_x, text_offset_y) { 321 | draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, draw_rectangle3d_soft_left); 322 | } 323 | 324 | function draw_checkbutton_soft_top(text, rect, state, value, text_offset_x, text_offset_y) { 325 | draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, draw_rectangle3d_soft_top); 326 | } 327 | 328 | function draw_checkbutton_soft_bottom(text, rect, state, value, text_offset_x, text_offset_y) { 329 | draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, draw_rectangle3d_soft_bottom); 330 | } 331 | 332 | function draw_checkbutton_override(text, rect, state, value, text_offset_x, text_offset_y, fn_draw_rectangle) { 333 | 334 | let rect1 = rectangle_erode(rect, 1); 335 | 336 | fn_draw_rectangle(rect, normal_back); 337 | 338 | if (state[_Held]) { 339 | fn_draw_rectangle(rect1, activating_face); 340 | } else if (state[_Hovered]) { 341 | fn_draw_rectangle(rect1, accent); 342 | } else { // normal 343 | if (value) { 344 | fn_draw_rectangle(rect1, accent); 345 | } else { 346 | fn_draw_rectangle(rect1, normal_face); 347 | } 348 | } 349 | 350 | let text_pos; 351 | if (text_offset_x == null || text_offset_y == null) { 352 | text_pos = point_translate(vertical_center_text(rect1), 3, 0); 353 | } else { 354 | const rect_text = rectangle_erode(rect, 1); 355 | text_pos = point_translate(rect_text, text_offset_x, text_offset_y); 356 | } 357 | if (state[_Held]) { 358 | text_pos.y = text_pos.y + 1; 359 | draw_label(text, text_pos); 360 | } else { 361 | draw_label(text, text_pos); 362 | } 363 | } 364 | 365 | function handle(rect) { 366 | draw_rectangle(rect, normal_back); 367 | } 368 | 369 | function handle_held(rect) { 370 | handle(rect); 371 | draw_rectangle(rectangle_erode(rect, 1), activating_face); 372 | } 373 | 374 | function handle_hovered(rect) { 375 | handle(rect); 376 | draw_rectangle(rectangle_erode(rect, 1), accent); 377 | } 378 | 379 | function draw_reticle(uiid, rect, state) { 380 | push_linewidth(6); 381 | if (state[_Held]) { 382 | draw_circle_outline(rect, activating_face); 383 | } else if (state[_Hovered]) { 384 | draw_circle_outline(rect, accent); 385 | } else { 386 | draw_circle_outline(rect, normal_back); 387 | } 388 | pop_linewidth(); 389 | 390 | push_linewidth(2); 391 | draw_circle_outline(rect, color_white); 392 | pop_linewidth(); 393 | } 394 | 395 | // StrokeStyle 396 | 397 | const _stack_strokestyle = [default_line_color]; 398 | 399 | function push_strokestyle(value) { 400 | _stack_strokestyle.push(value); 401 | SetStrokeStyle(value); 402 | //commands.push(SetStrokeStyle, 1, value); 403 | } 404 | 405 | function pop_strokestyle() { 406 | _stack_strokestyle.pop(); 407 | const prev = _stack_strokestyle[_stack_strokestyle.length - 1]; 408 | SetStrokeStyle(prev); 409 | //commands.push(SetStrokeStyle, 1, prev); 410 | } 411 | 412 | // FillStyle 413 | 414 | const _stack_fillstyle = [default_fill_color]; 415 | 416 | function push_fillstyle(value) { 417 | _stack_fillstyle.push(value); 418 | SetFillStyle(value); 419 | //commands.push(SetStrokeStyle, 1, value); 420 | } 421 | 422 | function pop_fillstyle() { 423 | _stack_fillstyle.pop(); 424 | const prev = _stack_fillstyle[_stack_fillstyle.length - 1]; 425 | SetFillStyle(prev); 426 | //commands.push(SetStrokeStyle, 1, prev); 427 | } 428 | 429 | 430 | // LineWidth 431 | 432 | const _stack_linewidth = [1]; 433 | 434 | function push_linewidth(value) { 435 | _stack_linewidth.push(value); 436 | SetLineWidth(value); 437 | //commands.push(SetLineWidth, 1, value); 438 | } 439 | 440 | function pop_linewidth() { 441 | _stack_linewidth.pop(); 442 | const prev = _stack_linewidth[_stack_linewidth.length - 1]; 443 | SetLineWidth(prev); 444 | //commands.push(SetLineWidth, 1, prev); 445 | } 446 | 447 | // LineDash 448 | 449 | const _stack_linedash = [[]]; 450 | 451 | function push_linedash(value) { 452 | console.assert(value); 453 | console.assert(value != null); 454 | _stack_linedash.push(value); 455 | SetLineDash(value); 456 | //commands.push(SetLineDash, 1, value); 457 | } 458 | 459 | function pop_linedash() { 460 | console.assert(_stack_linedash.length > 0); 461 | _stack_linedash.pop(); 462 | const prev = _stack_linedash[_stack_linedash.length - 1]; 463 | console.assert(prev != null); 464 | SetLineDash(prev); 465 | //commands.push(SetLineDash, 1, prev); 466 | } 467 | 468 | // 469 | 470 | const stroke = Stroke; 471 | const begin_path = BeginPath; 472 | const move_to = MoveTo; 473 | const line_to = LineTo; 474 | const begin_clip = BeginClip; 475 | const end_clip = EndClip; 476 | 477 | export { 478 | get_centered_offsets, 479 | //vertical_center, 480 | vertical_center_text, 481 | //rectangle_offset, 482 | //rectangle_offset_xy, 483 | rectangle_erode, 484 | rectangle_dilate, 485 | point_translate, 486 | // 487 | draw_text as text, 488 | // 489 | draw_rectangle as rectangle, 490 | draw_rectangle3d as rectangle3d, 491 | draw_rectangle_outline as rectangle_outline, 492 | draw_rectangle_soft as rectangle_soft, 493 | draw_rectangle_soft_right as rectangle_soft_right, 494 | draw_rectangle_soft_left as rectangle_soft_left, 495 | draw_rectangle_soft_top as rectangle_soft_top, 496 | draw_rectangle_soft_bottom as rectangle_soft_bottom, 497 | // 498 | draw_circle as circle, 499 | draw_circle_outline as circle_outline, 500 | draw_line as line, 501 | draw_label as label, 502 | button, button_held, button_hovered, 503 | checkbox, checkbox_held, checkbox_hovered, 504 | progressbar, 505 | draw_slider as slider, 506 | draw_vslider as vslider, 507 | draw_checkbutton as checkbutton, 508 | draw_checkbutton_soft_right as checkbutton_soft_right, 509 | draw_checkbutton_soft_left as checkbutton_soft_left, 510 | draw_checkbutton_soft_top as checkbutton_soft_top, 511 | draw_checkbutton_soft_bottom as checkbutton_soft_bottom, 512 | handle, handle_held, handle_hovered, 513 | draw_reticle as reticle, 514 | // 515 | commands, 516 | push_strokestyle, 517 | pop_strokestyle, 518 | push_fillstyle, 519 | pop_fillstyle, 520 | push_linewidth, 521 | pop_linewidth, 522 | push_linedash, 523 | pop_linedash, 524 | stroke, 525 | begin_path, 526 | move_to, 527 | line_to, 528 | begin_clip, 529 | end_clip, 530 | // 531 | //_checkbutton, 532 | //get_renderer, push_renderer, pop_renderer, 533 | }; 534 | -------------------------------------------------------------------------------- /src/simpleui_app_demo.js: -------------------------------------------------------------------------------- 1 | import * as ui from './simpleui.js'; 2 | import * as uidraw from './simpleui_drawing.js'; 3 | import * as consts from './simpleui_consts.js'; 4 | import { do_panel_begin, do_panel_end } from './simpleui_ex_panel.js'; 5 | import { do_gradient_stroke_edit } from './simpleui_ex_gradient.js'; 6 | import { do_gridfont } from './simpleui_ex_gridfont.js'; 7 | import { do_scroll_begin, do_scroll_end, do_scroll_item_begin, do_scroll_item_end } from './simpleui_ex_scroll.js'; 8 | import { do_linestar_edit } from './simpleui_ex_linestar.js'; 9 | 10 | const _r = consts._r; 11 | const _g = consts._g; 12 | const _b = consts._b; 13 | const _a = consts._a; 14 | const _none = consts._none; 15 | const _vertical = consts._vertical; 16 | const _horizontal = consts._horizontal; 17 | 18 | const Color = ui.Color; 19 | const ColorP = ui.ColorP; 20 | const Point = ui.Point; 21 | const PointP = ui.PointP; 22 | //const Rectangle = ui.Rectangle; 23 | const RectangleP = ui.RectangleP; 24 | const make_css_color = ui.make_css_color; 25 | 26 | function init_array(size, init_val) { 27 | let a = []; 28 | for (let i = 0; i < size; i++) { 29 | a[i] = init_val; 30 | } 31 | return a; 32 | } 33 | 34 | function sum(a) { 35 | let result = 0|0; 36 | for (let i = 0; i < a.length; i++) { 37 | let v = a[i]; 38 | result = result + v; 39 | } 40 | return result; 41 | } 42 | console.assert(sum([1, 2, 3]) == 6); 43 | 44 | function randomize_color(color) { 45 | let a = [0, 1, 2]; 46 | for (let i = 0; i < a.length; i++) { 47 | let k = a[i]; 48 | let v = 50 + Math.round(Math.random() * 150); 49 | color[k] = v; 50 | } 51 | } 52 | 53 | function do_color(uiid, color, label) { 54 | let _; 55 | 56 | let changed = false | 0; 57 | 58 | // base component sizes 59 | const h = 24 | 0; 60 | const w = (h * 5) | 0; 61 | 62 | const rect = RectangleP(0, 0, w, h); 63 | 64 | ui.layout_push(_horizontal); 65 | { 66 | 67 | // label & sliders 68 | ui.layout_push(_vertical); 69 | { 70 | ui.label(label, rect); 71 | 72 | _ = ui.slider(uiid + '-slider-r', rect, 0, 255, color[_r], 'r'); 73 | if (_.changed) { changed = 0 | true; color[_r] = _.value; } 74 | 75 | _ = ui.slider(uiid + '-slider-g', rect, 0, 255, color[_g], 'g'); 76 | if (_.changed) { changed = 0 | true; color[_g] = _.value; } 77 | 78 | _ = ui.slider(uiid + '-slider-b', rect, 0, 255, color[_b], 'b'); 79 | if (_.changed) { changed = 0 | true; color[_b] = _.value; } 80 | 81 | _ = ui.slider(uiid + '-slider-a', rect, 0, 255, color[_a], 'a'); 82 | if (_.changed) { changed = 0 | true; color[_a] = _.value; } 83 | 84 | } 85 | ui.layout_pop(); 86 | 87 | // swatch 88 | ui.layout_push(_vertical); 89 | const pad = ui.layout_peek().padding; // parent pad 90 | { 91 | 92 | // this increment moves the swatch down so it aligns with the sliders, not the label 93 | ui.layout_increment2(0, h); 94 | 95 | const swatch_dim = h * 3 + pad * 2; 96 | const swatch_rect = RectangleP(0, 0, swatch_dim, swatch_dim); 97 | //ui.rectangle(swatch_rect, color); 98 | uidraw.rectangle_soft(ui.layout_translated(swatch_rect), color); 99 | ui.layout_increment(swatch_rect); 100 | 101 | _ = ui.button(uiid + '-rand-button', 'random', RectangleP(0, 0, swatch_dim, h)); 102 | if (_.clicked) { 103 | randomize_color(color); 104 | changed = true | 0; 105 | } 106 | } 107 | ui.layout_pop(); 108 | 109 | } 110 | ui.layout_pop(); 111 | 112 | let state = ui.get_state(uiid); 113 | if (!state) { 114 | state = ui.set_state(uiid, {changed: 0 | false, value: null}); 115 | } 116 | state.changed = 0 | changed; 117 | state.value = color; 118 | return state; 119 | //return {changed: 0 | changed, value: color}; 120 | } 121 | 122 | function do_ms_meter(uiid, a_time, high_value) { 123 | 124 | let state = ui.get_state(uiid); 125 | if (!state) { 126 | state = ui.set_state(uiid, { 127 | 'times': init_array(30, 0) 128 | }); 129 | } 130 | let times = state.times; 131 | times.push(a_time); 132 | times.shift(); 133 | 134 | //let high_value = 32; 135 | 136 | const value = 0 | sum(times) / times.length; 137 | const constrained_value = 0 | Math.floor(Math.min(high_value, value)); 138 | ui.layout_push(_horizontal); 139 | ui.progressbar(uiid + '-progbar', RectangleP(0, 0, 100, 20), high_value, 0 | constrained_value); 140 | 141 | let tmp = ui.config.drawtext_enable; 142 | ui.config.drawtext_enable = true; 143 | ui.label(value + 'ms', RectangleP(0, 0, 100, 20)); 144 | ui.config.drawtext_enable = tmp; 145 | 146 | ui.layout_pop(); 147 | } 148 | 149 | function do_ms_graph(uiid, a_time, graph_height) { 150 | let state = ui.get_state(uiid); 151 | if (!state) { 152 | state = ui.set_state(uiid, { 153 | 'times': init_array(30, 0) 154 | }); 155 | } 156 | let times = state.times; 157 | times.push(a_time); 158 | times.shift(); 159 | 160 | //let graph_height = 40; 161 | ui.layout_push(_horizontal, 0); 162 | for (let i = 0; i < times.length; i++) { 163 | let v = 0 | Math.min(graph_height, times[i]); 164 | ui.rectangle(RectangleP(0, 0, 4, v), ColorP(255, 255, 255, 51)); 165 | } 166 | ui.layout_increment2(0, graph_height); 167 | ui.layout_pop(); 168 | } 169 | 170 | function sidelabel(text) { 171 | ui.label(text, RectangleP(0, 5, 100, 24)); 172 | } 173 | 174 | function do_sidepanel() { 175 | let _; 176 | let pad = 8; 177 | //let none1 = ui.layout_push(_none, 0, 0, 0); 178 | let rect = RectangleP(0, 0, 200, ui.driver.GetClientHeight() | 0); 179 | //let rect2 = uidraw.rectangle_erode(rect, 2); 180 | const sidepanel_color = ColorP(uidraw.panel_color[_r], uidraw.panel_color[_g], uidraw.panel_color[_b], 127); 181 | ui.rectangle(rect, sidepanel_color); 182 | 183 | //let vert1 = 184 | ui.layout_push(_vertical, pad, pad, pad); 185 | { 186 | ui.label('simpleui v' + ui.get_version(), RectangleP(0, 0, 100, 20)); 187 | 188 | // reload 189 | _ = ui.button('sidepanel-reload-button', 'reload', RectangleP(0, 0, 100, 24)); 190 | if (_.clicked) { 191 | document.location.reload(true); 192 | } 193 | 194 | // desktop select 195 | sidelabel('panels'); 196 | ui.layout_push(_vertical, -1); 197 | ui.group_buttons_begin(); 198 | for(var i=0; i< app.desktops.length; i++) { 199 | const name = app.desktops[i]; 200 | const is_desktop_active = 0 | name == app.desktop; 201 | const button_text = name; 202 | _ = ui.checkbutton('sidepanel-desktop-button-' + i, button_text, RectangleP(0,0,100,24), is_desktop_active); 203 | if (_.changed && _.value) { 204 | app.desktop = name; 205 | } 206 | } 207 | ui.group_buttons_end(); 208 | ui.layout_pop(); 209 | 210 | // mouse status :-> 211 | { 212 | let w_ = 180; // max-width 213 | 214 | let aspect = ui.driver.GetClientWidth() / ui.driver.GetClientHeight(); 215 | let h_ = 0 | w_ / aspect; 216 | 217 | if (h_ > 100) { // max-height 218 | w_ = w_ * 100 / h_; 219 | h_ = 100; 220 | } 221 | 222 | let w = 0 | w_; 223 | let h = 0 | h_; 224 | 225 | sidelabel('mouse status'); 226 | 227 | ui.rectangle(RectangleP(0, 0, w, h), uidraw.normal_back); 228 | 229 | let cursor_size = 0 | 4; 230 | if (ui.state.item_held) { 231 | cursor_size = 0 | 8; 232 | } 233 | let radar_cursor_pos_x = 0 | (((ui.driver.GetCursorX() / canvas.width) * w) - (cursor_size / 2)); 234 | let radar_cursor_pos_y = 0 | (((ui.driver.GetCursorY() / canvas.height) * w / aspect) - (cursor_size / 2)); 235 | 236 | const layout = ui.layout_peek(); 237 | ui.layout_push(_none, layout.padding, layout.x, layout.y - h - pad); 238 | const mouse_rect = RectangleP(0 | radar_cursor_pos_x, 0 | radar_cursor_pos_y, 0 | cursor_size, 0 | cursor_size); 239 | ui.rectangle(mouse_rect, uidraw.normal_back); 240 | ui.rectangle(mouse_rect, uidraw.accent); 241 | ui.layout_pop(); 242 | } 243 | 244 | // padding (lost this feature when modularized due to namespace/scope boundaries, might fix one day) 245 | /* 246 | sidelabel('padding'); 247 | _ = ui.slider('sidepanel-padding-slider', RectangleP(0, 0, 100, 20), 0, 12, app.panel_layout_padding, ''); 248 | if (_.changed) { 249 | app.panel_layout_padding = _.value; 250 | }*/ 251 | 252 | // cpu (not reasonably possible in js) 253 | 254 | // frame times + graph 255 | sidelabel('frame time'); 256 | do_ms_meter('sidepanel-ms-meter', app.main_loop_time, 50); 257 | do_ms_graph('sidepanel-frame-graph', app.main_loop_time, 20); 258 | 259 | // actual times + graph 260 | sidelabel('cpu time per frame'); 261 | do_ms_meter('sidepanel-actual-meter', app.main_proc_time, 10); 262 | do_ms_graph('sidepanel-actual-graph', app.main_proc_time, 20); 263 | 264 | // memory 265 | do_memory_graphs(); 266 | 267 | // canvas size hack 268 | // disabling until i do it with flicker 269 | /* 270 | ui.label('canvas trim', RectangleP(0, 0, 100, 20)); 271 | ui.layout_push(_horizontal); 272 | _ = ui.slider('sidepanel-canvas-size-hack-slider', RectangleP0, 0, 100, 20), 0, 60, app.canvas_size_hack, ''); 273 | if (_.changed) { 274 | app.canvas_size_hack = _.value; 275 | set_size(); 276 | } 277 | ui.label(app.canvas_size_hack + 'px', RectangleP(0, 0, 100, 20)); 278 | ui.layout_pop(); 279 | */ 280 | 281 | // show/hides 282 | /*{ 283 | let panels = ['color panel', 'gradient panel', 'gridfont paneli', 'scroll test panel']; 284 | 285 | for (let i = 0; i < panels.length; i++) { 286 | let uiid = panels[i]; 287 | let panel = ui.get_state(uiid); 288 | _ = ui.checkbutton('sidepanel-toggle-' + uiid, 'show ' + uiid, RectangleP(0, 0, 183, 24), panel && panel.visible); 289 | if (_.changed) { 290 | panel.visible = !panel.visible; 291 | } 292 | } 293 | }*/ 294 | 295 | // misc 296 | if (false) { 297 | sidelabel('pixel ratio: ' + window.devicePixelRatio); 298 | let memory = performance.memory; 299 | if (memory) { 300 | let mem1 = memory.usedJSHeapSize / (1024 * 1024); 301 | let mem2 = memory.jsHeapSizeLimit / (1024 * 1024); 302 | sidelabel('mem1: ' + ui.round(mem1) + 'MB'); 303 | sidelabel('mem2: ' + ui.round(mem2) + 'MB'); 304 | } 305 | } 306 | 307 | // flags 308 | { 309 | // editor help for function arguments needs some hjalp 310 | _ = ui.button('button-rtc', 'reset text cache', RectangleP(0, 0, 150, 24)); 311 | if (_.clicked) ui.driver.ResetDrawTextCache(); 312 | 313 | //_ = ui.button('button-rbc', 'reset box cache', RectangleP(0, 0, 150, 24)); 314 | //if (_.clicked) _drawbox_cache = {}; 315 | 316 | ui.layout_push(_horizontal); 317 | { 318 | _ = ui.checkbox('ui.config.drawtext_bitmap checkbox', 319 | RectangleP(0, 0, 20, 20), ui.config.drawtext_bitmap); 320 | if (_.changed) { 321 | ui.config.drawtext_bitmap = _.value; 322 | } 323 | ui.label('drawtext bitmap', RectangleP(0, 0, 100, 20)); 324 | } 325 | ui.layout_pop(); 326 | 327 | ui.layout_push(_horizontal); 328 | { 329 | _ = ui.checkbox('ui.config.drawhotspots_enable checkbox', 330 | RectangleP(0, 0, 20, 20), ui.config.drawhotspots_enable); // todo: move this let to ui.config. 331 | if (_.changed) { 332 | ui.config.drawhotspots_enable = _.value; 333 | } 334 | ui.label('draw hotspots', RectangleP(0, 0, 100, 20)); 335 | } 336 | ui.layout_pop(); 337 | 338 | ui.layout_push(_horizontal); 339 | { 340 | _ = ui.checkbox('ui.config.drawtext_enable checkbox', 341 | RectangleP(0, 0, 20, 20), ui.config.drawtext_enable); 342 | if (_.changed) { 343 | ui.config.drawtext_enable = _.value; 344 | } 345 | ui.label('draw text', RectangleP(0, 0, 100, 20)); 346 | } 347 | ui.layout_pop(); 348 | 349 | ui.layout_push(_horizontal); 350 | { 351 | _ = ui.checkbox('ui.config.drawbox_gradient', RectangleP(0, 0, 20, 20), ui.config.drawbox_gradient_enable); 352 | if (_.changed) { 353 | ui.config.drawbox_gradient_enable = _.value; 354 | } 355 | ui.label('drawbox gradient', RectangleP(0, 0, 100, 20)); 356 | } 357 | ui.layout_pop(); 358 | 359 | } 360 | 361 | } 362 | ui.layout_pop(); 363 | 364 | } 365 | 366 | function do_color_row(obj, keys) { 367 | for (let i = 0; i < keys.length; i++) { 368 | let k = keys[i]; 369 | do_color('color_' + k, obj[k], k); 370 | } 371 | } 372 | 373 | function do_color_panel(uiid, first_x, first_y, first_visible, first_expanded) { 374 | let panel = do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded); 375 | if (panel.visible && panel.expanded) { 376 | // row 1 377 | ui.layout_push(_horizontal); 378 | { 379 | do_color_row(uidraw, ['accent', 'panel_color', 'bg_color']); 380 | } 381 | ui.layout_pop(); 382 | 383 | // row 2 384 | ui.layout_push(_horizontal); 385 | do_color_row(uidraw, ['normal_back', 'normal_face', 'activating_face']); 386 | ui.layout_pop(); 387 | 388 | // row 3 389 | const peek = ui.layout_peek(); 390 | ui.layout_push(_horizontal, peek.padding, peek.x + 202, peek.y); 391 | do_color_row(uidraw, ['raised_face', 'raised_accent']); 392 | ui.layout_pop(); 393 | } 394 | do_panel_end(uiid); 395 | 396 | } 397 | 398 | function do_gradient_panel(uiid, first_x, first_y, first_visible, first_expanded) { 399 | let _; 400 | let panel = do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded); 401 | if (panel.visible && panel.expanded) { 402 | let changed = 0 | false; 403 | 404 | ui.layout_push(_horizontal); 405 | { 406 | 407 | //const min_x = -50; 408 | //const max_x = 150; 409 | const min_y = -50; 410 | const max_y = 150; 411 | //const dim_w = max_x - min_x; 412 | const dim_h = max_y - min_y; 413 | 414 | _ = do_gradient_stroke_edit(uiid + 'stroke-edit', -50, 150, uidraw.box_gradient.x1, uidraw.box_gradient.y1, uidraw.box_gradient.x2, uidraw.box_gradient.y2); 415 | if (_.changed) { 416 | changed = 0 | changed || _.changed; 417 | uidraw.box_gradient.x1 = 0 | _.x1; 418 | uidraw.box_gradient.y1 = 0 | _.y1; 419 | uidraw.box_gradient.x2 = 0 | _.x2; 420 | uidraw.box_gradient.y2 = 0 | _.y2; 421 | } 422 | 423 | ui.layout_push(_vertical); 424 | { 425 | _ = ui.slider('grad-panel-slider-x1', RectangleP(0, 0, 200, 20), -50, 150, uidraw.box_gradient.x1, 'x1'); 426 | if (_.changed) { 427 | changed = 0 | true; 428 | uidraw.box_gradient.x1 = 0 | _.value; 429 | } 430 | 431 | _ = ui.slider('grad-panel-slider-y1', RectangleP(0, 0, 200, 20), -50, 150, uidraw.box_gradient.y1, 'y1'); 432 | if (_.changed) { 433 | changed = 0 | true; 434 | uidraw.box_gradient.y1 = 0 | _.value; 435 | } 436 | 437 | _ = ui.slider('grad-panel-slider-x2', RectangleP(0, 0, 200, 20), -50, 150, uidraw.box_gradient.x2, 'x2'); 438 | if (_.changed) { 439 | changed = 0 | true; 440 | uidraw.box_gradient.x2 = 0 | _.value; 441 | } 442 | 443 | _ = ui.slider('grad-panel-slider-y2', RectangleP(0, 0, 200, 20), -50, 150, uidraw.box_gradient.y2, 'y2'); 444 | if (_.changed) { 445 | changed = 0 | true; 446 | uidraw.box_gradient.y2 = 0 | _.value; 447 | } 448 | 449 | ui.layout_increment2(0, 20); 450 | ui.label('pt1: ' + uidraw.box_gradient.x1 + ', ' + uidraw.box_gradient.y1, RectangleP(0, 0, 200, 20)); 451 | ui.label('pt2: ' + uidraw.box_gradient.x2 + ', ' + uidraw.box_gradient.y2, RectangleP(0, 0, 200, 20)); 452 | } 453 | ui.layout_pop(); 454 | 455 | ui.layout_increment2(0, dim_h); 456 | } // horizontal 1 457 | ui.layout_pop(); 458 | 459 | ui.layout_push(_horizontal); 460 | { 461 | _ = do_color('box_gradient.color_stop1', uidraw.box_gradient.color_stop1, 'stop1 color'); 462 | changed = 0 | changed || _.changed; 463 | 464 | _ = do_color('box_gradient.color_stop2', uidraw.box_gradient.color_stop2, 'stop2 color'); 465 | changed = 0 | changed || _.changed; 466 | } 467 | ui.layout_pop(); 468 | 469 | if (changed && ui.driver.config.has_drawbox_gradient) { 470 | ui.config.drawbox_gradient = ui.driver.CreateDrawboxGradient( 471 | context, 472 | uidraw.box_gradient.x1, uidraw.box_gradient.y1, 473 | uidraw.box_gradient.x2, uidraw.box_gradient.y2, 474 | uidraw.box_gradient.color_stop1, uidraw.box_gradient.color_stop2 475 | ); 476 | } 477 | } 478 | do_panel_end(uiid); 479 | } 480 | 481 | function do_gridfont_panel(uiid, first_x, first_y, first_visible, first_expanded) { 482 | 483 | 484 | let state = ui.get_state(uiid); 485 | if (!state) { 486 | state = ui.set_state(uiid, { 487 | 'uiid': uiid, 488 | 'run': 0 | true, 489 | 'reset': 0 | false 490 | }); 491 | } 492 | 493 | // cannot shared panel uiid here since do_gridfont_panel has state (above) (keyed by uiid) 494 | let panel_uiid = uiid + 'i'; 495 | 496 | let panel = do_panel_begin(panel_uiid, first_x, first_y, first_visible, first_expanded); 497 | 498 | if (panel.visible && panel.expanded) { 499 | 500 | ui.layout_push(_horizontal); 501 | let _ = ui.button(uiid + '-test-button', 'gridfont ' + (state.run ? 'stop' : 'run'), RectangleP(0, 0, 150, 24)); 502 | if (_.clicked) { 503 | state.run = 0 | (!state.run); 504 | } 505 | 506 | _ = ui.button(uiid + '-test-button2', 'gridfont reset', RectangleP(0, 0, 150, 24)); 507 | if (_.clicked) { 508 | state.reset = 0 | true; 509 | } 510 | ui.layout_pop(); 511 | 512 | ui.layout_increment2(0, 24); 513 | 514 | const rect = ui.layout_translated(RectangleP(0, 0, 0, 0)); 515 | 516 | let complete = 0 | (state.run && true); 517 | let reset_complete = 0 | (state.reset && true); 518 | if (state.run) { 519 | _ = do_gridfont(uiid + '-gridfont1', 'abcdefghijklmnopqrstuvwxyz', 'hint-four', rect.x, rect.y, 10, state.reset); 520 | complete = 0 | (complete && _.complete); 521 | reset_complete = 0 | (reset_complete && _.reset_complete); 522 | if (_.complete || (state.reset && _.reset_complete)) { 523 | _ = do_gridfont(uiid + '-gridfont2', 'leverage agile frameworks', 'hint-four', rect.x, 0 | (rect.y + 10 * 7), 10, state.reset); 524 | complete = 0 | (complete && _.complete); 525 | reset_complete = 0 | (reset_complete && _.reset_complete); 526 | if (_.complete || (state.reset && _.reset_complete)) { 527 | _ = do_gridfont(uiid + '-gridfont3', 'provide robust synopsis', 'hint-four', rect.x, 0 | (rect.y + 20 * 7), 10, state.reset); 528 | complete = 0 | (complete && _.complete); 529 | reset_complete = 0 | (reset_complete && _.reset_complete); 530 | } 531 | } 532 | } 533 | 534 | if (reset_complete) { 535 | state.reset = 0 | 0; 536 | } 537 | 538 | if (complete) { 539 | state.reset = 0 | 1; 540 | } 541 | 542 | ui.layout_increment2(780, 10 * 7 * 3 + 40); 543 | } 544 | do_panel_end(panel_uiid); 545 | } 546 | 547 | let _linestar_segments = 13; 548 | let _linestar_segments_direction = 1; 549 | let _linestar_segments_accumulator = 0; 550 | let _linestar_segments_accumulator_max = 17; 551 | let _linestar_joints = 12; 552 | let _linestar_joints_direction = 1; 553 | let _linestar_joints_accumulator = 0; 554 | let _linestar_joints_accumulator_max = 5; 555 | let _linestar_webs = 0; 556 | let _linestar_anim_enabled = 0 | false; 557 | function do_linestar_panel(uiid, first_x, first_y, first_visible, first_expanded) { 558 | let panel = do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded); 559 | 560 | if (panel.expanded) { 561 | let _ = do_linestar_edit(uiid + '-linestar-edit', _linestar_segments, _linestar_joints, _linestar_webs); 562 | if (_.changed) { 563 | _linestar_segments = _.segments; 564 | _linestar_joints = _.joints; 565 | _linestar_webs = _.webs; 566 | } else if (_linestar_anim_enabled) { 567 | _linestar_segments_accumulator++; 568 | if (_linestar_segments > 20 || _linestar_segments < 3) { 569 | _linestar_segments = Math.max(3, Math.min(20, _linestar_segments)); 570 | _linestar_segments_direction = -_linestar_segments_direction; 571 | } 572 | if (_linestar_segments_accumulator == _linestar_segments_accumulator_max) { 573 | _linestar_segments = _linestar_segments + _linestar_segments_direction; 574 | _linestar_segments_accumulator = 0; 575 | } 576 | 577 | _linestar_joints_accumulator++; 578 | if (_linestar_joints > 20 || _linestar_joints < 3) { 579 | _linestar_joints = Math.max(3, Math.min(20, _linestar_joints)); 580 | _linestar_joints_direction = -_linestar_joints_direction; 581 | } 582 | if (_linestar_joints_accumulator == _linestar_joints_accumulator_max) { 583 | _linestar_joints = _linestar_joints + _linestar_joints_direction; 584 | _linestar_joints_accumulator = 0; 585 | } 586 | } 587 | 588 | const label = (_linestar_anim_enabled ? 'disable' : 'enable') + ' idle animation'; 589 | _ = ui.checkbutton(uiid + '-enable-anim-button', label, RectangleP(0, 0, 200, 24), _linestar_anim_enabled); 590 | if (_.changed) { 591 | _linestar_anim_enabled = _.value; 592 | } 593 | 594 | const rect = RectangleP(0, 0, 200, 20); 595 | ui.layout_push(_horizontal); 596 | _ = ui.slider(uiid + '-slider-1', rect, 1, 20, _linestar_segments_accumulator_max, ''); 597 | if (_.changed) { 598 | _linestar_segments_accumulator = 0; 599 | _linestar_segments_accumulator_max = 0 | _.value; 600 | } 601 | ui.label('segment anim delay', rect); 602 | ui.layout_pop(); 603 | 604 | ui.layout_push(_horizontal); 605 | _ = ui.slider(uiid + '-slider-2', rect, 1, 20, _linestar_joints_accumulator_max, ''); 606 | if (_.changed) { 607 | _linestar_joints_accumulator = 0; 608 | _linestar_joints_accumulator_max = 0 | _.value; 609 | } 610 | ui.label('joint anim delay', rect); 611 | ui.layout_pop(); 612 | 613 | } 614 | 615 | do_panel_end(uiid); 616 | return panel; 617 | } 618 | 619 | function do_scrolltest_panel(uiid, first_x, first_y, first_visible, first_expanded) { 620 | 621 | let panel = do_panel_begin(uiid, first_x, first_y, first_visible, first_expanded); 622 | if (panel.visible && panel.expanded) { 623 | 624 | // todo: design so that when i remove the scroll widget, the contained widgets will just render as usual (no glue) 625 | let scroll_uiid = uiid + '-scroll'; 626 | let scroll = do_scroll_begin(scroll_uiid, RectangleP(0, 0, 200, 200), 20, 10000); 627 | 628 | // todo: 629 | // maybe an opt-in api for widgets to call to see if they are... "in view" 630 | // if (!in_view(uiid)) { return null; } // or something 631 | for (let i = scroll.first_visible_index; i < scroll.last_visible_index; i++) { 632 | do_scroll_item_begin(scroll_uiid, i); 633 | const _ = ui.button(uiid + '-button-' + i, 'button #' + i, RectangleP(0, 0, 200, 20)); 634 | if (_.clicked) console.log('clicked #' + i); 635 | do_scroll_item_end(scroll_uiid); 636 | } 637 | 638 | do_scroll_end(scroll_uiid); 639 | } 640 | do_panel_end(uiid); 641 | return panel; 642 | } 643 | 644 | let _used_heap_size = 0 | 0; 645 | let _uhs_sample_counter = 0 | 0; 646 | function do_memory_graphs() { 647 | if (window.performance) { 648 | const high_value = 100 * 1024 * 1024; 649 | let label_value; 650 | 651 | sidelabel('memory'); 652 | 653 | ui.layout_push(_horizontal); 654 | ui.progressbar('--memory-graph-progbar', RectangleP(0, 0, 100, 20), high_value, 0 | window.performance.memory.usedJSHeapSize); // total heap memory 655 | _uhs_sample_counter++; 656 | if (_uhs_sample_counter == 10) { 657 | _used_heap_size = window.performance.memory.usedJSHeapSize / 1024; 658 | _uhs_sample_counter = 0; 659 | } 660 | label_value = 0 | _used_heap_size; 661 | ui.label(label_value + 'k', RectangleP(0, 0, 100, 20), ColorP(255,255,255,255)); 662 | ui.layout_pop(); 663 | 664 | ui.layout_push(_horizontal); 665 | ui.progressbar('--memory-graph-progbar2', RectangleP(0, 0, 100, 20), high_value, 0 | window.performance.memory.totalJSHeapSize); // currently used heap memory 666 | label_value = 0 | window.performance.memory.totalJSHeapSize / 1024 / 1024; 667 | ui.label(label_value + 'meg', RectangleP(0, 0, 100, 20), ColorP(255,255,255,255)); 668 | ui.layout_pop(); 669 | 670 | } 671 | } 672 | 673 | /** create random point, with no zero components */ 674 | function random_anim_vector() { 675 | let vecx = 0 | 1; 676 | let vecy = 0 | 1; 677 | if (Math.random() < 0.5) { 678 | vecx = -vecx; 679 | } 680 | if (Math.random() < 0.5) { 681 | vecy = -vecy; 682 | } 683 | m_v8.assert_smi(vecx); 684 | m_v8.assert_smi(vecy); 685 | return Point(vecx, vecy); 686 | } 687 | 688 | const anim_items = [PointP(0, 0)]; 689 | const anim_vectors = [random_anim_vector()]; 690 | const anim_color1 = make_css_color(Color(255, 255, 255, 8)); 691 | const anim_color2 = make_css_color(Color(255, 255, 255, 8)); 692 | let _bg_init = 0 | false; 693 | 694 | function do_background_anim() { 695 | if (!_bg_init) { 696 | for (let i = 1; i < 100; i++) { 697 | const randx = 0 | (Math.random() * canvas.width * 3) - canvas.width; 698 | const randy = 0 | (Math.random() * canvas.height * 3) - canvas.height; 699 | anim_items.push(PointP(randx, randy)); 700 | anim_vectors.push(random_anim_vector()); 701 | } 702 | _bg_init = 0 | true; 703 | } 704 | 705 | uidraw.begin_path(); 706 | uidraw.move_to(0,0); 707 | for (let i = 0; i < anim_items.length; i++) { 708 | const item = anim_items[i]; 709 | const vec = anim_vectors[i]; 710 | 711 | uidraw.line_to(item.x, item.y); 712 | 713 | // update positions from vectors 714 | item.x = 0 | item.x + vec.x; 715 | item.y = 0 | item.y + vec.y; 716 | 717 | const min_x = 0 | -canvas.width; 718 | const max_x = 0 | canvas.width * 2; 719 | const min_y = 0 | -canvas.height; 720 | const max_y = 0 | canvas.height * 2; 721 | 722 | m_v8.assert_smi(item.x); 723 | m_v8.assert_smi(item.y); 724 | m_v8.assert_smi(vec.x); 725 | m_v8.assert_smi(vec.y); 726 | // reverse vectors when out of bounds 727 | if (item[0] < min_x || item[0] > max_x) { 728 | vec[0] *= -1; 729 | } 730 | if (item[1] < min_y || item[1] > max_y) { 731 | vec[1] *= -1; 732 | //vec[1] = 0 | -vec[1]; 733 | } 734 | } 735 | 736 | uidraw.push_linewidth(1); 737 | uidraw.push_strokestyle(anim_color1); 738 | uidraw.stroke(); 739 | uidraw.pop_linewidth(); 740 | uidraw.pop_strokestyle(); 741 | // todo: uidraw.custom_line2(x1, y1, x2, y2, width, style) 742 | 743 | const dash_scale = 20; 744 | uidraw.push_linedash([1 * dash_scale, 3 * dash_scale, 3 * dash_scale, 5 * dash_scale, 5 * dash_scale, 8 * dash_scale, 8 * dash_scale, 13 * dash_scale]); 745 | uidraw.push_linewidth(3); 746 | uidraw.push_strokestyle(anim_color2); 747 | uidraw.stroke(); 748 | uidraw.pop_strokestyle(); 749 | uidraw.pop_linewidth(); 750 | uidraw.pop_linedash(); 751 | // todo: uidraw.custom_line3(x1, y1, x2, y2, width, style, dash) 752 | } 753 | 754 | function do_app_demo() { 755 | let expanded = !ui.driver.IsTouchDevice(); 756 | 757 | const row_x0 = 222; 758 | const row_y0 = 47; 759 | do_linestar_panel('linestar panel', row_x0, row_y0, true, expanded); 760 | 761 | const row_x1 = row_x0 + 465; 762 | do_color_panel('color panel', row_x1, row_y0, true, expanded); 763 | 764 | const row_x2 = row_x1 + 666; 765 | do_gradient_panel('gradient panel', row_x2, row_y0, true, expanded); 766 | 767 | do_gridfont_panel('gridfont panel ', row_x1, 547 + 27 - 40 - 3, true, expanded); 768 | do_scrolltest_panel('scroll test panel', 1579 - 30 - 12, 486 + 25 - 42, true, expanded); 769 | } 770 | 771 | export { 772 | do_app_demo, 773 | do_background_anim, 774 | do_sidepanel, 775 | }; -------------------------------------------------------------------------------- /index-webgl-pixi.html: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 83 | 84 | 853 | 854 | 855 | 856 | --------------------------------------------------------------------------------