From 15b79b18e1f6b64ec21a0027edbc84868bd19c31 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/apple/appkit/event_loop.rs | 15 +++++++++++-- .../apple/appkit/window_delegate.rs | 22 +++++++++++-------- src/platform_impl/apple/uikit/event_loop.rs | 7 +++++- src/platform_impl/linux/mod.rs | 5 +++++ 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, 82 insertions(+), 20 deletions(-) diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index b1d18d5f..927d473c 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -55,6 +55,7 @@ changelog entry. information is available. This "detailed monitors" can be used in `Window::set_fullscreen()` as well. - On Android, add `{Active,}EventLoopExtAndroid::android_app()` to access the app used to create the loop. +- Add `ActiveEventLoop::system_theme()`, returning the current system theme. ### Changed @@ -111,3 +112,4 @@ changelog entry. - 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 f014b564..8f8a627e 100644 --- a/src/event_loop.rs +++ b/src/event_loop.rs @@ -23,7 +23,7 @@ use crate::application::ApplicationHandler; use crate::error::{EventLoopError, OsError}; 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. @@ -403,6 +403,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 1f597772..3375cc61 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -605,6 +605,11 @@ impl ActiveEventLoop { #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} + #[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/apple/appkit/event_loop.rs b/src/platform_impl/apple/appkit/event_loop.rs index cb070d57..4bb6fc7f 100644 --- a/src/platform_impl/apple/appkit/event_loop.rs +++ b/src/platform_impl/apple/appkit/event_loop.rs @@ -17,7 +17,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}; @@ -32,7 +32,7 @@ use crate::error::EventLoopError; use crate::event_loop::{ActiveEventLoop as RootWindowTarget, ControlFlow, DeviceEvents}; use crate::platform::macos::ActivationPolicy; use crate::platform::pump_events::PumpStatus; -use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource}; +use crate::window::{CustomCursor as RootCustomCursor, CustomCursorSource, Theme}; #[derive(Default)] pub struct PanicInfo { @@ -103,6 +103,17 @@ impl ActiveEventLoop { #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} + #[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/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index bebe5d66..af65f850 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -1623,14 +1623,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) { @@ -1813,7 +1817,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/apple/uikit/event_loop.rs b/src/platform_impl/apple/uikit/event_loop.rs index 476fee74..807fabb2 100644 --- a/src/platform_impl/apple/uikit/event_loop.rs +++ b/src/platform_impl/apple/uikit/event_loop.rs @@ -24,7 +24,7 @@ use crate::application::ApplicationHandler; use crate::error::EventLoopError; use crate::event::Event; use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents}; -use crate::window::{CustomCursor, CustomCursorSource}; +use crate::window::{CustomCursor, CustomCursorSource, Theme}; #[derive(Debug)] pub struct ActiveEventLoop { @@ -53,6 +53,11 @@ impl ActiveEventLoop { #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} + #[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 b82cf45d..abb94963 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -864,6 +864,11 @@ impl ActiveEventLoop { x11_or_wayland!(match self; Self(evlp) => evlp.listen_device_events(allowed)) } + #[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/orbital/event_loop.rs b/src/platform_impl/orbital/event_loop.rs index e2de4343..b8fe496e 100644 --- a/src/platform_impl/orbital/event_loop.rs +++ b/src/platform_impl/orbital/event_loop.rs @@ -25,7 +25,7 @@ use crate::keyboard::{ PhysicalKey, }; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; fn convert_scancode(scancode: u8) -> (PhysicalKey, Option) { @@ -747,6 +747,11 @@ impl ActiveEventLoop { #[inline] pub fn listen_device_events(&self, _allowed: DeviceEvents) {} + #[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 e4e92f4e..f21a2794 100644 --- a/src/platform_impl/web/event_loop/window_target.rs +++ b/src/platform_impl/web/event_loop/window_target.rs @@ -611,6 +611,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 4e3a8262..46580c42 100644 --- a/src/platform_impl/windows/dark_mode.rs +++ b/src/platform_impl/windows/dark_mode.rs @@ -122,7 +122,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 7f42f12c..05fe1c72 100644 --- a/src/platform_impl/windows/event_loop.rs +++ b/src/platform_impl/windows/event_loop.rs @@ -82,7 +82,7 @@ use crate::platform_impl::platform::{ }; use crate::utils::Lazy; use crate::window::{ - CustomCursor as RootCustomCursor, CustomCursorSource, WindowId as RootWindowId, + CustomCursor as RootCustomCursor, CustomCursorSource, Theme, WindowId as RootWindowId, }; pub(crate) struct WindowData { @@ -516,6 +516,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 ab79f8d9..99143124 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1367,14 +1367,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();