From 92fdf5ba85f920262a61cee4590f4a11ad5738d1 Mon Sep 17 00:00:00 2001 From: keiya sasaki <34934510+keiya01@users.noreply.github.com> Date: Wed, 19 Oct 2022 03:34:36 +0900 Subject: [PATCH] Rework theme API This commit adds support for theming on macOS and also unifies the system theme handling across platforms. --- CHANGELOG.md | 4 ++ FEATURES.md | 1 + examples/theme.rs | 40 +++++++++++++ src/event.rs | 2 +- src/platform/wayland.rs | 14 ----- src/platform/windows.rs | 19 +----- src/platform_impl/android/mod.rs | 6 +- src/platform_impl/ios/window.rs | 8 ++- src/platform_impl/linux/mod.rs | 13 ++-- src/platform_impl/linux/wayland/window/mod.rs | 7 ++- src/platform_impl/linux/x11/window.rs | 7 ++- src/platform_impl/macos/appkit/appearance.rs | 29 +++++++++ src/platform_impl/macos/appkit/application.rs | 9 ++- src/platform_impl/macos/appkit/mod.rs | 2 + src/platform_impl/macos/window.rs | 60 +++++++++++++++++-- src/platform_impl/macos/window_delegate.rs | 48 ++++++++++++++- src/platform_impl/web/window.rs | 7 ++- src/platform_impl/windows/mod.rs | 3 - src/platform_impl/windows/window.rs | 8 +-- src/window.rs | 29 +++++++++ 20 files changed, 256 insertions(+), 60 deletions(-) create mode 100644 examples/theme.rs create mode 100644 src/platform_impl/macos/appkit/appearance.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index a30f35e215..5d73e0fb85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,10 @@ And please only add new entries to the top of this list, right below the `# Unre - On Wayland, a new `wayland-csd-adwaita-crossfont` feature was added to use `crossfont` instead of `ab_glyph` for decorations. - On Wayland, if not otherwise specified use upstream automatic CSD theme selection. - On X11, added `WindowExtX11::with_parent` to create child windows. +- Added support for `WindowBuilder::with_theme` and `Window::theme` to support per-window dark/light/system theme configuration on macos, windows and wayland. +- On macOS, added support for `WindowEvent::ThemeChanged`. +- **Breaking:** Removed `WindowBuilderExtWindows::with_theme` and `WindowBuilderExtWayland::with_wayland_csd_theme` in favour of `WindowBuilder::with_theme`. +- **Breaking:** Removed `WindowExtWindows::theme` in favour of `Window::theme`. # 0.27.4 diff --git a/FEATURES.md b/FEATURES.md index 4890db8c34..580d05555c 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -130,6 +130,7 @@ If your PR makes notable changes to Winit's features, please update this section * Hidden titlebar buttons * Full-size content view * Accepts first mouse +* Set a preferred theme and get current theme. ### Unix * Window urgency diff --git a/examples/theme.rs b/examples/theme.rs new file mode 100644 index 0000000000..a642447bea --- /dev/null +++ b/examples/theme.rs @@ -0,0 +1,40 @@ +#![allow(clippy::single_match)] + +use simple_logger::SimpleLogger; +use winit::{ + event::{Event, WindowEvent}, + event_loop::{ControlFlow, EventLoop}, + window::{Theme, WindowBuilder}, +}; + +fn main() { + SimpleLogger::new().init().unwrap(); + let event_loop = EventLoop::new(); + + let window = WindowBuilder::new() + .with_title("A fantastic window!") + .with_theme(Some(Theme::Dark)) + .build(&event_loop) + .unwrap(); + + println!("Initial theme: {:?}", window.theme()); + + event_loop.run(move |event, _, control_flow| { + *control_flow = ControlFlow::Wait; + + match event { + Event::WindowEvent { + event: WindowEvent::CloseRequested, + .. + } => *control_flow = ControlFlow::Exit, + Event::WindowEvent { + event: WindowEvent::ThemeChanged(theme), + window_id, + .. + } if window_id == window.id() => { + println!("Theme is changed: {:?}", theme) + } + _ => (), + } + }); +} diff --git a/src/event.rs b/src/event.rs index 544b94e88d..da237672c7 100644 --- a/src/event.rs +++ b/src/event.rs @@ -504,7 +504,7 @@ pub enum WindowEvent<'a> { /// /// ## Platform-specific /// - /// At the moment this is only supported on Windows. + /// - **iOS / Android / X11 / Wayland:** Unsupported. ThemeChanged(Theme), /// The window has been occluded (completely hidden from view). diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index eedcb3652f..ae01870ea2 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -138,14 +138,6 @@ pub trait WindowBuilderExtWayland { /// For details about application ID conventions, see the /// [Desktop Entry Spec](https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id) fn with_name(self, general: impl Into, instance: impl Into) -> Self; - - /// Build window with certain decoration [`Theme`] - /// - /// You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme. - /// Possible values for env variable are: "dark" and light". - /// - /// When unspecified a theme is automatically selected. - fn with_wayland_csd_theme(self, theme: Theme) -> Self; } impl WindowBuilderExtWayland for WindowBuilder { @@ -154,12 +146,6 @@ impl WindowBuilderExtWayland for WindowBuilder { self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into())); self } - - #[inline] - fn with_wayland_csd_theme(mut self, theme: Theme) -> Self { - self.platform_specific.csd_theme = Some(theme); - self - } } /// Additional methods on `MonitorHandle` that are specific to Wayland. diff --git a/src/platform/windows.rs b/src/platform/windows.rs index d97c367811..e5d5f50797 100644 --- a/src/platform/windows.rs +++ b/src/platform/windows.rs @@ -6,7 +6,7 @@ use crate::{ event_loop::EventLoopBuilder, monitor::MonitorHandle, platform_impl::{Parent, WinIcon}, - window::{BadIcon, Icon, Theme, Window, WindowBuilder}, + window::{BadIcon, Icon, Window, WindowBuilder}, }; /// Window Handle type used by Win32 API @@ -136,9 +136,6 @@ pub trait WindowExtWindows { /// This sets `ICON_BIG`. A good ceiling here is 256x256. fn set_taskbar_icon(&self, taskbar_icon: Option); - /// Returns the current window theme. - fn theme(&self) -> Theme; - /// Whether to show or hide the window icon in the taskbar. fn set_skip_taskbar(&self, skip: bool); @@ -169,11 +166,6 @@ impl WindowExtWindows for Window { self.window.set_taskbar_icon(taskbar_icon) } - #[inline] - fn theme(&self) -> Theme { - self.window.theme() - } - #[inline] fn set_skip_taskbar(&self, skip: bool) { self.window.set_skip_taskbar(skip) @@ -232,9 +224,6 @@ pub trait WindowBuilderExtWindows { /// See for more information. fn with_drag_and_drop(self, flag: bool) -> WindowBuilder; - /// Forces a theme or uses the system settings if `None` was provided. - fn with_theme(self, theme: Option) -> WindowBuilder; - /// Whether show or hide the window icon in the taskbar. fn with_skip_taskbar(self, skip: bool) -> WindowBuilder; @@ -282,12 +271,6 @@ impl WindowBuilderExtWindows for WindowBuilder { self } - #[inline] - fn with_theme(mut self, theme: Option) -> WindowBuilder { - self.platform_specific.preferred_theme = theme; - self - } - #[inline] fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder { self.platform_specific.skip_taskbar = skip; diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 7f1b876abd..5f402cf9db 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -24,7 +24,7 @@ use crate::{ error, event::{self, VirtualKeyCode}, event_loop::{self, ControlFlow}, - window::{self, CursorGrabMode}, + window::{self, CursorGrabMode, Theme}, }; static CONFIG: Lazy> = Lazy::new(|| { @@ -852,6 +852,10 @@ impl Window { pub fn content_rect(&self) -> Rect { ndk_glue::content_rect() } + + pub fn theme(&self) -> Option { + None + } } #[derive(Default, Clone, Debug)] diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 18a54a8d4a..2333d60fca 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -23,7 +23,8 @@ use crate::{ monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle, }, window::{ - CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, }, }; @@ -341,6 +342,11 @@ impl Inner { pub fn raw_display_handle(&self) -> RawDisplayHandle { RawDisplayHandle::UiKit(UiKitDisplayHandle::empty()) } + + pub fn theme(&self) -> Option { + warn!("`Window::theme` is ignored on iOS"); + None + } } pub struct Window { diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index 5948e87641..4e5f7610cf 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -31,8 +31,6 @@ pub use self::x11::XNotSupported; use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError}; #[cfg(feature = "x11")] use crate::platform::x11::XlibErrorHook; -#[cfg(feature = "wayland")] -use crate::window::Theme; use crate::{ dpi::{PhysicalPosition, PhysicalSize, Position, Size}, error::{ExternalError, NotSupportedError, OsError as RootOsError}, @@ -41,7 +39,7 @@ use crate::{ ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW, }, icon::Icon, - window::{CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes}, + window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes}, }; pub(crate) use crate::icon::RgbaIcon as PlatformIcon; @@ -104,8 +102,6 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub x11_window_types: Vec, #[cfg(feature = "x11")] pub gtk_theme_variant: Option, - #[cfg(feature = "wayland")] - pub csd_theme: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -126,8 +122,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { x11_window_types: vec![XWindowType::Normal], #[cfg(feature = "x11")] gtk_theme_variant: None, - #[cfg(feature = "wayland")] - csd_theme: None, } } } @@ -590,6 +584,11 @@ impl Window { pub fn raw_display_handle(&self) -> RawDisplayHandle { x11_or_wayland!(match self; Window(window) => window.raw_display_handle()) } + + #[inline] + pub fn theme(&self) -> Option { + x11_or_wayland!(match self; Window(window) => window.theme()) + } } /// Hooks for X11 errors. diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index cd70288e99..18b84afa2a 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -170,7 +170,7 @@ impl Window { // Set CSD frame config from theme if specified, // otherwise use upstream automatic selection. #[cfg(feature = "sctk-adwaita")] - if let Some(theme) = platform_attributes.csd_theme.or_else(|| { + if let Some(theme) = attributes.preferred_theme.or_else(|| { std::env::var(WAYLAND_CSD_THEME_ENV_VAR) .ok() .and_then(|s| s.as_str().try_into().ok()) @@ -619,6 +619,11 @@ impl Window { self.window_requests.lock().unwrap().push(request); self.event_loop_awakener.ping(); } + + #[inline] + pub fn theme(&self) -> Option { + None + } } impl Drop for Window { diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 7f68510548..2c600a81be 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -20,7 +20,7 @@ use crate::{ Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError, PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode, }, - window::{CursorGrabMode, CursorIcon, Icon, UserAttentionType, WindowAttributes}, + window::{CursorGrabMode, CursorIcon, Icon, Theme, UserAttentionType, WindowAttributes}, }; use super::{ @@ -1546,4 +1546,9 @@ impl UnownedWindow { display_handle.screen = self.screen_id; RawDisplayHandle::Xlib(display_handle) } + + #[inline] + pub fn theme(&self) -> Option { + None + } } diff --git a/src/platform_impl/macos/appkit/appearance.rs b/src/platform_impl/macos/appkit/appearance.rs new file mode 100644 index 0000000000..c13abbff02 --- /dev/null +++ b/src/platform_impl/macos/appkit/appearance.rs @@ -0,0 +1,29 @@ +use objc2::foundation::{NSArray, NSObject, NSString}; +use objc2::rc::{Id, Shared}; +use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; + +extern_class!( + #[derive(Debug, PartialEq, Eq, Hash)] + pub(crate) struct NSAppearance; + + unsafe impl ClassType for NSAppearance { + type Super = NSObject; + } +); + +type NSAppearanceName = NSString; + +extern_methods!( + unsafe impl NSAppearance { + pub fn appearanceNamed(name: &NSAppearanceName) -> Id { + unsafe { msg_send_id![Self::class(), appearanceNamed: name] } + } + + pub fn bestMatchFromAppearancesWithNames( + &self, + appearances: &NSArray, + ) -> Id { + unsafe { msg_send_id![self, bestMatchFromAppearancesWithNames: appearances,] } + } + } +); diff --git a/src/platform_impl/macos/appkit/application.rs b/src/platform_impl/macos/appkit/application.rs index f75b9a2e48..56c760fdb6 100644 --- a/src/platform_impl/macos/appkit/application.rs +++ b/src/platform_impl/macos/appkit/application.rs @@ -4,7 +4,7 @@ use objc2::runtime::Object; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; use objc2::{Encode, Encoding}; -use super::{NSEvent, NSMenu, NSResponder, NSWindow}; +use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -82,6 +82,13 @@ extern_methods!( #[sel(setMainMenu:)] pub fn setMainMenu(&self, menu: &NSMenu); + pub fn effectiveAppearance(&self) -> Id { + unsafe { msg_send_id![self, effectiveAppearance] } + } + + #[sel(setAppearance:)] + pub fn setAppearance(&self, appearance: &NSAppearance); + #[sel(run)] pub unsafe fn run(&self); } diff --git a/src/platform_impl/macos/appkit/mod.rs b/src/platform_impl/macos/appkit/mod.rs index 7aee6335b5..8c9dc17176 100644 --- a/src/platform_impl/macos/appkit/mod.rs +++ b/src/platform_impl/macos/appkit/mod.rs @@ -11,6 +11,7 @@ #![allow(clippy::enum_variant_names)] #![allow(non_upper_case_globals)] +mod appearance; mod application; mod button; mod color; @@ -28,6 +29,7 @@ mod version; mod view; mod window; +pub(crate) use self::appearance::NSAppearance; pub(crate) use self::application::{ NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions, NSRequestUserAttentionType, diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 9d20d9ceb0..8723c2cb6b 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -29,7 +29,8 @@ use crate::{ Fullscreen, OsError, }, window::{ - CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, }, }; use core_graphics::display::{CGDisplay, CGPoint}; @@ -38,12 +39,12 @@ use objc2::foundation::{ is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString, }; use objc2::rc::{autoreleasepool, Id, Owned, Shared}; -use objc2::{declare_class, msg_send, msg_send_id, ClassType}; +use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType}; use super::appkit::{ - NSApp, NSAppKitVersion, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, - NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen, NSWindow, - NSWindowButton, NSWindowLevel, NSWindowStyleMask, NSWindowTitleVisibility, + NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType, + NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen, + NSWindow, NSWindowButton, NSWindowLevel, NSWindowStyleMask, NSWindowTitleVisibility, }; #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] @@ -149,6 +150,7 @@ pub struct SharedState { /// bar in exclusive fullscreen but want to restore the original options when /// transitioning back to borderless fullscreen. save_presentation_opts: Option, + pub current_theme: Option, } impl SharedState { @@ -391,6 +393,18 @@ impl WinitWindow { unsafe { NSFilenamesPboardType }.copy() ])); + match attrs.preferred_theme { + Some(theme) => { + set_ns_theme(theme); + let mut state = this.shared_state.lock().unwrap(); + state.current_theme = Some(theme); + } + None => { + let mut state = this.shared_state.lock().unwrap(); + state.current_theme = Some(get_ns_theme()); + } + } + let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.is_some()); // Set fullscreen mode after we setup everything @@ -1095,6 +1109,12 @@ impl WinitWindow { util::set_style_mask_sync(self, current_style_mask & (!mask)); } } + + #[inline] + pub fn theme(&self) -> Option { + let state = self.shared_state.lock().unwrap(); + state.current_theme + } } impl WindowExtMacOS for WinitWindow { @@ -1186,3 +1206,33 @@ impl WindowExtMacOS for WinitWindow { self.setHasShadow(has_shadow) } } + +pub(super) fn get_ns_theme() -> Theme { + let app = NSApp(); + let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] }; + if !has_theme { + return Theme::Light; + } + let appearance = app.effectiveAppearance(); + let name = appearance.bestMatchFromAppearancesWithNames(&NSArray::from_slice(&[ + NSString::from_str("NSAppearanceNameAqua"), + NSString::from_str("NSAppearanceNameDarkAqua"), + ])); + match &*name.to_string() { + "NSAppearanceNameDarkAqua" => Theme::Dark, + _ => Theme::Light, + } +} + +fn set_ns_theme(theme: Theme) { + let app = NSApp(); + let has_theme: bool = unsafe { msg_send![&app, respondsToSelector: sel!(effectiveAppearance)] }; + if has_theme { + let name = match theme { + Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"), + Theme::Light => NSString::from_str("NSAppearanceNameAqua"), + }; + let appearance = NSAppearance::appearanceNamed(&name); + app.setAppearance(&appearance); + } +} diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index da3af38970..539c626424 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -4,7 +4,7 @@ use objc2::declare::{Ivar, IvarDrop}; use objc2::foundation::{NSArray, NSObject, NSString}; use objc2::rc::{autoreleasepool, Id, Shared}; use objc2::runtime::Object; -use objc2::{declare_class, msg_send, msg_send_id, sel, ClassType}; +use objc2::{class, declare_class, msg_send, msg_send_id, sel, ClassType}; use super::appkit::{ NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, @@ -16,7 +16,7 @@ use crate::{ app_state::AppState, event::{EventProxy, EventWrapper}, util, - window::WinitWindow, + window::{get_ns_theme, WinitWindow}, Fullscreen, }, window::WindowId, @@ -69,6 +69,22 @@ declare_class!( this.emit_static_scale_factor_changed_event(); } this.window.setDelegate(Some(this)); + + // Enable theme change event + let notification_center: Id = + unsafe { msg_send_id![class!(NSDistributedNotificationCenter), defaultCenter] }; + let notification_name = + NSString::from_str("AppleInterfaceThemeChangedNotification"); + let _: () = unsafe { + msg_send![ + ¬ification_center, + addObserver: &*this + selector: sel!(effectiveAppearanceDidChange:) + name: &*notification_name + object: ptr::null::() + ] + }; + this }) } @@ -349,6 +365,34 @@ declare_class!( .contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible), )) } + + // Observe theme change + #[sel(effectiveAppearanceDidChange:)] + fn effective_appearance_did_change(&self, sender: Option<&Object>) { + trace_scope!("Triggered `effectiveAppearanceDidChange:`"); + unsafe { + msg_send![ + self, + performSelectorOnMainThread: sel!(effectiveAppearanceDidChangedOnMainThread:), + withObject: sender, + waitUntilDone: false, + ] + } + } + + #[sel(effectiveAppearanceDidChangedOnMainThread:)] + fn effective_appearance_did_changed_on_main_thread(&self, _: Option<&Object>) { + let theme = get_ns_theme(); + let mut shared_state = self + .window + .lock_shared_state("effective_appearance_did_change"); + let current_theme = shared_state.current_theme; + shared_state.current_theme = Some(theme); + drop(shared_state); + if current_theme != Some(theme) { + self.emit_event(WindowEvent::ThemeChanged(theme)); + } + } } ); diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 18be7bf844..3dc7a5dccf 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -3,7 +3,7 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE}; use crate::event; use crate::icon::Icon; use crate::window::{ - CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes, WindowId as RootWI, + CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowId as RootWI, }; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle}; @@ -373,6 +373,11 @@ impl Window { pub fn raw_display_handle(&self) -> RawDisplayHandle { RawDisplayHandle::Web(WebDisplayHandle::empty()) } + + #[inline] + pub fn theme(&self) -> Option { + None + } } impl Drop for Window { diff --git a/src/platform_impl/windows/mod.rs b/src/platform_impl/windows/mod.rs index 1b40fd2003..4e0a07a839 100644 --- a/src/platform_impl/windows/mod.rs +++ b/src/platform_impl/windows/mod.rs @@ -19,7 +19,6 @@ pub(self) use crate::platform_impl::Fullscreen; use crate::event::DeviceId as RootDeviceId; use crate::icon::Icon; -use crate::window::Theme; #[derive(Clone)] pub enum Parent { @@ -35,7 +34,6 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub taskbar_icon: Option, pub no_redirection_bitmap: bool, pub drag_and_drop: bool, - pub preferred_theme: Option, pub skip_taskbar: bool, pub decoration_shadow: bool, } @@ -48,7 +46,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { taskbar_icon: None, no_redirection_bitmap: false, drag_and_drop: true, - preferred_theme: None, skip_taskbar: false, decoration_shadow: false, } diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 47fdb5e2b5..cb57a3ca8d 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -694,8 +694,8 @@ impl Window { } #[inline] - pub fn theme(&self) -> Theme { - self.window_state_lock().current_theme + pub fn theme(&self) -> Option { + Some(self.window_state_lock().current_theme) } #[inline] @@ -781,7 +781,7 @@ impl<'a, T: 'static> InitData<'a, T> { // If the system theme is dark, we need to set the window theme now // before we update the window flags (and possibly show the // window for the first time). - let current_theme = try_theme(window, self.pl_attribs.preferred_theme); + let current_theme = try_theme(window, self.attributes.preferred_theme); let window_state = { let window_state = WindowState::new( @@ -789,7 +789,7 @@ impl<'a, T: 'static> InitData<'a, T> { self.pl_attribs.taskbar_icon.clone(), scale_factor, current_theme, - self.pl_attribs.preferred_theme, + self.attributes.preferred_theme, ); let window_state = Arc::new(Mutex::new(window_state)); WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| { diff --git a/src/window.rs b/src/window.rs index e1333405d9..65e652d8b3 100644 --- a/src/window.rs +++ b/src/window.rs @@ -134,6 +134,7 @@ pub(crate) struct WindowAttributes { pub decorations: bool, pub always_on_top: bool, pub window_icon: Option, + pub preferred_theme: Option, pub resize_increments: Option, } @@ -154,6 +155,7 @@ impl Default for WindowAttributes { decorations: true, always_on_top: false, window_icon: None, + preferred_theme: None, resize_increments: None, } } @@ -335,6 +337,23 @@ impl WindowBuilder { self } + /// Sets a specific theme for the window. + /// + /// If `None` is provided, the window will use the system theme. + /// + /// The default is `None`. + /// + /// ## Platform-specific + /// + /// - **Wayland:** This control only CSD. You can also use `WINIT_WAYLAND_CSD_THEME` env variable to set the theme. + /// Possible values for env variable are: "dark" and light". + /// - **iOS / Android / Web / x11:** Ignored. + #[inline] + pub fn with_theme(mut self, theme: Option) -> Self { + self.window.preferred_theme = theme; + self + } + /// Build window with resize increments hint. /// /// The default is `None`. @@ -923,6 +942,16 @@ impl Window { pub fn request_user_attention(&self, request_type: Option) { self.window.request_user_attention(request_type) } + + /// Returns the current window theme. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Web / x11:** Unsupported. + #[inline] + pub fn theme(&self) -> Option { + self.window.theme() + } } /// Cursor functions.