1
0
mirror of https://github.com/emilk/egui.git synced 2026-06-27 23:13:13 -04:00
Files
egui/crates/eframe/src/native/winit_integration.rs
Pandicon 93d2144294 Save state on suspend on Android and iOS (#5601)
<!--
Please read the "Making a PR" section of
[`CONTRIBUTING.md`](https://github.com/emilk/egui/blob/master/CONTRIBUTING.md)
before opening a Pull Request!

* Keep your PR:s small and focused.
* The PR title is what ends up in the changelog, so make it descriptive!
* If applicable, add a screenshot or gif.
* If it is a non-trivial addition, consider adding a demo for it to
`egui_demo_lib`, or a new example.
* Do NOT open PR:s from your `master` branch, as that makes it hard for
maintainers to test and add commits to your PR.
* Remember to run `cargo fmt` and `cargo clippy`.
* Open the PR as a draft until you have self-reviewed it and run
`./scripts/check.sh`.
* When you have addressed a PR comment, mark it as resolved.

Please be patient! I will review your PR, but my time is limited!
-->

This pull request fixes a subset of #5492 by saving the application
state when the `suspended` event is received on Android. This way, even
if the user exits the app and closes it manually right after changing
some state, it will be saved since `suspended` gets fired when the app
is exited. It does not fix the `on_exit` function not being fired - this
seems to be a winit bug (the `exiting` function in the winit application
handler trait is not called on exit). Once it gets fixed, it may be
possible to remove logic introduced by this PR (however, I am not sure
how it would handle the app being killed by the system when in the
background, that would have to be tested).

I've tested the logic by:
* Leaving from the app to the home screen, then killing it from the
"recent apps" menu
 * Leaving from the app to the "recent apps" menu and killing it
 * Restarting the device while the app was running

In all of these instances, the state was saved (the last one being a
pleasant surprise). It was tested on the repository mentioned in #5492
with my forked repository as the source for eframe (I unfortunately am
not able to test it in a larger project of mine due to dependence on
"3rd party" egui libraries (like egui_notify) which do not compile along
with the master branch of eframe (different versions of egui), but I
believe it should work in the same manner in all scenarios). Tests were
conducted on a Galaxy Tab S8 running Android 14, One UI 6.1.1.

CI passed on my fork.

* [x] I have followed the instructions in the PR template
2025-01-27 08:14:49 +01:00

162 lines
5.1 KiB
Rust

use std::{sync::Arc, time::Instant};
use winit::{
event_loop::ActiveEventLoop,
window::{Window, WindowId},
};
use egui::ViewportId;
#[cfg(feature = "accesskit")]
use egui_winit::accesskit_winit;
/// Create an egui context, restoring it from storage if possible.
pub fn create_egui_context(storage: Option<&dyn crate::Storage>) -> egui::Context {
profiling::function_scope!();
pub const IS_DESKTOP: bool = cfg!(any(
target_os = "freebsd",
target_os = "linux",
target_os = "macos",
target_os = "openbsd",
target_os = "windows",
));
let egui_ctx = egui::Context::default();
egui_ctx.set_embed_viewports(!IS_DESKTOP);
egui_ctx.options_mut(|o| {
// eframe supports multi-pass (Context::request_discard).
o.max_passes = 2.try_into().unwrap();
});
let memory = crate::native::epi_integration::load_egui_memory(storage).unwrap_or_default();
egui_ctx.memory_mut(|mem| *mem = memory);
egui_ctx
}
/// The custom even `eframe` uses with the [`winit`] event loop.
#[derive(Debug)]
pub enum UserEvent {
/// A repaint is requested.
RequestRepaint {
/// What to repaint.
viewport_id: ViewportId,
/// When to repaint.
when: Instant,
/// What the cumulative pass number was when the repaint was _requested_.
cumulative_pass_nr: u64,
},
/// A request related to [`accesskit`](https://accesskit.dev/).
#[cfg(feature = "accesskit")]
AccessKitActionRequest(accesskit_winit::Event),
}
#[cfg(feature = "accesskit")]
impl From<accesskit_winit::Event> for UserEvent {
fn from(inner: accesskit_winit::Event) -> Self {
Self::AccessKitActionRequest(inner)
}
}
pub trait WinitApp {
fn egui_ctx(&self) -> Option<&egui::Context>;
fn window(&self, window_id: WindowId) -> Option<Arc<Window>>;
fn window_id_from_viewport_id(&self, id: ViewportId) -> Option<WindowId>;
fn save(&mut self);
fn save_and_destroy(&mut self);
fn run_ui_and_paint(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
) -> crate::Result<EventResult>;
fn suspended(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult>;
fn resumed(&mut self, event_loop: &ActiveEventLoop) -> crate::Result<EventResult>;
fn device_event(
&mut self,
event_loop: &ActiveEventLoop,
device_id: winit::event::DeviceId,
event: winit::event::DeviceEvent,
) -> crate::Result<EventResult>;
fn window_event(
&mut self,
event_loop: &ActiveEventLoop,
window_id: WindowId,
event: winit::event::WindowEvent,
) -> crate::Result<EventResult>;
#[cfg(feature = "accesskit")]
fn on_accesskit_event(&mut self, event: accesskit_winit::Event) -> crate::Result<EventResult>;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum EventResult {
Wait,
/// Causes a synchronous repaint inside the event handler. This should only
/// be used in special situations if the window must be repainted while
/// handling a specific event. This occurs on Windows when handling resizes.
///
/// `RepaintNow` creates a new frame synchronously, and should therefore
/// only be used for extremely urgent repaints.
RepaintNow(WindowId),
/// Queues a repaint for once the event loop handles its next redraw. Exists
/// so that multiple input events can be handled in one frame. Does not
/// cause any delay like `RepaintNow`.
RepaintNext(WindowId),
RepaintAt(WindowId, Instant),
/// Causes a save of the client state when the persistence feature is enabled.
Save,
Exit,
}
#[cfg(feature = "accesskit")]
pub(crate) fn on_accesskit_window_event(
egui_winit: &mut egui_winit::State,
window_id: WindowId,
event: &accesskit_winit::WindowEvent,
) -> EventResult {
match event {
accesskit_winit::WindowEvent::InitialTreeRequested => {
egui_winit.egui_ctx().enable_accesskit();
// Because we can't provide the initial tree synchronously
// (because that would require the activation handler to access
// the same mutable state as the winit event handler), some
// AccessKit platform adapters will use a placeholder tree
// until we send the first tree update. To minimize the possible
// bad effects of that workaround, repaint and send the tree
// immediately.
EventResult::RepaintNow(window_id)
}
accesskit_winit::WindowEvent::ActionRequested(request) => {
egui_winit.on_accesskit_action_request(request.clone());
// As a form of user input, accessibility actions should cause
// a repaint, but not until the next regular frame.
EventResult::RepaintNext(window_id)
}
accesskit_winit::WindowEvent::AccessibilityDeactivated => {
egui_winit.egui_ctx().disable_accesskit();
// Disabling AccessKit support should have no visible effect,
// so there's no need to repaint.
EventResult::Wait
}
}
}