diff --git a/winit-appkit/src/lib.rs b/winit-appkit/src/lib.rs index 8548f8f73..3ff9c82f4 100644 --- a/winit-appkit/src/lib.rs +++ b/winit-appkit/src/lib.rs @@ -188,6 +188,25 @@ pub trait WindowExtMacOS { /// Getter for the [`WindowExtMacOS::set_unified_titlebar`]. fn unified_titlebar(&self) -> bool; + + /// Sets whether the window can be shown on the same Space as a fullscreen window. + /// + /// This corresponds to [`NSWindowCollectionBehaviorFullScreenAuxiliary`], and is useful + /// for floating palettes, inspectors and other secondary windows accompanying a fullscreen + /// window. Without it, ordering a new window on screen while another window of the + /// application is fullscreen on the active Space makes macOS switch Spaces or attempt + /// Split View tiling. + /// + /// A window marked as fullscreen auxiliary cannot itself enter (native) fullscreen; + /// [`Window::set_fullscreen`] will warn and do nothing. Call + /// `set_fullscreen_auxiliary(false)` first if you want to make the window fullscreen. + /// + /// [`NSWindowCollectionBehaviorFullScreenAuxiliary`]: https://developer.apple.com/documentation/appkit/nswindow/collectionbehavior-swift.struct/fullscreenauxiliary?language=objc + /// [`Window::set_fullscreen`]: winit_core::window::Window::set_fullscreen + fn set_fullscreen_auxiliary(&self, fullscreen_auxiliary: bool); + + /// Getter for the [`WindowExtMacOS::set_fullscreen_auxiliary`]. + fn fullscreen_auxiliary(&self) -> bool; } impl WindowExtMacOS for dyn Window + '_ { @@ -298,6 +317,18 @@ impl WindowExtMacOS for dyn Window + '_ { let window = self.cast_ref::().unwrap(); window.maybe_wait_on_main(|w| w.unified_titlebar()) } + + #[inline] + fn set_fullscreen_auxiliary(&self, fullscreen_auxiliary: bool) { + let window = self.cast_ref::().unwrap(); + window.maybe_wait_on_main(move |w| w.set_fullscreen_auxiliary(fullscreen_auxiliary)) + } + + #[inline] + fn fullscreen_auxiliary(&self) -> bool { + let window = self.cast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.fullscreen_auxiliary()) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -342,6 +373,7 @@ pub struct WindowAttributesMacOS { pub(crate) borderless_game: bool, pub(crate) unified_titlebar: bool, pub(crate) panel: bool, + pub(crate) fullscreen_auxiliary: bool, } impl WindowAttributesMacOS { @@ -449,6 +481,17 @@ impl WindowAttributesMacOS { self.panel = panel; self } + + /// See [`WindowExtMacOS::set_fullscreen_auxiliary`] for details on what this means if set. + /// + /// Contrary to the runtime setter, setting this attribute guarantees that the collection + /// behavior is already in place when the window is first ordered on screen, which is + /// required to avoid disturbing an active fullscreen Space. + #[inline] + pub fn with_fullscreen_auxiliary(mut self, fullscreen_auxiliary: bool) -> Self { + self.fullscreen_auxiliary = fullscreen_auxiliary; + self + } } impl Default for WindowAttributesMacOS { @@ -469,6 +512,7 @@ impl Default for WindowAttributesMacOS { borderless_game: false, unified_titlebar: false, panel: false, + fullscreen_auxiliary: false, } } } diff --git a/winit-appkit/src/window_delegate.rs b/winit-appkit/src/window_delegate.rs index 9a6515016..8bc14e3df 100644 --- a/winit-appkit/src/window_delegate.rs +++ b/winit-appkit/src/window_delegate.rs @@ -24,9 +24,9 @@ use objc2_app_kit::{ NSDraggingSession, NSDraggingSource, NSPasteboardTypeFileURL, NSPasteboardTypeHTML, NSPasteboardTypePNG, NSPasteboardTypeSound, NSPasteboardTypeString, NSPasteboardTypeTIFF, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, - NSWindow, NSWindowButton, NSWindowDelegate, NSWindowLevel, NSWindowOcclusionState, - NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, - NSWindowTitleVisibility, NSWindowToolbarStyle, + NSWindow, NSWindowButton, NSWindowCollectionBehavior, NSWindowDelegate, NSWindowLevel, + NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, + NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, }; use objc2_core_foundation::{CGFloat, CGPoint}; use objc2_core_graphics::{ @@ -829,6 +829,14 @@ fn new_window( if !macos_attrs.has_shadow { window.setHasShadow(false); } + if macos_attrs.fullscreen_auxiliary { + // This must happen before the window is ordered on screen (below, and in + // `WindowDelegate::new`), so that showing the window doesn't make macOS + // switch away from an active fullscreen Space or attempt Split View tiling. + window.setCollectionBehavior( + window.collectionBehavior() | NSWindowCollectionBehavior::FullScreenAuxiliary, + ); + } // Popups are positioned relative to their parent in `WindowDelegate::new`. if attrs.position.is_none() && !is_popup { window.center(); @@ -1646,6 +1654,20 @@ impl WindowDelegate { if self.ivars().is_simple_fullscreen.get() { return; } + if fullscreen.is_some() + && self + .window() + .collectionBehavior() + .contains(NSWindowCollectionBehavior::FullScreenAuxiliary) + { + // `toggleFullScreen:` is silently ignored on such windows, which would leave our + // internal fullscreen state out of sync, so bail out early instead. + warn!( + "cannot fullscreen a window marked as fullscreen auxiliary; call \ + `set_fullscreen_auxiliary(false)` first" + ); + return; + } if self.ivars().in_fullscreen_transition.get() { // We can't set fullscreen here. // Set fullscreen after transition. @@ -2245,6 +2267,24 @@ impl WindowExtMacOS for WindowDelegate { window.toolbar().is_some() && window.toolbarStyle() == NSWindowToolbarStyle::Unified } + + #[inline] + fn set_fullscreen_auxiliary(&self, fullscreen_auxiliary: bool) { + let window = self.window(); + let behavior = window.collectionBehavior(); + if fullscreen_auxiliary { + window + .setCollectionBehavior(behavior | NSWindowCollectionBehavior::FullScreenAuxiliary); + } else { + window + .setCollectionBehavior(behavior - NSWindowCollectionBehavior::FullScreenAuxiliary); + } + } + + #[inline] + fn fullscreen_auxiliary(&self) -> bool { + self.window().collectionBehavior().contains(NSWindowCollectionBehavior::FullScreenAuxiliary) + } } const DEFAULT_STANDARD_FRAME: NSRect = diff --git a/winit/src/changelog/unreleased.md b/winit/src/changelog/unreleased.md index 420859434..e2f7326ea 100644 --- a/winit/src/changelog/unreleased.md +++ b/winit/src/changelog/unreleased.md @@ -52,6 +52,11 @@ changelog entry. - On Wayland, added `HoldGesture` event for multi-finger hold gestures - On Wayland, added ext-background-effect-v1 support. - On Wayland, Windows and macOS, added native popups (`WindowType::Popup`). +- On macOS, add `WindowAttributesMacOS::with_fullscreen_auxiliary` and + `WindowExtMacOS::set_fullscreen_auxiliary` / `WindowExtMacOS::fullscreen_auxiliary`, allowing a + window to be shown on the same Space as a fullscreen window + (`NSWindowCollectionBehaviorFullScreenAuxiliary`) instead of triggering a Space switch or Split + View tiling. ### Changed