Skip to content

Commit

Permalink
Storage and frame refactor (#1418)
Browse files Browse the repository at this point in the history
The purpose of this is to expose `frame.storage()` and `frame.storage_mut()` so users can save/load app state from the `App::update` function, without having to add another parameter to that function.

Changes:
* Added `Frame::storage()` and `Frame::storage_mut()`
* `App::update` now takes a `&mut Frame` rather than just `&Frame`
* `Frame` is no longer `Clone` or `Sync` (doesn't have to be since #1366)
  • Loading branch information
emilk authored Mar 25, 2022
1 parent bc0fdef commit b7ebe16
Show file tree
Hide file tree
Showing 27 changed files with 154 additions and 196 deletions.
12 changes: 10 additions & 2 deletions eframe/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,16 @@ NOTE: [`egui_web`](../egui_web/CHANGELOG.md), [`egui-winit`](../egui-winit/CHANG
* Change default for `NativeOptions::drag_and_drop_support` to `true` ([#1329](https://github.com/emilk/egui/pull/1329)).
* Remove the `egui_glium` feature. `eframe` will now always use `egui_glow` as the native backend ([#1357](https://github.com/emilk/egui/pull/1357)).
* Removed `Frame::request_repaint` - just call `egui::Context::request_repaint` for the same effect ([#1366](https://github.com/emilk/egui/pull/1366)).
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
* Add new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
* Use full browser width by default ([#1378](https://github.com/emilk/egui/pull/1378)).
* Added new `NativeOptions`: `vsync`, `multisampling`, `depth_buffer`, `stencil_buffer`.
* Changed app creation/setup ([#1363](https://github.com/emilk/egui/pull/1363)):
* Removed `App::setup` and `App::name`.
* Provide `CreationContext` when creating app with egui context, storage, integration info and glow context.
* Change interface of `run_native` and `start_web`.
* Added `Frame::storage()` and `Frame::storage_mut()` ([#1418](https://github.com/emilk/egui/pull/1418)).
* You can now load/save state in `App::update`
* Changed `App::update` to take `&mut Frame` instead of `&Frame`.
* `Frame` is no longer `Clone` or `Sync`.


## 0.17.0 - 2022-02-22
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/confirm_exit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ impl eframe::App for MyApp {
self.can_exit
}

fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("Try to close the window");
});
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/custom_3d_glow.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ impl MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.horizontal(|ui| {
ui.spacing_mut().item_spacing.x = 0.0;
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/custom_3d_three-d.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ impl MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
egui::widgets::global_dark_light_mode_buttons(ui);

Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/custom_font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ impl MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("egui using custom fonts");
ui.text_edit_multiline(&mut self.text);
Expand Down
4 changes: 2 additions & 2 deletions eframe/examples/custom_window_frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl eframe::App for MyApp {
egui::Rgba::TRANSPARENT // Make sure we don't paint anything behind the rounded corners
}

fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
custon_window_frame(ctx, frame, "egui with custom frame", |ui| {
ui.label("This is just the contents of the window");
ui.horizontal(|ui| {
Expand All @@ -41,7 +41,7 @@ impl eframe::App for MyApp {

fn custon_window_frame(
ctx: &egui::Context,
frame: &eframe::Frame,
frame: &mut eframe::Frame,
title: &str,
add_contents: impl FnOnce(&mut egui::Ui),
) {
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/download_image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ struct MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
let promise = self.promise.get_or_insert_with(|| {
// Begin download.
// We download the image using `ehttp`, a library that works both in WASM and on native.
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/file_dialog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ struct MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.label("Drag-and-drop files onto the window!");

Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/hello_world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ impl Default for MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("My egui Application");
ui.horizontal(|ui| {
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ impl Default for MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("This is an image:");
self.image.show(ui);
Expand Down
2 changes: 1 addition & 1 deletion eframe/examples/svg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ impl Default for MyApp {
}

impl eframe::App for MyApp {
fn update(&mut self, ctx: &egui::Context, _frame: &eframe::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
ui.heading("SVG example");
ui.label("The SVG is rasterized and displayed as a texture.");
Expand Down
4 changes: 2 additions & 2 deletions eframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
//! }
//!
//! impl eframe::App for MyEguiApp {
//! fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
//! fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
//! egui::CentralPanel::default().show(ctx, |ui| {
//! ui.heading("Hello World!");
//! });
Expand Down Expand Up @@ -126,7 +126,7 @@ pub fn start_web(canvas_id: &str, app_creator: AppCreator) -> Result<(), wasm_bi
/// }
///
/// impl eframe::App for MyEguiApp {
/// fn update(&mut self, ctx: &egui::Context, frame: &eframe::Frame) {
/// fn update(&mut self, ctx: &egui::Context, frame: &mut eframe::Frame) {
/// egui::CentralPanel::default().show(ctx, |ui| {
/// ui.heading("Hello World!");
/// });
Expand Down
160 changes: 62 additions & 98 deletions egui-winit/src/epi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,104 +124,20 @@ pub fn handle_app_output(
// ----------------------------------------------------------------------------

/// For loading/saving app state and/or egui memory to disk.
pub struct Persistence {
storage: Option<Box<dyn epi::Storage>>,
last_auto_save: instant::Instant,
}

#[allow(clippy::unused_self)]
impl Persistence {
#[cfg(feature = "persistence")]
const EGUI_MEMORY_KEY: &'static str = "egui";
#[cfg(feature = "persistence")]
const WINDOW_KEY: &'static str = "window";

pub fn from_app_name(app_name: &str) -> Self {
fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
#[cfg(feature = "persistence")]
if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) {
return Some(Box::new(storage));
}
None
}

Self {
storage: create_storage(app_name),
last_auto_save: instant::Instant::now(),
}
}

pub fn storage(&self) -> Option<&dyn epi::Storage> {
self.storage.as_deref()
}

#[cfg(feature = "persistence")]
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
epi::get_value(&**self.storage.as_ref()?, Self::WINDOW_KEY)
}

#[cfg(not(feature = "persistence"))]
pub fn load_window_settings(&self) -> Option<crate::WindowSettings> {
None
}

pub fn create_storage(_app_name: &str) -> Option<Box<dyn epi::Storage>> {
#[cfg(feature = "persistence")]
pub fn load_memory(&self) -> Option<egui::Memory> {
epi::get_value(&**self.storage.as_ref()?, Self::EGUI_MEMORY_KEY)
}

#[cfg(not(feature = "persistence"))]
pub fn load_memory(&self) -> Option<egui::Memory> {
None
}

pub fn save(
&mut self,
_app: &mut dyn epi::App,
_egui_ctx: &egui::Context,
_window: &winit::window::Window,
) {
#[cfg(feature = "persistence")]
if let Some(storage) = &mut self.storage {
if _app.persist_native_window() {
epi::set_value(
storage.as_mut(),
Self::WINDOW_KEY,
&crate::WindowSettings::from_display(_window),
);
}
if _app.persist_egui_memory() {
epi::set_value(
storage.as_mut(),
Self::EGUI_MEMORY_KEY,
&*_egui_ctx.memory(),
);
}
_app.save(storage.as_mut());
storage.flush();
}
}

pub fn maybe_autosave(
&mut self,
app: &mut dyn epi::App,
egui_ctx: &egui::Context,
window: &winit::window::Window,
) {
let now = instant::Instant::now();
if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, egui_ctx, window);
self.last_auto_save = now;
}
if let Some(storage) = epi::file_storage::FileStorage::from_app_name(_app_name) {
return Some(Box::new(storage));
}
None
}

// ----------------------------------------------------------------------------

/// Everything needed to make a winit-based integration for [`epi`].
pub struct EpiIntegration {
pub frame: epi::Frame,
pub persistence: crate::epi::Persistence,
last_auto_save: instant::Instant,
pub egui_ctx: egui::Context,
pending_full_output: egui::FullOutput,
egui_winit: crate::State,
Expand All @@ -235,15 +151,15 @@ impl EpiIntegration {
integration_name: &'static str,
max_texture_side: usize,
window: &winit::window::Window,
persistence: crate::epi::Persistence,
storage: Option<Box<dyn epi::Storage>>,
) -> Self {
let egui_ctx = egui::Context::default();

*egui_ctx.memory() = persistence.load_memory().unwrap_or_default();
*egui_ctx.memory() = load_egui_memory(storage.as_deref()).unwrap_or_default();

let prefer_dark_mode = prefer_dark_mode();

let frame = epi::Frame::new(epi::backend::FrameData {
let frame = epi::Frame {
info: epi::IntegrationInfo {
name: integration_name,
web_info: None,
Expand All @@ -252,7 +168,8 @@ impl EpiIntegration {
native_pixels_per_point: Some(crate::native_pixels_per_point(window)),
},
output: Default::default(),
});
storage,
};

if prefer_dark_mode == Some(true) {
egui_ctx.set_visuals(egui::Visuals::dark());
Expand All @@ -262,7 +179,7 @@ impl EpiIntegration {

Self {
frame,
persistence,
last_auto_save: instant::Instant::now(),
egui_ctx,
egui_winit: crate::State::new(max_texture_side, window),
pending_full_output: Default::default(),
Expand Down Expand Up @@ -311,7 +228,7 @@ impl EpiIntegration {

let raw_input = self.egui_winit.take_egui_input(window);
let full_output = self.egui_ctx.run(raw_input, |egui_ctx| {
app.update(egui_ctx, &self.frame);
app.update(egui_ctx, &mut self.frame);
});
self.pending_full_output.append(full_output);
let full_output = std::mem::take(&mut self.pending_full_output);
Expand All @@ -327,7 +244,7 @@ impl EpiIntegration {
}

let frame_time = (instant::Instant::now() - frame_start).as_secs_f64() as f32;
self.frame.lock().info.cpu_usage = Some(frame_time);
self.frame.info.cpu_usage = Some(frame_time);

full_output
}
Expand All @@ -341,12 +258,59 @@ impl EpiIntegration {
.handle_platform_output(window, &self.egui_ctx, platform_output);
}

// ------------------------------------------------------------------------
// Persistance stuff:

pub fn maybe_autosave(&mut self, app: &mut dyn epi::App, window: &winit::window::Window) {
self.persistence
.maybe_autosave(&mut *app, &self.egui_ctx, window);
let now = instant::Instant::now();
if now - self.last_auto_save > app.auto_save_interval() {
self.save(app, window);
self.last_auto_save = now;
}
}

pub fn save(&mut self, _app: &mut dyn epi::App, _window: &winit::window::Window) {
#[cfg(feature = "persistence")]
if let Some(storage) = self.frame.storage_mut() {
if _app.persist_native_window() {
epi::set_value(
storage,
STORAGE_WINDOW_KEY,
&crate::WindowSettings::from_display(_window),
);
}
if _app.persist_egui_memory() {
epi::set_value(storage, STORAGE_EGUI_MEMORY_KEY, &*self.egui_ctx.memory());
}
_app.save(storage);
storage.flush();
}
}
}

#[cfg(feature = "persistence")]
const STORAGE_EGUI_MEMORY_KEY: &str = "egui";
#[cfg(feature = "persistence")]
const STORAGE_WINDOW_KEY: &str = "window";

pub fn load_window_settings(_storage: Option<&dyn epi::Storage>) -> Option<crate::WindowSettings> {
#[cfg(feature = "persistence")]
{
epi::get_value(_storage?, STORAGE_WINDOW_KEY)
}
#[cfg(not(feature = "persistence"))]
None
}

pub fn load_egui_memory(_storage: Option<&dyn epi::Storage>) -> Option<egui::Memory> {
#[cfg(feature = "persistence")]
{
epi::get_value(_storage?, STORAGE_EGUI_MEMORY_KEY)
}
#[cfg(not(feature = "persistence"))]
None
}

#[cfg(feature = "dark-light")]
fn prefer_dark_mode() -> Option<bool> {
match dark_light::detect() {
Expand Down
2 changes: 1 addition & 1 deletion egui_demo_lib/src/apps/color_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ impl Default for ColorTest {
}

impl epi::App for ColorTest {
fn update(&mut self, ctx: &egui::Context, frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, frame: &mut epi::Frame) {
egui::CentralPanel::default().show(ctx, |ui| {
if frame.is_web() {
ui.label(
Expand Down
2 changes: 1 addition & 1 deletion egui_demo_lib/src/apps/demo/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ pub struct DemoApp {
}

impl epi::App for DemoApp {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) {
self.demo_windows.ui(ctx);
}
}
2 changes: 1 addition & 1 deletion egui_demo_lib/src/apps/fractal_clock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl Default for FractalClock {
}

impl epi::App for FractalClock {
fn update(&mut self, ctx: &egui::Context, _frame: &epi::Frame) {
fn update(&mut self, ctx: &egui::Context, _frame: &mut epi::Frame) {
egui::CentralPanel::default()
.frame(Frame::dark_canvas(&ctx.style()))
.show(ctx, |ui| self.ui(ui, crate::seconds_since_midnight()));
Expand Down
Loading

0 comments on commit b7ebe16

Please sign in to comment.