Initial commit: Closed loop of TS->Rust->TS
This commit is contained in:
commit
856bbf4dae
14 changed files with 701 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
*.sublime*
|
||||||
|
/target
|
22
Cargo.toml
Normal file
22
Cargo.toml
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
[package]
|
||||||
|
name = "emgui"
|
||||||
|
version = "0.1.0"
|
||||||
|
authors = ["Emil Ernerfeldt <emilernerfeldt@gmail.com>"]
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# rand = { version="0.6", features = ['wasm-bindgen'] }
|
||||||
|
serde = "1"
|
||||||
|
serde_derive = "1"
|
||||||
|
serde_json = "1"
|
||||||
|
wasm-bindgen = "0.2"
|
||||||
|
web-sys = { version = "0.3.5", features = ['console', 'Performance', 'Window'] }
|
||||||
|
|
||||||
|
# Optimize for small code size:
|
||||||
|
[profile.dev]
|
||||||
|
opt-level = "s"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
opt-level = "s"
|
21
README.md
Normal file
21
README.md
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
# Emgui – An Experimental, Modularized immediate mode Graphical User Interface
|
||||||
|
|
||||||
|
Here are the steps, in chronological order of execution:
|
||||||
|
|
||||||
|
CODE: Input bindings, i.e. gathering GuiInput data from the system (web browser, Mac Window, iPhone App, ...)
|
||||||
|
DATA: GuiInput: mouse and keyboard state + window size
|
||||||
|
DATA: GuiSizes: this is a configuration of the ImLayout system, sets sizes of e.g. a slider.
|
||||||
|
CODE: ImLayout: Immediate mode layout Gui elements. THIS IS WHAT YOUR APP CODE CALLS!
|
||||||
|
DATA: GuiPaint: High-level commands to render e.g. a checked box with a hover-effect at a certain position.
|
||||||
|
DATA: GuiStyle: The colors/shading of the gui.
|
||||||
|
CODE: GuiPainter: Renders GuiPaint + GuiStyle into DrawCommands
|
||||||
|
DATA: PaintCommands: low-level commands (e.g. "Draw a rectangle with this color here")
|
||||||
|
CODE: Painter: paints the the PaintCommands to the screen (HTML canvas, OpenGL, ...)
|
||||||
|
|
||||||
|
This is similar to Dear ImGui but separates the layout from the rendering, and adds another step to the rendering.
|
||||||
|
|
||||||
|
# Implementation
|
||||||
|
|
||||||
|
Input is gathered in TypeScript.
|
||||||
|
PaintCommands rendered to a HTML canvas.
|
||||||
|
Everything else is written in Rust, compiled to WASM.
|
33
build.sh
Executable file
33
build.sh
Executable file
|
@ -0,0 +1,33 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
# Pre-requisites:
|
||||||
|
rustup target add wasm32-unknown-unknown
|
||||||
|
if ! [[ $(wasm-bindgen --version) ]]; then
|
||||||
|
cargo install wasm-bindgen-cli
|
||||||
|
fi
|
||||||
|
|
||||||
|
BUILD=debug
|
||||||
|
# BUILD=release
|
||||||
|
|
||||||
|
# Clear output from old stuff:
|
||||||
|
rm -rf docs/*.d.ts
|
||||||
|
rm -rf docs/*.js
|
||||||
|
rm -rf docs/*.wasm
|
||||||
|
|
||||||
|
echo "Build rust:"
|
||||||
|
cargo build --target wasm32-unknown-unknown
|
||||||
|
|
||||||
|
echo "Lint and clean up typescript:"
|
||||||
|
tslint --fix docs/*.ts
|
||||||
|
|
||||||
|
echo "Compile typescript:"
|
||||||
|
tsc
|
||||||
|
|
||||||
|
echo "Generate JS bindings for wasm:"
|
||||||
|
|
||||||
|
FOLDER_NAME=${PWD##*/}
|
||||||
|
TARGET_NAME="$FOLDER_NAME.wasm"
|
||||||
|
wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
|
||||||
|
--out-dir docs --no-modules
|
||||||
|
# --no-modules-global hoboho
|
6
build_and_run.sh
Executable file
6
build_and_run.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -eu
|
||||||
|
|
||||||
|
./build.sh
|
||||||
|
|
||||||
|
open "docs/index.html"
|
11
docs/emgui.d.ts
vendored
Normal file
11
docs/emgui.d.ts
vendored
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
/* tslint:disable */
|
||||||
|
export function show_gui(arg0: string): string;
|
||||||
|
|
||||||
|
export class Input {
|
||||||
|
free(): void;
|
||||||
|
screen_width: number
|
||||||
|
screen_height: number
|
||||||
|
mouse_x: number
|
||||||
|
mouse_y: number
|
||||||
|
|
||||||
|
}
|
151
docs/emgui.js
Normal file
151
docs/emgui.js
Normal file
|
@ -0,0 +1,151 @@
|
||||||
|
(function() {
|
||||||
|
var wasm;
|
||||||
|
const __exports = {};
|
||||||
|
|
||||||
|
|
||||||
|
let cachedTextEncoder = new TextEncoder('utf-8');
|
||||||
|
|
||||||
|
let cachegetUint8Memory = null;
|
||||||
|
function getUint8Memory() {
|
||||||
|
if (cachegetUint8Memory === null || cachegetUint8Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint8Memory = new Uint8Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint8Memory;
|
||||||
|
}
|
||||||
|
|
||||||
|
function passStringToWasm(arg) {
|
||||||
|
|
||||||
|
const buf = cachedTextEncoder.encode(arg);
|
||||||
|
const ptr = wasm.__wbindgen_malloc(buf.length);
|
||||||
|
getUint8Memory().set(buf, ptr);
|
||||||
|
return [ptr, buf.length];
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedTextDecoder = new TextDecoder('utf-8');
|
||||||
|
|
||||||
|
function getStringFromWasm(ptr, len) {
|
||||||
|
return cachedTextDecoder.decode(getUint8Memory().subarray(ptr, ptr + len));
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachedGlobalArgumentPtr = null;
|
||||||
|
function globalArgumentPtr() {
|
||||||
|
if (cachedGlobalArgumentPtr === null) {
|
||||||
|
cachedGlobalArgumentPtr = wasm.__wbindgen_global_argument_ptr();
|
||||||
|
}
|
||||||
|
return cachedGlobalArgumentPtr;
|
||||||
|
}
|
||||||
|
|
||||||
|
let cachegetUint32Memory = null;
|
||||||
|
function getUint32Memory() {
|
||||||
|
if (cachegetUint32Memory === null || cachegetUint32Memory.buffer !== wasm.memory.buffer) {
|
||||||
|
cachegetUint32Memory = new Uint32Array(wasm.memory.buffer);
|
||||||
|
}
|
||||||
|
return cachegetUint32Memory;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @param {string} arg0
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
__exports.show_gui = function(arg0) {
|
||||||
|
const [ptr0, len0] = passStringToWasm(arg0);
|
||||||
|
const retptr = globalArgumentPtr();
|
||||||
|
try {
|
||||||
|
wasm.show_gui(retptr, ptr0, len0);
|
||||||
|
const mem = getUint32Memory();
|
||||||
|
const rustptr = mem[retptr / 4];
|
||||||
|
const rustlen = mem[retptr / 4 + 1];
|
||||||
|
|
||||||
|
const realRet = getStringFromWasm(rustptr, rustlen).slice();
|
||||||
|
wasm.__wbindgen_free(rustptr, rustlen * 1);
|
||||||
|
return realRet;
|
||||||
|
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
wasm.__wbindgen_free(ptr0, len0 * 1);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
function freeInput(ptr) {
|
||||||
|
|
||||||
|
wasm.__wbg_input_free(ptr);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
*/
|
||||||
|
class Input {
|
||||||
|
|
||||||
|
free() {
|
||||||
|
const ptr = this.ptr;
|
||||||
|
this.ptr = 0;
|
||||||
|
freeInput(ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
get screen_width() {
|
||||||
|
return wasm.__wbg_get_input_screen_width(this.ptr);
|
||||||
|
}
|
||||||
|
set screen_width(arg0) {
|
||||||
|
return wasm.__wbg_set_input_screen_width(this.ptr, arg0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
get screen_height() {
|
||||||
|
return wasm.__wbg_get_input_screen_height(this.ptr);
|
||||||
|
}
|
||||||
|
set screen_height(arg0) {
|
||||||
|
return wasm.__wbg_set_input_screen_height(this.ptr, arg0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
get mouse_x() {
|
||||||
|
return wasm.__wbg_get_input_mouse_x(this.ptr);
|
||||||
|
}
|
||||||
|
set mouse_x(arg0) {
|
||||||
|
return wasm.__wbg_set_input_mouse_x(this.ptr, arg0);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @returns {number}
|
||||||
|
*/
|
||||||
|
get mouse_y() {
|
||||||
|
return wasm.__wbg_get_input_mouse_y(this.ptr);
|
||||||
|
}
|
||||||
|
set mouse_y(arg0) {
|
||||||
|
return wasm.__wbg_set_input_mouse_y(this.ptr, arg0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
__exports.Input = Input;
|
||||||
|
|
||||||
|
__exports.__wbindgen_throw = function(ptr, len) {
|
||||||
|
throw new Error(getStringFromWasm(ptr, len));
|
||||||
|
};
|
||||||
|
|
||||||
|
function init(path_or_module) {
|
||||||
|
let instantiation;
|
||||||
|
const imports = { './emgui': __exports };
|
||||||
|
if (path_or_module instanceof WebAssembly.Module) {
|
||||||
|
instantiation = WebAssembly.instantiate(path_or_module, imports)
|
||||||
|
.then(instance => {
|
||||||
|
return { instance, module: module_or_path }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const data = fetch(path_or_module);
|
||||||
|
if (typeof WebAssembly.instantiateStreaming === 'function') {
|
||||||
|
instantiation = WebAssembly.instantiateStreaming(data, imports);
|
||||||
|
} else {
|
||||||
|
instantiation = data
|
||||||
|
.then(response => response.arrayBuffer())
|
||||||
|
.then(buffer => WebAssembly.instantiate(buffer, imports));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return instantiation.then(({instance}) => {
|
||||||
|
wasm = init.wasm = instance.exports;
|
||||||
|
return;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
self.wasm_bindgen = Object.assign(init, __exports);
|
||||||
|
})();
|
BIN
docs/emgui_bg.wasm
Normal file
BIN
docs/emgui_bg.wasm
Normal file
Binary file not shown.
112
docs/frontend.js
Normal file
112
docs/frontend.js
Normal file
|
@ -0,0 +1,112 @@
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Paint module:
|
||||||
|
function paintCommand(canvas, cmd) {
|
||||||
|
var ctx = canvas.getContext("2d");
|
||||||
|
switch (cmd.kind) {
|
||||||
|
case "clear":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
return;
|
||||||
|
case "line":
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = cmd.line_width;
|
||||||
|
ctx.strokeStyle = cmd.stroke_style;
|
||||||
|
ctx.moveTo(cmd.from[0], cmd.from[1]);
|
||||||
|
ctx.lineTo(cmd.to[0], cmd.to[1]);
|
||||||
|
ctx.stroke();
|
||||||
|
return;
|
||||||
|
case "circle":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cmd.center[0], cmd.center[1], cmd.radius, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fill();
|
||||||
|
return;
|
||||||
|
case "rounded_rect":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
var x = cmd.pos[0];
|
||||||
|
var y = cmd.pos[1];
|
||||||
|
var width = cmd.size[0];
|
||||||
|
var height = cmd.size[1];
|
||||||
|
var radius = cmd.radius;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + radius, y);
|
||||||
|
ctx.lineTo(x + width - radius, y);
|
||||||
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||||
|
ctx.lineTo(x + width, y + height - radius);
|
||||||
|
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
|
||||||
|
ctx.lineTo(x + radius, y + height);
|
||||||
|
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||||
|
ctx.lineTo(x, y + radius);
|
||||||
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
return;
|
||||||
|
case "text":
|
||||||
|
ctx.font = cmd.font;
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.textAlign = cmd.text_align;
|
||||||
|
ctx.fillText(cmd.text, cmd.pos[0], cmd.pos[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// we'll defer our execution until the wasm is ready to go
|
||||||
|
function wasm_loaded() {
|
||||||
|
console.log("wasm loaded");
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
// here we tell bindgen the path to the wasm file so it can start
|
||||||
|
// initialization and return to us a promise when it's done
|
||||||
|
wasm_bindgen("./emgui_bg.wasm")
|
||||||
|
.then(wasm_loaded)["catch"](console.error);
|
||||||
|
function rust_gui(input) {
|
||||||
|
return JSON.parse(wasm_bindgen.show_gui(JSON.stringify(input)));
|
||||||
|
}
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
function js_gui(input) {
|
||||||
|
var commands = [];
|
||||||
|
commands.push({
|
||||||
|
fillStyle: "#111111",
|
||||||
|
kind: "clear"
|
||||||
|
});
|
||||||
|
commands.push({
|
||||||
|
fillStyle: "#ff1111",
|
||||||
|
kind: "rounded_rect",
|
||||||
|
pos: [100, 100],
|
||||||
|
radius: 20,
|
||||||
|
size: [200, 100]
|
||||||
|
});
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
function paint_gui(canvas, mouse_pos) {
|
||||||
|
var input = {
|
||||||
|
mouse_x: mouse_pos.x,
|
||||||
|
mouse_y: mouse_pos.y,
|
||||||
|
screen_height: canvas.height,
|
||||||
|
screen_width: canvas.width
|
||||||
|
};
|
||||||
|
var commands = rust_gui(input);
|
||||||
|
for (var _i = 0, commands_1 = commands; _i < commands_1.length; _i++) {
|
||||||
|
var cmd = commands_1[_i];
|
||||||
|
paintCommand(canvas, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
function mouse_pos_from_event(canvas, evt) {
|
||||||
|
var rect = canvas.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: evt.clientX - rect.left,
|
||||||
|
y: evt.clientY - rect.top
|
||||||
|
};
|
||||||
|
}
|
||||||
|
function initialize() {
|
||||||
|
var canvas = document.getElementById("canvas");
|
||||||
|
canvas.addEventListener("mousemove", function (evt) {
|
||||||
|
var mouse_pos = mouse_pos_from_event(canvas, evt);
|
||||||
|
paint_gui(canvas, mouse_pos);
|
||||||
|
}, false);
|
||||||
|
canvas.addEventListener("mousedown", function (evt) {
|
||||||
|
var mouse_pos = mouse_pos_from_event(canvas, evt);
|
||||||
|
paint_gui(canvas, mouse_pos);
|
||||||
|
}, false);
|
||||||
|
paint_gui(canvas, { x: 0, y: 0 });
|
||||||
|
}
|
206
docs/frontend.ts
Normal file
206
docs/frontend.ts
Normal file
|
@ -0,0 +1,206 @@
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
// Paint module:
|
||||||
|
|
||||||
|
interface Clear {
|
||||||
|
kind: "clear";
|
||||||
|
fill_style: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Line {
|
||||||
|
kind: "line";
|
||||||
|
from: [number, number];
|
||||||
|
line_width: number;
|
||||||
|
stroke_style: string;
|
||||||
|
to: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Circle {
|
||||||
|
kind: "circle";
|
||||||
|
center: [number, number];
|
||||||
|
fill_style: string;
|
||||||
|
radius: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RoundedRect {
|
||||||
|
kind: "rounded_rect";
|
||||||
|
fill_style: string;
|
||||||
|
pos: [number, number];
|
||||||
|
radius: number;
|
||||||
|
size: [number, number];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Text {
|
||||||
|
kind: "text";
|
||||||
|
fill_style: string;
|
||||||
|
font: string;
|
||||||
|
pos: [number, number];
|
||||||
|
text: string;
|
||||||
|
text_align: "start" | "center" | "end";
|
||||||
|
}
|
||||||
|
|
||||||
|
type PaintCmd = Clear | Line | Circle | RoundedRect | Text;
|
||||||
|
|
||||||
|
function paintCommand(canvas, cmd: PaintCmd) {
|
||||||
|
const ctx = canvas.getContext("2d");
|
||||||
|
|
||||||
|
switch (cmd.kind) {
|
||||||
|
case "clear":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "line":
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.lineWidth = cmd.line_width;
|
||||||
|
ctx.strokeStyle = cmd.stroke_style;
|
||||||
|
ctx.moveTo(cmd.from[0], cmd.from[1]);
|
||||||
|
ctx.lineTo(cmd.to[0], cmd.to[1]);
|
||||||
|
ctx.stroke();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "circle":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.arc(cmd.center[0], cmd.center[1], cmd.radius, 0, 2 * Math.PI, false);
|
||||||
|
ctx.fill();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "rounded_rect":
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
const x = cmd.pos[0];
|
||||||
|
const y = cmd.pos[1];
|
||||||
|
const width = cmd.size[0];
|
||||||
|
const height = cmd.size[1];
|
||||||
|
const radius = cmd.radius;
|
||||||
|
ctx.beginPath();
|
||||||
|
ctx.moveTo(x + radius, y);
|
||||||
|
ctx.lineTo(x + width - radius, y);
|
||||||
|
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
|
||||||
|
ctx.lineTo(x + width, y + height - radius);
|
||||||
|
ctx.quadraticCurveTo(
|
||||||
|
x + width,
|
||||||
|
y + height,
|
||||||
|
x + width - radius,
|
||||||
|
y + height,
|
||||||
|
);
|
||||||
|
ctx.lineTo(x + radius, y + height);
|
||||||
|
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||||
|
ctx.lineTo(x, y + radius);
|
||||||
|
ctx.quadraticCurveTo(x, y, x + radius, y);
|
||||||
|
ctx.closePath();
|
||||||
|
ctx.fill();
|
||||||
|
return;
|
||||||
|
|
||||||
|
case "text":
|
||||||
|
ctx.font = cmd.font;
|
||||||
|
ctx.fillStyle = cmd.fill_style;
|
||||||
|
ctx.textAlign = cmd.text_align;
|
||||||
|
ctx.fillText(cmd.text, cmd.pos[0], cmd.pos[1]);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
interface Coord {
|
||||||
|
x: number;
|
||||||
|
y: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Input {
|
||||||
|
mouse_x: number;
|
||||||
|
mouse_y: number;
|
||||||
|
screen_height: number;
|
||||||
|
screen_width: number;
|
||||||
|
// TODO: mouse down etc
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
// the `wasm_bindgen` global is set to the exports of the Rust module. Override with wasm-bindgen --no-modules-global
|
||||||
|
declare var wasm_bindgen: any;
|
||||||
|
|
||||||
|
// we'll defer our execution until the wasm is ready to go
|
||||||
|
function wasm_loaded() {
|
||||||
|
console.log(`wasm loaded`);
|
||||||
|
initialize();
|
||||||
|
}
|
||||||
|
|
||||||
|
// here we tell bindgen the path to the wasm file so it can start
|
||||||
|
// initialization and return to us a promise when it's done
|
||||||
|
wasm_bindgen("./emgui_bg.wasm")
|
||||||
|
.then(wasm_loaded)
|
||||||
|
.catch(console.error);
|
||||||
|
|
||||||
|
function rust_gui(input: Input): PaintCmd[] {
|
||||||
|
return JSON.parse(wasm_bindgen.show_gui(JSON.stringify(input)));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function js_gui(input: Input): PaintCmd[] {
|
||||||
|
const commands = [];
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
fillStyle: "#111111",
|
||||||
|
kind: "clear",
|
||||||
|
});
|
||||||
|
|
||||||
|
commands.push({
|
||||||
|
fillStyle: "#ff1111",
|
||||||
|
kind: "rounded_rect",
|
||||||
|
pos: [100, 100],
|
||||||
|
radius: 20,
|
||||||
|
size: [200, 100],
|
||||||
|
});
|
||||||
|
|
||||||
|
return commands;
|
||||||
|
}
|
||||||
|
|
||||||
|
function paint_gui(canvas, mouse_pos) {
|
||||||
|
const input = {
|
||||||
|
mouse_x: mouse_pos.x,
|
||||||
|
mouse_y: mouse_pos.y,
|
||||||
|
screen_height: canvas.height,
|
||||||
|
screen_width: canvas.width,
|
||||||
|
};
|
||||||
|
const commands = rust_gui(input);
|
||||||
|
|
||||||
|
for (const cmd of commands) {
|
||||||
|
paintCommand(canvas, cmd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
function mouse_pos_from_event(canvas, evt): Coord {
|
||||||
|
const rect = canvas.getBoundingClientRect();
|
||||||
|
return {
|
||||||
|
x: evt.clientX - rect.left,
|
||||||
|
y: evt.clientY - rect.top,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function initialize() {
|
||||||
|
const canvas = document.getElementById("canvas");
|
||||||
|
|
||||||
|
canvas.addEventListener(
|
||||||
|
"mousemove",
|
||||||
|
(evt) => {
|
||||||
|
const mouse_pos = mouse_pos_from_event(canvas, evt);
|
||||||
|
paint_gui(canvas, mouse_pos);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.addEventListener(
|
||||||
|
"mousedown",
|
||||||
|
(evt) => {
|
||||||
|
const mouse_pos = mouse_pos_from_event(canvas, evt);
|
||||||
|
paint_gui(canvas, mouse_pos);
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
|
||||||
|
paint_gui(canvas, { x: 0, y: 0 });
|
||||||
|
}
|
44
docs/index.html
Normal file
44
docs/index.html
Normal file
|
@ -0,0 +1,44 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
||||||
|
<head>
|
||||||
|
<title>Gui Experiment</title>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
html {
|
||||||
|
/* Remove touch delay: */
|
||||||
|
touch-action: manipulation;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: #111111;
|
||||||
|
color: #bbbbbb;
|
||||||
|
max-width: 480px;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<script>
|
||||||
|
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
|
||||||
|
// `WebAssembly.instantiateStreaming` to instantiate the wasm module,
|
||||||
|
// but this doesn't work with `file://` urls. This example is frequently
|
||||||
|
// viewed by simply opening `index.html` in a browser (with a `file://`
|
||||||
|
// url), so it would fail if we were to call this function!
|
||||||
|
//
|
||||||
|
// Work around this for now by deleting the function to ensure that the
|
||||||
|
// `no_modules.js` script doesn't have access to it. You won't need this
|
||||||
|
// hack when deploying over HTTP.
|
||||||
|
delete WebAssembly.instantiateStreaming;
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<!-- this is the JS generated by the `wasm-bindgen` CLI tool -->
|
||||||
|
<script src="emgui.js"></script>
|
||||||
|
|
||||||
|
<script src="frontend.js" type="module"></script>
|
||||||
|
|
||||||
|
<!-- TODO: make this cover the entire screen, with resize and all -->
|
||||||
|
<canvas id="canvas" width="480" height="800"></canvas>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
70
src/lib.rs
Normal file
70
src/lib.rs
Normal file
|
@ -0,0 +1,70 @@
|
||||||
|
extern crate serde;
|
||||||
|
extern crate serde_json;
|
||||||
|
extern crate wasm_bindgen;
|
||||||
|
extern crate web_sys;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate serde_derive;
|
||||||
|
|
||||||
|
use wasm_bindgen::prelude::*;
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
pub struct Input {
|
||||||
|
pub screen_width: f32,
|
||||||
|
pub screen_height: f32,
|
||||||
|
pub mouse_x: f32,
|
||||||
|
pub mouse_y: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
enum TextAlign {
|
||||||
|
Start,
|
||||||
|
Center,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize)]
|
||||||
|
#[serde(rename_all = "snake_case", tag = "kind")]
|
||||||
|
enum PaintCmd {
|
||||||
|
Clear {
|
||||||
|
fill_style: String,
|
||||||
|
},
|
||||||
|
RoundedRect {
|
||||||
|
fill_style: String,
|
||||||
|
pos: [f32; 2],
|
||||||
|
size: [f32; 2],
|
||||||
|
radius: f32,
|
||||||
|
},
|
||||||
|
Text {
|
||||||
|
fill_style: String,
|
||||||
|
font: String,
|
||||||
|
pos: [f32; 2],
|
||||||
|
text: String,
|
||||||
|
text_align: TextAlign,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen]
|
||||||
|
pub fn show_gui(input_json: &str) -> String {
|
||||||
|
let input: Input = serde_json::from_str(input_json).unwrap();
|
||||||
|
let commands = [
|
||||||
|
PaintCmd::Clear {
|
||||||
|
fill_style: "#44444400".to_string(),
|
||||||
|
},
|
||||||
|
PaintCmd::RoundedRect {
|
||||||
|
fill_style: "#1111ff".to_string(),
|
||||||
|
pos: [100.0, 100.0],
|
||||||
|
radius: 40.0,
|
||||||
|
size: [200.0, 200.0],
|
||||||
|
},
|
||||||
|
PaintCmd::Text {
|
||||||
|
fill_style: "#11ff00".to_string(),
|
||||||
|
font: "14px Palatino".to_string(),
|
||||||
|
pos: [200.0, 32.0],
|
||||||
|
text: format!("Mouse pos: {} {}", input.mouse_x, input.mouse_y),
|
||||||
|
text_align: TextAlign::Center,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
serde_json::to_string(&commands).unwrap()
|
||||||
|
}
|
8
tsconfig.json
Normal file
8
tsconfig.json
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "es2015",
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"docs/**/*"
|
||||||
|
]
|
||||||
|
}
|
15
tslint.json
Normal file
15
tslint.json
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
{
|
||||||
|
"defaultSeverity": "error",
|
||||||
|
"extends": [
|
||||||
|
"tslint:recommended"
|
||||||
|
],
|
||||||
|
"jsRules": {},
|
||||||
|
"rules": {
|
||||||
|
"interface-name": [true, "never-prefix"],
|
||||||
|
"max-classes-per-file": [false],
|
||||||
|
"no-bitwise": false,
|
||||||
|
"no-console": false,
|
||||||
|
"variable-name": false
|
||||||
|
},
|
||||||
|
"rulesDirectory": []
|
||||||
|
}
|
Loading…
Reference in a new issue