diff --git a/.changes/macos-accent-color.md b/.changes/macos-accent-color.md new file mode 100644 index 000000000..be6c28212 --- /dev/null +++ b/.changes/macos-accent-color.md @@ -0,0 +1,5 @@ +--- +"tao": minor +--- + +Add support and `WindowEvent` for accent color on macOS. diff --git a/examples/theme.rs b/examples/theme.rs index 4845284ca..7efa88d20 100644 --- a/examples/theme.rs +++ b/examples/theme.rs @@ -20,6 +20,7 @@ fn main() { .unwrap(); println!("Initial theme: {:?}", window.theme()); + println!("Initial accent color: {:?}", window.accent_color()); event_loop.run(move |event, _, control_flow| { *control_flow = ControlFlow::Wait; @@ -36,6 +37,12 @@ fn main() { } if window_id == window.id() => { println!("Theme is changed: {:?}", theme) } + Event::WindowEvent { + event: WindowEvent::AccentColorChanged(accent_color), + .. + } => { + println!("Accent color has changed: {:?}", accent_color) + } _ => (), } }); diff --git a/src/event.rs b/src/event.rs index 850f8b772..f3a8cf635 100644 --- a/src/event.rs +++ b/src/event.rs @@ -46,7 +46,7 @@ use crate::{ keyboard::{self, ModifiersState}, menu::{MenuId, MenuType}, platform_impl, - window::{Theme, WindowId}, + window::{AccentColor, Theme, WindowId}, }; /// Describes a generic event. @@ -482,6 +482,13 @@ pub enum WindowEvent<'a> { /// - **Linux / Android / iOS:** Unsupported ThemeChanged(Theme), + /// The system accent color has changed. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux / Windows:** Unsupported. + AccentColorChanged(Option), + /// The window decorations has been clicked. /// /// ## Platform-specific @@ -574,6 +581,7 @@ impl Clone for WindowEvent<'static> { }, Touch(touch) => Touch(*touch), ThemeChanged(theme) => ThemeChanged(*theme), + AccentColorChanged(color) => AccentColorChanged(*color), ScaleFactorChanged { .. } => { unreachable!("Static event can't be about scale factor changing") } @@ -661,6 +669,7 @@ impl<'a> WindowEvent<'a> { }), Touch(touch) => Some(Touch(touch)), ThemeChanged(theme) => Some(ThemeChanged(theme)), + AccentColorChanged(color) => Some(AccentColorChanged(color)), ScaleFactorChanged { .. } => None, DecorationsClick => Some(DecorationsClick), } diff --git a/src/platform_impl/macos/window.rs b/src/platform_impl/macos/window.rs index 1776c8cef..b7753f79c 100644 --- a/src/platform_impl/macos/window.rs +++ b/src/platform_impl/macos/window.rs @@ -35,7 +35,8 @@ use crate::{ OsError, }, window::{ - CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes, WindowId as RootWindowId, + AccentColor, CursorIcon, Fullscreen, Theme, UserAttentionType, WindowAttributes, + WindowId as RootWindowId, }, }; use cocoa::{ @@ -336,6 +337,32 @@ pub(super) fn set_ns_theme(theme: Theme) { } } +pub(super) fn get_ns_accent_color() -> Option { + unsafe { + let key_name = NSString::alloc(nil).init_str("AppleAccentColor"); + + let user_defaults: id = msg_send![class!(NSUserDefaults), standardUserDefaults]; + let color_obj: id = msg_send![user_defaults, objectForKey: key_name]; + + if color_obj == nil { + return None; + } + + let color_int: id = msg_send![user_defaults, integerForKey: key_name]; + + match color_int as i8 { + -1 => Some(AccentColor::Graphite), + 0 => Some(AccentColor::Red), + 1 => Some(AccentColor::Orange), + 2 => Some(AccentColor::Yellow), + 3 => Some(AccentColor::Green), + 4 => Some(AccentColor::Blue), + 5 => Some(AccentColor::Purple), + 6 => Some(AccentColor::Pink), + _ => None, + } + } +} struct WindowClass(*const Class); unsafe impl Send for WindowClass {} unsafe impl Sync for WindowClass {} @@ -402,6 +429,7 @@ pub struct SharedState { save_presentation_opts: Option, pub saved_desktop_display_mode: Option<(CGDisplay, CGDisplayMode)>, pub current_theme: Theme, + pub current_accent_color: Option, } impl SharedState { @@ -537,6 +565,11 @@ impl UnownedWindow { } } + { + let mut state = window.shared_state.lock().unwrap(); + state.current_accent_color = get_ns_accent_color(); + } + let delegate = new_delegate(&window, fullscreen.is_some()); // Set fullscreen mode after we setup everything @@ -1341,6 +1374,12 @@ impl UnownedWindow { state.current_theme } + #[inline] + pub fn accent_color(&self) -> Option { + let state = self.shared_state.lock().unwrap(); + state.current_accent_color + } + pub fn set_content_protection(&self, enabled: bool) { unsafe { let _: () = msg_send![*self.ns_window, setSharingType: !enabled as i32]; diff --git a/src/platform_impl/macos/window_delegate.rs b/src/platform_impl/macos/window_delegate.rs index 10450442f..e0761884e 100644 --- a/src/platform_impl/macos/window_delegate.rs +++ b/src/platform_impl/macos/window_delegate.rs @@ -27,7 +27,7 @@ use crate::{ event::{EventProxy, EventWrapper}, util::{self, IdRef}, view::ViewState, - window::{get_ns_theme, get_window_id, UnownedWindow}, + window::{get_ns_accent_color, get_ns_theme, get_window_id, UnownedWindow}, }, window::{Fullscreen, WindowId}, }; @@ -252,6 +252,10 @@ lazy_static! { sel!(effectiveAppearanceDidChangedOnMainThread:), effective_appearance_did_changed_on_main_thread as extern "C" fn(&Object, Sel, id), ); + decl.add_method( + sel!(accentColorChanged:), + accent_color_did_change as extern "C" fn(&Object, Sel, id), + ); decl.add_ivar::<*mut c_void>("taoState"); WindowDelegateClass(decl.register()) @@ -295,6 +299,17 @@ extern "C" fn init_with_tao(this: &Object, _sel: Sel, state: *mut c_void) -> id object: nil ]; + let notification_accent_color_name = + NSString::alloc(nil).init_str("AppleColorPreferencesChangedNotification"); + + let _: () = msg_send![ + notification_center, + addObserver: this + selector: sel!(accentColorChanged:) + name: notification_accent_color_name + object: nil + ]; + this } } @@ -659,3 +674,14 @@ extern "C" fn effective_appearance_did_changed_on_main_thread(this: &Object, _: }); trace!("Completed `effectiveAppearDidChange:`"); } + +extern "C" fn accent_color_did_change(this: &Object, _: Sel, _: id) { + with_state(this, |state| { + let accent_color = get_ns_accent_color(); + state.with_window(|window| { + let mut shared_state = window.shared_state.lock().unwrap(); + shared_state.current_accent_color = accent_color; + }); + state.emit_event(WindowEvent::AccentColorChanged(accent_color)); + }); +} diff --git a/src/window.rs b/src/window.rs index 26813e345..2a76d82fe 100644 --- a/src/window.rs +++ b/src/window.rs @@ -1062,6 +1062,17 @@ impl Window { self.window.theme() } + /// Returns the accent color of the system. + /// + /// ## Platform-specific + /// + /// - **iOS / Android / Linux / Windows:** Unsupported. + #[inline] + pub fn accent_color(&self) -> Option { + #[cfg(target_os = "macos")] + self.window.accent_color() + } + /// Prevents the window contents from being captured by other apps. /// /// ## Platform-specific @@ -1303,6 +1314,19 @@ pub enum Theme { Dark, } +#[non_exhaustive] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum AccentColor { + Graphite, + Red, + Orange, + Yellow, + Green, + Blue, + Purple, + Pink, +} + impl Default for Theme { fn default() -> Self { Theme::Light