Skip to content

Commit

Permalink
Initial commit: Closed loop of TS->Rust->TS
Browse files Browse the repository at this point in the history
  • Loading branch information
emilk committed Dec 23, 2018
0 parents commit 856bbf4
Show file tree
Hide file tree
Showing 14 changed files with 701 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.sublime*
/target
22 changes: 22 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -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 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 33 additions & 0 deletions build.sh
Original file line number Diff line number Diff line change
@@ -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 changes: 6 additions & 0 deletions build_and_run.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/bin/bash
set -eu

./build.sh

open "docs/index.html"
11 changes: 11 additions & 0 deletions docs/emgui.d.ts
Original file line number Diff line number Diff line change
@@ -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 changes: 151 additions & 0 deletions docs/emgui.js
Original file line number Diff line number Diff line change
@@ -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);
})();
Binary file added docs/emgui_bg.wasm
Binary file not shown.
112 changes: 112 additions & 0 deletions docs/frontend.js
Original file line number Diff line number Diff line change
@@ -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 });
}
Loading

0 comments on commit 856bbf4

Please sign in to comment.