From 44b573f6a6fc552b4315d48eb672b01ed96caca4 Mon Sep 17 00:00:00 2001 From: Emil Ernerfeldt Date: Mon, 7 Jun 2021 20:53:33 +0200 Subject: [PATCH] epi: merge App::load into App::setup, and provide Frame argument This gives users more control over the order of load/setup. It also allows users to load textures in setup. --- eframe/CHANGELOG.md | 2 + egui_demo_lib/src/apps/demo/app.rs | 11 +++- egui_demo_lib/src/wrap_app.rs | 13 +++-- egui_glium/CHANGELOG.md | 8 +-- egui_glium/src/backend.rs | 23 ++++++--- egui_web/src/backend.rs | 83 +++++++++++++++++++----------- epi/src/lib.rs | 18 ++++--- 7 files changed, 105 insertions(+), 53 deletions(-) diff --git a/eframe/CHANGELOG.md b/eframe/CHANGELOG.md index 315ac0db..68f9bf85 100644 --- a/eframe/CHANGELOG.md +++ b/eframe/CHANGELOG.md @@ -3,6 +3,8 @@ All notable changes to the `eframe` crate. ## Unreleased +* `App::setup` now takes a `Frame` and `Storage` by argument. +* `App::load` has been removed. Implement `App::setup` instead. ## 0.12.0 - 2021-05-10 diff --git a/egui_demo_lib/src/apps/demo/app.rs b/egui_demo_lib/src/apps/demo/app.rs index e17850bd..51d4f30e 100644 --- a/egui_demo_lib/src/apps/demo/app.rs +++ b/egui_demo_lib/src/apps/demo/app.rs @@ -15,8 +15,15 @@ impl epi::App for DemoApp { } #[cfg(feature = "persistence")] - fn load(&mut self, storage: &dyn epi::Storage) { - *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() + fn setup( + &mut self, + _ctx: &egui::CtxRef, + _frame: &mut epi::Frame<'_>, + storage: Option<&dyn epi::Storage>, + ) { + if let Some(storage) = storage { + *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() + } } #[cfg(feature = "persistence")] diff --git a/egui_demo_lib/src/wrap_app.rs b/egui_demo_lib/src/wrap_app.rs index da2b320d..490404e6 100644 --- a/egui_demo_lib/src/wrap_app.rs +++ b/egui_demo_lib/src/wrap_app.rs @@ -40,9 +40,16 @@ impl epi::App for WrapApp { "egui demo apps" } - #[cfg(feature = "persistence")] - fn load(&mut self, storage: &dyn epi::Storage) { - *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() + fn setup( + &mut self, + ctx: &egui::CtxRef, + frame: &mut epi::Frame<'_>, + storage: Option<&dyn epi::Storage>, + ) { + #[cfg(feature = "persistence")] + if let Some(storage) = storage { + *self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() + } } #[cfg(feature = "persistence")] diff --git a/egui_glium/CHANGELOG.md b/egui_glium/CHANGELOG.md index cbe94c48..306294cb 100644 --- a/egui_glium/CHANGELOG.md +++ b/egui_glium/CHANGELOG.md @@ -35,16 +35,16 @@ All notable changes to the `egui_glium` integration will be noted in this file. ## 0.7.0 - 2021-01-04 -### Changed +### Changed 🔧 * `http` `persistence` and `time` are now optional (and opt-in) features. ## 0.6.0 - 2020-12-26 -### Added +### Added ⭐ * `egui_glium` will auto-save your app state every 30 seconds. * `egui_glium` can now set windows as fixed size (e.g. the user can't resize the window). See `egui::App::is_resizable()`. -### Changed +### Changed 🔧 * `egui_glium` will now save you app state to [a better directory](https://docs.rs/directories-next/2.0.0/directories_next/struct.ProjectDirs.html#method.data_dir). * `egui_glium::run`: the parameter `app` now has signature `Box` (you need to add `Box::new(app)` to your code). * Window title is now passed via the `trait` function `egui::App::name()`. @@ -55,7 +55,7 @@ All notable changes to the `egui_glium` integration will be noted in this file. ## 0.5.0 - 2020-12-13 -### Changed +### Changed 🔧 * FileStorage::from_path now takes `Into` instead of `String` diff --git a/egui_glium/src/backend.rs b/egui_glium/src/backend.rs index 83b284e2..e11b8f24 100644 --- a/egui_glium/src/backend.rs +++ b/egui_glium/src/backend.rs @@ -167,9 +167,8 @@ fn load_icon(icon_data: epi::IconData) -> Option { pub fn run(mut app: Box, nativve_options: epi::NativeOptions) -> ! { let mut storage = create_storage(app.name()); - if let Some(storage) = &mut storage { - app.load(storage.as_ref()); - } + #[cfg(feature = "http")] + let http = std::sync::Arc::new(crate::http::GliumHttp {}); let window_settings = deserialize_window_settings(&storage); let event_loop = glutin::event_loop::EventLoop::with_user_event(); @@ -183,7 +182,20 @@ pub fn run(mut app: Box, nativve_options: epi::NativeOptions) -> ! let mut egui = EguiGlium::new(&display); *egui.ctx().memory() = deserialize_memory(&storage).unwrap_or_default(); - app.setup(&egui.ctx()); + { + let (ctx, painter) = egui.ctx_and_painter_mut(); + let mut app_output = epi::backend::AppOutput::default(); + let mut frame = epi::backend::FrameBuilder { + info: integration_info(&display, None), + tex_allocator: painter, + #[cfg(feature = "http")] + http: http.clone(), + output: &mut app_output, + repaint_signal: repaint_signal.clone(), + } + .build(); + app.setup(&ctx, &mut frame, storage.as_deref()); + } let mut previous_frame_time = None; @@ -192,9 +204,6 @@ pub fn run(mut app: Box, nativve_options: epi::NativeOptions) -> ! #[cfg(feature = "persistence")] let mut last_auto_save = Instant::now(); - #[cfg(feature = "http")] - let http = std::sync::Arc::new(crate::http::GliumHttp {}); - if app.warm_up_enabled() { let saved_memory = egui.ctx().memory().clone(); egui.ctx().memory().set_everything_is_visible(true); diff --git a/egui_web/src/backend.rs b/egui_web/src/backend.rs index 31eb957b..3839f22f 100644 --- a/egui_web/src/backend.rs +++ b/egui_web/src/backend.rs @@ -5,7 +5,7 @@ pub use egui::{pos2, Color32}; // ---------------------------------------------------------------------------- pub struct WebBackend { - ctx: egui::CtxRef, + egui_ctx: egui::CtxRef, painter: Box, previous_frame_time: Option, frame_start: Option, @@ -25,7 +25,7 @@ impl WebBackend { }; Ok(Self { - ctx, + egui_ctx: ctx, painter, previous_frame_time: None, frame_start: None, @@ -39,7 +39,7 @@ impl WebBackend { pub fn begin_frame(&mut self, raw_input: egui::RawInput) { self.frame_start = Some(now_sec()); - self.ctx.begin_frame(raw_input) + self.egui_ctx.begin_frame(raw_input) } pub fn end_frame(&mut self) -> Result<(egui::Output, Vec), JsValue> { @@ -48,8 +48,8 @@ impl WebBackend { .take() .expect("unmatched calls to begin_frame/end_frame"); - let (output, shapes) = self.ctx.end_frame(); - let clipped_meshes = self.ctx.tessellate(shapes); + let (output, shapes) = self.egui_ctx.end_frame(); + let clipped_meshes = self.egui_ctx.tessellate(shapes); let now = now_sec(); self.previous_frame_time = Some((now - frame_start) as f32); @@ -62,10 +62,10 @@ impl WebBackend { clear_color: egui::Rgba, clipped_meshes: Vec, ) -> Result<(), JsValue> { - self.painter.upload_egui_texture(&self.ctx.texture()); + self.painter.upload_egui_texture(&self.egui_ctx.texture()); self.painter.clear(clear_color); self.painter - .paint_meshes(clipped_meshes, self.ctx.pixels_per_point()) + .paint_meshes(clipped_meshes, self.egui_ctx.pixels_per_point()) } pub fn painter_debug_info(&self) -> String { @@ -145,12 +145,11 @@ pub struct AppRunner { } impl AppRunner { - pub fn new(web_backend: WebBackend, mut app: Box) -> Result { - load_memory(&web_backend.ctx); + pub fn new(web_backend: WebBackend, app: Box) -> Result { + load_memory(&web_backend.egui_ctx); let storage = LocalStorage::default(); - app.load(&storage); - app.setup(&web_backend.ctx); - Ok(Self { + let prefer_dark_mode = crate::prefer_dark_mode(); + let mut runner = Self { web_backend, input: Default::default(), app, @@ -161,11 +160,31 @@ impl AppRunner { #[cfg(feature = "http")] http: Arc::new(http::WebHttp {}), last_text_cursor_pos: None, - }) + }; + + { + let mut app_output = epi::backend::AppOutput::default(); + let mut frame = epi::backend::FrameBuilder { + info: runner.integration_info(), + tex_allocator: runner.web_backend.painter.as_tex_allocator(), + #[cfg(feature = "http")] + http: runner.http.clone(), + output: &mut app_output, + repaint_signal: runner.needs_repaint.clone(), + } + .build(); + runner.app.setup( + &runner.web_backend.egui_ctx, + &mut frame, + Some(&runner.storage), + ); + } + + Ok(runner) } pub fn egui_ctx(&self) -> &egui::CtxRef { - &self.web_backend.ctx + &self.web_backend.egui_ctx } pub fn auto_save(&mut self) { @@ -173,7 +192,7 @@ impl AppRunner { let time_since_last_save = now - self.last_save_time; if time_since_last_save > self.app.auto_save_interval().as_secs_f64() { - save_memory(&self.web_backend.ctx); + save_memory(&self.web_backend.egui_ctx); self.app.save(&mut self.storage); self.last_save_time = now; } @@ -185,37 +204,39 @@ impl AppRunner { pub fn warm_up(&mut self) -> Result<(), JsValue> { if self.app.warm_up_enabled() { - let saved_memory = self.web_backend.ctx.memory().clone(); + let saved_memory = self.web_backend.egui_ctx.memory().clone(); self.web_backend - .ctx + .egui_ctx .memory() .set_everything_is_visible(true); self.logic()?; - *self.web_backend.ctx.memory() = saved_memory; // We don't want to remember that windows were huge. - self.web_backend.ctx.clear_animations(); + *self.web_backend.egui_ctx.memory() = saved_memory; // We don't want to remember that windows were huge. + self.web_backend.egui_ctx.clear_animations(); } Ok(()) } + fn integration_info(&self) -> epi::IntegrationInfo { + epi::IntegrationInfo { + web_info: Some(epi::WebInfo { + web_location_hash: location_hash().unwrap_or_default(), + }), + cpu_usage: self.web_backend.previous_frame_time, + seconds_since_midnight: Some(seconds_since_midnight()), + native_pixels_per_point: Some(native_pixels_per_point()), + } + } + pub fn logic(&mut self) -> Result<(egui::Output, Vec), JsValue> { resize_canvas_to_screen_size(self.web_backend.canvas_id(), self.app.max_size_points()); let canvas_size = canvas_size_in_points(self.web_backend.canvas_id()); let raw_input = self.input.new_frame(canvas_size); - let info = epi::IntegrationInfo { - web_info: Some(epi::WebInfo { - web_location_hash: location_hash().unwrap_or_default(), - }), - cpu_usage: self.web_backend.previous_frame_time, - seconds_since_midnight: Some(seconds_since_midnight()), - native_pixels_per_point: Some(native_pixels_per_point()), - }; - self.web_backend.begin_frame(raw_input); let mut app_output = epi::backend::AppOutput::default(); let mut frame = epi::backend::FrameBuilder { - info, + info: self.integration_info(), tex_allocator: self.web_backend.painter.as_tex_allocator(), #[cfg(feature = "http")] http: self.http.clone(), @@ -224,10 +245,10 @@ impl AppRunner { } .build(); - self.app.update(&self.web_backend.ctx, &mut frame); + self.app.update(&self.web_backend.egui_ctx, &mut frame); let (egui_output, clipped_meshes) = self.web_backend.end_frame()?; - if self.web_backend.ctx.memory().options.screen_reader { + if self.web_backend.egui_ctx.memory().options.screen_reader { self.screen_reader.speak(&egui_output.events_description()); } handle_output(&egui_output, self); diff --git a/epi/src/lib.rs b/epi/src/lib.rs index 560324bc..0534f1d0 100644 --- a/epi/src/lib.rs +++ b/epi/src/lib.rs @@ -95,9 +95,18 @@ pub trait App { fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>); /// Called once before the first frame. - /// Allows you to do setup code and to call `ctx.set_fonts()`. - /// Optional. - fn setup(&mut self, _ctx: &egui::CtxRef) {} + /// + /// Allows you to do setup code, e.g to call `[Context::set_fonts]`, + /// `[Context::set_visuals]` etc. + /// + /// Also allows you to restore state, if there is a storage. + fn setup( + &mut self, + _ctx: &egui::CtxRef, + _frame: &mut Frame<'_>, + _storage: Option<&dyn Storage>, + ) { + } /// If `true` a warm-up call to [`Self::update`] will be issued where /// `ctx.memory().everything_is_visible()` will be set to `true`. @@ -107,9 +116,6 @@ pub trait App { false } - /// Called once on start. Allows you to restore state. - fn load(&mut self, _storage: &dyn Storage) {} - /// Called on shutdown, and perhaps at regular intervals. Allows you to save state. /// /// On web the states is stored to "Local Storage".