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",
|
"NSMenu",
|
||||||
"NSMenuItem",
|
"NSMenuItem",
|
||||||
"NSOpenGLView",
|
"NSOpenGLView",
|
||||||
|
"NSPanel",
|
||||||
"NSPasteboard",
|
"NSPasteboard",
|
||||||
"NSResponder",
|
"NSResponder",
|
||||||
"NSRunningApplication",
|
"NSRunningApplication",
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,7 @@ changelog entry.
|
||||||
- Added `Window::surface_position`, which is the position of the surface inside the window.
|
- 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.
|
- 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.
|
- 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
|
### Changed
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -332,6 +332,13 @@ pub trait WindowAttributesExtMacOS {
|
||||||
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.
|
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
|
||||||
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
|
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 {
|
impl WindowAttributesExtMacOS for WindowAttributes {
|
||||||
|
|
@ -412,6 +419,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
|
||||||
self.platform_specific.unified_titlebar = unified_titlebar;
|
self.platform_specific.unified_titlebar = unified_titlebar;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn with_panel(mut self, panel: bool) -> Self {
|
||||||
|
self.platform_specific.panel = panel;
|
||||||
|
self
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait EventLoopBuilderExtMacOS {
|
pub trait EventLoopBuilderExtMacOS {
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ use objc2::runtime::{AnyObject, Sel};
|
||||||
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
|
||||||
use objc2_app_kit::{
|
use objc2_app_kit::{
|
||||||
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
|
||||||
NSTrackingRectTag, NSView,
|
NSTrackingRectTag, NSView, NSWindow,
|
||||||
};
|
};
|
||||||
use objc2_foundation::{
|
use objc2_foundation::{
|
||||||
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
|
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,
|
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
|
||||||
scancode_to_physicalkey, KeyEventExtra,
|
scancode_to_physicalkey, KeyEventExtra,
|
||||||
};
|
};
|
||||||
use super::window::WinitWindow;
|
use super::window::window_id;
|
||||||
use crate::dpi::{LogicalPosition, LogicalSize};
|
use crate::dpi::{LogicalPosition, LogicalSize};
|
||||||
use crate::event::{
|
use crate::event::{
|
||||||
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
|
||||||
|
|
@ -201,7 +201,7 @@ declare_class!(
|
||||||
fn draw_rect(&self, _rect: NSRect) {
|
fn draw_rect(&self, _rect: NSRect) {
|
||||||
trace_scope!("drawRect:");
|
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:
|
// 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.
|
// 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| {
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
if let Some(handler) = app.macos_handler() {
|
if let Some(handler) = app.macos_handler() {
|
||||||
handler.standard_key_binding(event_loop, window_id, command.name());
|
handler.standard_key_binding(event_loop, window_id, command.name());
|
||||||
|
|
@ -828,19 +828,12 @@ impl WinitView {
|
||||||
this
|
this
|
||||||
}
|
}
|
||||||
|
|
||||||
fn window(&self) -> Retained<WinitWindow> {
|
fn window(&self) -> Retained<NSWindow> {
|
||||||
let window = (**self).window().expect("view must be installed in a 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 queue_event(&self, event: WindowEvent) {
|
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| {
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.window_event(event_loop, window_id, event);
|
app.window_event(event_loop, window_id, event);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@
|
||||||
use dpi::{Position, Size};
|
use dpi::{Position, Size};
|
||||||
use objc2::rc::{autoreleasepool, Retained};
|
use objc2::rc::{autoreleasepool, Retained};
|
||||||
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
|
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 objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
|
||||||
|
|
||||||
use super::event_loop::ActiveEventLoop;
|
use super::event_loop::ActiveEventLoop;
|
||||||
|
|
@ -16,7 +16,7 @@ use crate::window::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub(crate) struct 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.
|
/// The window only keeps a weak reference to this, so we must keep it around here.
|
||||||
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
delegate: MainThreadBound<Retained<WindowDelegate>>,
|
||||||
}
|
}
|
||||||
|
|
@ -360,8 +360,30 @@ declare_class!(
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
impl WinitWindow {
|
declare_class!(
|
||||||
pub(super) fn id(&self) -> WindowId {
|
#[derive(Debug)]
|
||||||
WindowId::from_raw(self as *const Self as usize)
|
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,
|
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
|
||||||
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
|
||||||
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
|
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
|
||||||
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
|
||||||
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
|
||||||
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
|
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::monitor::{self, flip_window_screen_coordinates, get_display_id};
|
||||||
use super::observer::RunLoop;
|
use super::observer::RunLoop;
|
||||||
use super::view::WinitView;
|
use super::view::WinitView;
|
||||||
use super::window::WinitWindow;
|
use super::window::{window_id, WinitPanel, WinitWindow};
|
||||||
use super::{ffi, Fullscreen, MonitorHandle};
|
use super::{ffi, Fullscreen, MonitorHandle};
|
||||||
use crate::dpi::{
|
use crate::dpi::{
|
||||||
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
|
||||||
|
|
@ -62,6 +62,7 @@ pub struct PlatformSpecificWindowAttributes {
|
||||||
pub option_as_alt: OptionAsAlt,
|
pub option_as_alt: OptionAsAlt,
|
||||||
pub borderless_game: bool,
|
pub borderless_game: bool,
|
||||||
pub unified_titlebar: bool,
|
pub unified_titlebar: bool,
|
||||||
|
pub panel: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for PlatformSpecificWindowAttributes {
|
impl Default for PlatformSpecificWindowAttributes {
|
||||||
|
|
@ -81,6 +82,7 @@ impl Default for PlatformSpecificWindowAttributes {
|
||||||
option_as_alt: Default::default(),
|
option_as_alt: Default::default(),
|
||||||
borderless_game: false,
|
borderless_game: false,
|
||||||
unified_titlebar: false,
|
unified_titlebar: false,
|
||||||
|
panel: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -90,7 +92,7 @@ pub(crate) struct State {
|
||||||
/// Strong reference to the global application state.
|
/// Strong reference to the global application state.
|
||||||
app_state: Rc<AppState>,
|
app_state: Rc<AppState>,
|
||||||
|
|
||||||
window: Retained<WinitWindow>,
|
window: Retained<NSWindow>,
|
||||||
|
|
||||||
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
// During `windowDidResize`, we use this to only send Moved if the position changed.
|
||||||
//
|
//
|
||||||
|
|
@ -501,7 +503,7 @@ fn new_window(
|
||||||
app_state: &Rc<AppState>,
|
app_state: &Rc<AppState>,
|
||||||
attrs: &WindowAttributes,
|
attrs: &WindowAttributes,
|
||||||
mtm: MainThreadMarker,
|
mtm: MainThreadMarker,
|
||||||
) -> Option<Retained<WinitWindow>> {
|
) -> Option<Retained<NSWindow>> {
|
||||||
autoreleasepool(|_| {
|
autoreleasepool(|_| {
|
||||||
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
let screen = match attrs.fullscreen.clone().map(Into::into) {
|
||||||
Some(Fullscreen::Borderless(Some(monitor)))
|
Some(Fullscreen::Borderless(Some(monitor)))
|
||||||
|
|
@ -584,16 +586,33 @@ fn new_window(
|
||||||
// confusing issues with the window not being properly activated.
|
// confusing issues with the window not being properly activated.
|
||||||
//
|
//
|
||||||
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
|
||||||
let window: Option<Retained<WinitWindow>> = unsafe {
|
let window: Retained<NSWindow> = if attrs.platform_specific.panel {
|
||||||
msg_send_id![
|
masks |= NSWindowStyleMask::NonactivatingPanel;
|
||||||
super(mtm.alloc().set_ivars(())),
|
|
||||||
initWithContentRect: frame,
|
let window: Option<Retained<WinitPanel>> = unsafe {
|
||||||
styleMask: masks,
|
msg_send_id![
|
||||||
backing: NSBackingStoreType::NSBackingStoreBuffered,
|
super(mtm.alloc().set_ivars(())),
|
||||||
defer: false,
|
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
|
// It is very important for correct memory management that we
|
||||||
// disable the extra release that would otherwise happen when
|
// disable the extra release that would otherwise happen when
|
||||||
|
|
@ -841,17 +860,17 @@ impl WindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(super) fn window(&self) -> &WinitWindow {
|
pub(super) fn window(&self) -> &NSWindow {
|
||||||
&self.ivars().window
|
&self.ivars().window
|
||||||
}
|
}
|
||||||
|
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub(crate) fn id(&self) -> WindowId {
|
pub(crate) fn id(&self) -> WindowId {
|
||||||
self.window().id()
|
window_id(self.window())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn queue_event(&self, event: WindowEvent) {
|
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| {
|
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
|
||||||
app.window_event(event_loop, window_id, event);
|
app.window_event(event_loop, window_id, event);
|
||||||
});
|
});
|
||||||
|
|
@ -950,7 +969,7 @@ impl WindowDelegate {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
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]
|
#[inline]
|
||||||
|
|
@ -1488,7 +1507,7 @@ impl WindowDelegate {
|
||||||
|
|
||||||
self.ivars().fullscreen.replace(fullscreen.clone());
|
self.ivars().fullscreen.replace(fullscreen.clone());
|
||||||
|
|
||||||
fn toggle_fullscreen(window: &WinitWindow) {
|
fn toggle_fullscreen(window: &NSWindow) {
|
||||||
// Window level must be restored from `CGShieldingWindowLevel()
|
// Window level must be restored from `CGShieldingWindowLevel()
|
||||||
// + 1` back to normal in order for `toggleFullScreen` to do
|
// + 1` back to normal in order for `toggleFullScreen` to do
|
||||||
// anything
|
// anything
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue