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.
This commit is contained in:
Valentine Briese 2024-10-25 14:22:52 -07:00 committed by GitHub
parent c913cdab0b
commit 3e9b80d47a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 70 additions and 1 deletions

View file

@ -136,6 +136,7 @@ objc2-app-kit = { version = "0.2.2", features = [
"NSScreen", "NSScreen",
"NSTextInputClient", "NSTextInputClient",
"NSTextInputContext", "NSTextInputContext",
"NSToolbar",
"NSView", "NSView",
"NSWindow", "NSWindow",
"NSWindowScripting", "NSWindowScripting",

View file

@ -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. - 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` - 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. 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 `WindowId::into_raw()` and `from_raw()`.
- Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId` and `position` to all pointer - Add `PointerKind`, `PointerSource`, `ButtonSource`, `FingerId` and `position` to all pointer
events as part of the pointer event overhaul. events as part of the pointer event overhaul.

View file

@ -157,6 +157,13 @@ pub trait WindowExtMacOS {
/// Getter for the [`WindowExtMacOS::set_borderless_game`]. /// Getter for the [`WindowExtMacOS::set_borderless_game`].
fn is_borderless_game(&self) -> bool; 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 + '_ { impl WindowExtMacOS for dyn Window + '_ {
@ -255,6 +262,18 @@ impl WindowExtMacOS for dyn Window + '_ {
let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap(); let window = self.as_any().downcast_ref::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.is_borderless_game()) 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::<crate::platform_impl::Window>().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::<crate::platform_impl::Window>().unwrap();
window.maybe_wait_on_main(|w| w.unified_titlebar())
}
} }
/// Corresponds to `NSApplicationActivationPolicy`. /// Corresponds to `NSApplicationActivationPolicy`.
@ -308,6 +327,8 @@ pub trait WindowAttributesExtMacOS {
fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self; fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
/// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set. /// See [`WindowExtMacOS::set_borderless_game`] for details on what this means if set.
fn with_borderless_game(self, borderless_game: bool) -> Self; 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 { impl WindowAttributesExtMacOS for WindowAttributes {
@ -382,6 +403,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
self.platform_specific.borderless_game = borderless_game; self.platform_specific.borderless_game = borderless_game;
self self
} }
#[inline]
fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
self.platform_specific.unified_titlebar = unified_titlebar;
self
}
} }
pub trait EventLoopBuilderExtMacOS { pub trait EventLoopBuilderExtMacOS {

View file

@ -15,9 +15,10 @@ use objc2_app_kit::{
NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization,
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
NSRequestUserAttentionType, NSScreen, NSView, NSWindowButton, NSWindowDelegate, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSWindowButton, NSWindowDelegate,
NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode,
NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility,
NSWindowToolbarStyle,
}; };
use objc2_foundation::{ use objc2_foundation::{
ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey, ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSKeyValueChangeKey,
@ -57,6 +58,7 @@ pub struct PlatformSpecificWindowAttributes {
pub tabbing_identifier: Option<String>, pub tabbing_identifier: Option<String>,
pub option_as_alt: OptionAsAlt, pub option_as_alt: OptionAsAlt,
pub borderless_game: bool, pub borderless_game: bool,
pub unified_titlebar: bool,
} }
impl Default for PlatformSpecificWindowAttributes { impl Default for PlatformSpecificWindowAttributes {
@ -75,6 +77,7 @@ impl Default for PlatformSpecificWindowAttributes {
tabbing_identifier: None, tabbing_identifier: None,
option_as_alt: Default::default(), option_as_alt: Default::default(),
borderless_game: false, borderless_game: false,
unified_titlebar: false,
} }
} }
} }
@ -616,6 +619,14 @@ fn new_window(
if attrs.platform_specific.movable_by_window_background { if attrs.platform_specific.movable_by_window_background {
window.setMovableByWindowBackground(true); 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 !attrs.enabled_buttons.contains(WindowButtons::MAXIMIZE) {
if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) { if let Some(button) = window.standardWindowButton(NSWindowButton::NSWindowZoomButton) {
@ -1837,6 +1848,34 @@ impl WindowExtMacOS for WindowDelegate {
fn is_borderless_game(&self) -> bool { fn is_borderless_game(&self) -> bool {
self.ivars().is_borderless_game.get() 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 = const DEFAULT_STANDARD_FRAME: NSRect =