From 251cde60f02695abcb79ed208acb5372c29f33a8 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Sat, 17 Oct 2020 12:33:30 +0200 Subject: [PATCH] [app] Refactor egui::app::App interface to be more data oriented --- CHANGELOG.md | 4 ++ demo_web/src/lib.rs | 2 +- egui/src/app.rs | 33 ++++++++++------ egui/src/demos/app.rs | 59 +++++++++++++++++----------- egui_glium/src/backend.rs | 83 +++++++++++++++------------------------ egui_web/src/backend.rs | 45 ++++++++++----------- example_glium/src/main.rs | 9 ++++- 7 files changed, 124 insertions(+), 111 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 20b5ed2f..e2721668 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## Unreleased +* `ui.horizontal(...)` etc returns `Response` +* Add ability to override text color with `visuals.override_text_color` +* Refactored the interface for `egui::app::App` + ## 0.2.0 - 2020-10-10 * Color picker diff --git a/demo_web/src/lib.rs b/demo_web/src/lib.rs index 0ce885d7..5bf62994 100644 --- a/demo_web/src/lib.rs +++ b/demo_web/src/lib.rs @@ -9,6 +9,6 @@ pub fn start(canvas_id: &str) -> Result<(), wasm_bindgen::JsValue> { let backend = egui_web::WebBackend::new(canvas_id)?; let app = Box::new(egui::DemoApp::default()); let runner = egui_web::AppRunner::new(backend, app)?; - egui_web::run(runner)?; + egui_web::start(runner)?; Ok(()) } diff --git a/egui/src/app.rs b/egui/src/app.rs index ac10e3de..53ffb9bf 100644 --- a/egui/src/app.rs +++ b/egui/src/app.rs @@ -13,7 +13,12 @@ use crate::Ui; /// and deployed as a web site using the [`egui_web`](https://crates.io/crates/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); + fn ui( + &mut self, + ui: &mut Ui, + info: &BackendInfo, + tex_allocator: Option<&mut dyn TextureAllocator>, + ) -> AppOutput; /// Called once on shutdown. Allows you to save state. fn on_exit(&mut self, _storage: &mut dyn Storage) {} @@ -24,25 +29,29 @@ pub struct WebInfo { pub web_location_hash: String, } -pub trait Backend { +/// Information about the backend passed to the use app each frame. +pub struct BackendInfo { /// If the app is running in a Web context, this returns information about the environment. - fn web_info(&self) -> Option { - None - } + pub web_info: Option, /// Seconds of cpu usage (in seconds) of UI code on the previous frame. - /// Zero if this is the first frame. - fn cpu_usage(&self) -> Option; + /// `None` if this is the first frame. + pub cpu_usage: Option, /// Local time. Used for the clock in the demo app. - fn seconds_since_midnight(&self) -> Option { - None - } + /// Set to `None` if you don't know. + pub seconds_since_midnight: Option, +} - /// Signal the backend that we'd like to exit the app now. +/// Action that can be taken by the user app. +#[derive(Clone, Copy, Debug, Default)] +pub struct AppOutput { + /// Set to `true` to stop the app. /// This does nothing for web apps. - fn quit(&mut self) {} + pub quit: bool, +} +pub trait TextureAllocator { /// Allocate a user texture (EXPERIMENTAL!) fn new_texture_srgba_premultiplied( &mut self, diff --git a/egui/src/demos/app.rs b/egui/src/demos/app.rs index 6bc5c5f3..253c0e9e 100644 --- a/egui/src/demos/app.rs +++ b/egui/src/demos/app.rs @@ -314,11 +314,13 @@ impl DemoApp { } // TODO: give cpu_usage and web_info via `struct BackendInfo` - fn backend_ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) { + fn backend_ui(&mut self, ui: &mut Ui, info: &app::BackendInfo) -> app::AppOutput { self.frame_history - .on_new_frame(ui.input().time, backend.cpu_usage()); + .on_new_frame(ui.input().time, info.cpu_usage); - let is_web = backend.web_info().is_some(); + let is_web = info.web_info.is_some(); + + let mut options = app::AppOutput::default(); if is_web { ui.label("Egui is an immediate mode GUI written in Rust, compiled to WebAssembly, rendered with WebGL."); @@ -332,10 +334,7 @@ impl DemoApp { }); } else { ui.heading("Egui"); - if ui.button("Quit").clicked { - backend.quit(); - return; - } + options.quit |= ui.button("Quit").clicked; } ui.separator(); @@ -359,6 +358,8 @@ impl DemoApp { &mut self.show_color_test, "Show color blend test (debug backend painter)", ); + + options } fn run_mode_ui(&mut self, ui: &mut Ui) { @@ -374,12 +375,19 @@ impl DemoApp { } impl app::App for DemoApp { - fn ui(&mut self, ui: &mut Ui, backend: &mut dyn app::Backend) { + fn ui( + &mut self, + ui: &mut Ui, + info: &app::BackendInfo, + tex_allocator: Option<&mut dyn app::TextureAllocator>, + ) -> app::AppOutput { + let mut output = app::AppOutput::default(); + Window::new("Backend") .min_width(360.0) .scroll(false) .show(ui.ctx(), |ui| { - self.backend_ui(ui, backend); + output = self.backend_ui(ui, info); }); let Self { @@ -388,28 +396,31 @@ impl app::App for DemoApp { .. } = self; - if *show_color_test { - let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| { - backend.new_texture_srgba_premultiplied(size, pixels) - }; - Window::new("Color Test") - .default_size(vec2(1024.0, 1024.0)) - .scroll(true) - .open(show_color_test) - .show(ui.ctx(), |ui| { - color_test.ui(ui, &mut tex_loader); - }); + // TODO: enable color test even without `tex_allocator` + if let Some(tex_allocator) = tex_allocator { + if *show_color_test { + let mut tex_loader = |size: (usize, usize), pixels: &[Srgba]| { + tex_allocator.new_texture_srgba_premultiplied(size, pixels) + }; + Window::new("Color Test") + .default_size(vec2(1024.0, 1024.0)) + .scroll(true) + .open(show_color_test) + .show(ui.ctx(), |ui| { + color_test.ui(ui, &mut tex_loader); + }); + } } - let web_info = backend.web_info(); - let web_location_hash = web_info + let web_location_hash = info + .web_info .as_ref() .map(|info| info.web_location_hash.clone()) .unwrap_or_default(); let environment = DemoEnvironment { web_location_hash, - seconds_since_midnight: backend.seconds_since_midnight(), + seconds_since_midnight: info.seconds_since_midnight, }; self.ui(ui, &environment); @@ -418,6 +429,8 @@ impl app::App for DemoApp { // Tell the backend to repaint as soon as possible ui.ctx().request_repaint(); } + + output } #[cfg(feature = "serde_json")] diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs index 1d6b070c..b7b0a55c 100644 --- a/egui_glium/src/backend.rs +++ b/egui_glium/src/backend.rs @@ -6,48 +6,20 @@ use crate::{ }; pub use egui::{ - app::{App, Backend, Storage}, + app::{self, App, Storage}, Srgba, }; const EGUI_MEMORY_KEY: &str = "egui"; const WINDOW_KEY: &str = "window"; -pub struct GliumBackend { - previous_frame_time: Option, - quit: bool, - painter: Painter, -} - -impl GliumBackend { - pub fn new(painter: Painter) -> Self { - Self { - previous_frame_time: None, - quit: false, - painter, - } - } -} - -impl Backend for GliumBackend { - fn cpu_usage(&self) -> Option { - self.previous_frame_time - } - - fn seconds_since_midnight(&self) -> Option { - Some(seconds_since_midnight()) - } - - fn quit(&mut self) { - self.quit = true; - } - +impl egui::app::TextureAllocator for Painter { fn new_texture_srgba_premultiplied( &mut self, size: (usize, usize), pixels: &[Srgba], ) -> egui::TextureId { - self.painter.new_user_texture(size, pixels) + self.new_user_texture(size, pixels) } } @@ -81,9 +53,9 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) - let mut raw_input = make_raw_input(&display); - // used to keep track of time for animations let start_time = Instant::now(); - let mut runner = GliumBackend::new(Painter::new(&display)); + let mut previous_frame_time = None; + let mut painter = Painter::new(&display); let mut clipboard = init_clipboard(); event_loop.run(move |event, _, control_flow| { @@ -91,27 +63,34 @@ pub fn run(title: &str, mut storage: FileStorage, mut app: impl App + 'static) - let egui_start = Instant::now(); raw_input.time = start_time.elapsed().as_nanos() as f64 * 1e-9; - let mut ui = ctx.begin_frame(raw_input.take()); - app.ui(&mut ui, &mut runner); - let (output, paint_jobs) = ctx.end_frame(); - - let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32; - runner.previous_frame_time = Some(frame_time); - - runner - .painter - .paint_jobs(&display, paint_jobs, &ctx.texture()); - - *control_flow = if runner.quit { - glutin::event_loop::ControlFlow::Exit - } else if output.needs_repaint { - display.gl_window().window().request_redraw(); - glutin::event_loop::ControlFlow::Poll - } else { - glutin::event_loop::ControlFlow::Wait + let backend_info = egui::app::BackendInfo { + web_info: None, + cpu_usage: previous_frame_time, + seconds_since_midnight: Some(seconds_since_midnight()), }; - handle_output(output, &display, clipboard.as_mut()); + let mut ui = ctx.begin_frame(raw_input.take()); + let app_output = app.ui(&mut ui, &backend_info, Some(&mut painter)); + let (egui_output, paint_jobs) = ctx.end_frame(); + + let frame_time = (Instant::now() - egui_start).as_secs_f64() as f32; + previous_frame_time = Some(frame_time); + painter.paint_jobs(&display, paint_jobs, &ctx.texture()); + + { + let egui::app::AppOutput { quit } = app_output; + + *control_flow = if quit { + glutin::event_loop::ControlFlow::Exit + } else if egui_output.needs_repaint { + display.gl_window().window().request_redraw(); + glutin::event_loop::ControlFlow::Poll + } else { + glutin::event_loop::ControlFlow::Wait + }; + } + + handle_output(egui_output, &display, clipboard.as_mut()); }; match event { diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 8ccdf78d..a0ae9db4 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -1,7 +1,7 @@ use crate::*; pub use egui::{ - app::{App, Backend, WebInfo}, + app::{App, WebInfo}, Srgba, }; @@ -80,27 +80,13 @@ impl WebBackend { } } -impl Backend for WebBackend { - fn web_info(&self) -> Option { - Some(WebInfo { - web_location_hash: location_hash().unwrap_or_default(), - }) - } - - fn cpu_usage(&self) -> Option { - self.previous_frame_time - } - - fn seconds_since_midnight(&self) -> Option { - Some(seconds_since_midnight()) - } - +impl egui::app::TextureAllocator for webgl::Painter { fn new_texture_srgba_premultiplied( &mut self, size: (usize, usize), pixels: &[Srgba], ) -> egui::TextureId { - self.painter.new_user_texture(size, pixels) + self.new_user_texture(size, pixels) } } @@ -160,11 +146,26 @@ impl AppRunner { let raw_input = self.web_input.new_frame(); + let backend_info = egui::app::BackendInfo { + web_info: Some(WebInfo { + web_location_hash: location_hash().unwrap_or_default(), + }), + cpu_usage: self.web_backend.previous_frame_time, + seconds_since_midnight: Some(seconds_since_midnight()), + }; + 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)) + let app_output = self + .app + .ui(&mut ui, &backend_info, Some(&mut self.web_backend.painter)); + let (egui_output, paint_jobs) = self.web_backend.end_frame()?; + handle_output(&egui_output); + + { + let egui::app::AppOutput { quit: _ } = app_output; + } + + Ok((egui_output, paint_jobs)) } pub fn paint(&mut self, paint_jobs: egui::PaintJobs) -> Result<(), JsValue> { @@ -174,7 +175,7 @@ impl AppRunner { /// Install event listeners to register different input events /// and starts running the given `AppRunner`. -pub fn run(app_runner: AppRunner) -> Result { +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)?; diff --git a/example_glium/src/main.rs b/example_glium/src/main.rs index e9eee1b2..4dabbc0a 100644 --- a/example_glium/src/main.rs +++ b/example_glium/src/main.rs @@ -16,7 +16,12 @@ struct 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 dyn egui::app::Backend) { + fn ui( + &mut self, + ui: &mut egui::Ui, + _info: &egui::app::BackendInfo, + _tex_allocator: Option<&mut dyn egui::app::TextureAllocator>, + ) -> egui::app::AppOutput { let MyApp { my_string, value } = self; // Example used in `README.md`. @@ -28,6 +33,8 @@ impl egui::app::App for MyApp { ui.text_edit(my_string); ui.add(Slider::f32(value, 0.0..=1.0).text("float")); }); + + Default::default() } fn on_exit(&mut self, storage: &mut dyn egui::app::Storage) {