diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 9f7b74bd05..6d4d3e6a5d 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 99e72040f8..b5d5d26402 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 9b12eaaa1d..950e310299 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 ac485b9e8f..52d8062bea 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 6cb99fee31..bee440189d 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 1680953c34..9b7be282f6 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 a946dcf00f..2a7877b8bc 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 59ba9e522d..91f76e7591 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 05f87dcc35..d741f09872 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 4f910ad5c9..9d4bad9279 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 35e3410d15..639a93865d 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 31ef19a330..f19265e3a1 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();