From 7d77ccfad3e279c6e46f1d64d99d8252e7f1ffd3 Mon Sep 17 00:00:00 2001 From: Marijn Suijten Date: Wed, 16 Oct 2024 12:17:59 +0200 Subject: [PATCH] android: Forward `suspended()` and `resumed()` events and patch up platform-specific documentation (#3786) 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 | 86 +++++++++++-------- src/changelog/unreleased.md | 6 +- src/event.rs | 36 ++++---- src/platform_impl/android/mod.rs | 8 +- .../apple/appkit/window_delegate.rs | 4 +- 5 files changed, 79 insertions(+), 61 deletions(-) diff --git a/src/application.rs b/src/application.rs index 2dc224c941..f361ca76c7 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; } @@ -71,26 +81,24 @@ 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. - /// - /// [`onResume`]: https://developer.android.com/reference/android/app/Activity#onResume() + /// On Android, the [`can_create_surfaces()`] method is called when a new [`NativeWindow`] + /// (native [`Surface`]) is created which backs the application window. This is expected to + /// closely correlate with the [`onStart`] lifecycle event which typically results in a surface + /// to be created after the app becomes visible. /// - /// 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 [`NativeWindow`]. 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 + /// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window + /// [`Surface`]: https://developer.android.com/reference/android/view/Surface + /// [`onStart`]: https://developer.android.com/reference/android/app/Activity#onStart() /// [`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 +243,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 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; } @@ -259,24 +279,22 @@ 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`] - /// lifecycle event but there may technically be a discrepancy. - /// - /// [`onPause`]: https://developer.android.com/reference/android/app/Activity#onPause() + /// On Android, the [`destroy_surfaces()`] method is called when the application's + /// [`NativeWindow`] (native [`Surface`]) is destroyed. This is expected to closely correlate + /// with the [`onStop`] lifecycle event which typically results in the surface to be destroyed + /// after the app becomes invisible. /// - /// Applications that need to run on Android should assume their [`SurfaceView`] has been + /// Applications that need to run on Android should assume their [`NativeWindow`] 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 should 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 + /// [`NativeWindow`]: https://developer.android.com/ndk/reference/group/a-native-window + /// [`Surface`]: https://developer.android.com/reference/android/view/Surface + /// [`onStop`]: https://developer.android.com/reference/android/app/Activity#onStop() /// [`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 +302,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 c1fbb8447b..31b64d1aba 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -84,14 +84,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 85a47c90a0..edb3ea7161 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: Option, 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 760d10a02a..912e61e54a 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -218,11 +218,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 { .. } => { @@ -232,11 +232,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 diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index c78ce2921d..10aac698f0 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -218,10 +218,10 @@ declare_class!( trace_scope!("windowDidResignKey:"); // It happens rather often, e.g. when the user is Cmd+Tabbing, that the // NSWindowDelegate will receive a didResignKey event despite no event - // being received when the modifiers are released. This is because + // being received when the modifiers are released. This is because // flagsChanged events are received by the NSView instead of the // NSWindowDelegate, and as a result a tracked modifiers state can quite - // easily fall out of synchrony with reality. This requires us to emit + // easily fall out of synchrony with reality. This requires us to emit // a synthetic ModifiersChanged event when we lose focus. self.view().reset_modifiers();