From 28e34c2e1b0a514148071b206ed1702a94d596e8 Mon Sep 17 00:00:00 2001 From: Amr Bashir Date: Tue, 29 Nov 2022 11:05:51 +0200 Subject: [PATCH] Add `Window::set_theme` (#2553) * Add `Window::set_theme` * typo * fix linux build * fix wayland * review changes * update docs * update changelog * pin `image` dep * suppport falling back to system default * fix linux * default to dark on macOS and x11 * fix `setAppearance` definition * add macOS notes * update docs * Update CHANGELOG.md Co-authored-by: Markus Siglreithmaier * update doc * Revert "pin `image` dep" This reverts commit 7517f7c5065b4089ca146ce8799dab445ec32068. * Update theme example with Window::set_theme * Fix Window::theme getter on macOS Co-authored-by: Markus Siglreithmaier Co-authored-by: Mads Marquart --- CHANGELOG.md | 4 ++- examples/theme.rs | 33 ++++++++++++++++++- src/platform/wayland.rs | 18 ---------- src/platform/x11.rs | 9 ----- src/platform_impl/android/mod.rs | 2 ++ src/platform_impl/ios/window.rs | 5 +++ src/platform_impl/linux/mod.rs | 9 ++--- src/platform_impl/linux/wayland/window/mod.rs | 10 +++--- .../linux/wayland/window/shim.rs | 27 ++++++++------- src/platform_impl/linux/x11/window.rs | 22 ++++++++++--- src/platform_impl/macos/appkit/application.rs | 2 +- src/platform_impl/macos/window.rs | 24 +++++++++----- src/platform_impl/web/window.rs | 3 ++ src/platform_impl/windows/window.rs | 5 +++ src/window.rs | 19 ++++++++++- 15 files changed, 127 insertions(+), 65 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 39877a8c..b142e2a7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,9 @@ And please only add new entries to the top of this list, right below the `# Unre # Unreleased -- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling +- On Windows, macOS, X11 and Wayland, add `Window::set_theme`. +- **Breaking:** Remove `WindowExtWayland::wayland_set_csd_theme` and `WindowBuilderExtX11::with_gtk_theme_variant`. +- On Windows, revert window background to an empty brush to avoid white flashes when changing scaling. - **Breaking:** Removed `Window::set_always_on_top` and related APIs in favor of `Window::set_window_level`. - On Windows, MacOS and X11, add always on bottom APIs. - On Windows, fix the value in `MouseButton::Other`. diff --git a/examples/theme.rs b/examples/theme.rs index a642447b..6cbbd8b9 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -2,7 +2,7 @@ use simple_logger::SimpleLogger; use winit::{ - event::{Event, WindowEvent}, + event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::{Theme, WindowBuilder}, }; @@ -18,6 +18,10 @@ fn main() { .unwrap(); println!("Initial theme: {:?}", window.theme()); + println!("debugging keys:"); + println!(" (A) Automatic theme"); + println!(" (L) Light theme"); + println!(" (D) Dark theme"); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -34,6 +38,33 @@ fn main() { } if window_id == window.id() => { println!("Theme is changed: {:?}", theme) } + Event::WindowEvent { + event: + WindowEvent::KeyboardInput { + input: + KeyboardInput { + virtual_keycode: Some(key), + state: ElementState::Pressed, + .. + }, + .. + }, + .. + } => match key { + VirtualKeyCode::A => { + println!("Theme was: {:?}", window.theme()); + window.set_theme(None); + } + VirtualKeyCode::L => { + println!("Theme was: {:?}", window.theme()); + window.set_theme(Some(Theme::Light)); + } + VirtualKeyCode::D => { + println!("Theme was: {:?}", window.theme()); + window.set_theme(Some(Theme::Dark)); + } + _ => (), + }, _ => (), } }); diff --git a/src/platform/wayland.rs b/src/platform/wayland.rs index ae01870e..313b4d00 100644 --- a/src/platform/wayland.rs +++ b/src/platform/wayland.rs @@ -88,14 +88,6 @@ pub trait WindowExtWayland { /// /// The pointer will become invalid when the [`Window`] is destroyed. fn wayland_display(&self) -> Option<*mut raw::c_void>; - - /// Updates [`Theme`] of window decorations. - /// - /// 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 wayland_set_csd_theme(&self, config: Theme); } impl WindowExtWayland for Window { @@ -116,16 +108,6 @@ impl WindowExtWayland for Window { _ => None, } } - - #[inline] - fn wayland_set_csd_theme(&self, theme: Theme) { - #[allow(clippy::single_match)] - match self.window { - LinuxWindow::Wayland(ref w) => w.set_csd_theme(theme), - #[cfg(feature = "x11")] - _ => (), - } - } } /// Additional methods on [`WindowBuilder`] that are specific to Wayland. diff --git a/src/platform/x11.rs b/src/platform/x11.rs index 37c52424..721d0563 100644 --- a/src/platform/x11.rs +++ b/src/platform/x11.rs @@ -190,9 +190,6 @@ pub trait WindowBuilderExtX11 { /// Build window with `_NET_WM_WINDOW_TYPE` hints; defaults to `Normal`. Only relevant on X11. fn with_x11_window_type(self, x11_window_type: Vec) -> Self; - /// Build window with `_GTK_THEME_VARIANT` hint set to the specified value. Currently only relevant on X11. - fn with_gtk_theme_variant(self, variant: String) -> Self; - /// Build window with base size hint. Only implemented on X11. /// /// ``` @@ -248,12 +245,6 @@ impl WindowBuilderExtX11 for WindowBuilder { self } - #[inline] - fn with_gtk_theme_variant(mut self, variant: String) -> Self { - self.platform_specific.gtk_theme_variant = Some(variant); - self - } - #[inline] fn with_base_size>(mut self, base_size: S) -> Self { self.platform_specific.base_size = Some(base_size.into()); diff --git a/src/platform_impl/android/mod.rs b/src/platform_impl/android/mod.rs index 4315f6a4..18f7c249 100644 --- a/src/platform_impl/android/mod.rs +++ b/src/platform_impl/android/mod.rs @@ -1041,6 +1041,8 @@ impl Window { self.app.content_rect() } + pub fn set_theme(&self, _theme: Option) {} + pub fn theme(&self) -> Option { None } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index b3d645d6..7413888b 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -348,6 +348,11 @@ impl Inner { None } + #[inline] + pub fn set_theme(&self, _theme: Option) { + warn!("`Window::set_theme` is ignored on iOS"); + } + pub fn title(&self) -> String { warn!("`Window::title` is ignored on iOS"); String::new() diff --git a/src/platform_impl/linux/mod.rs b/src/platform_impl/linux/mod.rs index a7f718a3..32b661d9 100644 --- a/src/platform_impl/linux/mod.rs +++ b/src/platform_impl/linux/mod.rs @@ -100,8 +100,6 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub override_redirect: bool, #[cfg(feature = "x11")] pub x11_window_types: Vec, - #[cfg(feature = "x11")] - pub gtk_theme_variant: Option, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -120,8 +118,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes { override_redirect: false, #[cfg(feature = "x11")] x11_window_types: vec![XWindowType::Normal], - #[cfg(feature = "x11")] - gtk_theme_variant: None, } } } @@ -585,6 +581,11 @@ impl Window { x11_or_wayland!(match self; Window(window) => window.raw_display_handle()) } + #[inline] + pub fn set_theme(&self, theme: Option) { + x11_or_wayland!(match self; Window(window) => window.set_theme(theme)) + } + #[inline] pub fn theme(&self) -> Option { x11_or_wayland!(match self; Window(window) => window.theme()) diff --git a/src/platform_impl/linux/wayland/window/mod.rs b/src/platform_impl/linux/wayland/window/mod.rs index e30d0873..cfc0a8a4 100644 --- a/src/platform_impl/linux/wayland/window/mod.rs +++ b/src/platform_impl/linux/wayland/window/mod.rs @@ -439,11 +439,6 @@ impl Window { self.decorated.load(Ordering::Relaxed) } - #[inline] - pub fn set_csd_theme(&self, theme: Theme) { - self.send_request(WindowRequest::CsdThemeVariant(theme)); - } - #[inline] pub fn set_minimized(&self, minimized: bool) { // You can't unminimize the window on Wayland. @@ -620,6 +615,11 @@ impl Window { self.event_loop_awakener.ping(); } + #[inline] + pub fn set_theme(&self, theme: Option) { + self.send_request(WindowRequest::Theme(theme)); + } + #[inline] pub fn theme(&self) -> Option { None diff --git a/src/platform_impl/linux/wayland/window/shim.rs b/src/platform_impl/linux/wayland/window/shim.rs index de0e3f2c..0b4fedf9 100644 --- a/src/platform_impl/linux/wayland/window/shim.rs +++ b/src/platform_impl/linux/wayland/window/shim.rs @@ -59,9 +59,6 @@ pub enum WindowRequest { /// Request decorations change. Decorate(bool), - /// Request decorations change. - CsdThemeVariant(Theme), - /// Make the window resizeable. Resizeable(bool), @@ -96,6 +93,9 @@ pub enum WindowRequest { /// Window should be closed. Close, + + /// Change window theme. + Theme(Option), } // The window update comming from the compositor. @@ -464,15 +464,6 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { let window_request = window_user_requests.get_mut(window_id).unwrap(); window_request.refresh_frame = true; } - #[cfg(feature = "sctk-adwaita")] - WindowRequest::CsdThemeVariant(theme) => { - window_handle.window.set_frame_config(theme.into()); - - let window_requst = window_user_requests.get_mut(window_id).unwrap(); - window_requst.refresh_frame = true; - } - #[cfg(not(feature = "sctk-adwaita"))] - WindowRequest::CsdThemeVariant(_) => {} WindowRequest::Resizeable(resizeable) => { window_handle.window.set_resizable(resizeable); @@ -537,6 +528,18 @@ pub fn handle_window_requests(winit_state: &mut WinitState) { let event_sink = &mut winit_state.event_sink; event_sink.push_window_event(WindowEvent::Destroyed, *window_id); } + WindowRequest::Theme(_theme) => { + #[cfg(feature = "sctk-adwaita")] + { + window_handle.window.set_frame_config(match _theme { + Some(theme) => theme.into(), + None => sctk_adwaita::FrameConfig::auto(), + }); + + let window_requst = window_user_requests.get_mut(window_id).unwrap(); + window_requst.refresh_frame = true; + } + } }; } } diff --git a/src/platform_impl/linux/x11/window.rs b/src/platform_impl/linux/x11/window.rs index 6c6f28ba..835379f3 100644 --- a/src/platform_impl/linux/x11/window.rs +++ b/src/platform_impl/linux/x11/window.rs @@ -301,6 +301,10 @@ impl UnownedWindow { .set_decorations_inner(window_attrs.decorations) .queue(); + if let Some(theme) = window_attrs.preferred_theme { + window.set_theme_inner(Some(theme)).queue(); + } + { // Enable drag and drop (TODO: extend API to make this toggleable) unsafe { @@ -359,10 +363,6 @@ impl UnownedWindow { window.set_window_types(pl_attribs.x11_window_types).queue(); - if let Some(variant) = pl_attribs.gtk_theme_variant { - window.set_gtk_theme_variant(variant).queue(); - } - // set size hints { let mut min_inner_size = window_attrs @@ -565,9 +565,14 @@ impl UnownedWindow { ) } - fn set_gtk_theme_variant(&self, variant: String) -> util::Flusher<'_> { + pub fn set_theme_inner(&self, theme: Option) -> util::Flusher<'_> { let hint_atom = unsafe { self.xconn.get_atom_unchecked(b"_GTK_THEME_VARIANT\0") }; let utf8_atom = unsafe { self.xconn.get_atom_unchecked(b"UTF8_STRING\0") }; + let variant = match theme { + Some(Theme::Dark) => "dark", + Some(Theme::Light) => "light", + None => "dark", + }; let variant = CString::new(variant).expect("`_GTK_THEME_VARIANT` contained null byte"); self.xconn.change_property( self.xwindow, @@ -578,6 +583,13 @@ impl UnownedWindow { ) } + #[inline] + pub fn set_theme(&self, theme: Option) { + self.set_theme_inner(theme) + .flush() + .expect("Failed to change window theme") + } + fn set_netwm( &self, operation: util::StateOperation, diff --git a/src/platform_impl/macos/appkit/application.rs b/src/platform_impl/macos/appkit/application.rs index 56c760fd..5895a24d 100644 --- a/src/platform_impl/macos/appkit/application.rs +++ b/src/platform_impl/macos/appkit/application.rs @@ -87,7 +87,7 @@ extern_methods!( } #[sel(setAppearance:)] - pub fn setAppearance(&self, appearance: &NSAppearance); + pub fn setAppearance(&self, appearance: Option<&NSAppearance>); #[sel(run)] pub unsafe fn run(&self); diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 4a420108..e4c2f5da 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -399,7 +399,7 @@ impl WinitWindow { match attrs.preferred_theme { Some(theme) => { - set_ns_theme(theme); + set_ns_theme(Some(theme)); let mut state = this.shared_state.lock().unwrap(); state.current_theme = Some(theme); } @@ -1125,6 +1125,12 @@ impl WinitWindow { state.current_theme } + #[inline] + pub fn set_theme(&self, theme: Option) { + set_ns_theme(theme); + self.lock_shared_state("set_theme").current_theme = theme.or_else(|| Some(get_ns_theme())); + } + #[inline] pub fn set_content_protected(&self, protected: bool) { self.setSharingType(if protected { @@ -1254,15 +1260,17 @@ pub(super) fn get_ns_theme() -> Theme { } } -fn set_ns_theme(theme: Theme) { +fn set_ns_theme(theme: Option) { 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); + let appearance = theme.map(|t| { + let name = match t { + Theme::Dark => NSString::from_str("NSAppearanceNameDarkAqua"), + Theme::Light => NSString::from_str("NSAppearanceNameAqua"), + }; + NSAppearance::appearanceNamed(&name) + }); + app.setAppearance(appearance.as_ref().map(|a| a.as_ref())); } } diff --git a/src/platform_impl/web/window.rs b/src/platform_impl/web/window.rs index 75f53c71..2ec46331 100644 --- a/src/platform_impl/web/window.rs +++ b/src/platform_impl/web/window.rs @@ -375,6 +375,9 @@ impl Window { RawDisplayHandle::Web(WebDisplayHandle::empty()) } + #[inline] + pub fn set_theme(&self, _theme: Option) {} + #[inline] pub fn theme(&self) -> Option { None diff --git a/src/platform_impl/windows/window.rs b/src/platform_impl/windows/window.rs index 9e86f21d..ceb6c34d 100644 --- a/src/platform_impl/windows/window.rs +++ b/src/platform_impl/windows/window.rs @@ -701,6 +701,11 @@ impl Window { }); } + #[inline] + pub fn set_theme(&self, theme: Option) { + try_theme(self.window.0, theme); + } + #[inline] pub fn theme(&self) -> Option { Some(self.window_state_lock().current_theme) diff --git a/src/window.rs b/src/window.rs index 442084ff..e00a73ac 100644 --- a/src/window.rs +++ b/src/window.rs @@ -349,8 +349,10 @@ impl WindowBuilder { /// /// ## Platform-specific /// + /// - **macOS:** This is an app-wide setting. /// - **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". + /// - **x11:** Build window with `_GTK_THEME_VARIANT` hint set to `dark` or `light`. /// - **iOS / Android / Web / x11:** Ignored. #[inline] pub fn with_theme(mut self, theme: Option) -> Self { @@ -963,11 +965,26 @@ impl Window { self.window.request_user_attention(request_type) } + /// Sets the current window theme. Use `None` to fallback to system default. + /// + /// ## Platform-specific + /// + /// - **macOS:** This is an app-wide setting. + /// - **Wayland:** 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. + /// -**x11:** Sets `_GTK_THEME_VARIANT` hint to `dark` or `light` and if `None` is used, it will default to [`Theme::Dark`]. + /// - **iOS / Android / Web / x11:** Unsupported. + #[inline] + pub fn set_theme(&self, theme: Option) { + self.window.set_theme(theme) + } + /// Returns the current window theme. /// /// ## Platform-specific /// - /// - **iOS / Android / Web / x11:** Unsupported. + /// - **macOS:** This is an app-wide setting. + /// - **iOS / Android / Web / Wayland / x11:** Unsupported. #[inline] pub fn theme(&self) -> Option { self.window.theme()