diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index b1d18d5f88..927d473c99 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 f014b56400..8f8a627e28 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 1f597772ac..3375cc61ab 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 cb070d5734..4bb6fc7f2d 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 bebe5d66d9..af65f85095 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 476fee7483..807fabb2a1 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 b82cf45dd4..abb94963c8 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 e2de434320..b8fe496ecf 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 e4e92f4e23..f21a2794d4 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 4e3a826296..46580c42ef 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 7f42f12c0a..05fe1c7283 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 ab79f8d900..991431247c 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();