diff --git a/src/changelog/unreleased.md b/src/changelog/unreleased.md index e8b7b1c21e..399c353ac0 100644 --- a/src/changelog/unreleased.md +++ b/src/changelog/unreleased.md @@ -63,6 +63,8 @@ changelog entry. and `Serialize` on many types. - Add `MonitorHandle::current_video_mode()`. - Add basic iOS IME support. The soft keyboard can now be shown using `Window::set_ime_allowed`. +- 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. ### Changed diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 6121dbf0c6..d36d5694e9 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -150,6 +150,12 @@ pub trait WindowExtMacOS { /// Getter for the [`WindowExtMacOS::set_option_as_alt`]. fn option_as_alt(&self) -> OptionAsAlt; + + /// Disable the Menu Bar and Dock in Borderless Fullscreen mode. Useful for games. + fn set_borderless_game(&self, borderless_game: bool); + + /// Getter for the [`WindowExtMacOS::set_borderless_game`]. + fn is_borderless_game(&self) -> bool; } impl WindowExtMacOS for dyn Window + '_ { @@ -236,6 +242,18 @@ impl WindowExtMacOS for dyn Window + '_ { let window = self.as_any().downcast_ref::().unwrap(); window.maybe_wait_on_main(|w| w.option_as_alt()) } + + #[inline] + fn set_borderless_game(&self, borderless_game: bool) { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.set_borderless_game(borderless_game)) + } + + #[inline] + fn is_borderless_game(&self) -> bool { + let window = self.as_any().downcast_ref::().unwrap(); + window.maybe_wait_on_main(|w| w.is_borderless_game()) + } } /// Corresponds to `NSApplicationActivationPolicy`. @@ -287,6 +305,8 @@ pub trait WindowAttributesExtMacOS { /// /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set. 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; } impl WindowAttributesExtMacOS for WindowAttributes { @@ -355,6 +375,12 @@ impl WindowAttributesExtMacOS for WindowAttributes { self.platform_specific.option_as_alt = option_as_alt; self } + + #[inline] + fn with_borderless_game(mut self, borderless_game: bool) -> Self { + self.platform_specific.borderless_game = borderless_game; + 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 6e58171952..7263b4d295 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -56,6 +56,7 @@ pub struct PlatformSpecificWindowAttributes { pub accepts_first_mouse: bool, pub tabbing_identifier: Option, pub option_as_alt: OptionAsAlt, + pub borderless_game: bool, } impl Default for PlatformSpecificWindowAttributes { @@ -73,6 +74,7 @@ impl Default for PlatformSpecificWindowAttributes { accepts_first_mouse: true, tabbing_identifier: None, option_as_alt: Default::default(), + borderless_game: false, } } } @@ -121,6 +123,7 @@ pub(crate) struct State { standard_frame: Cell>, is_simple_fullscreen: Cell, saved_style: Cell>, + is_borderless_game: Cell, } declare_class!( @@ -731,6 +734,7 @@ impl WindowDelegate { standard_frame: Cell::new(None), is_simple_fullscreen: Cell::new(false), saved_style: Cell::new(None), + is_borderless_game: Cell::new(attrs.platform_specific.borderless_game), }); let delegate: Retained = unsafe { msg_send_id![super(delegate), init] }; @@ -1417,7 +1421,7 @@ impl WindowDelegate { } match (old_fullscreen, fullscreen) { - (None, Some(_)) => { + (None, Some(fullscreen)) => { // `toggleFullScreen` doesn't work if the `StyleMask` is none, so we // set a normal style temporarily. The previous state will be // restored in `WindowDelegate::window_did_exit_fullscreen`. @@ -1427,6 +1431,17 @@ impl WindowDelegate { self.set_style_mask(required); self.ivars().saved_style.set(Some(curr_mask)); } + + // In borderless games, we want to disable the dock and menu bar + // by setting the presentation options. We do this here rather than in + // `window:willUseFullScreenPresentationOptions` because for some reason + // the menu bar remains interactable despite being hidden. + if self.is_borderless_game() && matches!(fullscreen, Fullscreen::Borderless(_)) { + let presentation_options = NSApplicationPresentationOptions::NSApplicationPresentationHideDock + | NSApplicationPresentationOptions::NSApplicationPresentationHideMenuBar; + app.setPresentationOptions(presentation_options); + } + toggle_fullscreen(self.window()); }, (Some(Fullscreen::Borderless(_)), None) => { @@ -1813,6 +1828,14 @@ impl WindowExtMacOS for WindowDelegate { fn option_as_alt(&self) -> OptionAsAlt { self.view().option_as_alt() } + + fn set_borderless_game(&self, borderless_game: bool) { + self.ivars().is_borderless_game.set(borderless_game); + } + + fn is_borderless_game(&self) -> bool { + self.ivars().is_borderless_game.get() + } } const DEFAULT_STANDARD_FRAME: NSRect =