From 0a2a17658a8f14110a776d8d4dbd2ab21a96d4f9 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 10 Jul 2024 15:16:26 +0200 Subject: [PATCH] android: Forward `suspended()` and `resumed()` events and patch up platform-specific documentation Key them off of `onStop()` and `onStart()` which seems to match the other backends most closely. These [Android Activity lifecycle] events denote when the application is visible on-screen, and recommend that any heavy lifting for startup and shutdown happens here, as the application may be demoted to the background and later shut down entirely unless the user navigates back to it. [Android Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle --- src/application.rs | 68 ++++++++++++++++++++------------ src/changelog/unreleased.md | 6 +-- src/event.rs | 36 ++++++++--------- src/platform_impl/android/mod.rs | 8 ++-- 4 files changed, 67 insertions(+), 51 deletions(-) diff --git a/src/application.rs b/src/application.rs index 8b268b05d5..9ad5c167fe 100644 --- a/src/application.rs +++ b/src/application.rs @@ -41,11 +41,21 @@ pub trait ApplicationHandler { /// [`pageshow`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pageshow_event /// [`bfcache`]: https://web.dev/bfcache/ /// + /// ### Android + /// + /// On Android, the [`resumed()`] method is called when the `Activity` is (again, if after a + /// prior [`suspended()`]) being displayed to the user. This is a good place to begin drawing + /// visual elements, running animations, etc. It is driven by Android's [`onStart()`] method. + /// + /// [`onStart()`]: https://developer.android.com/reference/android/app/Activity#onStart() + /// /// ### Others /// - /// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported. + /// **macOS / Orbital / Wayland / Windows / X11:** Unsupported. /// - /// [`resumed()`]: Self::resumed + /// [`resumed()`]: Self::resumed() + /// [`suspended()`]: Self::suspended() + /// [`exiting()`]: Self::exiting() fn resumed(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } @@ -72,25 +82,22 @@ pub trait ApplicationHandler { /// ### Android /// /// On Android, the [`can_create_surfaces()`] method is called when a new [`SurfaceView`] has - /// been created. This is expected to closely correlate with the [`onResume`] lifecycle - /// event but there may technically be a discrepancy. + /// been created. This is expected to closely correlate with the [`onStart`] lifecycle event but + /// there may technically be a discrepancy. /// - /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() + /// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart() /// - /// Applications that need to run on Android must wait until they have been "resumed" before + /// Applications that need to run on Android must wait until they have received a surface before /// they will be able to create a render surface (such as an `EGLSurface`, [`VkSurfaceKHR`] - /// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must also - /// assume that if they are [suspended], then their render surfaces are invalid and should - /// be dropped. + /// or [`wgpu::Surface`]) which depend on having a [`SurfaceView`]. Applications must handle + /// [`destroy_surfaces()`], where their render surfaces are invalid and should be dropped. /// - /// [suspended]: Self::destroy_surfaces /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// - /// [`can_create_surfaces()`]: Self::can_create_surfaces - /// [`destroy_surfaces()`]: Self::destroy_surfaces + /// [`can_create_surfaces()`]: Self::can_create_surfaces() + /// [`destroy_surfaces()`]: Self::destroy_surfaces() fn can_create_surfaces(&mut self, event_loop: &dyn ActiveEventLoop); /// Called after a wake up is requested using [`EventLoopProxy::wake_up()`]. @@ -235,18 +242,30 @@ pub trait ApplicationHandler { /// ### Web /// /// On Web, the [`suspended()`] method is called in response to a [`pagehide`] event if the - /// page is being restored from the [`bfcache`] (back/forward cache) - an in-memory cache that + /// page is being moved/stored in the [`bfcache`] (back/forward cache) - an in-memory cache that /// stores a complete snapshot of a page (including the JavaScript heap) as the user is /// navigating away. /// /// [`pagehide`]: https://developer.mozilla.org/en-US/docs/Web/API/Window/pagehide_event /// [`bfcache`]: https://web.dev/bfcache/ /// + /// ### Android + /// + /// On Android, the [`suspended()`] method is called when the `Activity` is no longer visible to + /// the user. This is a good place to stop refreshing UI, running animations and other visual + /// things. It is driven by Android's [`onStop()`] method. + /// + /// After this event the application either receives [`resumed()`] again, or [`exiting()`]. + /// + /// [`onStop()`]: https://developer.android.com/reference/android/app/Activity#onStop() + /// /// ### Others /// - /// **Android / macOS / Orbital / Wayland / Windows / X11:** Unsupported. + /// **macOS / Orbital / Wayland / Windows / X11:** Unsupported. /// - /// [`suspended()`]: Self::suspended + /// [`resumed()`]: Self::resumed() + /// [`suspended()`]: Self::suspended() + /// [`exiting()`]: Self::exiting() fn suspended(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } @@ -260,23 +279,20 @@ pub trait ApplicationHandler { /// ### Android /// /// On Android, the [`destroy_surfaces()`] method is called when the application's associated - /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onPause`] + /// [`SurfaceView`] is destroyed. This is expected to closely correlate with the [`onStop`] /// lifecycle event but there may technically be a discrepancy. /// - /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() + /// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop() /// /// Applications that need to run on Android should assume their [`SurfaceView`] has been /// destroyed, which indirectly invalidates any existing render surfaces that may have been /// created outside of Winit (such as an `EGLSurface`, [`VkSurfaceKHR`] or [`wgpu::Surface`]). /// - /// After being [suspended] on Android applications must drop all render surfaces before - /// the event callback completes, which may be re-created when the application is next - /// [resumed]. + /// When receiving [`destroy_surfaces()`] Android applications must drop all render surfaces + /// before the event callback completes, which may be re-created when the application next + /// receives [`can_create_surfaces()`]. /// - /// [suspended]: Self::destroy_surfaces - /// [resumed]: Self::can_create_surfaces /// [`SurfaceView`]: https://developer.android.com/reference/android/view/SurfaceView - /// [Activity lifecycle]: https://developer.android.com/guide/components/activities/activity-lifecycle /// [`VkSurfaceKHR`]: https://www.khronos.org/registry/vulkan/specs/1.3-extensions/man/html/VkSurfaceKHR.html /// [`wgpu::Surface`]: https://docs.rs/wgpu/latest/wgpu/struct.Surface.html /// @@ -284,8 +300,8 @@ pub trait ApplicationHandler { /// /// - **iOS / macOS / Orbital / Wayland / Web / Windows / X11:** Unsupported. /// - /// [`can_create_surfaces()`]: Self::can_create_surfaces - /// [`destroy_surfaces()`]: Self::destroy_surfaces + /// [`can_create_surfaces()`]: Self::can_create_surfaces() + /// [`destroy_surfaces()`]: Self::destroy_surfaces() fn destroy_surfaces(&mut self, event_loop: &dyn ActiveEventLoop) { let _ = event_loop; } diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 6f55ff7f2c..8e6158ea86 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -83,14 +83,14 @@ changelog entry. - Changed `EventLoopProxy::send_event` to `EventLoopProxy::wake_up`, it now only wakes up the loop. - On X11, implement smooth resizing through the sync extension API. -- `ApplicationHandler::create|destroy_surfaces()` was split off from +- `ApplicationHandler::can_create|destroy_surfaces()` was split off from `ApplicationHandler::resumed/suspended()`. `ApplicationHandler::can_create_surfaces()` should, for portability reasons to Android, be the only place to create render surfaces. - `ApplicationHandler::resumed/suspended()` are now only emitted by iOS and Web - and now signify actually resuming/suspending the application. + `ApplicationHandler::resumed/suspended()` are now only emitted by iOS, Web + and Android, and now signify actually resuming/suspending the application. - Rename `platform::web::*ExtWebSys` to `*ExtWeb`. - Change signature of `EventLoop::run_app`, `EventLoopExtPumpEvents::pump_app_events` and `EventLoopExtRunOnDemand::run_app_on_demand` to accept a `impl ApplicationHandler` directly, diff --git a/src/event.rs b/src/event.rs index 223992db8f..b4ed85c671 100644 --- a/src/event.rs +++ b/src/event.rs @@ -63,51 +63,51 @@ use crate::window::{ActivationToken, Theme, WindowId}; #[allow(dead_code)] #[derive(Debug, Clone, PartialEq)] pub(crate) enum Event { - /// See [`ApplicationHandler::new_events`] for details. + /// See [`ApplicationHandler::new_events()`] for details. /// - /// [`ApplicationHandler::new_events`]: crate::application::ApplicationHandler::new_events + /// [`ApplicationHandler::new_events()`]: crate::application::ApplicationHandler::new_events() NewEvents(StartCause), - /// See [`ApplicationHandler::window_event`] for details. + /// See [`ApplicationHandler::window_event()`] for details. /// - /// [`ApplicationHandler::window_event`]: crate::application::ApplicationHandler::window_event + /// [`ApplicationHandler::window_event()`]: crate::application::ApplicationHandler::window_event() #[allow(clippy::enum_variant_names)] WindowEvent { window_id: WindowId, event: WindowEvent }, - /// See [`ApplicationHandler::device_event`] for details. + /// See [`ApplicationHandler::device_event()`] for details. /// - /// [`ApplicationHandler::device_event`]: crate::application::ApplicationHandler::device_event + /// [`ApplicationHandler::device_event()`]: crate::application::ApplicationHandler::device_event() #[allow(clippy::enum_variant_names)] DeviceEvent { device_id: DeviceId, event: DeviceEvent }, - /// See [`ApplicationHandler::suspended`] for details. + /// See [`ApplicationHandler::suspended()`] for details. /// - /// [`ApplicationHandler::suspended`]: crate::application::ApplicationHandler::suspended + /// [`ApplicationHandler::suspended()`]: crate::application::ApplicationHandler::suspended() Suspended, - /// See [`ApplicationHandler::can_create_surfaces`] for details. + /// See [`ApplicationHandler::can_create_surfaces()`] for details. /// - /// [`ApplicationHandler::can_create_surfaces`]: crate::application::ApplicationHandler::can_create_surfaces + /// [`ApplicationHandler::can_create_surfaces()`]: crate::application::ApplicationHandler::can_create_surfaces() CreateSurfaces, - /// See [`ApplicationHandler::resumed`] for details. + /// See [`ApplicationHandler::resumed()`] for details. /// - /// [`ApplicationHandler::resumed`]: crate::application::ApplicationHandler::resumed + /// [`ApplicationHandler::resumed()`]: crate::application::ApplicationHandler::resumed() Resumed, - /// See [`ApplicationHandler::about_to_wait`] for details. + /// See [`ApplicationHandler::about_to_wait()`] for details. /// - /// [`ApplicationHandler::about_to_wait`]: crate::application::ApplicationHandler::about_to_wait + /// [`ApplicationHandler::about_to_wait()`]: crate::application::ApplicationHandler::about_to_wait() AboutToWait, - /// See [`ApplicationHandler::exiting`] for details. + /// See [`ApplicationHandler::exiting()`] for details. /// - /// [`ApplicationHandler::exiting`]: crate::application::ApplicationHandler::exiting + /// [`ApplicationHandler::exiting()`]: crate::application::ApplicationHandler::exiting() LoopExiting, - /// See [`ApplicationHandler::memory_warning`] for details. + /// See [`ApplicationHandler::memory_warning()`] for details. /// - /// [`ApplicationHandler::memory_warning`]: crate::application::ApplicationHandler::memory_warning + /// [`ApplicationHandler::memory_warning()`]: crate::application::ApplicationHandler::memory_warning() MemoryWarning, /// User requested a wake up. diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 426dd0fb35..1914c1159f 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -217,11 +217,11 @@ impl EventLoop { app.memory_warning(&self.window_target); }, MainEvent::Start => { - // XXX: how to forward this state to applications? - warn!("TODO: forward onStart notification to application"); + app.resumed(self.window_target()); }, MainEvent::Resume { .. } => { debug!("App Resumed - is running"); + // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897 self.running = true; }, MainEvent::SaveState { .. } => { @@ -231,11 +231,11 @@ impl EventLoop { }, MainEvent::Pause => { debug!("App Paused - stopped running"); + // TODO: This is incorrect - will be solved in https://github.com/rust-windowing/winit/pull/3897 self.running = false; }, MainEvent::Stop => { - // XXX: how to forward this state to applications? - warn!("TODO: forward onStop notification to application"); + app.suspended(self.window_target()); }, MainEvent::Destroy => { // XXX: maybe exit mainloop to drop things before being