diff --git a/Cargo.lock b/Cargo.lock index 6854f607..a96fce1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -580,6 +580,18 @@ dependencies = [ "serde", ] +[[package]] +name = "example_web" +version = "0.1.0" +dependencies = [ + "egui", + "egui_web", + "js-sys", + "serde", + "serde_json", + "wasm-bindgen", +] + [[package]] name = "fnv" version = "1.0.7" diff --git a/Cargo.toml b/Cargo.toml index 94c39259..7fc2f767 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,11 +1,12 @@ [workspace] members = [ "demo_glium", + "demo_web", "egui_glium", "egui_web", "egui", "example_glium", - "demo_web", + "example_web", ] diff --git a/build_web.sh b/build_demo_web.sh similarity index 91% rename from build_web.sh rename to build_demo_web.sh index 4f5917be..881ef6e6 100755 --- a/build_web.sh +++ b/build_demo_web.sh @@ -15,7 +15,7 @@ BUILD=release export RUSTFLAGS=--cfg=web_sys_unstable_apis # required for the clipboard API # Clear output from old stuff: -rm -rf docs/*.wasm +rm -rf docs/demo_web.wasm echo "Build rust:" # cargo build -p demo_web --target wasm32-unknown-unknown @@ -27,4 +27,4 @@ TARGET_NAME="demo_web.wasm" wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \ --out-dir docs --no-modules --no-typescript -open http://localhost:8888 +open http://localhost:8888/index.html diff --git a/build_example_web.sh b/build_example_web.sh new file mode 100755 index 00000000..ba5b1e5c --- /dev/null +++ b/build_example_web.sh @@ -0,0 +1,30 @@ +#!/bin/bash +set -eu + +# Pre-requisites: +rustup target add wasm32-unknown-unknown +if ! wasm-bindgen --version; then + cargo clean + cargo install -f wasm-bindgen-cli + cargo update +fi + +# BUILD=debug +BUILD=release + +export RUSTFLAGS=--cfg=web_sys_unstable_apis # required for the clipboard API + +# Clear output from old stuff: +rm -rf docs/example_web.wasm + +echo "Build rust:" +# cargo build -p example_web --target wasm32-unknown-unknown +cargo build --release -p example_web --target wasm32-unknown-unknown + +echo "Generate JS bindings for wasm:" +FOLDER_NAME=${PWD##*/} +TARGET_NAME="example_web.wasm" +wasm-bindgen "target/wasm32-unknown-unknown/$BUILD/$TARGET_NAME" \ + --out-dir docs --no-modules --no-typescript + +open http://localhost:8888/example.html diff --git a/check.sh b/check.sh index 1058d44c..8191ba2d 100755 --- a/check.sh +++ b/check.sh @@ -9,7 +9,10 @@ CARGO_INCREMENTAL=0 cargo clippy --workspace --all-targets --all-features -- -D cargo test --workspace --all-targets --all-features cargo test --workspace --doc +cargo check -p egui --target wasm32-unknown-unknown +cargo check -p egui_web --target wasm32-unknown-unknown cargo check -p demo_web --target wasm32-unknown-unknown +cargo check -p example_web --target wasm32-unknown-unknown # For finding bloat: # cargo bloat --release --bin demo_glium -n 200 | rg egui diff --git a/demo_glium/src/main.rs b/demo_glium/src/main.rs index a523bcaf..721c8e9a 100644 --- a/demo_glium/src/main.rs +++ b/demo_glium/src/main.rs @@ -1,3 +1,4 @@ +#![forbid(unsafe_code)] #![deny(warnings)] #![warn(clippy::all)] diff --git a/demo_web/src/lib.rs b/demo_web/src/lib.rs index 0248398a..3f634a7b 100644 --- a/demo_web/src/lib.rs +++ b/demo_web/src/lib.rs @@ -5,11 +5,14 @@ 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. #[wasm_bindgen] pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { + let app = egui::DemoApp::default(); let backend = egui_web::WebBackend::new(canvas_id)?; - let app = Box::new(egui::DemoApp::default()); - let runner = egui_web::AppRunner::new(backend, app)?; + let runner = egui_web::AppRunner::new(backend, Box::new(app))?; egui_web::start(runner)?; Ok(()) } diff --git a/docs/example.html b/docs/example.html new file mode 100644 index 00000000..7164f26c --- /dev/null +++ b/docs/example.html @@ -0,0 +1,67 @@ + + + + + + + + + Egui – An experimental immediate mode GUI written in Rust + + + + + + + + + + + + + + + + diff --git a/docs/example_web.js b/docs/example_web.js new file mode 100644 index 00000000..8b324947 --- /dev/null +++ b/docs/example_web.js @@ -0,0 +1,899 @@ +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; +} + +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 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; +} + +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_26(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hcf118a068d67b888(arg0, arg1); +} + +function __wbg_adapter_29(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_32(arg0, arg1) { + wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__ha5199f647b73bd92(arg0, arg1); +} + +function __wbg_adapter_35(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_38(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_41(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_44(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h232550930f57d037(arg0, arg1, addHeapObject(arg2)); +} + +function __wbg_adapter_47(arg0, arg1, arg2) { + wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hd5c13e870f8bfcb3(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_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.__wbindgen_string_new = function(arg0, arg1) { + var ret = getStringFromWasm0(arg0, arg1); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_Window_adf3196bdc02b386 = function(arg0) { + var ret = getObject(arg0) instanceof Window; + return ret; + }; + imports.wbg.__wbg_document_6cc8d0b87c0a99b9 = function(arg0) { + var ret = getObject(arg0).document; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_location_9b924f46d7090431 = function(arg0) { + var ret = getObject(arg0).location; + return addHeapObject(ret); + }; + imports.wbg.__wbg_navigator_71c234326c0a2ebb = function(arg0) { + var ret = getObject(arg0).navigator; + return addHeapObject(ret); + }; + imports.wbg.__wbg_innerWidth_60241abd729ed26f = handleError(function(arg0) { + var ret = getObject(arg0).innerWidth; + return addHeapObject(ret); + }); + imports.wbg.__wbg_innerHeight_2f860a67225f1fbd = handleError(function(arg0) { + var ret = getObject(arg0).innerHeight; + return addHeapObject(ret); + }); + imports.wbg.__wbg_devicePixelRatio_599d41a9267fa1ca = function(arg0) { + var ret = getObject(arg0).devicePixelRatio; + return ret; + }; + imports.wbg.__wbg_performance_8594a974edffb1dc = function(arg0) { + var ret = getObject(arg0).performance; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_localStorage_47e8ad68b9e5dcb9 = handleError(function(arg0) { + var ret = getObject(arg0).localStorage; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }); + imports.wbg.__wbg_open_7e3a4789bd559091 = 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_89935c9d6ac25d2f = handleError(function(arg0, arg1) { + var ret = getObject(arg0).requestAnimationFrame(getObject(arg1)); + return ret; + }); + imports.wbg.__wbg_fetch_91f098921cc7cca8 = function(arg0, arg1) { + var ret = getObject(arg0).fetch(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_setInterval_06df6f1ebeafd66f = handleError(function(arg0, arg1, arg2) { + var ret = getObject(arg0).setInterval(getObject(arg1), arg2); + return ret; + }); + imports.wbg.__wbg_body_8c888fe47d81765f = function(arg0) { + var ret = getObject(arg0).body; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_getElementById_0cb6ad9511b1efc0 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getElementById(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbindgen_object_clone_ref = function(arg0) { + var ret = getObject(arg0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_setProperty_42eabadfcd7d6199 = handleError(function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).setProperty(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }); + imports.wbg.__wbg_addEventListener_9e7b0c3f65ebc0d7 = handleError(function(arg0, arg1, arg2, arg3) { + getObject(arg0).addEventListener(getStringFromWasm0(arg1, arg2), getObject(arg3)); + }); + imports.wbg.__wbg_getItem_cb17cd47353971da = 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_71df4161bb87d575 = handleError(function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).setItem(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }); + imports.wbg.__wbg_writeText_f87e9b63346a7e47 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).writeText(getStringFromWasm0(arg1, arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_set_e0c72ee4d5eea3d5 = handleError(function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).set(getStringFromWasm0(arg1, arg2), getStringFromWasm0(arg3, arg4)); + }); + imports.wbg.__wbg_deltaX_ee242e8414135d41 = function(arg0) { + var ret = getObject(arg0).deltaX; + return ret; + }; + imports.wbg.__wbg_deltaY_35bf8632b9f25820 = function(arg0) { + var ret = getObject(arg0).deltaY; + return ret; + }; + imports.wbg.__wbg_instanceof_Response_328c03967a8e8902 = function(arg0) { + var ret = getObject(arg0) instanceof Response; + return ret; + }; + imports.wbg.__wbg_url_67bbdafba8ff6e85 = function(arg0, arg1) { + var ret = getObject(arg1).url; + 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_status_eb6dbb31556c329f = function(arg0) { + var ret = getObject(arg0).status; + return ret; + }; + imports.wbg.__wbg_ok_7f500542a5af3b60 = function(arg0) { + var ret = getObject(arg0).ok; + return ret; + }; + imports.wbg.__wbg_statusText_99a522577dbca4cb = function(arg0, arg1) { + var ret = getObject(arg1).statusText; + 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_text_966d07536ca6ccdc = handleError(function(arg0) { + var ret = getObject(arg0).text(); + return addHeapObject(ret); + }); + imports.wbg.__wbg_now_49847177a6d1d57e = function(arg0) { + var ret = getObject(arg0).now(); + return ret; + }; + imports.wbg.__wbg_headers_d4301f4888b4640a = function(arg0) { + var ret = getObject(arg0).headers; + return addHeapObject(ret); + }; + imports.wbg.__wbg_newwithstrandinit_d1de1bfcd175e38a = handleError(function(arg0, arg1, arg2) { + var ret = new Request(getStringFromWasm0(arg0, arg1), getObject(arg2)); + return addHeapObject(ret); + }); + imports.wbg.__wbg_instanceof_HtmlCanvasElement_4f5b5ec6cd53ccf3 = function(arg0) { + var ret = getObject(arg0) instanceof HTMLCanvasElement; + return ret; + }; + imports.wbg.__wbg_width_a22f9855caa54b53 = function(arg0) { + var ret = getObject(arg0).width; + return ret; + }; + imports.wbg.__wbg_setwidth_5f26a8ba9dbfa0d0 = function(arg0, arg1) { + getObject(arg0).width = arg1 >>> 0; + }; + imports.wbg.__wbg_height_9a404a6b3c61c7ef = function(arg0) { + var ret = getObject(arg0).height; + return ret; + }; + imports.wbg.__wbg_setheight_70f62727aa9383c2 = function(arg0, arg1) { + getObject(arg0).height = arg1 >>> 0; + }; + imports.wbg.__wbg_getContext_37ca0870acb096d9 = handleError(function(arg0, arg1, arg2) { + var ret = getObject(arg0).getContext(getStringFromWasm0(arg1, arg2)); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }); + imports.wbg.__wbg_pageX_234547d8f89fd3d0 = function(arg0) { + var ret = getObject(arg0).pageX; + return ret; + }; + imports.wbg.__wbg_pageY_4f94b607e8f1a8a2 = function(arg0) { + var ret = getObject(arg0).pageY; + return ret; + }; + imports.wbg.__wbg_clipboardData_d3dc621a701f9c49 = function(arg0) { + var ret = getObject(arg0).clipboardData; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_getData_0a2347233cf89d01 = 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_keyCode_689d196ab65a93d7 = function(arg0) { + var ret = getObject(arg0).keyCode; + return ret; + }; + imports.wbg.__wbg_altKey_1b58e09f218a0f4b = function(arg0) { + var ret = getObject(arg0).altKey; + return ret; + }; + imports.wbg.__wbg_ctrlKey_f080ec163dcc2703 = function(arg0) { + var ret = getObject(arg0).ctrlKey; + return ret; + }; + imports.wbg.__wbg_shiftKey_d11f615955404512 = function(arg0) { + var ret = getObject(arg0).shiftKey; + return ret; + }; + imports.wbg.__wbg_metaKey_9bc40bb1d5972ef2 = function(arg0) { + var ret = getObject(arg0).metaKey; + return ret; + }; + imports.wbg.__wbg_isComposing_c0f97b8c3f5992b5 = function(arg0) { + var ret = getObject(arg0).isComposing; + return ret; + }; + imports.wbg.__wbg_key_590d4d2a765d1b58 = 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_get_814461b8584a55e7 = function(arg0, arg1) { + var ret = getObject(arg0)[arg1 >>> 0]; + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_getBoundingClientRect_c6d612c06726983e = function(arg0) { + var ret = getObject(arg0).getBoundingClientRect(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_WebGlRenderingContext_a37cc8c6016098e4 = function(arg0) { + var ret = getObject(arg0) instanceof WebGLRenderingContext; + return ret; + }; + imports.wbg.__wbg_bufferData_0690087420a9f115 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).bufferData(arg1 >>> 0, getObject(arg2), arg3 >>> 0); + }; + imports.wbg.__wbg_texImage2D_8d677a54ab75452c = 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_7246ae8c464868b4 = function(arg0, arg1) { + getObject(arg0).activeTexture(arg1 >>> 0); + }; + imports.wbg.__wbg_attachShader_d213e7ecd3432f4a = function(arg0, arg1, arg2) { + getObject(arg0).attachShader(getObject(arg1), getObject(arg2)); + }; + imports.wbg.__wbg_bindBuffer_f0ba4bbfd5b08434 = function(arg0, arg1, arg2) { + getObject(arg0).bindBuffer(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_bindTexture_c00656e6f0530ee7 = function(arg0, arg1, arg2) { + getObject(arg0).bindTexture(arg1 >>> 0, getObject(arg2)); + }; + imports.wbg.__wbg_blendFunc_c7c9cda2a0e4b97f = function(arg0, arg1, arg2) { + getObject(arg0).blendFunc(arg1 >>> 0, arg2 >>> 0); + }; + imports.wbg.__wbg_clear_c9cc14c37d12a838 = function(arg0, arg1) { + getObject(arg0).clear(arg1 >>> 0); + }; + imports.wbg.__wbg_clearColor_73695d8d401f87e6 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).clearColor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_compileShader_961db910485f4a76 = function(arg0, arg1) { + getObject(arg0).compileShader(getObject(arg1)); + }; + imports.wbg.__wbg_createBuffer_4deb008968921e7f = function(arg0) { + var ret = getObject(arg0).createBuffer(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createProgram_b502951c403f671a = function(arg0) { + var ret = getObject(arg0).createProgram(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createShader_7bd4296ba9c32133 = function(arg0, arg1) { + var ret = getObject(arg0).createShader(arg1 >>> 0); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_createTexture_e0437703d5b41f24 = function(arg0) { + var ret = getObject(arg0).createTexture(); + return isLikeNone(ret) ? 0 : addHeapObject(ret); + }; + imports.wbg.__wbg_disable_6d4d32f05b00518e = function(arg0, arg1) { + getObject(arg0).disable(arg1 >>> 0); + }; + imports.wbg.__wbg_drawElements_b22db7173101346e = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).drawElements(arg1 >>> 0, arg2, arg3 >>> 0, arg4); + }; + imports.wbg.__wbg_enable_700dbd1724c67920 = function(arg0, arg1) { + getObject(arg0).enable(arg1 >>> 0); + }; + imports.wbg.__wbg_enableVertexAttribArray_4b6614b028d442ff = function(arg0, arg1) { + getObject(arg0).enableVertexAttribArray(arg1 >>> 0); + }; + imports.wbg.__wbg_getAttribLocation_98ff7fc515cda07d = function(arg0, arg1, arg2, arg3) { + var ret = getObject(arg0).getAttribLocation(getObject(arg1), getStringFromWasm0(arg2, arg3)); + return ret; + }; + imports.wbg.__wbg_getProgramInfoLog_a84afc629d343c75 = 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_327111ebb2bca7fb = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getProgramParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getShaderInfoLog_a9529ee3f2ebd3e0 = 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_d7853b2d4822ad9f = function(arg0, arg1, arg2) { + var ret = getObject(arg0).getShaderParameter(getObject(arg1), arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getUniformLocation_55700686ebe625a9 = 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_7c29f15a5150d174 = function(arg0, arg1) { + getObject(arg0).linkProgram(getObject(arg1)); + }; + imports.wbg.__wbg_scissor_5d19639b621ab86f = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).scissor(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_shaderSource_bf6be2cc97a14fc1 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).shaderSource(getObject(arg1), getStringFromWasm0(arg2, arg3)); + }; + imports.wbg.__wbg_texParameteri_c9ce5bb9e350c6cd = function(arg0, arg1, arg2, arg3) { + getObject(arg0).texParameteri(arg1 >>> 0, arg2 >>> 0, arg3); + }; + imports.wbg.__wbg_uniform1i_bbbce4278738d73e = function(arg0, arg1, arg2) { + getObject(arg0).uniform1i(getObject(arg1), arg2); + }; + imports.wbg.__wbg_uniform2f_aaa7fe970579cfd3 = function(arg0, arg1, arg2, arg3) { + getObject(arg0).uniform2f(getObject(arg1), arg2, arg3); + }; + imports.wbg.__wbg_useProgram_51f7808f5955c03a = function(arg0, arg1) { + getObject(arg0).useProgram(getObject(arg1)); + }; + imports.wbg.__wbg_vertexAttribPointer_76ddec1ed8425967 = function(arg0, arg1, arg2, arg3, arg4, arg5, arg6) { + getObject(arg0).vertexAttribPointer(arg1 >>> 0, arg2, arg3 >>> 0, arg4 !== 0, arg5, arg6); + }; + imports.wbg.__wbg_viewport_dd0dedc488a8dba4 = function(arg0, arg1, arg2, arg3, arg4) { + getObject(arg0).viewport(arg1, arg2, arg3, arg4); + }; + imports.wbg.__wbg_log_3bafd82835c6de6d = function(arg0) { + console.log(getObject(arg0)); + }; + imports.wbg.__wbg_style_9a41d46c005f7596 = function(arg0) { + var ret = getObject(arg0).style; + return addHeapObject(ret); + }; + imports.wbg.__wbg_preventDefault_93d06688748bfc14 = function(arg0) { + getObject(arg0).preventDefault(); + }; + imports.wbg.__wbg_stopPropagation_a47dd3b6ffe6b400 = function(arg0) { + getObject(arg0).stopPropagation(); + }; + imports.wbg.__wbg_touches_1cfb9ad18bd08588 = function(arg0) { + var ret = getObject(arg0).touches; + return addHeapObject(ret); + }; + imports.wbg.__wbg_top_a6f8db7db6d2bf17 = function(arg0) { + var ret = getObject(arg0).top; + return ret; + }; + imports.wbg.__wbg_left_b19dce37a1320f04 = function(arg0) { + var ret = getObject(arg0).left; + return ret; + }; + imports.wbg.__wbg_clipboard_7862c97f3561ff94 = function(arg0) { + var ret = getObject(arg0).clipboard; + return addHeapObject(ret); + }; + imports.wbg.__wbg_hash_1e68d402e53cef74 = 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_clientX_c1a2c3a6a07188a2 = function(arg0) { + var ret = getObject(arg0).clientX; + return ret; + }; + imports.wbg.__wbg_clientY_090f8ba07f76875d = function(arg0) { + var ret = getObject(arg0).clientY; + return ret; + }; + imports.wbg.__wbg_call_8e95613cc6524977 = handleError(function(arg0, arg1) { + var ret = getObject(arg0).call(getObject(arg1)); + return addHeapObject(ret); + }); + imports.wbg.__wbg_newnoargs_f3b8a801d5d4b079 = function(arg0, arg1) { + var ret = new Function(getStringFromWasm0(arg0, arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_getHours_551ae9f419d47a3b = function(arg0) { + var ret = getObject(arg0).getHours(); + return ret; + }; + imports.wbg.__wbg_getMilliseconds_c188d73c551e97d1 = function(arg0) { + var ret = getObject(arg0).getMilliseconds(); + return ret; + }; + imports.wbg.__wbg_getMinutes_054a5442be4b9aa6 = function(arg0) { + var ret = getObject(arg0).getMinutes(); + return ret; + }; + imports.wbg.__wbg_getSeconds_9cda850b6648721a = function(arg0) { + var ret = getObject(arg0).getSeconds(); + return ret; + }; + imports.wbg.__wbg_new0_a3af66503e735141 = function() { + var ret = new Date(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_3e06d4f36713e4cb = function() { + var ret = new Object(); + return addHeapObject(ret); + }; + imports.wbg.__wbg_resolve_2529512c3bb73938 = function(arg0) { + var ret = Promise.resolve(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_4a7a614abbbe6d81 = function(arg0, arg1) { + var ret = getObject(arg0).then(getObject(arg1)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_then_3b7ac098cfda2fa5 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).then(getObject(arg1), getObject(arg2)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_self_07b2f89e82ceb76d = handleError(function() { + var ret = self.self; + return addHeapObject(ret); + }); + imports.wbg.__wbg_window_ba85d88572adc0dc = handleError(function() { + var ret = window.window; + return addHeapObject(ret); + }); + imports.wbg.__wbg_globalThis_b9277fc37e201fe5 = handleError(function() { + var ret = globalThis.globalThis; + return addHeapObject(ret); + }); + imports.wbg.__wbg_global_e16303fe83e1d57f = 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_49131c283a06686f = function(arg0) { + var ret = getObject(arg0).buffer; + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_20cc9c1e8e319ac2 = function(arg0) { + var ret = new Int16Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_9b295d24cf1d706f = function(arg0) { + var ret = new Uint8Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_new_79f4487112eba5a7 = function(arg0) { + var ret = new Float32Array(getObject(arg0)); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_4cf58941ccdff8e8 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_4eaeb3de00cf1955 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_subarray_f5aa665f0873e6e8 = function(arg0, arg1, arg2) { + var ret = getObject(arg0).subarray(arg1 >>> 0, arg2 >>> 0); + return addHeapObject(ret); + }; + imports.wbg.__wbg_instanceof_Memory_8d2ddec6afb83aaa = function(arg0) { + var ret = getObject(arg0) instanceof WebAssembly.Memory; + return ret; + }; + imports.wbg.__wbg_set_304f2ec1a3ab3b79 = handleError(function(arg0, arg1, arg2) { + var ret = Reflect.set(getObject(arg0), getObject(arg1), getObject(arg2)); + 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_string_get = function(arg0, arg1) { + const obj = getObject(arg1); + var ret = typeof(obj) === 'string' ? obj : undefined; + 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.__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_wrapper472 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_26); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper473 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_29); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper475 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_32); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper477 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_35); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper479 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_38); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper482 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_41); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper484 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 101, __wbg_adapter_44); + return addHeapObject(ret); + }; + imports.wbg.__wbindgen_closure_wrapper1212 = function(arg0, arg1, arg2) { + var ret = makeMutClosure(arg0, arg1, 132, __wbg_adapter_47); + 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); + +})(); diff --git a/docs/example_web_bg.wasm b/docs/example_web_bg.wasm new file mode 100644 index 00000000..4559127d Binary files /dev/null and b/docs/example_web_bg.wasm differ diff --git a/docs/index.html b/docs/index.html index d44c8e6a..b65c5c33 100644 --- a/docs/index.html +++ b/docs/index.html @@ -29,7 +29,7 @@ - + diff --git a/egui/src/demos/mod.rs b/egui/src/demos/mod.rs index 957d2d78..d2040d32 100644 --- a/egui/src/demos/mod.rs +++ b/egui/src/demos/mod.rs @@ -61,7 +61,7 @@ pub fn warn_if_debug_build(ui: &mut crate::Ui) { macro_rules! github_link_file_line { ($github_url:expr, $label:expr) => {{ let url = format!("{}{}#L{}", $github_url, file!(), line!()); - Hyperlink::new(url).text($label) + $crate::Hyperlink::new(url).text($label) }}; } @@ -71,7 +71,7 @@ macro_rules! github_link_file_line { macro_rules! github_link_file { ($github_url:expr, $label:expr) => {{ let url = format!("{}{}", $github_url, file!()); - Hyperlink::new(url).text($label) + $crate::Hyperlink::new(url).text($label) }}; } diff --git a/egui_web/Cargo.toml b/egui_web/Cargo.toml index 40d4b1d9..1aba6529 100644 --- a/egui_web/Cargo.toml +++ b/egui_web/Cargo.toml @@ -35,6 +35,7 @@ features = [ 'Document', 'DomRect', 'Element', + 'Headers', 'HtmlCanvasElement', 'HtmlElement', 'KeyboardEvent', @@ -42,6 +43,10 @@ features = [ 'MouseEvent', 'Navigator', 'Performance', + 'Request', + 'RequestInit', + 'RequestMode', + 'Response', 'Storage', 'Touch', 'TouchEvent', diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index bc76346d..5eccb89c 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -200,6 +200,7 @@ pub fn start(app_runner: AppRunner) -> Result { let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner))); install_canvas_events(&runner_ref)?; install_document_events(&runner_ref)?; + repaint_every_ms(&runner_ref, 1000)?; // just in case. TODO: make it a parameter paint_and_schedule(runner_ref.clone())?; Ok(runner_ref) } diff --git a/egui_web/src/fetch.rs b/egui_web/src/fetch.rs new file mode 100644 index 00000000..5c691082 --- /dev/null +++ b/egui_web/src/fetch.rs @@ -0,0 +1,53 @@ +use wasm_bindgen::prelude::*; + +pub struct Response { + pub url: String, + pub ok: bool, + pub status: u16, + pub status_text: String, + + pub body: String, +} + +/// NOTE: Ok(..) is returned on network error. +/// Err is only for failure to use the fetch api. +pub async fn get_text(url: &str) -> Result { + get_text_jsvalue(url) + .await + .map_err(|err| err.as_string().unwrap_or_default()) +} + +/// NOTE: Ok(..) is returned on network error. +/// Err is only for failure to use the fetch api. +async fn get_text_jsvalue(url: &str) -> Result { + // https://rustwasm.github.io/wasm-bindgen/examples/fetch.html + + use wasm_bindgen::JsCast; + use wasm_bindgen_futures::JsFuture; + + let mut opts = web_sys::RequestInit::new(); + opts.method("GET"); + opts.mode(web_sys::RequestMode::Cors); + + let request = web_sys::Request::new_with_str_and_init(&url, &opts)?; + request.headers().set("Accept", "*/*")?; + + let window = web_sys::window().unwrap(); + let resp_value = JsFuture::from(window.fetch_with_request(&request)).await?; + + assert!(resp_value.is_instance_of::()); + let resp: web_sys::Response = resp_value.dyn_into().unwrap(); + + // TODO: headers + // TODO: support binary get + let body = JsFuture::from(resp.text()?).await?; + let body = body.as_string().unwrap_or_default(); + + Ok(Response { + status_text: resp.status_text(), + url: resp.url(), + ok: resp.ok(), + status: resp.status(), + body, + }) +} diff --git a/egui_web/src/lib.rs b/egui_web/src/lib.rs index 571df05a..a6ed6467 100644 --- a/egui_web/src/lib.rs +++ b/egui_web/src/lib.rs @@ -3,6 +3,7 @@ #![warn(clippy::all)] pub mod backend; +pub mod fetch; pub mod webgl; pub use backend::*; @@ -14,10 +15,14 @@ use wasm_bindgen::prelude::*; // ---------------------------------------------------------------------------- // Helpers to hide some of the verbosity of web_sys -pub fn console_log(s: String) { +pub fn console_log(s: impl Into) { web_sys::console::log_1(&s.into()); } +pub fn console_error(s: impl Into) { + web_sys::console::error_1(&s.into()); +} + pub fn now_sec() -> f64 { web_sys::window() .expect("should have a Window") @@ -182,6 +187,13 @@ pub fn set_clipboard_text(s: &str) { } } +pub fn spawn_future(future: F) +where + F: std::future::Future + 'static, +{ + wasm_bindgen_futures::spawn_local(future); +} + fn cursor_web_name(cursor: egui::CursorIcon) -> &'static str { use egui::CursorIcon::*; match cursor { @@ -412,6 +424,23 @@ fn install_document_events(runner_ref: &AppRunnerRef) -> Result<(), JsValue> { Ok(()) } +/// Repaint at least every `ms` milliseconds. +fn repaint_every_ms(runner_ref: &AppRunnerRef, milliseconds: i32) -> Result<(), JsValue> { + assert!(milliseconds >= 0); + use wasm_bindgen::JsCast; + let window = web_sys::window().unwrap(); + let runner_ref = runner_ref.clone(); + let closure = Closure::wrap(Box::new(move || { + runner_ref.0.lock().needs_repaint = true; + }) as Box); + window.set_interval_with_callback_and_timeout_and_arguments_0( + closure.as_ref().unchecked_ref(), + milliseconds, + )?; + closure.forget(); + Ok(()) +} + fn modifiers_from_event(event: &web_sys::KeyboardEvent) -> egui::Modifiers { egui::Modifiers { alt: event.alt_key(), diff --git a/example_glium/src/example_app.rs b/example_glium/src/example_app.rs new file mode 100644 index 00000000..9fb2a705 --- /dev/null +++ b/example_glium/src/example_app.rs @@ -0,0 +1,55 @@ +/// We derive Deserialize/Serialize so we can persist app state on shutdown. +#[derive(serde::Deserialize, serde::Serialize)] +pub struct ExampleApp { + name: String, + age: u32, +} + +impl Default for ExampleApp { + fn default() -> Self { + Self { + name: "Arthur".to_owned(), + age: 42, + } + } +} + +impl egui::app::App for ExampleApp { + /// 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: &std::sync::Arc, + integration_context: &mut egui::app::IntegrationContext, + ) { + let ExampleApp { name, age } = self; + + // Example used in `README.md`. + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("My Egui Application"); + + ui.horizontal(|ui| { + ui.label("Your name: "); + ui.text_edit_singleline(name); + }); + + ui.add(egui::Slider::u32(age, 0..=120).text("age")); + if ui.button("Click each year").clicked { + *age += 1; + } + + ui.label(format!("Hello '{}', age {}", name, age)); + + ui.advance_cursor(16.0); + if ui.button("Quit").clicked { + integration_context.output.quit = true; + } + }); + + integration_context.output.window_size = Some(ctx.used_size()); // resize the window to be just the size we need it to be + } + + fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) { + egui::app::set_value(storage, egui::app::APP_KEY, self); + } +} diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index 5db844be..a3a00d10 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -1,63 +1,10 @@ //! Example of how to use Egui - +#![forbid(unsafe_code)] #![deny(warnings)] #![warn(clippy::all)] -/// We derive Deserialize/Serialize so we can persist app state on shutdown. -#[derive(serde::Deserialize, serde::Serialize)] -struct MyApp { - name: String, - age: u32, -} - -impl Default for MyApp { - fn default() -> Self { - Self { - name: "Arthur".to_owned(), - age: 42, - } - } -} - -impl egui::app::App for MyApp { - /// 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: &std::sync::Arc, - integration_context: &mut egui::app::IntegrationContext, - ) { - let MyApp { name, age } = self; - - // Example used in `README.md`. - egui::CentralPanel::default().show(ctx, |ui| { - ui.heading("My Egui Application"); - - ui.horizontal(|ui| { - ui.label("Your name: "); - ui.text_edit_singleline(name); - }); - - ui.add(egui::Slider::u32(age, 0..=120).text("age")); - if ui.button("Click each year").clicked { - *age += 1; - } - - ui.label(format!("Hello '{}', age {}", name, age)); - - ui.advance_cursor(16.0); - if ui.button("Quit").clicked { - integration_context.output.quit = true; - } - }); - - integration_context.output.window_size = Some(ctx.used_size()); // resize the window to be just the size we need it to be - } - - fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) { - egui::app::set_value(storage, egui::app::APP_KEY, self); - } -} +mod example_app; +use example_app::ExampleApp; fn main() { let title = "My Egui Window"; @@ -68,6 +15,8 @@ fn main() { // Alternative: store nowhere // let storage = egui::app::DummyStorage::default(); - let app: MyApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default(); // Restore `MyApp` from file, or create new `MyApp`. + // Restore `example_app` from file, or create new `ExampleApp`: + let app: ExampleApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default(); + egui_glium::run(title, Box::new(storage), app); } diff --git a/example_web/Cargo.toml b/example_web/Cargo.toml new file mode 100644 index 00000000..87e9eb86 --- /dev/null +++ b/example_web/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "example_web" +version = "0.1.0" +authors = ["Emil Ernerfeldt "] +license = "MIT OR Apache-2.0" +edition = "2018" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +egui = { path = "../egui", features = ["serde"] } +egui_web = { path = "../egui_web" } +js-sys = "0.3" +serde = { version = "1", features = ["derive"] } +serde_json = "1" +wasm-bindgen = "0.2" diff --git a/example_web/src/example_app.rs b/example_web/src/example_app.rs new file mode 100644 index 00000000..fe3ec721 --- /dev/null +++ b/example_web/src/example_app.rs @@ -0,0 +1,106 @@ +use egui_web::fetch::Response; +use std::sync::mpsc::Receiver; + +pub struct ExampleApp { + url: String, + receivers: Vec>>, + fetch_result: Option>, +} + +impl Default for ExampleApp { + fn default() -> Self { + Self { + url: "https://raw.githubusercontent.com/emilk/egui/master/README.md".to_owned(), + receivers: Default::default(), + fetch_result: Default::default(), + } + } +} + +impl egui::app::App for ExampleApp { + /// 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: &std::sync::Arc, + _integration_context: &mut egui::app::IntegrationContext, + ) { + egui::CentralPanel::default().show(ctx, |ui| { + ui.heading("HTTP Get inside of Egui"); + ui.add(egui::github_link_file!( + "https://github.com/emilk/egui/blob/master/", + "(source code)" + )); + + { + let mut trigger_fetch = false; + + ui.horizontal(|ui| { + ui.label("URL:"); + trigger_fetch |= ui.text_edit_singleline(&mut self.url).lost_kb_focus; + + if ui.button("Egui README.md").clicked { + self.url = "https://raw.githubusercontent.com/emilk/egui/master/README.md" + .to_owned(); + trigger_fetch = true; + } + if ui.button("Source code for this file").clicked { + self.url = + format!("https://raw.githubusercontent.com/emilk/egui/{}", file!()); + trigger_fetch = true; + } + }); + trigger_fetch |= ui.button("GET").clicked; + + if trigger_fetch { + let (sender, receiver) = std::sync::mpsc::channel(); + self.receivers.push(receiver); + let url = self.url.clone(); + + let future = async move { + let result = egui_web::fetch::get_text(&url).await; + sender.send(result).ok(); + // TODO: trigger egui repaint somehow + }; + + egui_web::spawn_future(future); + } + } + + // Show finished download (if any): + if let Some(result) = &self.fetch_result { + ui.separator(); + match result { + Ok(response) => { + ui_response(ui, response); + } + Err(error) => { + // This should only happen if the fetch API isn't available or something similar. + ui.add(egui::Label::new(error).text_color(egui::color::RED)); + } + } + } + }); + + for i in (0..self.receivers.len()).rev() { + if let Ok(result) = self.receivers[i].try_recv() { + self.fetch_result = Some(result); + let _ = self.receivers.swap_remove(i); + } + } + } +} + +fn ui_response(ui: &mut egui::Ui, response: &Response) { + ui.monospace(format!("url: {}", response.url)); + ui.monospace(format!( + "status: {} ({})", + response.status, response.status_text + )); + + ui.monospace("Body:"); + ui.separator(); + egui::ScrollArea::auto_sized().show(ui, |ui| { + ui.monospace(&response.body); + }); +} diff --git a/example_web/src/lib.rs b/example_web/src/lib.rs new file mode 100644 index 00000000..787187b4 --- /dev/null +++ b/example_web/src/lib.rs @@ -0,0 +1,20 @@ +#![forbid(unsafe_code)] +#![deny(warnings)] +#![warn(clippy::all)] + +mod example_app; + +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. +#[wasm_bindgen] +pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { + let app = example_app::ExampleApp::default(); + let backend = egui_web::WebBackend::new(canvas_id)?; + let runner = egui_web::AppRunner::new(backend, Box::new(app))?; + egui_web::start(runner)?; + Ok(()) +}