Skip to content

Commit

Permalink
Rework theme API
Browse files Browse the repository at this point in the history
This commit adds support for theming on macOS and
also unifies the system theme handling across platforms.
  • Loading branch information
keiya01 authored Oct 18, 2022
1 parent 4f06cfc commit 92fdf5b
Show file tree
Hide file tree
Showing 20 changed files with 256 additions and 60 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions FEATURES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
40 changes: 40 additions & 0 deletions examples/theme.rs
Original file line number Diff line number Diff line change
@@ -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)
}
_ => (),
}
});
}
2 changes: 1 addition & 1 deletion src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down
14 changes: 0 additions & 14 deletions src/platform/wayland.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<String>, instance: impl Into<String>) -> 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 {
Expand All @@ -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.
Expand Down
19 changes: 1 addition & 18 deletions src/platform/windows.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Icon>);

/// 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);

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -232,9 +224,6 @@ pub trait WindowBuilderExtWindows {
/// See <https://docs.microsoft.com/en-us/windows/win32/api/objbase/nf-objbase-coinitialize#remarks> 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<Theme>) -> WindowBuilder;

/// Whether show or hide the window icon in the taskbar.
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;

Expand Down Expand Up @@ -282,12 +271,6 @@ impl WindowBuilderExtWindows for WindowBuilder {
self
}

#[inline]
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
self.platform_specific.preferred_theme = theme;
self
}

#[inline]
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
self.platform_specific.skip_taskbar = skip;
Expand Down
6 changes: 5 additions & 1 deletion src/platform_impl/android/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use crate::{
error,
event::{self, VirtualKeyCode},
event_loop::{self, ControlFlow},
window::{self, CursorGrabMode},
window::{self, CursorGrabMode, Theme},
};

static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
Expand Down Expand Up @@ -852,6 +852,10 @@ impl Window {
pub fn content_rect(&self) -> Rect {
ndk_glue::content_rect()
}

pub fn theme(&self) -> Option<Theme> {
None
}
}

#[derive(Default, Clone, Debug)]
Expand Down
8 changes: 7 additions & 1 deletion src/platform_impl/ios/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
},
};

Expand Down Expand Up @@ -341,6 +342,11 @@ impl Inner {
pub fn raw_display_handle(&self) -> RawDisplayHandle {
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
}

pub fn theme(&self) -> Option<Theme> {
warn!("`Window::theme` is ignored on iOS");
None
}
}

pub struct Window {
Expand Down
13 changes: 6 additions & 7 deletions src/platform_impl/linux/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand All @@ -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;
Expand Down Expand Up @@ -104,8 +102,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub x11_window_types: Vec<XWindowType>,
#[cfg(feature = "x11")]
pub gtk_theme_variant: Option<String>,
#[cfg(feature = "wayland")]
pub csd_theme: Option<Theme>,
}

impl Default for PlatformSpecificWindowBuilderAttributes {
Expand All @@ -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,
}
}
}
Expand Down Expand Up @@ -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<Theme> {
x11_or_wayland!(match self; Window(window) => window.theme())
}
}

/// Hooks for X11 errors.
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/wayland/window/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -619,6 +619,11 @@ impl Window {
self.window_requests.lock().unwrap().push(request);
self.event_loop_awakener.ping();
}

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

impl Drop for Window {
Expand Down
7 changes: 6 additions & 1 deletion src/platform_impl/linux/x11/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::{
Expand Down Expand Up @@ -1546,4 +1546,9 @@ impl UnownedWindow {
display_handle.screen = self.screen_id;
RawDisplayHandle::Xlib(display_handle)
}

#[inline]
pub fn theme(&self) -> Option<Theme> {
None
}
}
29 changes: 29 additions & 0 deletions src/platform_impl/macos/appkit/appearance.rs
Original file line number Diff line number Diff line change
@@ -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<Self, Shared> {
unsafe { msg_send_id![Self::class(), appearanceNamed: name] }
}

pub fn bestMatchFromAppearancesWithNames(
&self,
appearances: &NSArray<NSAppearanceName>,
) -> Id<NSAppearanceName, Shared> {
unsafe { msg_send_id![self, bestMatchFromAppearancesWithNames: appearances,] }
}
}
);
9 changes: 8 additions & 1 deletion src/platform_impl/macos/appkit/application.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -82,6 +82,13 @@ extern_methods!(
#[sel(setMainMenu:)]
pub fn setMainMenu(&self, menu: &NSMenu);

pub fn effectiveAppearance(&self) -> Id<NSAppearance, Shared> {
unsafe { msg_send_id![self, effectiveAppearance] }
}

#[sel(setAppearance:)]
pub fn setAppearance(&self, appearance: &NSAppearance);

#[sel(run)]
pub unsafe fn run(&self);
}
Expand Down
2 changes: 2 additions & 0 deletions src/platform_impl/macos/appkit/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#![allow(clippy::enum_variant_names)]
#![allow(non_upper_case_globals)]

mod appearance;
mod application;
mod button;
mod color;
Expand All @@ -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,
Expand Down
Loading

0 comments on commit 92fdf5b

Please sign in to comment.