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.
This commit is contained in:
Emil Ernerfeldt 2021-06-07 20:53:33 +02:00
parent 31769d400f
commit 44b573f6a6
7 changed files with 105 additions and 53 deletions

View file

@ -3,6 +3,8 @@ All notable changes to the `eframe` crate.
## Unreleased ## 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 ## 0.12.0 - 2021-05-10

View file

@ -15,8 +15,15 @@ impl epi::App for DemoApp {
} }
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
fn load(&mut self, storage: &dyn epi::Storage) { fn setup(
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() &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")] #[cfg(feature = "persistence")]

View file

@ -40,9 +40,16 @@ impl epi::App for WrapApp {
"egui demo apps" "egui demo apps"
} }
#[cfg(feature = "persistence")] fn setup(
fn load(&mut self, storage: &dyn epi::Storage) { &mut self,
*self = epi::get_value(storage, epi::APP_KEY).unwrap_or_default() 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")] #[cfg(feature = "persistence")]

View file

@ -35,16 +35,16 @@ All notable changes to the `egui_glium` integration will be noted in this file.
## 0.7.0 - 2021-01-04 ## 0.7.0 - 2021-01-04
### Changed ### Changed 🔧
* `http` `persistence` and `time` are now optional (and opt-in) features. * `http` `persistence` and `time` are now optional (and opt-in) features.
## 0.6.0 - 2020-12-26 ## 0.6.0 - 2020-12-26
### Added ### Added
* `egui_glium` will auto-save your app state every 30 seconds. * `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()`. * `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` 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<dyn App>` (you need to add `Box::new(app)` to your code). * `egui_glium::run`: the parameter `app` now has signature `Box<dyn App>` (you need to add `Box::new(app)` to your code).
* Window title is now passed via the `trait` function `egui::App::name()`. * 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 ## 0.5.0 - 2020-12-13
### Changed ### Changed 🔧
* FileStorage::from_path now takes `Into<Path>` instead of `String` * FileStorage::from_path now takes `Into<Path>` instead of `String`

View file

@ -167,9 +167,8 @@ fn load_icon(icon_data: epi::IconData) -> Option<glutin::window::Icon> {
pub fn run(mut app: Box<dyn epi::App>, nativve_options: epi::NativeOptions) -> ! { pub fn run(mut app: Box<dyn epi::App>, nativve_options: epi::NativeOptions) -> ! {
let mut storage = create_storage(app.name()); let mut storage = create_storage(app.name());
if let Some(storage) = &mut storage { #[cfg(feature = "http")]
app.load(storage.as_ref()); let http = std::sync::Arc::new(crate::http::GliumHttp {});
}
let window_settings = deserialize_window_settings(&storage); let window_settings = deserialize_window_settings(&storage);
let event_loop = glutin::event_loop::EventLoop::with_user_event(); let event_loop = glutin::event_loop::EventLoop::with_user_event();
@ -183,7 +182,20 @@ pub fn run(mut app: Box<dyn epi::App>, nativve_options: epi::NativeOptions) -> !
let mut egui = EguiGlium::new(&display); let mut egui = EguiGlium::new(&display);
*egui.ctx().memory() = deserialize_memory(&storage).unwrap_or_default(); *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; let mut previous_frame_time = None;
@ -192,9 +204,6 @@ pub fn run(mut app: Box<dyn epi::App>, nativve_options: epi::NativeOptions) -> !
#[cfg(feature = "persistence")] #[cfg(feature = "persistence")]
let mut last_auto_save = Instant::now(); let mut last_auto_save = Instant::now();
#[cfg(feature = "http")]
let http = std::sync::Arc::new(crate::http::GliumHttp {});
if app.warm_up_enabled() { if app.warm_up_enabled() {
let saved_memory = egui.ctx().memory().clone(); let saved_memory = egui.ctx().memory().clone();
egui.ctx().memory().set_everything_is_visible(true); egui.ctx().memory().set_everything_is_visible(true);

View file

@ -5,7 +5,7 @@ pub use egui::{pos2, Color32};
// ---------------------------------------------------------------------------- // ----------------------------------------------------------------------------
pub struct WebBackend { pub struct WebBackend {
ctx: egui::CtxRef, egui_ctx: egui::CtxRef,
painter: Box<dyn Painter>, painter: Box<dyn Painter>,
previous_frame_time: Option<f32>, previous_frame_time: Option<f32>,
frame_start: Option<f64>, frame_start: Option<f64>,
@ -25,7 +25,7 @@ impl WebBackend {
}; };
Ok(Self { Ok(Self {
ctx, egui_ctx: ctx,
painter, painter,
previous_frame_time: None, previous_frame_time: None,
frame_start: None, frame_start: None,
@ -39,7 +39,7 @@ impl WebBackend {
pub fn begin_frame(&mut self, raw_input: egui::RawInput) { pub fn begin_frame(&mut self, raw_input: egui::RawInput) {
self.frame_start = Some(now_sec()); 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<egui::ClippedMesh>), JsValue> { pub fn end_frame(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
@ -48,8 +48,8 @@ impl WebBackend {
.take() .take()
.expect("unmatched calls to begin_frame/end_frame"); .expect("unmatched calls to begin_frame/end_frame");
let (output, shapes) = self.ctx.end_frame(); let (output, shapes) = self.egui_ctx.end_frame();
let clipped_meshes = self.ctx.tessellate(shapes); let clipped_meshes = self.egui_ctx.tessellate(shapes);
let now = now_sec(); let now = now_sec();
self.previous_frame_time = Some((now - frame_start) as f32); self.previous_frame_time = Some((now - frame_start) as f32);
@ -62,10 +62,10 @@ impl WebBackend {
clear_color: egui::Rgba, clear_color: egui::Rgba,
clipped_meshes: Vec<egui::ClippedMesh>, clipped_meshes: Vec<egui::ClippedMesh>,
) -> Result<(), JsValue> { ) -> 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.clear(clear_color);
self.painter 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 { pub fn painter_debug_info(&self) -> String {
@ -145,12 +145,11 @@ pub struct AppRunner {
} }
impl AppRunner { impl AppRunner {
pub fn new(web_backend: WebBackend, mut app: Box<dyn epi::App>) -> Result<Self, JsValue> { pub fn new(web_backend: WebBackend, app: Box<dyn epi::App>) -> Result<Self, JsValue> {
load_memory(&web_backend.ctx); load_memory(&web_backend.egui_ctx);
let storage = LocalStorage::default(); let storage = LocalStorage::default();
app.load(&storage); let prefer_dark_mode = crate::prefer_dark_mode();
app.setup(&web_backend.ctx); let mut runner = Self {
Ok(Self {
web_backend, web_backend,
input: Default::default(), input: Default::default(),
app, app,
@ -161,11 +160,31 @@ impl AppRunner {
#[cfg(feature = "http")] #[cfg(feature = "http")]
http: Arc::new(http::WebHttp {}), http: Arc::new(http::WebHttp {}),
last_text_cursor_pos: None, 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 { pub fn egui_ctx(&self) -> &egui::CtxRef {
&self.web_backend.ctx &self.web_backend.egui_ctx
} }
pub fn auto_save(&mut self) { pub fn auto_save(&mut self) {
@ -173,7 +192,7 @@ impl AppRunner {
let time_since_last_save = now - self.last_save_time; let time_since_last_save = now - self.last_save_time;
if time_since_last_save > self.app.auto_save_interval().as_secs_f64() { 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.app.save(&mut self.storage);
self.last_save_time = now; self.last_save_time = now;
} }
@ -185,37 +204,39 @@ impl AppRunner {
pub fn warm_up(&mut self) -> Result<(), JsValue> { pub fn warm_up(&mut self) -> Result<(), JsValue> {
if self.app.warm_up_enabled() { 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 self.web_backend
.ctx .egui_ctx
.memory() .memory()
.set_everything_is_visible(true); .set_everything_is_visible(true);
self.logic()?; self.logic()?;
*self.web_backend.ctx.memory() = saved_memory; // We don't want to remember that windows were huge. *self.web_backend.egui_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.clear_animations();
} }
Ok(()) 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<egui::ClippedMesh>), JsValue> { pub fn logic(&mut self) -> Result<(egui::Output, Vec<egui::ClippedMesh>), JsValue> {
resize_canvas_to_screen_size(self.web_backend.canvas_id(), self.app.max_size_points()); 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 canvas_size = canvas_size_in_points(self.web_backend.canvas_id());
let raw_input = self.input.new_frame(canvas_size); 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); self.web_backend.begin_frame(raw_input);
let mut app_output = epi::backend::AppOutput::default(); let mut app_output = epi::backend::AppOutput::default();
let mut frame = epi::backend::FrameBuilder { let mut frame = epi::backend::FrameBuilder {
info, info: self.integration_info(),
tex_allocator: self.web_backend.painter.as_tex_allocator(), tex_allocator: self.web_backend.painter.as_tex_allocator(),
#[cfg(feature = "http")] #[cfg(feature = "http")]
http: self.http.clone(), http: self.http.clone(),
@ -224,10 +245,10 @@ impl AppRunner {
} }
.build(); .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()?; 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()); self.screen_reader.speak(&egui_output.events_description());
} }
handle_output(&egui_output, self); handle_output(&egui_output, self);

View file

@ -95,9 +95,18 @@ pub trait App {
fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>); fn update(&mut self, ctx: &egui::CtxRef, frame: &mut Frame<'_>);
/// Called once before the first frame. /// Called once before the first frame.
/// Allows you to do setup code and to call `ctx.set_fonts()`. ///
/// Optional. /// Allows you to do setup code, e.g to call `[Context::set_fonts]`,
fn setup(&mut self, _ctx: &egui::CtxRef) {} /// `[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 /// If `true` a warm-up call to [`Self::update`] will be issued where
/// `ctx.memory().everything_is_visible()` will be set to `true`. /// `ctx.memory().everything_is_visible()` will be set to `true`.
@ -107,9 +116,6 @@ pub trait App {
false 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. /// Called on shutdown, and perhaps at regular intervals. Allows you to save state.
/// ///
/// On web the states is stored to "Local Storage". /// On web the states is stored to "Local Storage".