Initial commit

Using the latest unpublished Egui.
Will update to 0.6 once released.
This commit is contained in:
Emil Ernerfeldt 2020-12-19 21:52:39 +01:00
commit 8d86ae1715
12 changed files with 1245 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

34
Cargo.toml Normal file
View file

@ -0,0 +1,34 @@
[package]
name = "egui_template"
version = "0.1.0"
authors = ["Emil Ernerfeldt <emilernerfeldt@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
egui = "0.5"
# Gives us persistence. Remove if you do like.
serde = { version = "1", features = ["derive"] }
# For compiling natively:
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
egui_glium = "0.5"
# For compiling to web:
[target.'cfg(target_arch = "wasm32")'.dependencies]
egui_web = "0.5"
js-sys = "0.3"
wasm-bindgen = "0.2"
[patch.crates-io]
egui = { git = "https://github.com/emilk/egui", rev = "58c025a7e3d509e93e8f525aa747af1c9bd3dcb1" } # 2020-12-19
egui_glium = { git = "https://github.com/emilk/egui", rev = "58c025a7e3d509e93e8f525aa747af1c9bd3dcb1" } # 2020-12-19
egui_web = { git = "https://github.com/emilk/egui", rev = "58c025a7e3d509e93e8f525aa747af1c9bd3dcb1" } # 2020-12-19
[profile.release]
opt-level = 2 # fast and small wasm

44
README.md Normal file
View file

@ -0,0 +1,44 @@
# Egui Template
This is a template repo for [Egui](https://github.com/emilk/egui/).
The goal is for this to be the simplest way to get started writing a GUI app in Rust.
You can compile your app natively or for the web, and share it using Github Pages.
## Getting started
Start by clicking "Use this template" at https://github.com/emilk/egui_template/ or follow [these instructions](https://docs.github.com/en/free-pro-team@latest/github/creating-cloning-and-archiving-repositories/creating-a-repository-from-a-template).
`src/app.rs` contains a simple example app. This is just to give some inspiration - most of it can be removed if you like.
### Testing locally
`cargo run --release`
### Compiling for the web
You can compile your app to [WASM](https://en.wikipedia.org/wiki/WebAssembly) and publish it as a web page. For this you need to set up some tools. There are a few simple scripts that help you with this:
``` sh
./setup_web.sh
./build_web.sh
./start_server.sh
open http://127.0.0.1:8080/
```
* `setup_web.sh` installs the tools required to build for web
* `build_web.sh` compiles your code to wasm and puts it in the `docs/` folder (see below)
* `start_server.sh` starts a local HTTP server so you can test before you publish
* Open http://127.0.0.1:8080/ in a web browser to view
The finished we app is found in the `docs/` folder (this is so that you can easily share it with [GitHub Pages](https://docs.github.com/en/free-pro-team@latest/github/working-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site)). It consists of three files:
* `index.html`: A few lines of HTML, CSS and JS that loads your app. **You need to edit this** (once) to replace `egui_template` with the name of your crate!
* `your_crate_bg.wasm`: What the Rust code compiles to.
* `your_crate.js`: Auto-generated binding between Rust and JS.
## Updating Egui
As of 2020, Egui is in active development with frequent releases with breaking changes. When updating Egui, update the version string in `Cargo.toml` of `egui`, `egui_glium` and `egui_web` (they should match). You can also check out the [egui_template](https://github.com/emilk/egui_template/) repository to see what changes has happened to it.

23
build_web.sh Executable file
View file

@ -0,0 +1,23 @@
#!/bin/bash
set -eu
# ./setup_web.sh # <- call this first!
FOLDER_NAME=${PWD##*/}
CRATE_NAME=$FOLDER_NAME # assume crate name is the same as the folder name
export RUSTFLAGS=--cfg=web_sys_unstable_apis # required for the clipboard API
# Clear output from old stuff:
rm -rf docs/$CRATE_NAME.wasm
echo "Building rust…"
BUILD=release
cargo build --release -p $CRATE_NAME --lib --target wasm32-unknown-unknown
echo "Generating JS bindings for wasm…"
TARGET_NAME="$CRATE_NAME.wasm"
wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \
--out-dir docs --no-modules --no-typescript
echo "Finished: docs/$CRATE_NAME.wasm"

845
docs/egui_template.js Normal file
View file

@ -0,0 +1,845 @@
let wasm_bindgen;
(function() {
const __exports = {};
let wasm;
const heap = new Array(32).fill(undefined);
heap.push(undefined, null, true, false);
function getObject(idx) { return heap[idx]; }
let heap_next = heap.length;
function dropObject(idx) {
if (idx < 36) return;
heap[idx] = heap_next;
heap_next = idx;
}
function takeObject(idx) {
const ret = getObject(idx);
dropObject(idx);
return ret;
}
let cachedTextDecoder = new TextDecoder('utf-8', { ignoreBOM: true, fatal: true });
cachedTextDecoder.decode();
let cachegetUint8Memory0 = null;
function getUint8Memory0() {
if (cachegetUint8Memory0 === null || cachegetUint8Memory0.buffer !== wasm.memory.buffer) {
cachegetUint8Memory0 = new Uint8Array(wasm.memory.buffer);
}
return cachegetUint8Memory0;
}
function getStringFromWasm0(ptr, len) {
return cachedTextDecoder.decode(getUint8Memory0().subarray(ptr, ptr + len));
}
function addHeapObject(obj) {
if (heap_next === heap.length) heap.push(heap.length + 1);
const idx = heap_next;
heap_next = heap[idx];
heap[idx] = obj;
return idx;
}
function isLikeNone(x) {
return x === undefined || x === null;
}
let cachegetFloat64Memory0 = null;
function getFloat64Memory0() {
if (cachegetFloat64Memory0 === null || cachegetFloat64Memory0.buffer !== wasm.memory.buffer) {
cachegetFloat64Memory0 = new Float64Array(wasm.memory.buffer);
}
return cachegetFloat64Memory0;
}
let cachegetInt32Memory0 = null;
function getInt32Memory0() {
if (cachegetInt32Memory0 === null || cachegetInt32Memory0.buffer !== wasm.memory.buffer) {
cachegetInt32Memory0 = new Int32Array(wasm.memory.buffer);
}
return cachegetInt32Memory0;
}
function debugString(val) {
// primitive types
const type = typeof val;
if (type == 'number' || type == 'boolean' || val == null) {
return `${val}`;
}
if (type == 'string') {
return `"${val}"`;
}
if (type == 'symbol') {
const description = val.description;
if (description == null) {
return 'Symbol';
} else {
return `Symbol(${description})`;
}
}
if (type == 'function') {
const name = val.name;
if (typeof name == 'string' && name.length > 0) {
return `Function(${name})`;
} else {
return 'Function';
}
}
// objects
if (Array.isArray(val)) {
const length = val.length;
let debug = '[';
if (length > 0) {
debug += debugString(val[0]);
}
for(let i = 1; i < length; i++) {
debug += ', ' + debugString(val[i]);
}
debug += ']';
return debug;
}
// Test for built-in
const builtInMatches = /\[object ([^\]]+)\]/.exec(toString.call(val));
let className;
if (builtInMatches.length > 1) {
className = builtInMatches[1];
} else {
// Failed to match the standard '[object ClassName]'
return toString.call(val);
}
if (className == 'Object') {
// we're a user defined class or Object
// JSON.stringify avoids problems with cycles, and is generally much
// easier than looping through ownProperties of `val`.
try {
return 'Object(' + JSON.stringify(val) + ')';
} catch (_) {
return 'Object';
}
}
// errors
if (val instanceof Error) {
return `${val.name}: ${val.message}\n${val.stack}`;
}
// TODO we could test for more things here, like `Set`s and `Map`s.
return className;
}
let WASM_VECTOR_LEN = 0;
let cachedTextEncoder = new TextEncoder('utf-8');
const encodeString = (typeof cachedTextEncoder.encodeInto === 'function'
? function (arg, view) {
return cachedTextEncoder.encodeInto(arg, view);
}
: function (arg, view) {
const buf = cachedTextEncoder.encode(arg);
view.set(buf);
return {
read: arg.length,
written: buf.length
};
});
function passStringToWasm0(arg, malloc, realloc) {
if (realloc === undefined) {
const buf = cachedTextEncoder.encode(arg);
const ptr = malloc(buf.length);
getUint8Memory0().subarray(ptr, ptr + buf.length).set(buf);
WASM_VECTOR_LEN = buf.length;
return ptr;
}
let len = arg.length;
let ptr = malloc(len);
const mem = getUint8Memory0();
let offset = 0;
for (; offset < len; offset++) {
const code = arg.charCodeAt(offset);
if (code > 0x7F) break;
mem[ptr + offset] = code;
}
if (offset !== len) {
if (offset !== 0) {
arg = arg.slice(offset);
}
ptr = realloc(ptr, len, len = offset + arg.length * 3);
const view = getUint8Memory0().subarray(ptr + offset, ptr + len);
const ret = encodeString(arg, view);
offset += ret.written;
}
WASM_VECTOR_LEN = offset;
return ptr;
}
function makeMutClosure(arg0, arg1, dtor, f) {
const state = { a: arg0, b: arg1, cnt: 1, dtor };
const real = (...args) => {
// First up with a closure we increment the internal reference
// count. This ensures that the Rust closure environment won't
// be deallocated while we're invoking it.
state.cnt++;
const a = state.a;
state.a = 0;
try {
return f(a, state.b, ...args);
} finally {
if (--state.cnt === 0) {
wasm.__wbindgen_export_2.get(state.dtor)(a, state.b);
} else {
state.a = a;
}
}
};
real.original = state;
return real;
}
function __wbg_adapter_24(arg0, arg1) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h3ffe8afa46a512ea(arg0, arg1);
}
function __wbg_adapter_27(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h61a9738176591bc0(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_30(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h61a9738176591bc0(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_33(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h61a9738176591bc0(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_36(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h61a9738176591bc0(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_39(arg0, arg1) {
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h21a8ad866b381ebb(arg0, arg1);
}
function __wbg_adapter_42(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h61a9738176591bc0(arg0, arg1, addHeapObject(arg2));
}
function __wbg_adapter_45(arg0, arg1, arg2) {
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h49f53e48165122c2(arg0, arg1, addHeapObject(arg2));
}
/**
* This is the entry-point for all the web-assembly.
* This is called once from the HTML.
* It loads the app, installs some callbacks, then returns.
* You can add more callbacks like this if you want to call in to your code.
* @param {string} canvas_id
*/
__exports.start = function(canvas_id) {
var ptr0 = passStringToWasm0(canvas_id, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
wasm.start(ptr0, len0);
};
function handleError(f) {
return function () {
try {
return f.apply(this, arguments);
} catch (e) {
wasm.__wbindgen_exn_store(addHeapObject(e));
}
};
}
function getArrayU8FromWasm0(ptr, len) {
return getUint8Memory0().subarray(ptr / 1, ptr / 1 + len);
}
async function load(module, imports) {
if (typeof Response === 'function' && module instanceof Response) {
if (typeof WebAssembly.instantiateStreaming === 'function') {
try {
return await WebAssembly.instantiateStreaming(module, imports);
} catch (e) {
if (module.headers.get('Content-Type') != 'application/wasm') {
console.warn("`WebAssembly.instantiateStreaming` failed because your server does not serve wasm with `application/wasm` MIME type. Falling back to `WebAssembly.instantiate` which is slower. Original error:\n", e);
} else {
throw e;
}
}
}
const bytes = await module.arrayBuffer();
return await WebAssembly.instantiate(bytes, imports);
} else {
const instance = await WebAssembly.instantiate(module, imports);
if (instance instanceof WebAssembly.Instance) {
return { instance, module };
} else {
return instance;
}
}
}
async function init(input) {
if (typeof input === 'undefined') {
let src;
if (typeof document === 'undefined') {
src = location.href;
} else {
src = document.currentScript.src;
}
input = src.replace(/\.js$/, '_bg.wasm');
}
const imports = {};
imports.wbg = {};
imports.wbg.__wbindgen_object_drop_ref = function(arg0) {
takeObject(arg0);
};
imports.wbg.__wbindgen_string_new = function(arg0, arg1) {
var ret = getStringFromWasm0(arg0, arg1);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_cb_drop = function(arg0) {
const obj = takeObject(arg0).original;
if (obj.cnt-- == 1) {
obj.a = 0;
return true;
}
var ret = false;
return ret;
};
imports.wbg.__wbg_instanceof_Window_49f532f06a9786ee = function(arg0) {
var ret = getObject(arg0) instanceof Window;
return ret;
};
imports.wbg.__wbg_document_c0366b39e4f4c89a = function(arg0) {
var ret = getObject(arg0).document;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_location_c1e50a6e4c53d45c = function(arg0) {
var ret = getObject(arg0).location;
return addHeapObject(ret);
};
imports.wbg.__wbg_navigator_95ba9cd684cf90aa = function(arg0) {
var ret = getObject(arg0).navigator;
return addHeapObject(ret);
};
imports.wbg.__wbg_innerWidth_cea04a991524ea87 = handleError(function(arg0) {
var ret = getObject(arg0).innerWidth;
return addHeapObject(ret);
});
imports.wbg.__wbg_innerHeight_83651dca462998d1 = handleError(function(arg0) {
var ret = getObject(arg0).innerHeight;
return addHeapObject(ret);
});
imports.wbg.__wbg_devicePixelRatio_268c49438a600d53 = function(arg0) {
var ret = getObject(arg0).devicePixelRatio;
return ret;
};
imports.wbg.__wbg_performance_87e4f3b6f966469f = function(arg0) {
var ret = getObject(arg0).performance;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_localStorage_a6fd83fc300473fc = handleError(function(arg0) {
var ret = getObject(arg0).localStorage;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
});
imports.wbg.__wbg_open_f355af0fd051a9d8 = handleError(function(arg0, arg1, arg2, arg3, arg4) {
var ret = getObject(arg0).open(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
});
imports.wbg.__wbg_requestAnimationFrame_ef0e2294dc8b1088 = handleError(function(arg0, arg1) {
var ret = getObject(arg0).requestAnimationFrame(getObject(arg1));
return ret;
});
imports.wbg.__wbg_setInterval_a7f9e1aa48a6feb8 = handleError(function(arg0, arg1, arg2) {
var ret = getObject(arg0).setInterval(getObject(arg1), arg2);
return ret;
});
imports.wbg.__wbg_body_c8cb19d760637268 = function(arg0) {
var ret = getObject(arg0).body;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_getElementById_15aef17a620252b4 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_clientX_3a14a1583294607f = function(arg0) {
var ret = getObject(arg0).clientX;
return ret;
};
imports.wbg.__wbg_clientY_4b4a322b80551002 = function(arg0) {
var ret = getObject(arg0).clientY;
return ret;
};
imports.wbg.__wbindgen_object_clone_ref = function(arg0) {
var ret = getObject(arg0);
return addHeapObject(ret);
};
imports.wbg.__wbg_addEventListener_6a37bc32387cb66d = handleError(function(arg0, arg1, arg2, arg3) {
getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3));
});
imports.wbg.__wbg_deltaX_5fac4f36a42e6ec9 = function(arg0) {
var ret = getObject(arg0).deltaX;
return ret;
};
imports.wbg.__wbg_deltaY_2722120e563d3160 = function(arg0) {
var ret = getObject(arg0).deltaY;
return ret;
};
imports.wbg.__wbg_writeText_f3dba2a1b4785c80 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_now_7628760b7b640632 = function(arg0) {
var ret = getObject(arg0).now();
return ret;
};
imports.wbg.__wbg_pageX_7d397506a4ad73f2 = function(arg0) {
var ret = getObject(arg0).pageX;
return ret;
};
imports.wbg.__wbg_pageY_83f6542b172abf6f = function(arg0) {
var ret = getObject(arg0).pageY;
return ret;
};
imports.wbg.__wbg_instanceof_HtmlCanvasElement_7bd3ee7838f11fc3 = function(arg0) {
var ret = getObject(arg0) instanceof HTMLCanvasElement;
return ret;
};
imports.wbg.__wbg_width_0efa4604d41c58c5 = function(arg0) {
var ret = getObject(arg0).width;
return ret;
};
imports.wbg.__wbg_setwidth_1d0e975feecff3ef = function(arg0, arg1) {
getObject(arg0).width = arg1 >>> 0;
};
imports.wbg.__wbg_height_aa24e3fef658c4a8 = function(arg0) {
var ret = getObject(arg0).height;
return ret;
};
imports.wbg.__wbg_setheight_7758ee3ff5c65474 = function(arg0, arg1) {
getObject(arg0).height = arg1 >>> 0;
};
imports.wbg.__wbg_getContext_3db9399e6dc524ff = handleError(function(arg0, arg1, arg2) {
var ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
});
imports.wbg.__wbg_keyCode_47f9e9228bc483bf = function(arg0) {
var ret = getObject(arg0).keyCode;
return ret;
};
imports.wbg.__wbg_altKey_8a59e1cf32636010 = function(arg0) {
var ret = getObject(arg0).altKey;
return ret;
};
imports.wbg.__wbg_ctrlKey_17377b46ca5a072d = function(arg0) {
var ret = getObject(arg0).ctrlKey;
return ret;
};
imports.wbg.__wbg_shiftKey_09be9a7e6cad7a99 = function(arg0) {
var ret = getObject(arg0).shiftKey;
return ret;
};
imports.wbg.__wbg_metaKey_a707288e6c45a0e0 = function(arg0) {
var ret = getObject(arg0).metaKey;
return ret;
};
imports.wbg.__wbg_isComposing_15a35cffb04ab10f = function(arg0) {
var ret = getObject(arg0).isComposing;
return ret;
};
imports.wbg.__wbg_key_d9b602f48baca7bc = function(arg0, arg1) {
var ret = getObject(arg1).key;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_getBoundingClientRect_505844bd8eb35668 = function(arg0) {
var ret = getObject(arg0).getBoundingClientRect();
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_WebGlRenderingContext_ef4e51c6e4133d85 = function(arg0) {
var ret = getObject(arg0) instanceof WebGLRenderingContext;
return ret;
};
imports.wbg.__wbg_bufferData_dc5899657e9f1803 = function(arg0, arg1, arg2, arg3) {
getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0);
};
imports.wbg.__wbg_texImage2D_a4011abffe0229fb = handleError(function(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9, arg10) {
getObject(arg0).texImage2D(arg1 >>> 0, arg2, arg3, arg4, arg5, arg6, arg7 >>> 0, arg8 >>> 0, arg9 === 0 ? undefined : getArrayU8FromWasm0(arg9, arg10));
});
imports.wbg.__wbg_activeTexture_a51ec6273de88bc6 = function(arg0, arg1) {
getObject(arg0).activeTexture(arg1 >>> 0);
};
imports.wbg.__wbg_attachShader_0dd248f6ab98fcf2 = function(arg0, arg1, arg2) {
getObject(arg0).attachShader(getObject(arg1), getObject(arg2));
};
imports.wbg.__wbg_bindBuffer_1ceb83e9674e812a = function(arg0, arg1, arg2) {
getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2));
};
imports.wbg.__wbg_bindTexture_6121e6db3f879582 = function(arg0, arg1, arg2) {
getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2));
};
imports.wbg.__wbg_blendFunc_34a6bb31770822c5 = function(arg0, arg1, arg2) {
getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0);
};
imports.wbg.__wbg_clear_f6b2dd48aeed2752 = function(arg0, arg1) {
getObject(arg0).clear(arg1 >>> 0);
};
imports.wbg.__wbg_clearColor_89f7819aa9f80129 = function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).clearColor(arg1, arg2, arg3, arg4);
};
imports.wbg.__wbg_compileShader_28bdbafe4445d24b = function(arg0, arg1) {
getObject(arg0).compileShader(getObject(arg1));
};
imports.wbg.__wbg_createBuffer_acedc3831832a280 = function(arg0) {
var ret = getObject(arg0).createBuffer();
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_createProgram_7e2f44b7b74694d4 = function(arg0) {
var ret = getObject(arg0).createProgram();
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_createShader_64c474f1d1d0c1f8 = function(arg0, arg1) {
var ret = getObject(arg0).createShader(arg1 >>> 0);
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_createTexture_0a156dab1efc3499 = function(arg0) {
var ret = getObject(arg0).createTexture();
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_disable_5b9c6f74d5efd3a5 = function(arg0, arg1) {
getObject(arg0).disable(arg1 >>> 0);
};
imports.wbg.__wbg_drawElements_3eb5ba8a511ce0f0 = function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4);
};
imports.wbg.__wbg_enable_87f39f6396535e1f = function(arg0, arg1) {
getObject(arg0).enable(arg1 >>> 0);
};
imports.wbg.__wbg_enableVertexAttribArray_f29c8dde9c8c5cf5 = function(arg0, arg1) {
getObject(arg0).enableVertexAttribArray(arg1 >>> 0);
};
imports.wbg.__wbg_getAttribLocation_ba61f837da80e249 = function(arg0, arg1, arg2, arg3) {
var ret = getObject(arg0).getAttribLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
return ret;
};
imports.wbg.__wbg_getProgramInfoLog_aacf06c959070653 = function(arg0, arg1, arg2) {
var ret = getObject(arg1).getProgramInfoLog(getObject(arg2));
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_getProgramParameter_a89bf14502c109f7 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_getShaderInfoLog_1eb885f2468e2429 = function(arg0, arg1, arg2) {
var ret = getObject(arg1).getShaderInfoLog(getObject(arg2));
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbg_getShaderParameter_99510442d33c6589 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_getUniformLocation_ca853de4f2f9270d = function(arg0, arg1, arg2, arg3) {
var ret = getObject(arg0).getUniformLocation(getObject(arg1), getStringFromWasm0(arg2, arg3));
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_linkProgram_46a36cb158f10676 = function(arg0, arg1) {
getObject(arg0).linkProgram(getObject(arg1));
};
imports.wbg.__wbg_scissor_59172d697cc43dc8 = function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).scissor(arg1, arg2, arg3, arg4);
};
imports.wbg.__wbg_shaderSource_700ae72fca39850d = function(arg0, arg1, arg2, arg3) {
getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3));
};
imports.wbg.__wbg_texParameteri_e45f3977eb998137 = function(arg0, arg1, arg2, arg3) {
getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3);
};
imports.wbg.__wbg_uniform1i_e76b668973ae0655 = function(arg0, arg1, arg2) {
getObject(arg0).uniform1i(getObject(arg1), arg2);
};
imports.wbg.__wbg_uniform2f_6298542797865c61 = function(arg0, arg1, arg2, arg3) {
getObject(arg0).uniform2f(getObject(arg1), arg2, arg3);
};
imports.wbg.__wbg_useProgram_d63a57db0571e803 = function(arg0, arg1) {
getObject(arg0).useProgram(getObject(arg1));
};
imports.wbg.__wbg_vertexAttribPointer_b4b829a4f5a3778e = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) {
getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6);
};
imports.wbg.__wbg_viewport_54305c74f5668b33 = function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).viewport(arg1, arg2, arg3, arg4);
};
imports.wbg.__wbg_error_e325755affc8634b = function(arg0) {
console.error(getObject(arg0));
};
imports.wbg.__wbg_warn_9e92ccdc67085e1b = function(arg0) {
console.warn(getObject(arg0));
};
imports.wbg.__wbg_style_9b773f0fc441eddc = function(arg0) {
var ret = getObject(arg0).style;
return addHeapObject(ret);
};
imports.wbg.__wbg_clipboardData_503a7e4407c6231c = function(arg0) {
var ret = getObject(arg0).clipboardData;
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_getData_10c8cb329803d2b8 = handleError(function(arg0, arg1, arg2, arg3) {
var ret = getObject(arg1).getData(getStringFromWasm0(arg2, arg3));
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
});
imports.wbg.__wbg_touches_3d22a22deb0f5409 = function(arg0) {
var ret = getObject(arg0).touches;
return addHeapObject(ret);
};
imports.wbg.__wbg_get_3315e8e7e59a2c40 = function(arg0, arg1) {
var ret = getObject(arg0)[arg1 >>> 0];
return isLikeNone(ret) ? 0 : addHeapObject(ret);
};
imports.wbg.__wbg_preventDefault_9aab6c264e5df3ee = function(arg0) {
getObject(arg0).preventDefault();
};
imports.wbg.__wbg_stopPropagation_697200010cec9b7e = function(arg0) {
getObject(arg0).stopPropagation();
};
imports.wbg.__wbg_clipboard_a2f55b432ef7d0b0 = function(arg0) {
var ret = getObject(arg0).clipboard;
return addHeapObject(ret);
};
imports.wbg.__wbg_top_80a2533bf82e7a3e = function(arg0) {
var ret = getObject(arg0).top;
return ret;
};
imports.wbg.__wbg_left_479514b443c100f4 = function(arg0) {
var ret = getObject(arg0).left;
return ret;
};
imports.wbg.__wbg_setProperty_46b9bd1b0fad730b = handleError(function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
});
imports.wbg.__wbg_hash_6e2c452e02822d19 = handleError(function(arg0, arg1) {
var ret = getObject(arg1).hash;
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
});
imports.wbg.__wbg_getItem_400dba7536e6a1d8 = handleError(function(arg0, arg1, arg2, arg3) {
var ret = getObject(arg1).getItem(getStringFromWasm0(arg2, arg3));
var ptr0 = isLikeNone(ret) ? 0 : passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
});
imports.wbg.__wbg_setItem_57767b71f09c3545 = handleError(function(arg0, arg1, arg2, arg3, arg4) {
getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4));
});
imports.wbg.__wbg_get_85e0a3b459845fe2 = handleError(function(arg0, arg1) {
var ret = Reflect.get(getObject(arg0), getObject(arg1));
return addHeapObject(ret);
});
imports.wbg.__wbg_call_951bd0c6d815d6f1 = handleError(function(arg0, arg1) {
var ret = getObject(arg0).call(getObject(arg1));
return addHeapObject(ret);
});
imports.wbg.__wbg_newnoargs_7c6bd521992b4022 = function(arg0, arg1) {
var ret = new Function(getStringFromWasm0(arg0, arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_getHours_3dd5c65c39f8b9b6 = function(arg0) {
var ret = getObject(arg0).getHours();
return ret;
};
imports.wbg.__wbg_getMilliseconds_6a8f7d8a5dae61b1 = function(arg0) {
var ret = getObject(arg0).getMilliseconds();
return ret;
};
imports.wbg.__wbg_getMinutes_37175b451da6ea22 = function(arg0) {
var ret = getObject(arg0).getMinutes();
return ret;
};
imports.wbg.__wbg_getSeconds_cea5a5592039b5ba = function(arg0) {
var ret = getObject(arg0).getSeconds();
return ret;
};
imports.wbg.__wbg_new0_abd359df4aeb5b55 = function() {
var ret = new Date();
return addHeapObject(ret);
};
imports.wbg.__wbg_resolve_6e61e640925a0db9 = function(arg0) {
var ret = Promise.resolve(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_dd3785597974798a = function(arg0, arg1) {
var ret = getObject(arg0).then(getObject(arg1));
return addHeapObject(ret);
};
imports.wbg.__wbg_then_0f957e0f4c3e537a = function(arg0, arg1, arg2) {
var ret = getObject(arg0).then(getObject(arg1), getObject(arg2));
return addHeapObject(ret);
};
imports.wbg.__wbg_self_6baf3a3aa7b63415 = handleError(function() {
var ret = self.self;
return addHeapObject(ret);
});
imports.wbg.__wbg_window_63fc4027b66c265b = handleError(function() {
var ret = window.window;
return addHeapObject(ret);
});
imports.wbg.__wbg_globalThis_513fb247e8e4e6d2 = handleError(function() {
var ret = globalThis.globalThis;
return addHeapObject(ret);
});
imports.wbg.__wbg_global_b87245cd886d7113 = handleError(function() {
var ret = global.global;
return addHeapObject(ret);
});
imports.wbg.__wbindgen_is_undefined = function(arg0) {
var ret = getObject(arg0) === undefined;
return ret;
};
imports.wbg.__wbg_buffer_3f12a1c608c6d04e = function(arg0) {
var ret = getObject(arg0).buffer;
return addHeapObject(ret);
};
imports.wbg.__wbg_new_b43247aed67bdcb6 = function(arg0) {
var ret = new Int16Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_new_c6c0228e6d22a2f9 = function(arg0) {
var ret = new Uint8Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_new_2863e4d532e8dfb4 = function(arg0) {
var ret = new Float32Array(getObject(arg0));
return addHeapObject(ret);
};
imports.wbg.__wbg_subarray_099195a64c29d8a3 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_subarray_02e2fcfa6b285cb2 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_subarray_f5deb93e9cb33975 = function(arg0, arg1, arg2) {
var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0);
return addHeapObject(ret);
};
imports.wbg.__wbg_instanceof_Memory_fdb0928d3f70cd49 = function(arg0) {
var ret = getObject(arg0) instanceof WebAssembly.Memory;
return ret;
};
imports.wbg.__wbindgen_number_get = function(arg0, arg1) {
const obj = getObject(arg1);
var ret = typeof(obj) === 'number' ? obj : undefined;
getFloat64Memory0()[arg0 / 8 + 1] = isLikeNone(ret) ? 0 : ret;
getInt32Memory0()[arg0 / 4 + 0] = !isLikeNone(ret);
};
imports.wbg.__wbindgen_boolean_get = function(arg0) {
const v = getObject(arg0);
var ret = typeof(v) === 'boolean' ? (v ? 1 : 0) : 2;
return ret;
};
imports.wbg.__wbindgen_debug_string = function(arg0, arg1) {
var ret = debugString(getObject(arg1));
var ptr0 = passStringToWasm0(ret, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
var len0 = WASM_VECTOR_LEN;
getInt32Memory0()[arg0 / 4 + 1] = len0;
getInt32Memory0()[arg0 / 4 + 0] = ptr0;
};
imports.wbg.__wbindgen_throw = function(arg0, arg1) {
throw new Error(getStringFromWasm0(arg0, arg1));
};
imports.wbg.__wbindgen_rethrow = function(arg0) {
throw takeObject(arg0);
};
imports.wbg.__wbindgen_memory = function() {
var ret = wasm.memory;
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper411 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_24);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper412 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_27);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper414 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_30);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper416 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_33);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper418 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_36);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper420 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_39);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper423 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 85, __wbg_adapter_42);
return addHeapObject(ret);
};
imports.wbg.__wbindgen_closure_wrapper602 = function(arg0, arg1, arg2) {
var ret = makeMutClosure(arg0, arg1, 152, __wbg_adapter_45);
return addHeapObject(ret);
};
if (typeof input === 'string' || (typeof Request === 'function' && input instanceof Request) || (typeof URL === 'function' && input instanceof URL)) {
input = fetch(input);
}
const { instance, module } = await load(await input, imports);
wasm = instance.exports;
init.__wbindgen_wasm_module = module;
return wasm;
}
wasm_bindgen = Object.assign(init, __exports);
})();

BIN
docs/egui_template_bg.wasm Normal file

Binary file not shown.

78
docs/index.html Normal file
View file

@ -0,0 +1,78 @@
<!DOCTYPE html>
<html>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<!-- Disable zooming: -->
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<head>
<title>Egui Template</title>
<style>
html {
/* Remove touch delay: */
touch-action: manipulation;
}
body {
/* Background color for what is not covered by the canvas, and where the canvas is translucent. */
background: #202020;
}
/* Allow canvas to fill entire web page: */
html,
body {
overflow: hidden;
margin: 0 !important;
padding: 0 !important;
}
/* Center canvas vertically and horizontally: */
canvas {
margin-right: auto;
margin-left: auto;
display: block;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
}
</style>
</head>
<body>
<!-- The WASM code will resize this canvas to cover the entire screen -->
<canvas id="the_canvas_id"></canvas>
<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="egui_template.js"></script>
<script>
// We'll defer our execution until the wasm is ready to go.
// 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("./egui_template_bg.wasm")
.then(on_wasm_loaded)["catch"](console.error);
function on_wasm_loaded() {
// This call installs a bunch of callbacks and then returns.
wasm_bindgen.start("the_canvas_id");
}
</script>
</body>
</html>
<!-- Powered by Egui: https://github.com/emilk/egui/ -->

7
setup_web.sh Executable file
View file

@ -0,0 +1,7 @@
#!/bin/bash
set -eu
# Pre-requisites:
rustup target add wasm32-unknown-unknown
cargo install -f wasm-bindgen-cli
cargo update

176
src/app.rs Normal file
View file

@ -0,0 +1,176 @@
/// We derive Deserialize/Serialize so we can persist app state on shutdown.
#[derive(serde::Deserialize, serde::Serialize)]
pub struct EguiApp {
// Example stuff:
label: String,
value: f32,
painting: Painting,
}
impl Default for EguiApp {
fn default() -> Self {
Self {
// Example stuff:
label: "Hello World!".to_owned(),
value: 2.7,
painting: Default::default(),
}
}
}
impl egui::app::App for EguiApp {
fn name(&self) -> &str {
"Egui Template"
}
/// Called by the framework to load old app state (if any).
fn load(&mut self, storage: &dyn egui::app::Storage) {
*self = egui::app::get_value(storage, egui::app::APP_KEY).unwrap_or_default()
}
/// Called by the frame work to save state before shutdown.
fn save(&mut self, storage: &mut dyn egui::app::Storage) {
egui::app::set_value(storage, egui::app::APP_KEY, self);
}
/// Called each time the UI needs repainting, which may be many times per second.
/// Put your widgets into a `SidePanel`, `TopPanel`, `CentralPanel`, `Window` or `Area`.
fn ui(&mut self, ctx: &egui::CtxRef, integration_context: &mut egui::app::IntegrationContext) {
let EguiApp {
label,
value,
painting,
} = self;
// Examples of how to create differens panels and windows.
// Pick whichever suits you.
// Tip: a good default choice is to just keep the `CentralPanel`.
// For inspiration and more examples, go to https://emilk.github.io/egui
egui::SidePanel::left("side_panel", 200.0).show(ctx, |ui| {
ui.heading("Side Panel");
ui.horizontal(|ui| {
ui.label("Write something: ");
ui.text_edit_singleline(label);
});
ui.add(egui::Slider::f32(value, 0.0..=10.0).text("value"));
if ui.button("Increment").clicked {
*value += 1.0;
}
ui.with_layout(egui::Layout::bottom_up(egui::Align::Center), |ui| {
ui.add(
egui::Hyperlink::new("https://github.com/emilk/egui/").text("Powered by Egui"),
);
});
});
egui::TopPanel::top("top_panel").show(ctx, |ui| {
// The top panel is often a good place for a menu bar:
egui::menu::bar(ui, |ui| {
egui::menu::menu(ui, "File", |ui| {
if ui.button("Quit").clicked {
integration_context.output.quit = true;
}
});
});
});
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Egui Template");
ui.hyperlink("https://github.com/emilk/egui_template");
ui.add(egui::github_link_file_line!(
"https://github.com/emilk/egui_template/blob/master/",
"Direct link to source code."
));
egui::demos::warn_if_debug_build(ui);
ui.separator();
ui.heading("Central Panel");
ui.label("The central panel the region left after adding TopPanel's and SidePanel's");
ui.label("It is often a great place for big things, like drawings:");
ui.heading("Draw with your mouse to paint:");
painting.ui_control(ui);
egui::Frame::dark_canvas(ui.style()).show(ui, |ui| {
painting.ui_content(ui);
});
});
if false {
egui::Window::new("Window").show(ctx, |ui| {
ui.label("Windows can be moved by dragging them.");
ui.label("They are automatically sized based on contents.");
ui.label("You can turn on resizing and scrolling if you like.");
ui.label("You would normally chose either panels OR windows.");
});
}
// Resize the glium window to be just the size we need it to be:
integration_context.output.window_size = Some(ctx.used_size());
}
}
// ----------------------------------------------------------------------------
/// Example code for painting on a canvas with your mouse
#[derive(Clone, serde::Deserialize, serde::Serialize)]
struct Painting {
lines: Vec<Vec<egui::Vec2>>,
stroke: egui::Stroke,
}
impl Default for Painting {
fn default() -> Self {
Self {
lines: Default::default(),
stroke: egui::Stroke::new(1.0, egui::color::LIGHT_BLUE),
}
}
}
impl Painting {
pub fn ui_control(&mut self, ui: &mut egui::Ui) {
ui.horizontal(|ui| {
self.stroke.ui(ui, "Stroke");
ui.separator();
if ui.button("Clear Painting").clicked {
self.lines.clear();
}
});
}
pub fn ui_content(&mut self, ui: &mut egui::Ui) {
let painter = ui.allocate_painter(ui.available_size_before_wrap_finite());
let rect = painter.clip_rect();
let id = ui.make_position_id();
let response = ui.interact(rect, id, egui::Sense::drag());
if self.lines.is_empty() {
self.lines.push(vec![]);
}
let current_line = self.lines.last_mut().unwrap();
if response.active {
if let Some(mouse_pos) = ui.input().mouse.pos {
let canvas_pos = mouse_pos - rect.min;
if current_line.last() != Some(&canvas_pos) {
current_line.push(canvas_pos);
}
}
} else if !current_line.is_empty() {
self.lines.push(vec![]);
}
for line in &self.lines {
if line.len() >= 2 {
let points: Vec<egui::Pos2> = line.iter().map(|p| rect.min + *p).collect();
painter.add(egui::PaintCmd::line(points, self.stroke));
}
}
}
}

24
src/lib.rs Normal file
View file

@ -0,0 +1,24 @@
#![forbid(unsafe_code)]
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
#![warn(clippy::all)]
pub mod app;
pub use app::EguiApp;
// ----------------------------------------------------------------------------
// When compiling for web:
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
/// This is the entry-point for all the web-assembly.
/// This is called once from the HTML.
/// It loads the app, installs some callbacks, then returns.
/// You can add more callbacks like this if you want to call in to your code.
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
let app = EguiApp::default();
egui_web::start(canvas_id, Box::new(app))?;
Ok(())
}

9
src/main.rs Normal file
View file

@ -0,0 +1,9 @@
#![forbid(unsafe_code)]
#![cfg_attr(not(debug_assertions), deny(warnings))] // Forbid warnings in release builds
#![warn(clippy::all)]
// When compiling natively:
fn main() {
let app = egui_template::EguiApp::default();
egui_glium::run(Box::new(app));
}

4
start_server.sh Executable file
View file

@ -0,0 +1,4 @@
#!/bin/bash
set -eu
(cd docs && python3 -m http.server 8080 --bind 127.0.0.1)