mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2025-01-26 03:36:32 +11:00
Rework theme API
This commit adds support for theming on macOS and also unifies the system theme handling across platforms.
This commit is contained in:
parent
4f06cfcf5b
commit
92fdf5ba85
20 changed files with 256 additions and 60 deletions
|
@ -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, 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 Wayland, if not otherwise specified use upstream automatic CSD theme selection.
|
||||||
- On X11, added `WindowExtX11::with_parent` to create child windows.
|
- 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
|
# 0.27.4
|
||||||
|
|
||||||
|
|
|
@ -130,6 +130,7 @@ If your PR makes notable changes to Winit's features, please update this section
|
||||||
* Hidden titlebar buttons
|
* Hidden titlebar buttons
|
||||||
* Full-size content view
|
* Full-size content view
|
||||||
* Accepts first mouse
|
* Accepts first mouse
|
||||||
|
* Set a preferred theme and get current theme.
|
||||||
|
|
||||||
### Unix
|
### Unix
|
||||||
* Window urgency
|
* Window urgency
|
||||||
|
|
40
examples/theme.rs
Normal file
40
examples/theme.rs
Normal file
|
@ -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)
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
|
@ -504,7 +504,7 @@ pub enum WindowEvent<'a> {
|
||||||
///
|
///
|
||||||
/// ## Platform-specific
|
/// ## Platform-specific
|
||||||
///
|
///
|
||||||
/// At the moment this is only supported on Windows.
|
/// - **iOS / Android / X11 / Wayland:** Unsupported.
|
||||||
ThemeChanged(Theme),
|
ThemeChanged(Theme),
|
||||||
|
|
||||||
/// The window has been occluded (completely hidden from view).
|
/// The window has been occluded (completely hidden from view).
|
||||||
|
|
|
@ -138,14 +138,6 @@ pub trait WindowBuilderExtWayland {
|
||||||
/// For details about application ID conventions, see the
|
/// 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)
|
/// [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;
|
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 {
|
impl WindowBuilderExtWayland for WindowBuilder {
|
||||||
|
@ -154,12 +146,6 @@ impl WindowBuilderExtWayland for WindowBuilder {
|
||||||
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
self.platform_specific.name = Some(ApplicationName::new(general.into(), instance.into()));
|
||||||
self
|
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.
|
/// Additional methods on `MonitorHandle` that are specific to Wayland.
|
||||||
|
|
|
@ -6,7 +6,7 @@ use crate::{
|
||||||
event_loop::EventLoopBuilder,
|
event_loop::EventLoopBuilder,
|
||||||
monitor::MonitorHandle,
|
monitor::MonitorHandle,
|
||||||
platform_impl::{Parent, WinIcon},
|
platform_impl::{Parent, WinIcon},
|
||||||
window::{BadIcon, Icon, Theme, Window, WindowBuilder},
|
window::{BadIcon, Icon, Window, WindowBuilder},
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Window Handle type used by Win32 API
|
/// Window Handle type used by Win32 API
|
||||||
|
@ -136,9 +136,6 @@ pub trait WindowExtWindows {
|
||||||
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
/// This sets `ICON_BIG`. A good ceiling here is 256x256.
|
||||||
fn set_taskbar_icon(&self, taskbar_icon: Option<Icon>);
|
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.
|
/// Whether to show or hide the window icon in the taskbar.
|
||||||
fn set_skip_taskbar(&self, skip: bool);
|
fn set_skip_taskbar(&self, skip: bool);
|
||||||
|
|
||||||
|
@ -169,11 +166,6 @@ impl WindowExtWindows for Window {
|
||||||
self.window.set_taskbar_icon(taskbar_icon)
|
self.window.set_taskbar_icon(taskbar_icon)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn theme(&self) -> Theme {
|
|
||||||
self.window.theme()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn set_skip_taskbar(&self, skip: bool) {
|
fn set_skip_taskbar(&self, skip: bool) {
|
||||||
self.window.set_skip_taskbar(skip)
|
self.window.set_skip_taskbar(skip)
|
||||||
|
@ -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.
|
/// 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;
|
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.
|
/// Whether show or hide the window icon in the taskbar.
|
||||||
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
fn with_skip_taskbar(self, skip: bool) -> WindowBuilder;
|
||||||
|
|
||||||
|
@ -282,12 +271,6 @@ impl WindowBuilderExtWindows for WindowBuilder {
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn with_theme(mut self, theme: Option<Theme>) -> WindowBuilder {
|
|
||||||
self.platform_specific.preferred_theme = theme;
|
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
|
fn with_skip_taskbar(mut self, skip: bool) -> WindowBuilder {
|
||||||
self.platform_specific.skip_taskbar = skip;
|
self.platform_specific.skip_taskbar = skip;
|
||||||
|
|
|
@ -24,7 +24,7 @@ use crate::{
|
||||||
error,
|
error,
|
||||||
event::{self, VirtualKeyCode},
|
event::{self, VirtualKeyCode},
|
||||||
event_loop::{self, ControlFlow},
|
event_loop::{self, ControlFlow},
|
||||||
window::{self, CursorGrabMode},
|
window::{self, CursorGrabMode, Theme},
|
||||||
};
|
};
|
||||||
|
|
||||||
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
|
static CONFIG: Lazy<RwLock<Configuration>> = Lazy::new(|| {
|
||||||
|
@ -852,6 +852,10 @@ impl Window {
|
||||||
pub fn content_rect(&self) -> Rect {
|
pub fn content_rect(&self) -> Rect {
|
||||||
ndk_glue::content_rect()
|
ndk_glue::content_rect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default, Clone, Debug)]
|
#[derive(Default, Clone, Debug)]
|
||||||
|
|
|
@ -23,7 +23,8 @@ use crate::{
|
||||||
monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||||
},
|
},
|
||||||
window::{
|
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 {
|
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||||
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
RawDisplayHandle::UiKit(UiKitDisplayHandle::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
warn!("`Window::theme` is ignored on iOS");
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Window {
|
pub struct Window {
|
||||||
|
|
|
@ -31,8 +31,6 @@ pub use self::x11::XNotSupported;
|
||||||
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
use self::x11::{ffi::XVisualInfo, util::WindowType as XWindowType, XConnection, XError};
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
use crate::platform::x11::XlibErrorHook;
|
use crate::platform::x11::XlibErrorHook;
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
use crate::window::Theme;
|
|
||||||
use crate::{
|
use crate::{
|
||||||
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
dpi::{PhysicalPosition, PhysicalSize, Position, Size},
|
||||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||||
|
@ -41,7 +39,7 @@ use crate::{
|
||||||
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
ControlFlow, DeviceEventFilter, EventLoopClosed, EventLoopWindowTarget as RootELW,
|
||||||
},
|
},
|
||||||
icon::Icon,
|
icon::Icon,
|
||||||
window::{CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes},
|
window::{CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
pub(crate) use crate::icon::RgbaIcon as PlatformIcon;
|
||||||
|
@ -104,8 +102,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub x11_window_types: Vec<XWindowType>,
|
pub x11_window_types: Vec<XWindowType>,
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
pub gtk_theme_variant: Option<String>,
|
pub gtk_theme_variant: Option<String>,
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
pub csd_theme: Option<Theme>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowBuilderAttributes {
|
impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
|
@ -126,8 +122,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
x11_window_types: vec![XWindowType::Normal],
|
x11_window_types: vec![XWindowType::Normal],
|
||||||
#[cfg(feature = "x11")]
|
#[cfg(feature = "x11")]
|
||||||
gtk_theme_variant: None,
|
gtk_theme_variant: None,
|
||||||
#[cfg(feature = "wayland")]
|
|
||||||
csd_theme: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -590,6 +584,11 @@ impl Window {
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||||
x11_or_wayland!(match self; Window(window) => window.raw_display_handle())
|
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.
|
/// Hooks for X11 errors.
|
||||||
|
|
|
@ -170,7 +170,7 @@ impl Window {
|
||||||
// Set CSD frame config from theme if specified,
|
// Set CSD frame config from theme if specified,
|
||||||
// otherwise use upstream automatic selection.
|
// otherwise use upstream automatic selection.
|
||||||
#[cfg(feature = "sctk-adwaita")]
|
#[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)
|
std::env::var(WAYLAND_CSD_THEME_ENV_VAR)
|
||||||
.ok()
|
.ok()
|
||||||
.and_then(|s| s.as_str().try_into().ok())
|
.and_then(|s| s.as_str().try_into().ok())
|
||||||
|
@ -619,6 +619,11 @@ impl Window {
|
||||||
self.window_requests.lock().unwrap().push(request);
|
self.window_requests.lock().unwrap().push(request);
|
||||||
self.event_loop_awakener.ping();
|
self.event_loop_awakener.ping();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Window {
|
impl Drop for Window {
|
||||||
|
|
|
@ -20,7 +20,7 @@ use crate::{
|
||||||
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
|
Fullscreen, MonitorHandle as PlatformMonitorHandle, OsError,
|
||||||
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
PlatformSpecificWindowBuilderAttributes, VideoMode as PlatformVideoMode,
|
||||||
},
|
},
|
||||||
window::{CursorGrabMode, CursorIcon, Icon, UserAttentionType, WindowAttributes},
|
window::{CursorGrabMode, CursorIcon, Icon, Theme, UserAttentionType, WindowAttributes},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
|
@ -1546,4 +1546,9 @@ impl UnownedWindow {
|
||||||
display_handle.screen = self.screen_id;
|
display_handle.screen = self.screen_id;
|
||||||
RawDisplayHandle::Xlib(display_handle)
|
RawDisplayHandle::Xlib(display_handle)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
29
src/platform_impl/macos/appkit/appearance.rs
Normal file
29
src/platform_impl/macos/appkit/appearance.rs
Normal file
|
@ -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,] }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
|
@ -4,7 +4,7 @@ use objc2::runtime::Object;
|
||||||
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
|
||||||
use objc2::{Encode, Encoding};
|
use objc2::{Encode, Encoding};
|
||||||
|
|
||||||
use super::{NSEvent, NSMenu, NSResponder, NSWindow};
|
use super::{NSAppearance, NSEvent, NSMenu, NSResponder, NSWindow};
|
||||||
|
|
||||||
extern_class!(
|
extern_class!(
|
||||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||||
|
@ -82,6 +82,13 @@ extern_methods!(
|
||||||
#[sel(setMainMenu:)]
|
#[sel(setMainMenu:)]
|
||||||
pub fn setMainMenu(&self, menu: &NSMenu);
|
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)]
|
#[sel(run)]
|
||||||
pub unsafe fn run(&self);
|
pub unsafe fn run(&self);
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#![allow(clippy::enum_variant_names)]
|
#![allow(clippy::enum_variant_names)]
|
||||||
#![allow(non_upper_case_globals)]
|
#![allow(non_upper_case_globals)]
|
||||||
|
|
||||||
|
mod appearance;
|
||||||
mod application;
|
mod application;
|
||||||
mod button;
|
mod button;
|
||||||
mod color;
|
mod color;
|
||||||
|
@ -28,6 +29,7 @@ mod version;
|
||||||
mod view;
|
mod view;
|
||||||
mod window;
|
mod window;
|
||||||
|
|
||||||
|
pub(crate) use self::appearance::NSAppearance;
|
||||||
pub(crate) use self::application::{
|
pub(crate) use self::application::{
|
||||||
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
NSApp, NSApplication, NSApplicationActivationPolicy, NSApplicationPresentationOptions,
|
||||||
NSRequestUserAttentionType,
|
NSRequestUserAttentionType,
|
||||||
|
|
|
@ -29,7 +29,8 @@ use crate::{
|
||||||
Fullscreen, OsError,
|
Fullscreen, OsError,
|
||||||
},
|
},
|
||||||
window::{
|
window::{
|
||||||
CursorGrabMode, CursorIcon, UserAttentionType, WindowAttributes, WindowId as RootWindowId,
|
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes,
|
||||||
|
WindowId as RootWindowId,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
use core_graphics::display::{CGDisplay, CGPoint};
|
use core_graphics::display::{CGDisplay, CGPoint};
|
||||||
|
@ -38,12 +39,12 @@ use objc2::foundation::{
|
||||||
is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString,
|
is_main_thread, CGFloat, NSArray, NSCopying, NSObject, NSPoint, NSRect, NSSize, NSString,
|
||||||
};
|
};
|
||||||
use objc2::rc::{autoreleasepool, Id, Owned, Shared};
|
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::{
|
use super::appkit::{
|
||||||
NSApp, NSAppKitVersion, NSApplicationPresentationOptions, NSBackingStoreType, NSColor,
|
NSApp, NSAppKitVersion, NSAppearance, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||||
NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen, NSWindow,
|
NSColor, NSCursor, NSFilenamesPboardType, NSRequestUserAttentionType, NSResponder, NSScreen,
|
||||||
NSWindowButton, NSWindowLevel, NSWindowStyleMask, NSWindowTitleVisibility,
|
NSWindow, NSWindowButton, NSWindowLevel, NSWindowStyleMask, NSWindowTitleVisibility,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
#[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
|
/// bar in exclusive fullscreen but want to restore the original options when
|
||||||
/// transitioning back to borderless fullscreen.
|
/// transitioning back to borderless fullscreen.
|
||||||
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
save_presentation_opts: Option<NSApplicationPresentationOptions>,
|
||||||
|
pub current_theme: Option<Theme>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SharedState {
|
impl SharedState {
|
||||||
|
@ -391,6 +393,18 @@ impl WinitWindow {
|
||||||
unsafe { NSFilenamesPboardType }.copy()
|
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());
|
let delegate = WinitWindowDelegate::new(&this, attrs.fullscreen.is_some());
|
||||||
|
|
||||||
// Set fullscreen mode after we setup everything
|
// Set fullscreen mode after we setup everything
|
||||||
|
@ -1095,6 +1109,12 @@ impl WinitWindow {
|
||||||
util::set_style_mask_sync(self, current_style_mask & (!mask));
|
util::set_style_mask_sync(self, current_style_mask & (!mask));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
let state = self.shared_state.lock().unwrap();
|
||||||
|
state.current_theme
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WindowExtMacOS for WinitWindow {
|
impl WindowExtMacOS for WinitWindow {
|
||||||
|
@ -1186,3 +1206,33 @@ impl WindowExtMacOS for WinitWindow {
|
||||||
self.setHasShadow(has_shadow)
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@ use objc2::declare::{Ivar, IvarDrop};
|
||||||
use objc2::foundation::{NSArray, NSObject, NSString};
|
use objc2::foundation::{NSArray, NSObject, NSString};
|
||||||
use objc2::rc::{autoreleasepool, Id, Shared};
|
use objc2::rc::{autoreleasepool, Id, Shared};
|
||||||
use objc2::runtime::Object;
|
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::{
|
use super::appkit::{
|
||||||
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
|
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
|
||||||
|
@ -16,7 +16,7 @@ use crate::{
|
||||||
app_state::AppState,
|
app_state::AppState,
|
||||||
event::{EventProxy, EventWrapper},
|
event::{EventProxy, EventWrapper},
|
||||||
util,
|
util,
|
||||||
window::WinitWindow,
|
window::{get_ns_theme, WinitWindow},
|
||||||
Fullscreen,
|
Fullscreen,
|
||||||
},
|
},
|
||||||
window::WindowId,
|
window::WindowId,
|
||||||
|
@ -69,6 +69,22 @@ declare_class!(
|
||||||
this.emit_static_scale_factor_changed_event();
|
this.emit_static_scale_factor_changed_event();
|
||||||
}
|
}
|
||||||
this.window.setDelegate(Some(this));
|
this.window.setDelegate(Some(this));
|
||||||
|
|
||||||
|
// Enable theme change event
|
||||||
|
let notification_center: Id<Object, Shared> =
|
||||||
|
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::<Object>()
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
this
|
this
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -349,6 +365,34 @@ declare_class!(
|
||||||
.contains(NSWindowOcclusionState::NSWindowOcclusionStateVisible),
|
.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));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@ use crate::error::{ExternalError, NotSupportedError, OsError as RootOE};
|
||||||
use crate::event;
|
use crate::event;
|
||||||
use crate::icon::Icon;
|
use crate::icon::Icon;
|
||||||
use crate::window::{
|
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};
|
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, WebDisplayHandle, WebWindowHandle};
|
||||||
|
@ -373,6 +373,11 @@ impl Window {
|
||||||
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
pub fn raw_display_handle(&self) -> RawDisplayHandle {
|
||||||
RawDisplayHandle::Web(WebDisplayHandle::empty())
|
RawDisplayHandle::Web(WebDisplayHandle::empty())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Drop for Window {
|
impl Drop for Window {
|
||||||
|
|
|
@ -19,7 +19,6 @@ pub(self) use crate::platform_impl::Fullscreen;
|
||||||
|
|
||||||
use crate::event::DeviceId as RootDeviceId;
|
use crate::event::DeviceId as RootDeviceId;
|
||||||
use crate::icon::Icon;
|
use crate::icon::Icon;
|
||||||
use crate::window::Theme;
|
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub enum Parent {
|
pub enum Parent {
|
||||||
|
@ -35,7 +34,6 @@ pub struct PlatformSpecificWindowBuilderAttributes {
|
||||||
pub taskbar_icon: Option<Icon>,
|
pub taskbar_icon: Option<Icon>,
|
||||||
pub no_redirection_bitmap: bool,
|
pub no_redirection_bitmap: bool,
|
||||||
pub drag_and_drop: bool,
|
pub drag_and_drop: bool,
|
||||||
pub preferred_theme: Option<Theme>,
|
|
||||||
pub skip_taskbar: bool,
|
pub skip_taskbar: bool,
|
||||||
pub decoration_shadow: bool,
|
pub decoration_shadow: bool,
|
||||||
}
|
}
|
||||||
|
@ -48,7 +46,6 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
|
||||||
taskbar_icon: None,
|
taskbar_icon: None,
|
||||||
no_redirection_bitmap: false,
|
no_redirection_bitmap: false,
|
||||||
drag_and_drop: true,
|
drag_and_drop: true,
|
||||||
preferred_theme: None,
|
|
||||||
skip_taskbar: false,
|
skip_taskbar: false,
|
||||||
decoration_shadow: false,
|
decoration_shadow: false,
|
||||||
}
|
}
|
||||||
|
|
|
@ -694,8 +694,8 @@ impl Window {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn theme(&self) -> Theme {
|
pub fn theme(&self) -> Option<Theme> {
|
||||||
self.window_state_lock().current_theme
|
Some(self.window_state_lock().current_theme)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
#[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
|
// If the system theme is dark, we need to set the window theme now
|
||||||
// before we update the window flags (and possibly show the
|
// before we update the window flags (and possibly show the
|
||||||
// window for the first time).
|
// 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 = {
|
||||||
let window_state = WindowState::new(
|
let window_state = WindowState::new(
|
||||||
|
@ -789,7 +789,7 @@ impl<'a, T: 'static> InitData<'a, T> {
|
||||||
self.pl_attribs.taskbar_icon.clone(),
|
self.pl_attribs.taskbar_icon.clone(),
|
||||||
scale_factor,
|
scale_factor,
|
||||||
current_theme,
|
current_theme,
|
||||||
self.pl_attribs.preferred_theme,
|
self.attributes.preferred_theme,
|
||||||
);
|
);
|
||||||
let window_state = Arc::new(Mutex::new(window_state));
|
let window_state = Arc::new(Mutex::new(window_state));
|
||||||
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
WindowState::set_window_flags(window_state.lock().unwrap(), window, |f| {
|
||||||
|
|
|
@ -134,6 +134,7 @@ pub(crate) struct WindowAttributes {
|
||||||
pub decorations: bool,
|
pub decorations: bool,
|
||||||
pub always_on_top: bool,
|
pub always_on_top: bool,
|
||||||
pub window_icon: Option<Icon>,
|
pub window_icon: Option<Icon>,
|
||||||
|
pub preferred_theme: Option<Theme>,
|
||||||
pub resize_increments: Option<Size>,
|
pub resize_increments: Option<Size>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,6 +155,7 @@ impl Default for WindowAttributes {
|
||||||
decorations: true,
|
decorations: true,
|
||||||
always_on_top: false,
|
always_on_top: false,
|
||||||
window_icon: None,
|
window_icon: None,
|
||||||
|
preferred_theme: None,
|
||||||
resize_increments: None,
|
resize_increments: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -335,6 +337,23 @@ impl WindowBuilder {
|
||||||
self
|
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<Theme>) -> Self {
|
||||||
|
self.window.preferred_theme = theme;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
/// Build window with resize increments hint.
|
/// Build window with resize increments hint.
|
||||||
///
|
///
|
||||||
/// The default is `None`.
|
/// The default is `None`.
|
||||||
|
@ -923,6 +942,16 @@ impl Window {
|
||||||
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
pub fn request_user_attention(&self, request_type: Option<UserAttentionType>) {
|
||||||
self.window.request_user_attention(request_type)
|
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<Theme> {
|
||||||
|
self.window.theme()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Cursor functions.
|
/// Cursor functions.
|
||||||
|
|
Loading…
Add table
Reference in a new issue