From fa3ec006e2225beae468c8fa7990ad1979563f02 Mon Sep 17 00:00:00 2001 From: daxpedda Date: Mon, 5 Aug 2024 20:51:38 +0200 Subject: [PATCH] Add `ActiveEventLoop::system_theme()` This also fixes macOS returning `None` in `Window::theme()` if no theme override is set, instead it now returns the system theme. MacOS and Wayland were the only ones working correctly according to the documentation, which was an oversight. The documentation was "fixed" now. Fixes #3837. --- src/changelog/unreleased.md | 2 ++ src/event_loop.rs | 13 ++++++++++- src/platform_impl/android/mod.rs | 5 +++++ src/platform_impl/ios/event_loop.rs | 9 ++++++-- src/platform_impl/linux/mod.rs | 5 +++++ src/platform_impl/macos/event_loop.rs | 15 +++++++++++-- src/platform_impl/macos/window_delegate.rs | 22 +++++++++++-------- src/platform_impl/orbital/event_loop.rs | 7 +++++- .../web/event_loop/window_target.rs | 10 +++++++++ src/platform_impl/windows/dark_mode.rs | 2 +- src/platform_impl/windows/event_loop.rs | 6 ++++- src/window.rs | 8 +++---- 12 files changed, 83 insertions(+), 21 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9f7b74bd0..6d4d3e6a5 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -42,6 +42,7 @@ changelog entry. ### Added +- Add `ActiveEventLoop::system_theme()`, returning the current system theme. - On Web, implement `Error` for `platform::web::CustomCursorError`. - On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. @@ -50,3 +51,4 @@ changelog entry. - On MacOS, fix building with `feature = "rwh_04"`. - On Web, pen events are now routed through to `WindowEvent::Cursor*`. - On macOS, fix panic when releasing not available monitor. +- On MacOS, return the system theme in `Window::theme()` if no theme override is set. diff --git a/src/event_loop.rs b/src/event_loop.rs index 99e72040f..b5d5d2640 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -23,7 +23,7 @@ use crate::error::{EventLoopError, OsError}; use crate::event::Event; use crate::monitor::MonitorHandle; use crate::platform_impl; -use crate::window::{CustomCursor, CustomCursorSource, Window, WindowAttributes}; +use crate::window::{CustomCursor, CustomCursorSource, Theme, Window, WindowAttributes}; /// Provides a way to retrieve events from the system and from the windows that were registered to /// the events loop. @@ -437,6 +437,17 @@ impl ActiveEventLoop { self.p.listen_device_events(allowed); } + /// Returns the current system theme. + /// + /// Returns `None` if it cannot be determined on the current platform. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported. + pub fn system_theme(&self) -> Option { + self.p.system_theme() + } + /// Sets the [`ControlFlow`]. pub fn set_control_flow(&self, control_flow: ControlFlow) { self.p.set_control_flow(control_flow) diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 9b12eaaa1..950e31029 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -677,6 +677,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::Android(rwh_05::AndroidDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index ac485b9e8..52d8062be 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -22,8 +22,8 @@ use crate::event_loop::{ ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents, EventLoopClosed, }; use crate::platform::ios::Idiom; -use crate::platform_impl::platform::app_state::{EventLoopHandler, HandlePendingUserEvents}; -use crate::window::{CustomCursor, CustomCursorSource}; +use crate::platform_impl::ios::app_state::{EventLoopHandler, HandlePendingUserEvents}; +use crate::window::{CustomCursor, CustomCursorSource, Theme}; use super::app_delegate::AppDelegate; use super::app_state::AppState; @@ -58,6 +58,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::UiKit(rwh_05::UiKitDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 6cb99fee3..bee440189 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -897,6 +897,11 @@ impl ActiveEventLoop { x11_or_wayland!(match self; Self(evlp) => evlp.raw_display_handle_rwh_05()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/macos/event_loop.rs b/src/platform_impl/macos/event_loop.rs index 1680953c3..9b7be282f 100644 --- a/src/platform_impl/macos/event_loop.rs +++ b/src/platform_impl/macos/event_loop.rs @@ -16,7 +16,7 @@ use core_foundation::runloop::{ }; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::ProtocolObject; -use objc2::{msg_send_id, ClassType}; +use objc2::{msg_send_id, sel, ClassType}; use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSWindow}; use objc2_foundation::{MainThreadMarker, NSObjectProtocol}; @@ -33,7 +33,7 @@ use crate::event_loop::{ use crate::platform::macos::ActivationPolicy; use crate::platform::pump_events::PumpStatus; use crate::platform_impl::platform::cursor::CustomCursor; -use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme}; #[derive(Default)] pub struct PanicInfo { @@ -106,6 +106,17 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::AppKit(rwh_05::AppKitDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + let app = NSApplication::sharedApplication(self.mtm); + + if app.respondsToSelector(sel!(effectiveAppearance)) { + Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) + } else { + Some(Theme::Light) + } + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index a946dcf00..2a7877b8b 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -1645,14 +1645,18 @@ impl WindowDelegate { } pub fn theme(&self) -> Option { - // Note: We could choose between returning the value of `effectiveAppearance` or - // `appearance`, depending on what the user is asking about: - // - "how should I render on this particular frame". - // - "what is the configuration for this window". - // - // We choose the latter for consistency with the `set_theme` call, though it might also be - // useful to expose the former. - Some(appearance_to_theme(unsafe { &*self.window().appearance()? })) + unsafe { self.window().appearance() } + .map(|appearance| appearance_to_theme(&appearance)) + .or_else(|| { + let mtm = MainThreadMarker::from(self); + let app = NSApplication::sharedApplication(mtm); + + if app.respondsToSelector(sel!(effectiveAppearance)) { + Some(super::window_delegate::appearance_to_theme(&app.effectiveAppearance())) + } else { + Some(Theme::Light) + } + }) } pub fn set_theme(&self, theme: Option) { @@ -1835,7 +1839,7 @@ fn dark_appearance_name() -> &'static NSString { ns_string!("NSAppearanceNameDarkAqua") } -fn appearance_to_theme(appearance: &NSAppearance) -> Theme { +pub fn appearance_to_theme(appearance: &NSAppearance) -> Theme { let best_match = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_id_slice(&[ unsafe { NSAppearanceNameAqua.copy() }, dark_appearance_name().copy(), diff --git a/src/platform_impl/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index 59ba9e522..91f76e759 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -20,7 +20,7 @@ use crate::keyboard::{ PhysicalKey, }; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use super::{ @@ -775,6 +775,11 @@ impl ActiveEventLoop { rwh_05::RawDisplayHandle::Orbital(rwh_05::OrbitalDisplayHandle::empty()) } + #[inline] + pub fn system_theme(&self) -> Option { + None + } + #[cfg(feature = "rwh_06")] #[inline] pub fn raw_display_handle_rwh_06( diff --git a/src/platform_impl/web/event_loop/window_target.rs b/src/platform_impl/web/event_loop/window_target.rs index 05f87dcc3..d741f0987 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -614,6 +614,16 @@ impl ActiveEventLoop { self.runner.listen_device_events(allowed) } + pub fn system_theme(&self) -> Option { + backend::is_dark_mode(self.runner.window()).map(|is_dark_mode| { + if is_dark_mode { + Theme::Dark + } else { + Theme::Light + } + }) + } + pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner.set_control_flow(control_flow) } diff --git a/src/platform_impl/windows/dark_mode.rs b/src/platform_impl/windows/dark_mode.rs index 4f910ad5c..9d4bad927 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -123,7 +123,7 @@ fn set_dark_mode_for_window(hwnd: HWND, is_dark_mode: bool) -> bool { } } -fn should_use_dark_mode() -> bool { +pub fn should_use_dark_mode() -> bool { should_apps_use_dark_mode() && !is_high_contrast() } diff --git a/src/platform_impl/windows/event_loop.rs b/src/platform_impl/windows/event_loop.rs index 35e3410d1..639a93865 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -81,7 +81,7 @@ use crate::platform_impl::platform::{ raw_input, util, wrap_device_id, Fullscreen, WindowId, DEVICE_ID, }; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; use runner::{EventLoopRunner, EventLoopRunnerShared}; @@ -550,6 +550,10 @@ impl ActiveEventLoop { raw_input::register_all_mice_and_keyboards_for_raw_input(self.thread_msg_target, allowed); } + pub fn system_theme(&self) -> Option { + Some(if super::dark_mode::should_use_dark_mode() { Theme::Dark } else { Theme::Light }) + } + pub(crate) fn set_control_flow(&self, control_flow: ControlFlow) { self.runner_shared.set_control_flow(control_flow) } diff --git a/src/window.rs b/src/window.rs index 31ef19a33..f19265e3a 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1371,14 +1371,14 @@ impl Window { self.window.maybe_queue_on_main(move |w| w.set_theme(theme)) } - /// Returns the current window theme override. + /// Returns the current window theme. /// - /// Returns `None` if the current theme is set as the system default, or if it cannot be - /// determined on the current platform. + /// Returns `None` if it cannot be determined on the current platform. /// /// ## Platform-specific /// - /// - **iOS / Android / Wayland / x11 / Orbital:** Unsupported, returns `None`. + /// - **iOS / Android / x11 / Orbital:** Unsupported. + /// - **Wayland:** Only returns theme overrides. #[inline] pub fn theme(&self) -> Option { let _span = tracing::debug_span!("winit::Window::theme",).entered();