Add example_web
app
This commit is contained in:
parent
7e9b5de250
commit
0cb3bb791b
14 changed files with 272 additions and 64 deletions
12
Cargo.lock
generated
12
Cargo.lock
generated
|
@ -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"
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
[workspace]
|
||||
members = [
|
||||
"demo_glium",
|
||||
"demo_web",
|
||||
"egui_glium",
|
||||
"egui_web",
|
||||
"egui",
|
||||
"example_glium",
|
||||
"demo_web",
|
||||
"example_web",
|
||||
]
|
||||
|
||||
|
||||
|
|
30
build_example_web.sh
Executable file
30
build_example_web.sh
Executable file
|
@ -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
|
|
@ -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
|
||||
|
|
3
check.sh
3
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
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
#![forbid(unsafe_code)]
|
||||
#![deny(warnings)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
|
|
67
docs/example.html
Normal file
67
docs/example.html
Normal file
|
@ -0,0 +1,67 @@
|
|||
<!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 – An experimental immediate mode GUI written in Rust</title>
|
||||
<style>
|
||||
html {
|
||||
/* Remove touch delay: */
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #101010;
|
||||
}
|
||||
|
||||
/* Allow canvas to fill entire web page: */
|
||||
html,
|
||||
body {
|
||||
overflow: hidden;
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<!--
|
||||
THis is where the app will show up.
|
||||
The WASM code will resize this 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="example_web.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("./example_web_bg.wasm")
|
||||
.then(on_wasm_loaded)["catch"](console.error);
|
||||
|
||||
function on_wasm_loaded() {
|
||||
// This call installs a bunch of callbacks and then return
|
||||
wasm_bindgen.start("the_canvas_id");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -29,7 +29,7 @@
|
|||
|
||||
<body>
|
||||
<!-- The WASM code will resize this to cover the entire screen -->
|
||||
<canvas id="canvas"></canvas>
|
||||
<canvas id="the_canvas_id"></canvas>
|
||||
|
||||
<script>
|
||||
// The `--no-modules`-generated JS from `wasm-bindgen` attempts to use
|
||||
|
@ -55,7 +55,8 @@
|
|||
.then(on_wasm_loaded)["catch"](console.error);
|
||||
|
||||
function on_wasm_loaded() {
|
||||
wasm_bindgen.start("canvas");
|
||||
// This call installs a bunch of callbacks and then return
|
||||
wasm_bindgen.start("the_canvas_id");
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
|
|
55
example_glium/src/example_app.rs
Normal file
55
example_glium/src/example_app.rs
Normal file
|
@ -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<egui::Context>,
|
||||
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);
|
||||
}
|
||||
}
|
|
@ -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<egui::Context>,
|
||||
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);
|
||||
}
|
||||
|
|
17
example_web/Cargo.toml
Normal file
17
example_web/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
|||
[package]
|
||||
name = "example_web"
|
||||
version = "0.1.0"
|
||||
authors = ["Emil Ernerfeldt <emil.ernerfeldt@gmail.com>"]
|
||||
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"
|
49
example_web/src/example_app.rs
Normal file
49
example_web/src/example_app.rs
Normal file
|
@ -0,0 +1,49 @@
|
|||
/// 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<egui::Context>,
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
20
example_web/src/lib.rs
Normal file
20
example_web/src/lib.rs
Normal file
|
@ -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(())
|
||||
}
|
Loading…
Reference in a new issue