Skip to content

Commit e301605

Browse files
committed
Implement ability to make non-activating window on macOS
1 parent 5835c91 commit e301605

File tree

6 files changed

+109
-33
lines changed

6 files changed

+109
-33
lines changed

Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ objc2-app-kit = { version = "0.2.2", features = [
128128
"NSMenu",
129129
"NSMenuItem",
130130
"NSOpenGLView",
131+
"NSPanel",
131132
"NSPasteboard",
132133
"NSResponder",
133134
"NSRunningApplication",

src/changelog/unreleased.md

+1
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ changelog entry.
7676
- Added `Window::surface_position`, which is the position of the surface inside the window.
7777
- Added `Window::safe_area`, which describes the area of the surface that is unobstructed.
7878
- On X11, Wayland, Windows and macOS, improved scancode conversions for more obscure key codes.
79+
- Add ability to make non-activating window on macOS using `NSPanel` with `NSWindowStyleMask::NonactivatingPanel`
7980

8081
### Changed
8182

src/platform/macos.rs

+27
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,8 @@ pub trait WindowAttributesExtMacOS {
332332
fn with_borderless_game(self, borderless_game: bool) -> Self;
333333
/// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
334334
fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
335+
/// Defines kind of the window
336+
fn with_window_kind(self, kind: WindowKind) -> Self;
335337
}
336338

337339
impl WindowAttributesExtMacOS for WindowAttributes {
@@ -412,6 +414,12 @@ impl WindowAttributesExtMacOS for WindowAttributes {
412414
self.platform_specific.unified_titlebar = unified_titlebar;
413415
self
414416
}
417+
418+
#[inline]
419+
fn with_window_kind(mut self, kind: WindowKind) -> Self {
420+
self.platform_specific.window_kind = kind;
421+
self
422+
}
415423
}
416424

417425
pub trait EventLoopBuilderExtMacOS {
@@ -580,6 +588,25 @@ pub enum OptionAsAlt {
580588
None,
581589
}
582590

591+
/// Window Kind
592+
///
593+
/// The default is `Normal`.
594+
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
595+
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
596+
pub enum WindowKind {
597+
/// Normal MacOS using `NSWindow`
598+
///
599+
/// [`NSWindow`]: objc2_app_kit::NSWindow
600+
#[default]
601+
Normal,
602+
603+
/// [`NSPanel`] window with [`NonactivatingPanel`] window style mask
604+
///
605+
/// [`NSPanel`]: objc2_app_kit::NSPanel
606+
/// [`NonactivatingPanel`]: objc2_app_kit::NSWindowStyleMask::NonactivatingPanel
607+
Popup,
608+
}
609+
583610
/// Additional events on [`ApplicationHandler`] that are specific to macOS.
584611
///
585612
/// This can be registered with [`ApplicationHandler::macos_handler`].

src/platform_impl/apple/appkit/view.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ use objc2::runtime::{AnyObject, Sel};
99
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
1010
use objc2_app_kit::{
1111
NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient,
12-
NSTrackingRectTag, NSView,
12+
NSTrackingRectTag, NSView, NSWindow,
1313
};
1414
use objc2_foundation::{
1515
MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying,
@@ -23,7 +23,7 @@ use super::event::{
2323
code_to_key, code_to_location, create_key_event, event_mods, lalt_pressed, ralt_pressed,
2424
scancode_to_physicalkey, KeyEventExtra,
2525
};
26-
use super::window::WinitWindow;
26+
use super::window::ns_window_id;
2727
use crate::dpi::{LogicalPosition, LogicalSize};
2828
use crate::event::{
2929
DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta,
@@ -201,7 +201,7 @@ declare_class!(
201201
fn draw_rect(&self, _rect: NSRect) {
202202
trace_scope!("drawRect:");
203203

204-
self.ivars().app_state.handle_redraw(self.window().id());
204+
self.ivars().app_state.handle_redraw(ns_window_id(&self.window()));
205205

206206
// This is a direct subclass of NSView, no need to call superclass' drawRect:
207207
}
@@ -426,7 +426,7 @@ declare_class!(
426426
}
427427

428428
// Send command action to user if they requested it.
429-
let window_id = self.window().id();
429+
let window_id = ns_window_id(&self.window());
430430
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
431431
if let Some(handler) = app.macos_handler() {
432432
handler.standard_key_binding(event_loop, window_id, command.name());
@@ -828,19 +828,19 @@ impl WinitView {
828828
this
829829
}
830830

831-
fn window(&self) -> Retained<WinitWindow> {
831+
fn window(&self) -> Retained<NSWindow> {
832832
let window = (**self).window().expect("view must be installed in a window");
833833

834-
if !window.isKindOfClass(WinitWindow::class()) {
835-
unreachable!("view installed in non-WinitWindow");
834+
if !window.isKindOfClass(NSWindow::class()) {
835+
unreachable!("view installed in non-NSWindow");
836836
}
837837

838-
// SAFETY: Just checked that the window is `WinitWindow`
838+
// SAFETY: Just checked that the window is `NSWindow`
839839
unsafe { Retained::cast(window) }
840840
}
841841

842842
fn queue_event(&self, event: WindowEvent) {
843-
let window_id = self.window().id();
843+
let window_id = ns_window_id(&self.window());
844844
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
845845
app.window_event(event_loop, window_id, event);
846846
});

src/platform_impl/apple/appkit/window.rs

+31-5
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
use dpi::{Position, Size};
44
use objc2::rc::{autoreleasepool, Retained};
55
use objc2::{declare_class, mutability, ClassType, DeclaredClass};
6-
use objc2_app_kit::{NSResponder, NSWindow};
6+
use objc2_app_kit::{NSPanel, NSResponder, NSWindow};
77
use objc2_foundation::{MainThreadBound, MainThreadMarker, NSObject};
88

99
use super::event_loop::ActiveEventLoop;
@@ -16,7 +16,7 @@ use crate::window::{
1616
};
1717

1818
pub(crate) struct Window {
19-
window: MainThreadBound<Retained<WinitWindow>>,
19+
window: MainThreadBound<Retained<NSWindow>>,
2020
/// The window only keeps a weak reference to this, so we must keep it around here.
2121
delegate: MainThreadBound<Retained<WindowDelegate>>,
2222
}
@@ -360,8 +360,34 @@ declare_class!(
360360
}
361361
);
362362

363-
impl WinitWindow {
364-
pub(super) fn id(&self) -> WindowId {
365-
WindowId::from_raw(self as *const Self as usize)
363+
declare_class!(
364+
#[derive(Debug)]
365+
pub struct WinitPanel;
366+
367+
unsafe impl ClassType for WinitPanel {
368+
#[inherits(NSWindow, NSResponder, NSObject)]
369+
type Super = NSPanel;
370+
type Mutability = mutability::MainThreadOnly;
371+
const NAME: &'static str = "WinitPanel";
372+
}
373+
374+
impl DeclaredClass for WinitPanel {}
375+
376+
unsafe impl WinitPanel {
377+
#[method(canBecomeMainWindow)]
378+
fn can_become_main_window(&self) -> bool {
379+
trace_scope!("canBecomeMainWindow");
380+
true
381+
}
382+
383+
#[method(canBecomeKeyWindow)]
384+
fn can_become_key_window(&self) -> bool {
385+
trace_scope!("canBecomeKeyWindow");
386+
true
387+
}
366388
}
389+
);
390+
391+
pub(super) fn ns_window_id(window: &NSWindow) -> WindowId {
392+
WindowId::from_raw(window as *const _ as usize)
367393
}

src/platform_impl/apple/appkit/window_delegate.rs

+40-19
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use objc2_app_kit::{
1616
NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType,
1717
NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard,
1818
NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification,
19-
NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
19+
NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel,
2020
NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask,
2121
NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle,
2222
};
@@ -33,22 +33,23 @@ use super::cursor::cursor_from_icon;
3333
use super::monitor::{self, flip_window_screen_coordinates, get_display_id};
3434
use super::observer::RunLoop;
3535
use super::view::WinitView;
36-
use super::window::WinitWindow;
36+
use super::window::{ns_window_id, WinitPanel, WinitWindow};
3737
use super::{ffi, Fullscreen, MonitorHandle};
3838
use crate::dpi::{
3939
LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize,
4040
Position, Size,
4141
};
4242
use crate::error::{NotSupportedError, RequestError};
4343
use crate::event::{SurfaceSizeWriter, WindowEvent};
44-
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS};
44+
use crate::platform::macos::{OptionAsAlt, WindowExtMacOS, WindowKind};
4545
use crate::window::{
4646
Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
4747
WindowAttributes, WindowButtons, WindowId, WindowLevel,
4848
};
4949

5050
#[derive(Clone, Debug, PartialEq)]
5151
pub struct PlatformSpecificWindowAttributes {
52+
pub window_kind: WindowKind,
5253
pub movable_by_window_background: bool,
5354
pub titlebar_transparent: bool,
5455
pub title_hidden: bool,
@@ -68,6 +69,7 @@ impl Default for PlatformSpecificWindowAttributes {
6869
#[inline]
6970
fn default() -> Self {
7071
Self {
72+
window_kind: WindowKind::default(),
7173
movable_by_window_background: false,
7274
titlebar_transparent: false,
7375
title_hidden: false,
@@ -90,7 +92,7 @@ pub(crate) struct State {
9092
/// Strong reference to the global application state.
9193
app_state: Rc<AppState>,
9294

93-
window: Retained<WinitWindow>,
95+
window: Retained<NSWindow>,
9496

9597
// During `windowDidResize`, we use this to only send Moved if the position changed.
9698
//
@@ -501,7 +503,7 @@ fn new_window(
501503
app_state: &Rc<AppState>,
502504
attrs: &WindowAttributes,
503505
mtm: MainThreadMarker,
504-
) -> Option<Retained<WinitWindow>> {
506+
) -> Option<Retained<NSWindow>> {
505507
autoreleasepool(|_| {
506508
let screen = match attrs.fullscreen.clone().map(Into::into) {
507509
Some(Fullscreen::Borderless(Some(monitor)))
@@ -584,16 +586,35 @@ fn new_window(
584586
// confusing issues with the window not being properly activated.
585587
//
586588
// Winit ensures this by not allowing access to `ActiveEventLoop` before handling events.
587-
let window: Option<Retained<WinitWindow>> = unsafe {
588-
msg_send_id![
589-
super(mtm.alloc().set_ivars(())),
590-
initWithContentRect: frame,
591-
styleMask: masks,
592-
backing: NSBackingStoreType::NSBackingStoreBuffered,
593-
defer: false,
594-
]
589+
let window: Retained<NSWindow> = match attrs.platform_specific.window_kind {
590+
WindowKind::Normal => {
591+
let window: Option<Retained<WinitWindow>> = unsafe {
592+
msg_send_id![
593+
super(mtm.alloc().set_ivars(())),
594+
initWithContentRect: frame,
595+
styleMask: masks,
596+
backing: NSBackingStoreType::NSBackingStoreBuffered,
597+
defer: false,
598+
]
599+
};
600+
601+
window?.as_super().retain()
602+
},
603+
WindowKind::Popup => {
604+
masks |= NSWindowStyleMask::NonactivatingPanel;
605+
let window: Option<Retained<WinitPanel>> = unsafe {
606+
msg_send_id![
607+
super(mtm.alloc().set_ivars(())),
608+
initWithContentRect: frame,
609+
styleMask: masks,
610+
backing: NSBackingStoreType::NSBackingStoreBuffered,
611+
defer: false,
612+
]
613+
};
614+
615+
window?.as_super().as_super().retain()
616+
},
595617
};
596-
let window = window?;
597618

598619
// It is very important for correct memory management that we
599620
// disable the extra release that would otherwise happen when
@@ -841,17 +862,17 @@ impl WindowDelegate {
841862
}
842863

843864
#[track_caller]
844-
pub(super) fn window(&self) -> &WinitWindow {
865+
pub(super) fn window(&self) -> &NSWindow {
845866
&self.ivars().window
846867
}
847868

848869
#[track_caller]
849870
pub(crate) fn id(&self) -> WindowId {
850-
self.window().id()
871+
ns_window_id(&self.window())
851872
}
852873

853874
pub(crate) fn queue_event(&self, event: WindowEvent) {
854-
let window_id = self.window().id();
875+
let window_id = ns_window_id(&self.window());
855876
self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| {
856877
app.window_event(event_loop, window_id, event);
857878
});
@@ -950,7 +971,7 @@ impl WindowDelegate {
950971
}
951972

952973
pub fn request_redraw(&self) {
953-
self.ivars().app_state.queue_redraw(self.window().id());
974+
self.ivars().app_state.queue_redraw(ns_window_id(&self.window()));
954975
}
955976

956977
#[inline]
@@ -1488,7 +1509,7 @@ impl WindowDelegate {
14881509

14891510
self.ivars().fullscreen.replace(fullscreen.clone());
14901511

1491-
fn toggle_fullscreen(window: &WinitWindow) {
1512+
fn toggle_fullscreen(window: &NSWindow) {
14921513
// Window level must be restored from `CGShieldingWindowLevel()
14931514
// + 1` back to normal in order for `toggleFullScreen` to do
14941515
// anything

0 commit comments

Comments
 (0)