├── 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 | 
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 += `${leftPad(element, 8, '0')}`, ''
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 |
--------------------------------------------------------------------------------