[app] unify web and glium demo app
This commit is contained in:
parent
b79c76b9ce
commit
554e6e7120
15 changed files with 445 additions and 442 deletions
8
Cargo.lock
generated
8
Cargo.lock
generated
|
@ -427,12 +427,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
[[package]]
|
||||
name = "egui"
|
||||
version = "0.1.2"
|
||||
source = "git+https://github.com/emilk/emigui#3552f7f82861fb10ae646c61f3c283256e79940e"
|
||||
source = "git+https://github.com/emilk/emigui#e3d1d6c99ce37090d496b4f210aaef700235dd9b"
|
||||
dependencies = [
|
||||
"ahash 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusttype 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -444,12 +445,13 @@ dependencies = [
|
|||
"parking_lot 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rusttype 0.9.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde 1.0.114 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"serde_json 1.0.56 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "egui_glium"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/emilk/emigui#3552f7f82861fb10ae646c61f3c283256e79940e"
|
||||
source = "git+https://github.com/emilk/emigui#e3d1d6c99ce37090d496b4f210aaef700235dd9b"
|
||||
dependencies = [
|
||||
"chrono 0.4.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"clipboard 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
@ -476,7 +478,7 @@ dependencies = [
|
|||
[[package]]
|
||||
name = "egui_web"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/emilk/emigui#3552f7f82861fb10ae646c61f3c283256e79940e"
|
||||
source = "git+https://github.com/emilk/emigui#e3d1d6c99ce37090d496b4f210aaef700235dd9b"
|
||||
dependencies = [
|
||||
"egui 0.1.2 (git+https://github.com/emilk/emigui)",
|
||||
"js-sys 0.3.42 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -1,70 +1,11 @@
|
|||
#![deny(warnings)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
use egui_glium::{persistence::Persistence, RunMode, Runner};
|
||||
|
||||
const APP_KEY: &str = "app";
|
||||
|
||||
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
||||
struct MyApp {
|
||||
egui_demo_app: egui::DemoApp,
|
||||
frames_painted: u64,
|
||||
}
|
||||
|
||||
impl egui_glium::App for MyApp {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, runner: &mut Runner) {
|
||||
self.egui_demo_app.ui(ui, "");
|
||||
|
||||
use egui::*;
|
||||
let mut ui = ui.centered_column(ui.available().width().min(480.0));
|
||||
ui.set_layout(Layout::vertical(Align::Min));
|
||||
ui.add(label!("Egui inside of Glium").text_style(TextStyle::Heading));
|
||||
if ui.add(Button::new("Quit")).clicked {
|
||||
runner.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
ui.add(
|
||||
label!(
|
||||
"CPU usage: {:.2} ms / frame (excludes painting)",
|
||||
1e3 * runner.cpu_time()
|
||||
)
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut run_mode = runner.run_mode();
|
||||
ui.label("Run mode:");
|
||||
ui.radio_value("Continuous", &mut run_mode, RunMode::Continuous)
|
||||
.tooltip_text("Repaint everything each frame");
|
||||
ui.radio_value("Reactive", &mut run_mode, RunMode::Reactive)
|
||||
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
|
||||
runner.set_run_mode(run_mode);
|
||||
});
|
||||
|
||||
if runner.run_mode() == RunMode::Continuous {
|
||||
ui.add(
|
||||
label!("Repainting the UI each frame. FPS: {:.1}", runner.fps())
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
} else {
|
||||
ui.label("Only running UI code when there are animations or input");
|
||||
}
|
||||
|
||||
self.frames_painted += 1;
|
||||
ui.label(format!("Total frames painted: {}", self.frames_painted));
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, persistence: &mut Persistence) {
|
||||
persistence.set_value(APP_KEY, self);
|
||||
}
|
||||
}
|
||||
use egui_glium::{storage::FileStorage, RunMode};
|
||||
|
||||
fn main() {
|
||||
let title = "Egui glium demo";
|
||||
let persistence = Persistence::from_path(".egui_demo_glium.json".into());
|
||||
let app: MyApp = persistence.get_value(APP_KEY).unwrap_or_default();
|
||||
egui_glium::run(title, RunMode::Reactive, persistence, app);
|
||||
let storage = FileStorage::from_path(".egui_demo_glium.json".into());
|
||||
let app: egui::DemoApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default();
|
||||
egui_glium::run(title, RunMode::Reactive, storage, app);
|
||||
}
|
||||
|
|
|
@ -1,86 +1,14 @@
|
|||
#![deny(warnings)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
use egui::{label, TextStyle};
|
||||
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// This is the entry-point for all the web-assembly.
|
||||
#[wasm_bindgen]
|
||||
pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> {
|
||||
let backend = egui_web::Backend::new(canvas_id, egui_web::RunMode::Reactive)?;
|
||||
let app = Box::new(MyApp::default());
|
||||
let backend = egui_web::WebBackend::new(canvas_id, egui_web::RunMode::Reactive)?;
|
||||
let app = Box::new(egui::DemoApp::default());
|
||||
let runner = egui_web::AppRunner::new(backend, app)?;
|
||||
egui_web::run(runner)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct MyApp {
|
||||
demo_app: egui::demos::DemoApp,
|
||||
frames_painted: u64,
|
||||
}
|
||||
|
||||
impl MyApp {
|
||||
fn window_ui(&mut self, ui: &mut egui::Ui, backend: &mut egui_web::Backend) {
|
||||
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||
ui.label(
|
||||
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements."
|
||||
);
|
||||
ui.label("This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
|
||||
ui.label("This is also work in progress, and not ready for production... yet :)");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Project home page:");
|
||||
ui.hyperlink("https://github.com/emilk/emigui/");
|
||||
});
|
||||
ui.separator();
|
||||
|
||||
ui.add(
|
||||
label!(
|
||||
"CPU usage: {:.2} ms / frame (excludes painting)",
|
||||
1e3 * backend.cpu_time()
|
||||
)
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut run_mode = backend.run_mode();
|
||||
ui.label("Run mode:");
|
||||
ui.radio_value("Continuous", &mut run_mode, egui_web::RunMode::Continuous)
|
||||
.tooltip_text("Repaint everything each frame");
|
||||
ui.radio_value("Reactive", &mut run_mode, egui_web::RunMode::Reactive)
|
||||
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
|
||||
backend.set_run_mode(run_mode);
|
||||
});
|
||||
|
||||
if backend.run_mode() == egui_web::RunMode::Continuous {
|
||||
ui.add(
|
||||
label!("Repainting the UI each frame. FPS: {:.1}", backend.fps())
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
} else {
|
||||
ui.label("Only running UI code when there are animations or input");
|
||||
}
|
||||
|
||||
self.frames_painted += 1;
|
||||
ui.label(format!("Total frames painted: {}", self.frames_painted));
|
||||
}
|
||||
}
|
||||
|
||||
impl egui_web::App for MyApp {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, backend: &mut egui_web::Backend, info: &egui_web::WebInfo) {
|
||||
egui::Window::new("Egui")
|
||||
.default_width(500.0)
|
||||
.show(ui.ctx(), |ui| {
|
||||
self.window_ui(ui, backend);
|
||||
});
|
||||
|
||||
self.demo_app.ui(ui, &info.web_location_hash);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -208,27 +208,27 @@ function makeMutClosure(arg0, arg1, dtor, f) {
|
|||
return real;
|
||||
}
|
||||
function __wbg_adapter_26(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h426caa8a7645dc38(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_29(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h426caa8a7645dc38(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__h5402719cc6dde927(arg0, arg1);
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__hb0f876788be788af(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__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2));
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h426caa8a7645dc38(arg0, arg1, addHeapObject(arg2));
|
||||
}
|
||||
|
||||
function __wbg_adapter_38(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h84da5f062b972f09(arg0, arg1);
|
||||
function __wbg_adapter_38(arg0, arg1, arg2) {
|
||||
wasm._dyn_core__ops__function__FnMut__A____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h426caa8a7645dc38(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__h22fd33d9f501a695(arg0, arg1, addHeapObject(arg2));
|
||||
function __wbg_adapter_41(arg0, arg1) {
|
||||
wasm._dyn_core__ops__function__FnMut_____Output___R_as_wasm_bindgen__closure__WasmClosure___describe__invoke__h400d42c2242ce5bd(arg0, arg1);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -304,6 +304,10 @@ async function init(input) {
|
|||
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_forget = function(arg0) {
|
||||
takeObject(arg0);
|
||||
};
|
||||
|
@ -316,10 +320,6 @@ async function init(input) {
|
|||
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_0e8decd0a6179699 = function(arg0) {
|
||||
var ret = getObject(arg0) instanceof Window;
|
||||
return ret;
|
||||
|
@ -717,28 +717,28 @@ async function init(input) {
|
|||
var ret = wasm.memory;
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper380 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_26);
|
||||
imports.wbg.__wbindgen_closure_wrapper364 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_35);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper366 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_38);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper363 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_41);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper368 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_26);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper370 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_32);
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_29);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper378 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_41);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper376 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_38);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper374 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_35);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
imports.wbg.__wbindgen_closure_wrapper371 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 68, __wbg_adapter_29);
|
||||
imports.wbg.__wbindgen_closure_wrapper373 = function(arg0, arg1, arg2) {
|
||||
var ret = makeMutClosure(arg0, arg1, 76, __wbg_adapter_32);
|
||||
return addHeapObject(ret);
|
||||
};
|
||||
|
||||
|
|
Binary file not shown.
|
@ -19,9 +19,10 @@ ahash = "0.4"
|
|||
parking_lot = "0.11"
|
||||
rusttype = "0.9"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_json = { version = "1", optional = true }
|
||||
|
||||
[features]
|
||||
with_serde = ["serde"]
|
||||
with_serde = ["serde", "serde_json"]
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = { version = "0.3", default-features = false }
|
||||
|
|
79
egui/src/app.rs
Normal file
79
egui/src/app.rs
Normal file
|
@ -0,0 +1,79 @@
|
|||
//! Traits and helper for writing Egui apps.
|
||||
//!
|
||||
//! Egui can be used as a library, but you can also use it as a framework to write apps in.
|
||||
//! This module defined the `App` trait that can be implemented and used with the `egui_web` and `egui_glium` crates.
|
||||
|
||||
use crate::Ui;
|
||||
|
||||
/// Implement this trait to write apps that can be compiled both natively using the `egui_glium` crate,
|
||||
/// and deployed as a web site using the `egui_web` crate.
|
||||
pub trait App {
|
||||
/// Called each time the UI needs repainting, which may be many times per second.
|
||||
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn Backend);
|
||||
|
||||
/// Called once on shutdown. Allows you to save state.
|
||||
fn on_exit(&mut self, _storage: &mut dyn Storage) {}
|
||||
}
|
||||
|
||||
// TODO: replace with manually calling `egui::Context::request_repaint()` each frame.
|
||||
/// How the backend runs the app
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RunMode {
|
||||
/// Rapint the UI all the time (at the display refresh rate of e.g. 60 Hz).
|
||||
/// This is good for games where things are constantly moving.
|
||||
/// This can also be achieved with `RunMode::Reactive` combined with calling `egui::Context::request_repaint()` each frame.
|
||||
Continuous,
|
||||
|
||||
/// Only repaint when there are animations or input (mouse movement, keyboard input etc).
|
||||
/// This saves CPU.
|
||||
Reactive,
|
||||
}
|
||||
|
||||
pub struct WebInfo {
|
||||
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
|
||||
pub web_location_hash: String,
|
||||
}
|
||||
|
||||
pub trait Backend {
|
||||
fn run_mode(&self) -> RunMode;
|
||||
fn set_run_mode(&mut self, run_mode: RunMode);
|
||||
|
||||
/// If the app is running in a Web context, this returns information about the environment.
|
||||
fn web_info(&self) -> Option<WebInfo> {
|
||||
None
|
||||
}
|
||||
|
||||
/// excludes painting
|
||||
fn cpu_time(&self) -> f32;
|
||||
|
||||
/// Smoothed frames per second
|
||||
fn fps(&self) -> f32;
|
||||
|
||||
/// Signal the backend that we'd like to exit the app now.
|
||||
/// This does nothing for web apps.s
|
||||
fn quit(&mut self) {}
|
||||
}
|
||||
|
||||
/// A place where you can store custom data in a way that persists when you restart the app.
|
||||
///
|
||||
/// On the web this is backed by [local storage](https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage).
|
||||
/// On desktop this is backed by the file system.
|
||||
pub trait Storage {
|
||||
fn get_string(&self, key: &str) -> Option<&str>;
|
||||
fn set_string(&mut self, key: &str, value: String);
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_serde")]
|
||||
pub fn get_value<T: serde::de::DeserializeOwned>(storage: &dyn Storage, key: &str) -> Option<T> {
|
||||
storage
|
||||
.get_string(key)
|
||||
.and_then(|value| serde_json::from_str(value).ok())
|
||||
}
|
||||
|
||||
#[cfg(feature = "with_serde")]
|
||||
pub fn set_value<T: serde::Serialize>(storage: &mut dyn Storage, key: &str, value: &T) {
|
||||
storage.set_string(key, serde_json::to_string(value).unwrap());
|
||||
}
|
||||
|
||||
/// storage key used for app
|
||||
pub const APP_KEY: &str = "app";
|
|
@ -1,6 +1,6 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use crate::{color::*, containers::*, demos::FractalClock, paint::*, widgets::*, *};
|
||||
use crate::{app, color::*, containers::*, demos::FractalClock, paint::*, widgets::*, *};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
|
@ -12,6 +12,7 @@ pub struct DemoApp {
|
|||
open_windows: OpenWindows,
|
||||
demo_window: DemoWindow,
|
||||
fractal_clock: FractalClock,
|
||||
num_frames_painted: u64,
|
||||
}
|
||||
|
||||
impl DemoApp {
|
||||
|
@ -68,6 +69,84 @@ impl DemoApp {
|
|||
|
||||
fractal_clock.window(ctx, &mut open_windows.fractal_clock);
|
||||
}
|
||||
|
||||
fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
||||
let is_web = backend.web_info().is_some();
|
||||
|
||||
if is_web {
|
||||
ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL.");
|
||||
ui.label(
|
||||
"Everything you see is rendered as textured triangles. There is no DOM. There are no HTML elements."
|
||||
);
|
||||
ui.label("This is not JavaScript. This is Rust, running at 60 FPS. This is the web page, reinvented with game tech.");
|
||||
ui.label("This is also work in progress, and not ready for production... yet :)");
|
||||
ui.horizontal(|ui| {
|
||||
ui.label("Project home page:");
|
||||
ui.hyperlink("https://github.com/emilk/emigui/");
|
||||
});
|
||||
} else {
|
||||
ui.add(label!("Egui").text_style(TextStyle::Heading));
|
||||
if ui.add(Button::new("Quit")).clicked {
|
||||
backend.quit();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.add(
|
||||
label!(
|
||||
"CPU usage: {:.2} ms / frame (excludes painting)",
|
||||
1e3 * backend.cpu_time()
|
||||
)
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
|
||||
ui.separator();
|
||||
|
||||
ui.horizontal(|ui| {
|
||||
let mut run_mode = backend.run_mode();
|
||||
ui.label("Run mode:");
|
||||
ui.radio_value("Continuous", &mut run_mode, app::RunMode::Continuous)
|
||||
.tooltip_text("Repaint everything each frame");
|
||||
ui.radio_value("Reactive", &mut run_mode, app::RunMode::Reactive)
|
||||
.tooltip_text("Repaint when there are animations or input (e.g. mouse movement)");
|
||||
backend.set_run_mode(run_mode);
|
||||
});
|
||||
|
||||
if backend.run_mode() == app::RunMode::Continuous {
|
||||
ui.add(
|
||||
label!("Repainting the UI each frame. FPS: {:.1}", backend.fps())
|
||||
.text_style(TextStyle::Monospace),
|
||||
);
|
||||
} else {
|
||||
ui.label("Only running UI code when there are animations or input");
|
||||
}
|
||||
|
||||
self.num_frames_painted += 1;
|
||||
ui.label(format!("Total frames painted: {}", self.num_frames_painted));
|
||||
}
|
||||
}
|
||||
|
||||
impl app::App for DemoApp {
|
||||
fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) {
|
||||
Window::new("Backend")
|
||||
.default_width(500.0)
|
||||
.show(ui.ctx(), |ui| {
|
||||
self.backend_ui(ui, backend);
|
||||
});
|
||||
|
||||
let web_info = backend.web_info();
|
||||
let web_location_hash = web_info
|
||||
.as_ref()
|
||||
.map(|info| info.web_location_hash.as_str())
|
||||
.unwrap_or_default();
|
||||
self.ui(ui, web_location_hash);
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, storage: &mut dyn app::Storage) {
|
||||
app::set_value(storage, app::APP_KEY, self);
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
rust_2018_idioms,
|
||||
)]
|
||||
|
||||
pub mod app;
|
||||
pub mod containers;
|
||||
mod context;
|
||||
pub mod demos;
|
||||
|
|
|
@ -1,38 +1,22 @@
|
|||
use std::time::Instant;
|
||||
|
||||
use crate::{
|
||||
persistence::{Persistence, WindowSettings},
|
||||
storage::{FileStorage, WindowSettings},
|
||||
*,
|
||||
};
|
||||
|
||||
pub use egui::app::{App, Backend, RunMode, Storage};
|
||||
|
||||
const EGUI_MEMORY_KEY: &str = "egui";
|
||||
const WINDOW_KEY: &str = "window";
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RunMode {
|
||||
/// Uses `request_animation_frame` to repaint the UI on each display Hz.
|
||||
/// This is good for games and stuff where you want to run logic at e.g. 60 FPS.
|
||||
Continuous,
|
||||
|
||||
/// Only repaint when there are animations or input (mouse movement, keyboard input etc).
|
||||
Reactive,
|
||||
}
|
||||
|
||||
pub trait App {
|
||||
/// Called onced per frame for you to draw the UI.
|
||||
fn ui(&mut self, ui: &mut egui::Ui, runner: &mut Runner);
|
||||
|
||||
/// Called once on shutdown. Allows you to save state.
|
||||
fn on_exit(&mut self, persistence: &mut Persistence);
|
||||
}
|
||||
|
||||
pub struct Runner {
|
||||
pub struct GliumBackend {
|
||||
frame_times: egui::MovementTracker<f32>,
|
||||
quit: bool,
|
||||
run_mode: RunMode,
|
||||
}
|
||||
|
||||
impl Runner {
|
||||
impl GliumBackend {
|
||||
pub fn new(run_mode: RunMode) -> Self {
|
||||
Self {
|
||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
||||
|
@ -40,33 +24,35 @@ impl Runner {
|
|||
run_mode,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run_mode(&self) -> RunMode {
|
||||
impl Backend for GliumBackend {
|
||||
fn run_mode(&self) -> RunMode {
|
||||
self.run_mode
|
||||
}
|
||||
|
||||
pub fn set_run_mode(&mut self, run_mode: RunMode) {
|
||||
fn set_run_mode(&mut self, run_mode: RunMode) {
|
||||
self.run_mode = run_mode;
|
||||
}
|
||||
|
||||
pub fn quit(&mut self) {
|
||||
self.quit = true;
|
||||
}
|
||||
|
||||
pub fn cpu_time(&self) -> f32 {
|
||||
fn cpu_time(&self) -> f32 {
|
||||
self.frame_times.average().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fps(&self) -> f32 {
|
||||
fn fps(&self) -> f32 {
|
||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn quit(&mut self) {
|
||||
self.quit = true;
|
||||
}
|
||||
}
|
||||
|
||||
/// Run an egui app
|
||||
pub fn run(
|
||||
title: &str,
|
||||
run_mode: RunMode,
|
||||
mut persistence: Persistence,
|
||||
mut storage: FileStorage,
|
||||
mut app: impl App + 'static,
|
||||
) -> ! {
|
||||
let event_loop = glutin::event_loop::EventLoop::new();
|
||||
|
@ -76,7 +62,7 @@ pub fn run(
|
|||
.with_title(title)
|
||||
.with_transparent(false);
|
||||
|
||||
let window_settings: Option<WindowSettings> = persistence.get_value(WINDOW_KEY);
|
||||
let window_settings: Option<WindowSettings> = egui::app::get_value(&storage, WINDOW_KEY);
|
||||
if let Some(window_settings) = &window_settings {
|
||||
window = window_settings.initialize_size(window);
|
||||
}
|
||||
|
@ -93,14 +79,14 @@ pub fn run(
|
|||
}
|
||||
|
||||
let mut ctx = egui::Context::new();
|
||||
*ctx.memory() = persistence.get_value(EGUI_MEMORY_KEY).unwrap_or_default();
|
||||
*ctx.memory() = egui::app::get_value(&storage, EGUI_MEMORY_KEY).unwrap_or_default();
|
||||
|
||||
let mut painter = Painter::new(&display);
|
||||
let mut raw_input = make_raw_input(&display);
|
||||
|
||||
// used to keep track of time for animations
|
||||
let start_time = Instant::now();
|
||||
let mut runner = Runner::new(run_mode);
|
||||
let mut runner = GliumBackend::new(run_mode);
|
||||
let mut clipboard = init_clipboard();
|
||||
|
||||
event_loop.run(move |event, _, control_flow| {
|
||||
|
@ -134,10 +120,14 @@ pub fn run(
|
|||
display.gl_window().window().request_redraw(); // TODO: maybe only on some events?
|
||||
}
|
||||
glutin::event::Event::LoopDestroyed => {
|
||||
persistence.set_value(WINDOW_KEY, &WindowSettings::from_display(&display));
|
||||
persistence.set_value(EGUI_MEMORY_KEY, &*ctx.memory());
|
||||
app.on_exit(&mut persistence);
|
||||
persistence.save();
|
||||
egui::app::set_value(
|
||||
&mut storage,
|
||||
WINDOW_KEY,
|
||||
&WindowSettings::from_display(&display),
|
||||
);
|
||||
egui::app::set_value(&mut storage, EGUI_MEMORY_KEY, &*ctx.memory());
|
||||
app.on_exit(&mut storage);
|
||||
storage.save();
|
||||
}
|
||||
_ => (),
|
||||
}
|
|
@ -3,12 +3,12 @@
|
|||
#![allow(clippy::single_match)]
|
||||
#![allow(deprecated)] // TODO: remove
|
||||
|
||||
mod backend;
|
||||
mod painter;
|
||||
pub mod persistence;
|
||||
mod runner;
|
||||
pub mod storage;
|
||||
|
||||
pub use backend::*;
|
||||
pub use painter::Painter;
|
||||
pub use runner::*;
|
||||
|
||||
use {
|
||||
clipboard::ClipboardProvider,
|
||||
|
|
|
@ -4,13 +4,13 @@ use std::collections::HashMap;
|
|||
|
||||
/// A key-value store backed by a JSON file on disk.
|
||||
/// Used to restore egui state, glium window position/size and app state.
|
||||
pub struct Persistence {
|
||||
pub struct FileStorage {
|
||||
path: String,
|
||||
kv: HashMap<String, String>,
|
||||
dirty: bool,
|
||||
}
|
||||
|
||||
impl Persistence {
|
||||
impl FileStorage {
|
||||
pub fn from_path(path: String) -> Self {
|
||||
Self {
|
||||
kv: read_json(&path).unwrap_or_default(),
|
||||
|
@ -19,23 +19,6 @@ impl Persistence {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn get_value<T: serde::de::DeserializeOwned>(&self, key: &str) -> Option<T> {
|
||||
self.kv
|
||||
.get(key)
|
||||
.and_then(|value| serde_json::from_str(value).ok())
|
||||
}
|
||||
|
||||
pub fn set_string(&mut self, key: &str, value: String) {
|
||||
if self.kv.get(key) != Some(&value) {
|
||||
self.kv.insert(key.to_owned(), value);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_value<T: serde::Serialize>(&mut self, key: &str, value: &T) {
|
||||
self.set_string(key, serde_json::to_string(value).unwrap());
|
||||
}
|
||||
|
||||
pub fn save(&mut self) {
|
||||
if self.dirty {
|
||||
serde_json::to_writer(std::fs::File::create(&self.path).unwrap(), &self.kv).unwrap();
|
||||
|
@ -44,6 +27,19 @@ impl Persistence {
|
|||
}
|
||||
}
|
||||
|
||||
impl egui::app::Storage for FileStorage {
|
||||
fn get_string(&self, key: &str) -> Option<&str> {
|
||||
self.kv.get(key).map(String::as_str)
|
||||
}
|
||||
|
||||
fn set_string(&mut self, key: &str, value: String) {
|
||||
if self.kv.get(key) != Some(&value) {
|
||||
self.kv.insert(key.to_owned(), value);
|
||||
self.dirty = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub fn read_json<T>(memory_json_path: impl AsRef<std::path::Path>) -> Option<T>
|
||||
|
@ -69,7 +65,7 @@ where
|
|||
}
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
/// Alternative to `Persistence`
|
||||
/// Alternative to `FileStorage`
|
||||
pub fn read_memory(ctx: &egui::Context, memory_json_path: impl AsRef<std::path::Path>) {
|
||||
let memory: Option<egui::Memory> = read_json(memory_json_path);
|
||||
if let Some(memory) = memory {
|
||||
|
@ -77,7 +73,7 @@ pub fn read_memory(ctx: &egui::Context, memory_json_path: impl AsRef<std::path::
|
|||
}
|
||||
}
|
||||
|
||||
/// Alternative to `Persistence`
|
||||
/// Alternative to `FileStorage`
|
||||
pub fn write_memory(
|
||||
ctx: &egui::Context,
|
||||
memory_json_path: impl AsRef<std::path::Path>,
|
184
egui_web/src/backend.rs
Normal file
184
egui_web/src/backend.rs
Normal file
|
@ -0,0 +1,184 @@
|
|||
use crate::*;
|
||||
|
||||
pub use egui::app::{App, Backend, RunMode, WebInfo};
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct WebBackend {
|
||||
ctx: Arc<egui::Context>,
|
||||
painter: webgl::Painter,
|
||||
frame_times: egui::MovementTracker<f32>,
|
||||
frame_start: Option<f64>,
|
||||
run_mode: RunMode,
|
||||
last_save_time: Option<f64>,
|
||||
}
|
||||
|
||||
impl WebBackend {
|
||||
pub fn new(canvas_id: &str, run_mode: RunMode) -> Result<Self, JsValue> {
|
||||
let ctx = egui::Context::new();
|
||||
load_memory(&ctx);
|
||||
Ok(Self {
|
||||
ctx,
|
||||
painter: webgl::Painter::new(canvas_id)?,
|
||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
||||
frame_start: None,
|
||||
run_mode,
|
||||
last_save_time: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.painter.canvas_id()
|
||||
}
|
||||
|
||||
/// Returns a master fullscreen UI, covering the entire screen.
|
||||
pub fn begin_frame(&mut self, raw_input: egui::RawInput) -> egui::Ui {
|
||||
self.frame_start = Some(now_sec());
|
||||
self.ctx.begin_frame(raw_input)
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> {
|
||||
let frame_start = self
|
||||
.frame_start
|
||||
.take()
|
||||
.expect("unmatched calls to begin_frame/end_frame");
|
||||
|
||||
let (output, paint_jobs) = self.ctx.end_frame();
|
||||
|
||||
self.auto_save();
|
||||
|
||||
let now = now_sec();
|
||||
self.frame_times.add(now, (now - frame_start) as f32);
|
||||
|
||||
Ok((output, paint_jobs))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
|
||||
let bg_color = egui::color::TRANSPARENT; // Use background css color.
|
||||
self.painter.paint_jobs(
|
||||
bg_color,
|
||||
paint_jobs,
|
||||
self.ctx.texture(),
|
||||
self.ctx.pixels_per_point(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn auto_save(&mut self) {
|
||||
let now = now_sec();
|
||||
let time_since_last_save = now - self.last_save_time.unwrap_or(std::f64::NEG_INFINITY);
|
||||
const AUTO_SAVE_INTERVAL: f64 = 5.0;
|
||||
if time_since_last_save > AUTO_SAVE_INTERVAL {
|
||||
self.last_save_time = Some(now);
|
||||
save_memory(&self.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn painter_debug_info(&self) -> String {
|
||||
self.painter.debug_info()
|
||||
}
|
||||
}
|
||||
|
||||
impl Backend for WebBackend {
|
||||
fn run_mode(&self) -> RunMode {
|
||||
self.run_mode
|
||||
}
|
||||
|
||||
fn set_run_mode(&mut self, run_mode: RunMode) {
|
||||
self.run_mode = run_mode;
|
||||
}
|
||||
|
||||
fn web_info(&self) -> Option<WebInfo> {
|
||||
Some(WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
})
|
||||
}
|
||||
|
||||
/// excludes painting
|
||||
fn cpu_time(&self) -> f32 {
|
||||
self.frame_times.average().unwrap_or_default()
|
||||
}
|
||||
|
||||
fn fps(&self) -> f32 {
|
||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// TODO: Just use RawInput?
|
||||
/// Data gathered between frames.
|
||||
/// Is translated to `egui::RawInput` at the start of each frame.
|
||||
#[derive(Default)]
|
||||
pub struct WebInput {
|
||||
pub mouse_pos: Option<egui::Pos2>,
|
||||
pub mouse_down: bool, // TODO: which button
|
||||
pub is_touch: bool,
|
||||
pub scroll_delta: egui::Vec2,
|
||||
pub events: Vec<egui::Event>,
|
||||
}
|
||||
|
||||
impl WebInput {
|
||||
pub fn new_frame(&mut self) -> egui::RawInput {
|
||||
egui::RawInput {
|
||||
mouse_down: self.mouse_down,
|
||||
mouse_pos: self.mouse_pos,
|
||||
scroll_delta: std::mem::take(&mut self.scroll_delta),
|
||||
screen_size: screen_size().unwrap(),
|
||||
pixels_per_point: Some(pixels_per_point()),
|
||||
time: now_sec(),
|
||||
seconds_since_midnight: Some(seconds_since_midnight()),
|
||||
events: std::mem::take(&mut self.events),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
pub web_backend: WebBackend,
|
||||
pub web_input: WebInput,
|
||||
pub app: Box<dyn App>,
|
||||
pub needs_repaint: bool, // TODO: move
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(web_backend: WebBackend, app: Box<dyn App>) -> Result<Self, JsValue> {
|
||||
Ok(Self {
|
||||
web_backend,
|
||||
web_input: Default::default(),
|
||||
app,
|
||||
needs_repaint: true, // TODO: move
|
||||
})
|
||||
}
|
||||
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.web_backend.canvas_id()
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> {
|
||||
resize_to_screen_size(self.web_backend.canvas_id());
|
||||
|
||||
let raw_input = self.web_input.new_frame();
|
||||
|
||||
let mut ui = self.web_backend.begin_frame(raw_input);
|
||||
self.app.ui(&mut ui, &mut self.web_backend);
|
||||
let (output, paint_jobs) = self.web_backend.end_frame()?;
|
||||
handle_output(&output);
|
||||
Ok((output, paint_jobs))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
|
||||
self.web_backend.paint(paint_jobs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and starts running the given `AppRunner`.
|
||||
pub fn run(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||
let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner)));
|
||||
install_canvas_events(&runner_ref)?;
|
||||
install_document_events(&runner_ref)?;
|
||||
paint_and_schedule(runner_ref.clone())?;
|
||||
Ok(runner_ref)
|
||||
}
|
|
@ -1,211 +1,15 @@
|
|||
#![deny(warnings)]
|
||||
#![warn(clippy::all)]
|
||||
|
||||
pub mod backend;
|
||||
pub mod webgl;
|
||||
|
||||
pub use backend::*;
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::sync::Arc;
|
||||
use wasm_bindgen::prelude::*;
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct WebInfo {
|
||||
/// e.g. "#fragment" part of "www.example.com/index.html#fragment"
|
||||
pub web_location_hash: String,
|
||||
}
|
||||
|
||||
/// Implement this and use `egui_web::AppRunner` to run your app.
|
||||
pub trait App {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, backend: &mut Backend, info: &WebInfo);
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum RunMode {
|
||||
/// Uses `request_animation_frame` to repaint the UI on each display Hz.
|
||||
/// This is good for games and stuff where you want to run logic at e.g. 60 FPS.
|
||||
Continuous,
|
||||
|
||||
/// Only repaint when there are animations or input (mouse movement, keyboard input etc).
|
||||
Reactive,
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct Backend {
|
||||
ctx: Arc<egui::Context>,
|
||||
painter: webgl::Painter,
|
||||
frame_times: egui::MovementTracker<f32>,
|
||||
frame_start: Option<f64>,
|
||||
run_mode: RunMode,
|
||||
last_save_time: Option<f64>,
|
||||
}
|
||||
|
||||
impl Backend {
|
||||
pub fn new(canvas_id: &str, run_mode: RunMode) -> Result<Backend, JsValue> {
|
||||
let ctx = egui::Context::new();
|
||||
load_memory(&ctx);
|
||||
Ok(Backend {
|
||||
ctx,
|
||||
painter: webgl::Painter::new(canvas_id)?,
|
||||
frame_times: egui::MovementTracker::new(1000, 1.0),
|
||||
frame_start: None,
|
||||
run_mode,
|
||||
last_save_time: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run_mode(&self) -> RunMode {
|
||||
self.run_mode
|
||||
}
|
||||
|
||||
pub fn set_run_mode(&mut self, run_mode: RunMode) {
|
||||
self.run_mode = run_mode;
|
||||
}
|
||||
|
||||
/// id of the canvas html element containing the rendering
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.painter.canvas_id()
|
||||
}
|
||||
|
||||
/// Returns a master fullscreen UI, covering the entire screen.
|
||||
pub fn begin_frame(&mut self, raw_input: egui::RawInput) -> egui::Ui {
|
||||
self.frame_start = Some(now_sec());
|
||||
self.ctx.begin_frame(raw_input)
|
||||
}
|
||||
|
||||
pub fn end_frame(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> {
|
||||
let frame_start = self
|
||||
.frame_start
|
||||
.take()
|
||||
.expect("unmatched calls to begin_frame/end_frame");
|
||||
|
||||
let (output, paint_jobs) = self.ctx.end_frame();
|
||||
|
||||
self.auto_save();
|
||||
|
||||
let now = now_sec();
|
||||
self.frame_times.add(now, (now - frame_start) as f32);
|
||||
|
||||
Ok((output, paint_jobs))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
|
||||
let bg_color = egui::color::TRANSPARENT; // Use background css color.
|
||||
self.painter.paint_jobs(
|
||||
bg_color,
|
||||
paint_jobs,
|
||||
self.ctx.texture(),
|
||||
self.ctx.pixels_per_point(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn auto_save(&mut self) {
|
||||
let now = now_sec();
|
||||
let time_since_last_save = now - self.last_save_time.unwrap_or(std::f64::NEG_INFINITY);
|
||||
const AUTO_SAVE_INTERVAL: f64 = 5.0;
|
||||
if time_since_last_save > AUTO_SAVE_INTERVAL {
|
||||
self.last_save_time = Some(now);
|
||||
save_memory(&self.ctx);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn painter_debug_info(&self) -> String {
|
||||
self.painter.debug_info()
|
||||
}
|
||||
|
||||
/// excludes painting
|
||||
pub fn cpu_time(&self) -> f32 {
|
||||
self.frame_times.average().unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn fps(&self) -> f32 {
|
||||
1.0 / self.frame_times.mean_time_interval().unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
// TODO: Just use RawInput?
|
||||
/// Data gathered between frames.
|
||||
/// Is translated to `egui::RawInput` at the start of each frame.
|
||||
#[derive(Default)]
|
||||
pub struct WebInput {
|
||||
pub mouse_pos: Option<egui::Pos2>,
|
||||
pub mouse_down: bool, // TODO: which button
|
||||
pub is_touch: bool,
|
||||
pub scroll_delta: egui::Vec2,
|
||||
pub events: Vec<egui::Event>,
|
||||
}
|
||||
|
||||
impl WebInput {
|
||||
pub fn new_frame(&mut self) -> egui::RawInput {
|
||||
egui::RawInput {
|
||||
mouse_down: self.mouse_down,
|
||||
mouse_pos: self.mouse_pos,
|
||||
scroll_delta: std::mem::take(&mut self.scroll_delta),
|
||||
screen_size: screen_size().unwrap(),
|
||||
pixels_per_point: Some(pixels_per_point()),
|
||||
time: now_sec(),
|
||||
seconds_since_midnight: Some(seconds_since_midnight()),
|
||||
events: std::mem::take(&mut self.events),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
|
||||
pub struct AppRunner {
|
||||
pub backend: Backend,
|
||||
pub web_input: WebInput,
|
||||
pub app: Box<dyn App>,
|
||||
pub needs_repaint: bool, // TODO: move
|
||||
}
|
||||
|
||||
impl AppRunner {
|
||||
pub fn new(backend: Backend, app: Box<dyn App>) -> Result<Self, JsValue> {
|
||||
Ok(Self {
|
||||
backend,
|
||||
web_input: Default::default(),
|
||||
app,
|
||||
needs_repaint: true, // TODO: move
|
||||
})
|
||||
}
|
||||
|
||||
pub fn canvas_id(&self) -> &str {
|
||||
self.backend.canvas_id()
|
||||
}
|
||||
|
||||
pub fn logic(&mut self) -> Result<(egui::Output, egui::PaintJobs), JsValue> {
|
||||
resize_to_screen_size(self.backend.canvas_id());
|
||||
|
||||
let raw_input = self.web_input.new_frame();
|
||||
|
||||
let info = WebInfo {
|
||||
web_location_hash: location_hash().unwrap_or_default(),
|
||||
};
|
||||
|
||||
let mut ui = self.backend.begin_frame(raw_input);
|
||||
self.app.ui(&mut ui, &mut self.backend, &info);
|
||||
let (output, paint_jobs) = self.backend.end_frame()?;
|
||||
handle_output(&output);
|
||||
Ok((output, paint_jobs))
|
||||
}
|
||||
|
||||
pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> {
|
||||
self.backend.paint(paint_jobs)
|
||||
}
|
||||
}
|
||||
|
||||
/// Install event listeners to register different input events
|
||||
/// and starts running the given `AppRunner`.
|
||||
pub fn run(app_runner: AppRunner) -> Result<AppRunnerRef, JsValue> {
|
||||
let runner_ref = AppRunnerRef(Arc::new(Mutex::new(app_runner)));
|
||||
install_canvas_events(&runner_ref)?;
|
||||
install_document_events(&runner_ref)?;
|
||||
paint_and_schedule(runner_ref.clone())?;
|
||||
Ok(runner_ref)
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------------------
|
||||
// Helpers to hide some of the verbosity of web_sys
|
||||
|
||||
|
@ -412,7 +216,7 @@ pub struct AppRunnerRef(Arc<Mutex<AppRunner>>);
|
|||
fn paint_and_schedule(runner_ref: AppRunnerRef) -> Result<(), JsValue> {
|
||||
fn paint_if_needed(runner_ref: &AppRunnerRef) -> Result<(), JsValue> {
|
||||
let mut runner_lock = runner_ref.0.lock();
|
||||
if runner_lock.backend.run_mode() == RunMode::Continuous || runner_lock.needs_repaint {
|
||||
if runner_lock.web_backend.run_mode() == RunMode::Continuous || runner_lock.needs_repaint {
|
||||
runner_lock.needs_repaint = false;
|
||||
let (output, paint_jobs) = runner_lock.logic()?;
|
||||
runner_lock.paint(paint_jobs)?;
|
||||
|
|
|
@ -1,6 +1,4 @@
|
|||
use egui_glium::{persistence::Persistence, RunMode, Runner};
|
||||
|
||||
const APP_KEY: &str = "app";
|
||||
use egui_glium::{storage::FileStorage, RunMode};
|
||||
|
||||
/// We dervive Deserialize/Serialize so we can persist app state on shutdown.
|
||||
#[derive(Default, serde::Deserialize, serde::Serialize)]
|
||||
|
@ -8,10 +6,10 @@ struct MyApp {
|
|||
counter: u64,
|
||||
}
|
||||
|
||||
impl egui_glium::App for MyApp {
|
||||
impl egui::app::App for MyApp {
|
||||
/// This function will be called whenever the Ui needs to be shown,
|
||||
/// which may be many times per second.
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _: &mut Runner) {
|
||||
fn ui(&mut self, ui: &mut egui::Ui, _: &mut dyn egui::app::Backend) {
|
||||
if ui.button("Increment").clicked {
|
||||
self.counter += 1;
|
||||
}
|
||||
|
@ -21,14 +19,14 @@ impl egui_glium::App for MyApp {
|
|||
ui.label(format!("Counter: {}", self.counter));
|
||||
}
|
||||
|
||||
fn on_exit(&mut self, persistence: &mut Persistence) {
|
||||
persistence.set_value(APP_KEY, self); // Save app state
|
||||
fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) {
|
||||
egui::app::set_value(storage, egui::app::APP_KEY, self);
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let title = "My Egui Window";
|
||||
let persistence = Persistence::from_path(".egui_example_glium.json".into()); // Where to persist app state
|
||||
let app: MyApp = persistence.get_value(APP_KEY).unwrap_or_default(); // Restore `MyApp` from file, or create new `MyApp`.
|
||||
egui_glium::run(title, RunMode::Reactive, persistence, app);
|
||||
let storage = FileStorage::from_path(".egui_example_glium.json".into()); // Where to persist app state
|
||||
let app: MyApp = egui::app::get_value(&storage, egui::app::APP_KEY).unwrap_or_default(); // Restore `MyApp` from file, or create new `MyApp`.
|
||||
egui_glium::run(title, RunMode::Reactive, storage, app);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue