diff --git a/Cargo.toml b/Cargo.toml index 37d69a837f..f8c78b0805 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -128,6 +128,7 @@ objc2-app-kit = { version = "0.2.2", features = [ "NSMenu", "NSMenuItem", "NSOpenGLView", + "NSPanel", "NSPasteboard", "NSResponder", "NSRunningApplication", diff --git a/src/platform/macos.rs b/src/platform/macos.rs index 79c0d57c8b..b1f9ce35ac 100644 --- a/src/platform/macos.rs +++ b/src/platform/macos.rs @@ -332,6 +332,8 @@ 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; + /// Defines kind of the window + fn with_window_kind(self, kind: WindowKind) -> Self; } impl WindowAttributesExtMacOS for WindowAttributes { @@ -412,6 +414,12 @@ impl WindowAttributesExtMacOS for WindowAttributes { self.platform_specific.unified_titlebar = unified_titlebar; self } + + #[inline] + fn with_window_kind(mut self, kind: WindowKind) -> Self { + self.platform_specific.window_kind = kind; + self + } } pub trait EventLoopBuilderExtMacOS { @@ -580,6 +588,25 @@ pub enum OptionAsAlt { None, } +/// Window Kind +/// +/// The default is `Normal`. +#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +pub enum WindowKind { + /// Normal MacOS using `NSWindow` + /// + /// [`NSWindow`]: objc2_app_kit::NSWindow + #[default] + Normal, + + /// [`NSPanel`] window with [`NonactivatingPanel`] window style mask + /// + /// [`NSPanel`]: objc2_app_kit::NSPanel + /// [`NonactivatingPanel`]: objc2_app_kit::NSWindowStyleMask::NonactivatingPanel + Popup, +} + /// Additional events on [`ApplicationHandler`] that are specific to macOS. /// /// This can be registered with [`ApplicationHandler::macos_handler`]. diff --git a/src/platform_impl/apple/appkit/view.rs b/src/platform_impl/apple/appkit/view.rs index 14a0a11939..4eb0a6c82c 100644 --- a/src/platform_impl/apple/appkit/view.rs +++ b/src/platform_impl/apple/appkit/view.rs @@ -7,10 +7,7 @@ use std::rc::Rc; use objc2::rc::Retained; 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, -}; +use objc2_app_kit::{NSApplication, NSCursor, NSEvent, NSEventPhase, NSResponder, NSTextInputClient, NSTrackingRectTag, NSView, NSWindow}; use objc2_foundation::{ MainThreadMarker, NSArray, NSAttributedString, NSAttributedStringKey, NSCopying, NSMutableAttributedString, NSNotFound, NSObject, NSObjectProtocol, NSPoint, NSRange, NSRect, @@ -23,7 +20,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::ns_window_id; use crate::dpi::{LogicalPosition, LogicalSize}; use crate::event::{ DeviceEvent, ElementState, Ime, KeyEvent, Modifiers, MouseButton, MouseScrollDelta, @@ -201,7 +198,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(ns_window_id(&self.window())); // This is a direct subclass of NSView, no need to call superclass' drawRect: } @@ -426,7 +423,7 @@ declare_class!( } // Send command action to user if they requested it. - let window_id = self.window().id(); + let window_id = ns_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 +825,19 @@ impl WinitView { this } - fn window(&self) -> Retained { + fn window(&self) -> Retained { let window = (**self).window().expect("view must be installed in a window"); - if !window.isKindOfClass(WinitWindow::class()) { - unreachable!("view installed in non-WinitWindow"); + if !window.isKindOfClass(NSWindow::class()) { + unreachable!("view installed in non-NSWindow"); } - // SAFETY: Just checked that the window is `WinitWindow` + // SAFETY: Just checked that the window is `NSWindow` unsafe { Retained::cast(window) } } fn queue_event(&self, event: WindowEvent) { - let window_id = self.window().id(); + let window_id = ns_window_id(&self.window()); self.ivars().app_state.maybe_queue_with_handler(move |app, event_loop| { app.window_event(event_loop, window_id, event); }); diff --git a/src/platform_impl/apple/appkit/window.rs b/src/platform_impl/apple/appkit/window.rs index abc3bb2f48..a0ac1c8649 100644 --- a/src/platform_impl/apple/appkit/window.rs +++ b/src/platform_impl/apple/appkit/window.rs @@ -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>, + window: MainThreadBound>, /// The window only keeps a weak reference to this, so we must keep it around here. delegate: MainThreadBound>, } @@ -360,8 +360,34 @@ 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 { + #[method(canBecomeMainWindow)] + fn can_become_main_window(&self) -> bool { + trace_scope!("canBecomeMainWindow"); + true + } + + #[method(canBecomeKeyWindow)] + fn can_become_key_window(&self) -> bool { + trace_scope!("canBecomeKeyWindow"); + true + } + } +); + +pub(super) fn ns_window_id(window: &NSWindow) -> WindowId { + WindowId::from_raw(window as *const _ as usize) +} \ No newline at end of file diff --git a/src/platform_impl/apple/appkit/window_delegate.rs b/src/platform_impl/apple/appkit/window_delegate.rs index 6c2f434180..b8bcaad2c8 100644 --- a/src/platform_impl/apple/appkit/window_delegate.rs +++ b/src/platform_impl/apple/appkit/window_delegate.rs @@ -11,15 +11,7 @@ use monitor::VideoModeHandle; use objc2::rc::{autoreleasepool, Retained}; use objc2::runtime::{AnyObject, ProtocolObject}; use objc2::{declare_class, msg_send_id, mutability, sel, ClassType, DeclaredClass}; -use objc2_app_kit::{ - NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, - NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, - NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, - NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, - NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, - NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, - NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle, -}; +use objc2_app_kit::{NSAppKitVersionNumber, NSAppKitVersionNumber10_12, NSAppearance, NSAppearanceCustomization, NSAppearanceNameAqua, NSApplication, NSApplicationPresentationOptions, NSBackingStoreType, NSColor, NSDraggingDestination, NSFilenamesPboardType, NSPasteboard, NSRequestUserAttentionType, NSScreen, NSToolbar, NSView, NSViewFrameDidChangeNotification, NSWindow, NSWindowButton, NSWindowDelegate, NSWindowFullScreenButton, NSWindowLevel, NSWindowOcclusionState, NSWindowOrderingMode, NSWindowSharingType, NSWindowStyleMask, NSWindowTabbingMode, NSWindowTitleVisibility, NSWindowToolbarStyle}; use objc2_foundation::{ ns_string, CGFloat, MainThreadMarker, NSArray, NSCopying, NSDictionary, NSEdgeInsets, NSKeyValueChangeKey, NSKeyValueChangeNewKey, NSKeyValueChangeOldKey, @@ -33,7 +25,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::{ns_window_id, WinitPanel, WinitWindow}; use super::{ffi, Fullscreen, MonitorHandle}; use crate::dpi::{ LogicalInsets, LogicalPosition, LogicalSize, PhysicalInsets, PhysicalPosition, PhysicalSize, @@ -41,7 +33,7 @@ use crate::dpi::{ }; use crate::error::{NotSupportedError, RequestError}; use crate::event::{SurfaceSizeWriter, WindowEvent}; -use crate::platform::macos::{OptionAsAlt, WindowExtMacOS}; +use crate::platform::macos::{OptionAsAlt, WindowExtMacOS, WindowKind}; use crate::window::{ Cursor, CursorGrabMode, Icon, ImePurpose, ResizeDirection, Theme, UserAttentionType, WindowAttributes, WindowButtons, WindowId, WindowLevel, @@ -49,6 +41,7 @@ use crate::window::{ #[derive(Clone, Debug, PartialEq)] pub struct PlatformSpecificWindowAttributes { + pub window_kind: WindowKind, pub movable_by_window_background: bool, pub titlebar_transparent: bool, pub title_hidden: bool, @@ -68,6 +61,7 @@ impl Default for PlatformSpecificWindowAttributes { #[inline] fn default() -> Self { Self { + window_kind: WindowKind::default(), movable_by_window_background: false, titlebar_transparent: false, title_hidden: false, @@ -90,7 +84,7 @@ pub(crate) struct State { /// Strong reference to the global application state. app_state: Rc, - window: Retained, + window: Retained, // During `windowDidResize`, we use this to only send Moved if the position changed. // @@ -501,7 +495,7 @@ fn new_window( app_state: &Rc, attrs: &WindowAttributes, mtm: MainThreadMarker, -) -> Option> { +) -> Option> { autoreleasepool(|_| { let screen = match attrs.fullscreen.clone().map(Into::into) { Some(Fullscreen::Borderless(Some(monitor))) @@ -584,16 +578,35 @@ 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> = unsafe { - msg_send_id![ - super(mtm.alloc().set_ivars(())), - initWithContentRect: frame, - styleMask: masks, - backing: NSBackingStoreType::NSBackingStoreBuffered, - defer: false, - ] + let window: Retained = match attrs.platform_specific.window_kind { + WindowKind::Normal => { + let window: Option> = unsafe { + msg_send_id![ + super(mtm.alloc().set_ivars(())), + initWithContentRect: frame, + styleMask: masks, + backing: NSBackingStoreType::NSBackingStoreBuffered, + defer: false, + ] + }; + + window?.as_super().retain() + } + WindowKind::Popup => { + masks |= NSWindowStyleMask::NonactivatingPanel; + let window: Option> = unsafe { + msg_send_id![ + super(mtm.alloc().set_ivars(())), + initWithContentRect: frame, + styleMask: masks, + backing: NSBackingStoreType::NSBackingStoreBuffered, + defer: false, + ] + }; + + window?.as_super().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 +854,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() + ns_window_id(&self.window()) } pub(crate) fn queue_event(&self, event: WindowEvent) { - let window_id = self.window().id(); + let window_id = ns_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 +963,7 @@ impl WindowDelegate { } pub fn request_redraw(&self) { - self.ivars().app_state.queue_redraw(self.window().id()); + self.ivars().app_state.queue_redraw(ns_window_id(&self.window())); } #[inline] @@ -1488,7 +1501,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