From 3e9b80d47a9452b72e749a4890db23b92d65abc2 Mon Sep 17 00:00:00 2001 From: Valentine Briese Date: Fri, 25 Oct 2024 14:22:52 -0700 Subject: [PATCH] macOS: add ability to make titlebar unified (#3960) Adds `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar`, which allow you to use a larger titlebar style on macOS. --- Cargo.toml | 1 + src/changelog/unreleased.md | 2 + src/platform/macos.rs | 27 ++++++++++++ .../apple/appkit/window_delegate.rs | 41 ++++++++++++++++++- 4 files changed, 70 insertions(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 37932f9871..17377fb9c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -136,6 +136,7 @@ objc2-app-kit = { version = "0.2.2", features = [ "NSScreen", "NSTextInputClient", "NSTextInputContext", + "NSToolbar", "NSView", "NSWindow", "NSWindowScripting", diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index 0a3458fc58..a504fd595f 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -64,6 +64,8 @@ changelog entry. - Add a `standard_key_binding` method to the `ApplicationHandlerExtMacOS` trait. This allows handling of standard keybindings such as "go to end of line" on macOS. - On macOS, add `WindowExtMacOS::set_borderless_game` and `WindowAttributesExtMacOS::with_borderless_game` to fully disable the menu bar and dock in Borderless Fullscreen as commonly done in games. +- On macOS, add `WindowExtMacOS::set_unified_titlebar` and `WindowAttributesExtMacOS::with_unified_titlebar` + to use a larger style of titlebar. - Add `WindowId::into_raw()` and `from_raw()`. - Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId` and `position` to all pointer events as part of the pointer event overhaul. diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 977327d5e4..beeee0148d 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -157,6 +157,13 @@ pub trait WindowExtMacOS { /// Getter for the [`WindowExtMacOS::set_borderless_game`]. fn is_borderless_game(&self) -> bool; + + /// Makes the titlebar bigger, effectively adding more space around the + /// window controls if the titlebar is invisible. + fn set_unified_titlebar(&self, unified_titlebar: bool); + + /// Getter for the [`WindowExtMacOS::set_unified_titlebar`]. + fn unified_titlebar(&self) -> bool; } impl WindowExtMacOS for dyn Window + '_ { @@ -255,6 +262,18 @@ impl WindowExtMacOS for dyn Window + '_ { let window = self.as_any().downcast_ref::().unwrap(); window.maybe_wait_on_main(|w| w.is_borderless_game()) } + + #[inline] + fn set_unified_titlebar(&self, unified_titlebar: bool) { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar)) + } + + #[inline] + fn unified_titlebar(&self) -> bool { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.unified_titlebar()) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -308,6 +327,8 @@ pub trait WindowAttributesExtMacOS { fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. fn with_borderless_game(self, borderless_game: bool) -> Self; + /// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set. + fn with_unified_titlebar(self, unified_titlebar: bool) -> Self; } impl WindowAttributesExtMacOS for WindowAttributes { @@ -382,6 +403,12 @@ impl WindowAttributesExtMacOS for WindowAttributes { self.platform_specific.borderless_game = borderless_game; self } + + #[inline] + fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self { + self.platform_specific.unified_titlebar = unified_titlebar; + self + } } pub trait EventLoopBuilderExtMacOS { diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index e5652ba2f8..490a283e26 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -15,9 +15,10 @@ use objc2_app_kit::{ NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, - NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, + NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, + NSWindowToolbarStyle, }; use objc2_foundation::{ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey, @@ -57,6 +58,7 @@ pub struct PlatformSpecificWindowAttributes { pub tabbing_identifier: Option, pub option_as_alt: OptionAsAlt, pub borderless_game: bool, + pub unified_titlebar: bool, } impl Default for PlatformSpecificWindowAttributes { @@ -75,6 +77,7 @@ impl Default for PlatformSpecificWindowAttributes { tabbing_identifier: None, option_as_alt: Default::default(), borderless_game: false, + unified_titlebar: false, } } } @@ -616,6 +619,14 @@ fn new_window( if attrs.platform_specific.movable_by_window_background { window.setMovableByWindowBackground(true); } + if attrs.platform_specific.unified_titlebar { + unsafe { + // The toolbar style is ignored if there is no toolbar, so it is + // necessary to add one. + window.setToolbar(Some(&NSToolbar::new(mtm))); + window.setToolbarStyle(NSWindowToolbarStyle::Unified); + } + } if !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) { if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) { @@ -1837,6 +1848,34 @@ impl WindowExtMacOS for WindowDelegate { fn is_borderless_game(&self) -> bool { self.ivars().is_borderless_game.get() } + + fn set_unified_titlebar(&self, unified_titlebar: bool) { + let window = self.window(); + + if unified_titlebar { + let mtm = MainThreadMarker::from(self); + + unsafe { + // The toolbar style is ignored if there is no toolbar, so it is + // necessary to add one. + window.setToolbar(Some(&NSToolbar::new(mtm))); + window.setToolbarStyle(NSWindowToolbarStyle::Unified); + } + } else { + unsafe { + window.setToolbar(None); + window.setToolbarStyle(NSWindowToolbarStyle::Automatic); + } + } + } + + fn unified_titlebar(&self) -> bool { + let window = self.window(); + + unsafe { + window.toolbar().is_some() && window.toolbarStyle() == NSWindowToolbarStyle::Unified + } + } } const DEFAULT_STANDARD_FRAME: NSRect =