Implement ability to make non-activating window on macOS (#4035)
Fixes #3894
This commit is contained in:
parent
5835c9102e
commit
4d5e68c6e2
6 changed files with 86 additions and 37 deletions
|
|
@ -128,6 +128,7 @@ objc2-app-kit = { version = "0.2.2", features = [
|
|||
"NSMenu",
|
||||
"NSMenuItem",
|
||||
"NSOpenGLView",
|
||||
"NSPanel",
|
||||
"NSPasteboard",
|
||||
"NSResponder",
|
||||
"NSRunningApplication",
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ changelog entry.
|
|||
- Added `Window::surface_position`, which is the position of the surface inside the window.
|
||||
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
|
||||
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
|
||||
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`.
|
||||
|
||||
### Changed
|
||||
|
||||
|
|
|
|||
|
|
@ -332,6 +332,13 @@ pub trait WindowAttributesExtMacOS {
|
|||
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;
|
||||
/// Use [`NSPanel`] window with [`NonactivatingPanel`] window style mask instead of
|
||||
/// [`NSWindow`].
|
||||
///
|
||||
/// [`NSWindow`]: https://developer.apple.com/documentation/appkit/NSWindow?language=objc
|
||||
/// [`NSPanel`]: https://developer.apple.com/documentation/appkit/NSPanel?language=objc
|
||||
/// [`NonactivatingPanel`]: https://developer.apple.com/documentation/appkit/nswindow/stylemask-swift.struct/nonactivatingpanel?language=objc
|
||||
fn with_panel(self, panel: bool) -> Self;
|
||||
}
|
||||
|
||||
impl WindowAttributesExtMacOS for WindowAttributes {
|
||||
|
|
@ -412,6 +419,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
|
|||
self.platform_specific.unified_titlebar = unified_titlebar;
|
||||
self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn with_panel(mut self, panel: bool) -> Self {
|
||||
self.platform_specific.panel = panel;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
pub trait EventLoopBuilderExtMacOS {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use objc2::runtime::{AnyObject, Sel};
|
|||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{
|
||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||
NSTrackingRectTag, NSView,
|
||||
NSTrackingRectTag, NSView, NSWindow,
|
||||
};
|
||||
use objc2_foundation::{
|
||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
||||
|
|
@ -23,7 +23,7 @@ use super::event::{
|
|||
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
||||
scancode_to_physicalkey, KeyEventExtra,
|
||||
};
|
||||
use super::window::WinitWindow;
|
||||
use super::window::window_id;
|
||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||
use crate::event::{
|
||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||
|
|
@ -201,7 +201,7 @@ declare_class!(
|
|||
fn draw_rect(&self, _rect: NSRect) {
|
||||
trace_scope!("drawRect:");
|
||||
|
||||
self.ivars().app_state.handle_redraw(self.window().id());
|
||||
self.ivars().app_state.handle_redraw(window_id(&self.window()));
|
||||
|
||||
// This is a direct subclass of NSView, no need to call superclass' drawRect:
|
||||
}
|
||||
|
|
@ -426,7 +426,7 @@ declare_class!(
|
|||
}
|
||||
|
||||
// Send command action to user if they requested it.
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
if let Some(handler) = app.macos_handler() {
|
||||
handler.standard_key_binding(event_loop, window_id, command.name());
|
||||
|
|
@ -828,19 +828,12 @@ impl WinitView {
|
|||
this
|
||||
}
|
||||
|
||||
fn window(&self) -> Retained<WinitWindow> {
|
||||
let window = (**self).window().expect("view must be installed in a window");
|
||||
|
||||
if !window.isKindOfClass(WinitWindow::class()) {
|
||||
unreachable!("view installed in non-WinitWindow");
|
||||
}
|
||||
|
||||
// SAFETY: Just checked that the window is `WinitWindow`
|
||||
unsafe { Retained::cast(window) }
|
||||
fn window(&self) -> Retained<NSWindow> {
|
||||
(**self).window().expect("view must be installed in a window")
|
||||
}
|
||||
|
||||
fn queue_event(&self, event: WindowEvent) {
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(&self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
use dpi::{Position, Size};
|
||||
use objc2::rc::{autoreleasepool, Retained};
|
||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
||||
use objc2_app_kit::{NSResponder, NSWindow};
|
||||
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
|
||||
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||
|
||||
use super::event_loop::ActiveEventLoop;
|
||||
|
|
@ -16,7 +16,7 @@ use crate::window::{
|
|||
};
|
||||
|
||||
pub(crate) struct Window {
|
||||
window: MainThreadBound<Retained<WinitWindow>>,
|
||||
window: MainThreadBound<Retained<NSWindow>>,
|
||||
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||
}
|
||||
|
|
@ -360,8 +360,30 @@ declare_class!(
|
|||
}
|
||||
);
|
||||
|
||||
impl WinitWindow {
|
||||
pub(super) fn id(&self) -> WindowId {
|
||||
WindowId::from_raw(self as *const Self as usize)
|
||||
declare_class!(
|
||||
#[derive(Debug)]
|
||||
pub struct WinitPanel;
|
||||
|
||||
unsafe impl ClassType for WinitPanel {
|
||||
#[inherits(NSWindow, NSResponder, NSObject)]
|
||||
type Super = NSPanel;
|
||||
type Mutability = mutability::MainThreadOnly;
|
||||
const NAME: &'static str = "WinitPanel";
|
||||
}
|
||||
|
||||
impl DeclaredClass for WinitPanel {}
|
||||
|
||||
unsafe impl WinitPanel {
|
||||
// although NSPanel can become key window
|
||||
// it doesn't if window doesn't have NSWindowStyleMask::Titled
|
||||
#[method(canBecomeKeyWindow)]
|
||||
fn can_become_key_window(&self) -> bool {
|
||||
trace_scope!("canBecomeKeyWindow");
|
||||
true
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
pub(super) fn window_id(window: &NSWindow) -> WindowId {
|
||||
WindowId::from_raw(window as *const _ as usize)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ use objc2_app_kit::{
|
|||
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
||||
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
|
||||
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
|
||||
};
|
||||
|
|
@ -33,7 +33,7 @@ use super::cursor::cursor_from_icon;
|
|||
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||
use super::observer::RunLoop;
|
||||
use super::view::WinitView;
|
||||
use super::window::WinitWindow;
|
||||
use super::window::{window_id, WinitPanel, WinitWindow};
|
||||
use super::{ffi, Fullscreen, MonitorHandle};
|
||||
use crate::dpi::{
|
||||
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
||||
|
|
@ -62,6 +62,7 @@ pub struct PlatformSpecificWindowAttributes {
|
|||
pub option_as_alt: OptionAsAlt,
|
||||
pub borderless_game: bool,
|
||||
pub unified_titlebar: bool,
|
||||
pub panel: bool,
|
||||
}
|
||||
|
||||
impl Default for PlatformSpecificWindowAttributes {
|
||||
|
|
@ -81,6 +82,7 @@ impl Default for PlatformSpecificWindowAttributes {
|
|||
option_as_alt: Default::default(),
|
||||
borderless_game: false,
|
||||
unified_titlebar: false,
|
||||
panel: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +92,7 @@ pub(crate) struct State {
|
|||
/// Strong reference to the global application state.
|
||||
app_state: Rc<AppState>,
|
||||
|
||||
window: Retained<WinitWindow>,
|
||||
window: Retained<NSWindow>,
|
||||
|
||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||
//
|
||||
|
|
@ -501,7 +503,7 @@ fn new_window(
|
|||
app_state: &Rc<AppState>,
|
||||
attrs: &WindowAttributes,
|
||||
mtm: MainThreadMarker,
|
||||
) -> Option<Retained<WinitWindow>> {
|
||||
) -> Option<Retained<NSWindow>> {
|
||||
autoreleasepool(|_| {
|
||||
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
||||
Some(Fullscreen::Borderless(Some(monitor)))
|
||||
|
|
@ -584,16 +586,33 @@ fn new_window(
|
|||
// confusing issues with the window not being properly activated.
|
||||
//
|
||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send_id![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
let window: Retained<NSWindow> = if attrs.platform_specific.panel {
|
||||
masks |= NSWindowStyleMask::NonactivatingPanel;
|
||||
|
||||
let window: Option<Retained<WinitPanel>> = unsafe {
|
||||
msg_send_id![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
window?.as_super().as_super().retain()
|
||||
} else {
|
||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
||||
msg_send_id![
|
||||
super(mtm.alloc().set_ivars(())),
|
||||
initWithContentRect: frame,
|
||||
styleMask: masks,
|
||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
||||
defer: false,
|
||||
]
|
||||
};
|
||||
|
||||
window?.as_super().retain()
|
||||
};
|
||||
let window = window?;
|
||||
|
||||
// It is very important for correct memory management that we
|
||||
// disable the extra release that would otherwise happen when
|
||||
|
|
@ -841,17 +860,17 @@ impl WindowDelegate {
|
|||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(super) fn window(&self) -> &WinitWindow {
|
||||
pub(super) fn window(&self) -> &NSWindow {
|
||||
&self.ivars().window
|
||||
}
|
||||
|
||||
#[track_caller]
|
||||
pub(crate) fn id(&self) -> WindowId {
|
||||
self.window().id()
|
||||
window_id(self.window())
|
||||
}
|
||||
|
||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
||||
let window_id = self.window().id();
|
||||
let window_id = window_id(self.window());
|
||||
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||
app.window_event(event_loop, window_id, event);
|
||||
});
|
||||
|
|
@ -950,7 +969,7 @@ impl WindowDelegate {
|
|||
}
|
||||
|
||||
pub fn request_redraw(&self) {
|
||||
self.ivars().app_state.queue_redraw(self.window().id());
|
||||
self.ivars().app_state.queue_redraw(window_id(self.window()));
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
@ -1488,7 +1507,7 @@ impl WindowDelegate {
|
|||
|
||||
self.ivars().fullscreen.replace(fullscreen.clone());
|
||||
|
||||
fn toggle_fullscreen(window: &WinitWindow) {
|
||||
fn toggle_fullscreen(window: &NSWindow) {
|
||||
// Window level must be restored from `CGShieldingWindowLevel()
|
||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||
// anything
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue