Skip to content

Commit

Permalink
Add ActiveEventLoop::system_theme()
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
daxpedda authored Aug 5, 2024
1 parent 54ff9c3 commit 15b79b1
Show file tree
Hide file tree
Showing 12 changed files with 82 additions and 20 deletions.
2 changes: 2 additions & 0 deletions src/changelog/unreleased.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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.
13 changes: 12 additions & 1 deletion src/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Theme> {
self.p.system_theme()
}

/// Sets the [`ControlFlow`].
pub fn set_control_flow(&self, control_flow: ControlFlow) {
self.p.set_control_flow(control_flow)
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,6 +605,11 @@ impl ActiveEventLoop {
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}

#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}

#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
Expand Down
15 changes: 13 additions & 2 deletions src/platform_impl/apple/appkit/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};

Expand All @@ -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 {
Expand Down Expand Up @@ -103,6 +103,17 @@ impl ActiveEventLoop {
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}

#[inline]
pub fn system_theme(&self) -> Option<Theme> {
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(
Expand Down
22 changes: 13 additions & 9 deletions src/platform_impl/apple/appkit/window_delegate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1623,14 +1623,18 @@ impl WindowDelegate {
}

pub fn theme(&self) -> Option<Theme> {
// 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<Theme>) {
Expand Down Expand Up @@ -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(),
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/apple/uikit/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -53,6 +53,11 @@ impl ActiveEventLoop {
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}

#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}

#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
Expand Down
5 changes: 5 additions & 0 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Theme> {
None
}

#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/orbital/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<NamedKey>) {
Expand Down Expand Up @@ -747,6 +747,11 @@ impl ActiveEventLoop {
#[inline]
pub fn listen_device_events(&self, _allowed: DeviceEvents) {}

#[inline]
pub fn system_theme(&self) -> Option<Theme> {
None
}

#[cfg(feature = "rwh_06")]
#[inline]
pub fn raw_display_handle_rwh_06(
Expand Down
10 changes: 10 additions & 0 deletions src/platform_impl/web/event_loop/window_target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,16 @@ impl ActiveEventLoop {
self.runner.listen_device_events(allowed)
}

pub fn system_theme(&self) -> Option<Theme> {
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)
}
Expand Down
2 changes: 1 addition & 1 deletion src/platform_impl/windows/dark_mode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()
}

Expand Down
6 changes: 5 additions & 1 deletion src/platform_impl/windows/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<Theme> {
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)
}
Expand Down
8 changes: 4 additions & 4 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Theme> {
let _span = tracing::debug_span!("winit::Window::theme",).entered();
Expand Down

0 comments on commit 15b79b1

Please sign in to comment.