mirror of
https://github.com/emilk/egui.git
synced 2026-08-29 12:50:04 -04:00
[app] unify web and glium demo app
This commit is contained in:
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)?;
|
||||
|
||||
Reference in New Issue
Block a user