tauri_nspanel/
panel.rs

1// Re-export commonly used types
2pub use objc2_app_kit::{
3    NSAutoresizingMaskOptions, NSTrackingAreaOptions, NSWindowCollectionBehavior, NSWindowStyleMask,
4};
5
6/// Macro to create a custom NSPanel class
7///
8/// This macro generates a custom NSPanel subclass with the specified configuration.
9/// The first parameter is the name of your custom panel class.
10///
11/// **Implementation Details**:
12/// - The macro generates an internal `Raw{ClassName}` Objective-C class
13/// - A public `{ClassName}` wrapper type that implements `Send` and `Sync`
14/// - All methods are implemented on the wrapper type
15///
16/// **Thread Safety**: The wrapper type implements `Send` and `Sync` to allow
17/// passing references through Tauri's command system. However, all actual panel
18/// operations must be performed on the main thread.
19///
20/// ## Sections:
21/// - `config`: Override NSPanel methods that return boolean values (use snake_case names)
22/// - `with`: Optional configurations (tracking_area, etc.)
23///
24/// ## Mouse Tracking:
25/// When you enable tracking_area in the `with` section, mouse event callbacks become available
26/// on your event handler. You can set callbacks for:
27/// - `on_mouse_entered()` - Called when mouse enters the panel
28/// - `on_mouse_exited()` - Called when mouse exits the panel
29/// - `on_mouse_moved()` - Called when mouse moves within the panel
30/// - `on_cursor_update()` - Called when cursor needs to be updated
31///
32/// ## Usage:
33/// ```rust
34/// use tauri_nspanel::{panel, panel_event};
35///
36/// // Define your custom panel class
37/// panel!(MyCustomPanel {
38///     // Config overrides - these affect compile-time behavior
39///     config: {
40///         can_become_key_window: true,
41///         can_become_main_window: false,
42///     },
43///     // Optional configurations
44///     with: {
45///         tracking_area: {
46///             options: NSTrackingAreaOptions::NSTrackingActiveAlways
47///                    | NSTrackingAreaOptions::NSTrackingMouseEnteredAndExited
48///                    | NSTrackingAreaOptions::NSTrackingMouseMoved,
49///             auto_resize: true,
50///         }
51///     }
52/// });
53///
54/// // In your Tauri app:
55/// fn create_panel(window: tauri::WebviewWindow) -> Result<(), Box<dyn std::error::Error>> {
56///     // Convert existing Tauri window to your custom panel
57///     let panel = MyCustomPanel::from_window(window)?;
58///
59///     // Use control methods
60///     panel.show();
61///     panel.set_level(5i64); // NSStatusWindowLevel
62///     panel.set_floating_panel(true);
63///
64///     // Create and attach an event handler
65///     let handler = MyPanelEventHandler::new();
66///     handler.window_did_become_key(|args| {
67///         println!("Panel became key window");
68///         None
69///     });
70///
71///     // If tracking_area is enabled, you can set mouse event callbacks
72///     handler.on_mouse_entered(|event| {
73///         println!("Mouse entered the panel");
74///     });
75///
76///     handler.on_mouse_moved(|event| {
77///         let location = unsafe { event.locationInWindow() };
78///         println!("Mouse moved to: x={}, y={}", location.x, location.y);
79///     });
80///
81///     panel.set_event_handler(Some(handler.as_ref()));
82///
83///     Ok(())
84/// }
85/// ```
86///
87/// ## Available Methods:
88/// - `show()`, `hide()`, `to_window()`
89/// - `make_key_window()`, `resign_key_window()`
90/// - `set_level()`, `set_alpha_value()`, `set_content_size()`
91/// - `set_floating_panel()`, `set_has_shadow()`, `set_opaque()`
92/// - `set_accepts_mouse_moved_events()`, `set_ignores_mouse_events()`
93/// - And many more...
94/// ```
95#[macro_export]
96macro_rules! panel {
97    (
98        $class_name:ident {
99            $(config: {
100                $($method:ident: $value:expr),* $(,)?
101            })?
102            $(with: {
103                $(tracking_area: {
104                    options: $tracking_options:expr,
105                    auto_resize: $auto_resize:expr $(,)?
106                })?
107            })?
108        }
109    ) => {
110        $crate::pastey::paste! {
111            struct [<$class_name Ivars>];
112
113            $crate::objc2::define_class!(
114                #[unsafe(super = $crate::objc2_app_kit::NSPanel)]
115                #[name = stringify!($class_name)]
116                #[ivars = [<$class_name Ivars>]]
117
118                struct [<Raw $class_name>];
119
120                unsafe impl NSObjectProtocol for [<Raw $class_name>] {}
121
122                impl [<Raw $class_name>] {
123                    $($(
124                        #[doc = concat!(" Returns whether panels of this class ", stringify!([<$method:lower_camel>]))]
125                        #[unsafe(method([<$method:lower_camel>]))]
126                        fn [<__ $method:snake>]() -> bool {
127                            $value
128                        }
129
130                        #[doc = concat!(" Returns whether this specific panel instance ", stringify!([<$method:lower_camel>]))]
131                        #[unsafe(method([<$method:lower_camel>]))]
132                        fn [<__ $method:snake _instance>](&self) -> bool {
133                            $value
134                        }
135                    )*)?
136
137                    // Mouse tracking methods - forward to delegate if set
138                    #[unsafe(method(mouseEntered:))]
139                    fn __mouse_entered(&self, event: &$crate::objc2_app_kit::NSEvent) {
140                        unsafe {
141                            // Get the delegate directly from the window
142                            let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
143                            if let Some(ref d) = delegate {
144                                // Check if delegate responds to selector before calling
145                                let selector = $crate::objc2::sel!(mouseEntered:);
146                                let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
147                                if responds {
148                                    let _: () = $crate::objc2::msg_send![&**d, mouseEntered: event];
149                                }
150                            }
151                        }
152                    }
153
154                    #[unsafe(method(mouseExited:))]
155                    fn __mouse_exited(&self, event: &$crate::objc2_app_kit::NSEvent) {
156                        unsafe {
157                            // Get the delegate directly from the window
158                            let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
159                            if let Some(ref d) = delegate {
160                                // Check if delegate responds to selector before calling
161                                let selector = $crate::objc2::sel!(mouseExited:);
162                                let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
163                                if responds {
164                                    let _: () = $crate::objc2::msg_send![&**d, mouseExited: event];
165                                }
166                            }
167                        }
168                    }
169
170                    #[unsafe(method(mouseMoved:))]
171                    fn __mouse_moved(&self, event: &$crate::objc2_app_kit::NSEvent) {
172                        unsafe {
173                            // Get the delegate directly from the window
174                            let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
175                            if let Some(ref d) = delegate {
176                                // Check if delegate responds to selector before calling
177                                let selector = $crate::objc2::sel!(mouseMoved:);
178                                let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
179                                if responds {
180                                    let _: () = $crate::objc2::msg_send![&**d, mouseMoved: event];
181                                }
182                            }
183                        }
184                    }
185
186                    #[unsafe(method(cursorUpdate:))]
187                    fn __cursor_update(&self, event: &$crate::objc2_app_kit::NSEvent) {
188                        unsafe {
189                            // Get the delegate directly from the window
190                            let delegate: Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>> = $crate::objc2::msg_send![self, delegate];
191                            if let Some(ref d) = delegate {
192                                // Check if delegate responds to selector before calling
193                                let selector = $crate::objc2::sel!(cursorUpdate:);
194                                let responds: bool = $crate::objc2::msg_send![&**d, respondsToSelector: selector];
195                                if responds {
196                                    let _: () = $crate::objc2::msg_send![&**d, cursorUpdate: event];
197                                }
198                            }
199                        }
200                    }
201                }
202            );
203
204            #[doc = " A public wrapper for `Raw" $class_name "` "]
205            pub struct $class_name<R: tauri::Runtime = tauri::Wry> {
206                panel: $crate::objc2::rc::Retained<[<Raw $class_name>]>,
207                label: String,
208                original_class: *const $crate::objc2::runtime::AnyClass,
209                original_delegate: std::cell::OnceCell<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>>,
210                app_handle: tauri::AppHandle<R>,
211                event_handler: std::cell::RefCell<Option<$crate::objc2::rc::Retained<$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>>>,
212            }
213
214            // SAFETY: While NSPanel must only be used on the main thread, we implement Send + Sync
215            // to allow passing references through Tauri's command system. Users must ensure
216            // actual panel operations happen on the main thread.
217            unsafe impl<R: tauri::Runtime> Send for $class_name<R> {}
218            unsafe impl<R: tauri::Runtime> Sync for $class_name<R> {}
219
220            impl<R: tauri::Runtime> $class_name<R> where $class_name<R>: $crate::Panel<R> {
221                fn with_label(panel: $crate::objc2::rc::Retained<[<Raw $class_name>]>, label: String, original_class: *const $crate::objc2::runtime::AnyClass, app_handle: tauri::AppHandle<R>) -> Self {
222                    Self {
223                        panel,
224                        label,
225                        original_class,
226                        original_delegate: std::cell::OnceCell::new(),
227                        app_handle,
228                        event_handler: std::cell::RefCell::new(None),
229                    }
230                }
231
232                /// Convert a Tauri window to this panel type (convenience method)
233                pub fn from_window(window: &tauri::WebviewWindow<R>) -> tauri::Result<Self> {
234                    let label = window.label().to_string();
235                    <Self as $crate::FromWindow<R>>::from_window(window.clone(), label)
236                }
237
238            }
239
240            // Implement Panel trait
241            impl<R: tauri::Runtime> $crate::Panel<R> for $class_name<R> {
242                fn show(&self) {
243                    unsafe {
244                        let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
245                    }
246                }
247
248                fn hide(&self) {
249                    unsafe {
250                        let _: () = $crate::objc2::msg_send![&*self.panel, orderOut: $crate::objc2::ffi::nil];
251                    }
252                }
253
254                /// Convert panel back to a regular Tauri window
255                fn to_window(&self) -> Option<tauri::WebviewWindow<R>> {
256                    use tauri::Manager;
257                    use $crate::ManagerExt;
258
259                    unsafe extern "C" {
260                        fn object_setClass(
261                            obj: *mut $crate::objc2_foundation::NSObject,
262                            cls: *const $crate::objc2::runtime::AnyClass,
263                        ) -> *const $crate::objc2::runtime::AnyClass;
264                    }
265
266                    if let Some(_) = self.app_handle.remove_webview_panel(self.label.as_str()) {
267                        self.set_event_handler(None);
268                        self.set_released_when_closed(true);
269
270                        unsafe {
271                            let target_class = if !self.original_class.is_null() {
272                                self.original_class
273                            } else {
274                                $crate::objc2_app_kit::NSWindow::class()
275                            };
276
277                            object_setClass(
278                                &*self.panel as *const [<Raw $class_name>] as *mut $crate::objc2_foundation::NSObject,
279                                target_class,
280                            );
281                        }
282
283                        self.app_handle.get_webview_window(&self.label)
284                    } else {
285                        None
286                    }
287                }
288
289                fn as_panel(&self) -> &$crate::objc2_app_kit::NSPanel {
290                    // SAFETY: Raw class inherits from NSPanel
291                    unsafe { &*(&*self.panel as *const [<Raw $class_name>] as *const $crate::objc2_app_kit::NSPanel) }
292                    // Cast the retained Raw panel to NSPanel reference
293                }
294
295                fn label(&self) -> &str {
296                    &self.label
297                }
298
299                fn as_any(&self) -> &dyn std::any::Any {
300                    self
301                }
302
303                fn set_event_handler(
304                    &self,
305                    handler: Option<&$crate::objc2::runtime::ProtocolObject<dyn $crate::objc2_app_kit::NSWindowDelegate>>,
306                ) {
307                    unsafe {
308                        match handler {
309                            Some(h) => {
310                                // Store original delegate if this is the first time we're setting a custom one
311                                if self.event_handler.borrow().is_none() && self.original_delegate.get().is_none() {
312                                    if let Some(current_delegate) = unsafe { self.panel.delegate() } {
313                                        let _ = self.original_delegate.set(current_delegate);
314                                    }
315                                }
316
317                                // Store the retained handler
318                                let retained_handler = h.retain();
319                                *self.event_handler.borrow_mut() = Some(retained_handler);
320
321                                // Set as window delegate
322                                let _: () = $crate::objc2::msg_send![&*self.panel, setDelegate: h];
323                            }
324                            None => {
325                                if self.original_delegate.get().is_none() {
326                                    return;
327                                }
328
329                                // Clear stored handler (automatic cleanup when Option becomes None)
330                                *self.event_handler.borrow_mut() = None;
331
332                                // Restore original delegate
333                                if let Some(orig_delegate) = self.original_delegate.get() {
334                                    let _: () = $crate::objc2::msg_send![&*self.panel, setDelegate: &**orig_delegate];
335                                }
336                            }
337                        }
338                    }
339                }
340
341                // Query methods
342                fn is_visible(&self) -> bool {
343                    unsafe {
344                        $crate::objc2::msg_send![&*self.panel, isVisible]
345                    }
346                }
347
348                fn is_floating_panel(&self) -> bool {
349                    unsafe {
350                        $crate::objc2::msg_send![&*self.panel, isFloatingPanel]
351                    }
352                }
353
354                fn becomes_key_only_if_needed(&self) -> bool {
355                    unsafe {
356                        $crate::objc2::msg_send![&*self.panel, becomesKeyOnlyIfNeeded]
357                    }
358                }
359
360                fn can_become_key_window(&self) -> bool {
361                    unsafe {
362                        $crate::objc2::msg_send![&*self.panel, canBecomeKeyWindow]
363                    }
364                }
365
366                fn can_become_main_window(&self) -> bool {
367                    unsafe {
368                        $crate::objc2::msg_send![&*self.panel, canBecomeMainWindow]
369                    }
370                }
371
372                fn hides_on_deactivate(&self) -> bool {
373                    unsafe {
374                        $crate::objc2::msg_send![&*self.panel, hidesOnDeactivate]
375                    }
376                }
377
378                // Window state methods
379                fn make_key_window(&self) {
380                    unsafe {
381                        let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyWindow];
382                    }
383                }
384
385                fn make_main_window(&self) {
386                    unsafe {
387                        let _: () = $crate::objc2::msg_send![&*self.panel, makeMainWindow];
388                    }
389                }
390
391                fn resign_key_window(&self) {
392                    unsafe {
393                        let _: () = $crate::objc2::msg_send![&*self.panel, resignKeyWindow];
394                    }
395                }
396
397                fn make_key_and_order_front(&self) {
398                    unsafe {
399                        let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyAndOrderFront: $crate::objc2::ffi::nil];
400                    }
401                }
402
403                fn order_front_regardless(&self) {
404                    unsafe {
405                        let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
406                    }
407                }
408
409                fn show_and_make_key(&self) {
410                    unsafe {
411                        let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
412                            $crate::objc2::msg_send![&*self.panel, contentView];
413                        let _: bool = $crate::objc2::msg_send![&*self.panel, makeFirstResponder: &*content_view];
414                        let _: () = $crate::objc2::msg_send![&*self.panel, orderFrontRegardless];
415                        let _: () = $crate::objc2::msg_send![&*self.panel, makeKeyWindow];
416                    }
417                }
418
419                // Configuration methods
420                fn set_level(&self, level: i64) {
421                    unsafe {
422                        let _: () = $crate::objc2::msg_send![&*self.panel, setLevel: level];
423                    }
424                }
425
426                fn set_floating_panel(&self, value: bool) {
427                    unsafe {
428                        let _: () = $crate::objc2::msg_send![&*self.panel, setFloatingPanel: value];
429                    }
430                }
431
432                fn set_becomes_key_only_if_needed(&self, value: bool) {
433                    unsafe {
434                        let _: () = $crate::objc2::msg_send![&*self.panel, setBecomesKeyOnlyIfNeeded: value];
435                    }
436                }
437
438                fn set_hides_on_deactivate(&self, value: bool) {
439                    unsafe {
440                        let _: () = $crate::objc2::msg_send![&*self.panel, setHidesOnDeactivate: value];
441                    }
442                }
443
444                fn set_works_when_modal(&self, value: bool) {
445                    unsafe {
446                        let _: () = $crate::objc2::msg_send![&*self.panel, setWorksWhenModal: value];
447                    }
448                }
449
450                fn set_alpha_value(&self, value: f64) {
451                    unsafe {
452                        let _: () = $crate::objc2::msg_send![&*self.panel, setAlphaValue: value];
453                    }
454                }
455
456                fn set_released_when_closed(&self, released: bool) {
457                    unsafe {
458                        let _: () = $crate::objc2::msg_send![&*self.panel, setReleasedWhenClosed: released];
459                    }
460                }
461
462                fn set_content_size(&self, width: f64, height: f64) {
463                    unsafe {
464                        let size = $crate::objc2_foundation::NSSize::new(width, height);
465                        let _: () = $crate::objc2::msg_send![&*self.panel, setContentSize: size];
466                    }
467                }
468
469                fn set_has_shadow(&self, value: bool) {
470                    unsafe {
471                        let _: () = $crate::objc2::msg_send![&*self.panel, setHasShadow: value];
472                    }
473                }
474
475                fn set_opaque(&self, value: bool) {
476                    unsafe {
477                        let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: value];
478                    }
479                }
480
481                fn set_accepts_mouse_moved_events(&self, value: bool) {
482                    unsafe {
483                        let _: () = $crate::objc2::msg_send![&*self.panel, setAcceptsMouseMovedEvents: value];
484                    }
485                }
486
487                fn set_ignores_mouse_events(&self, value: bool) {
488                    unsafe {
489                        let _: () = $crate::objc2::msg_send![&*self.panel, setIgnoresMouseEvents: value];
490                    }
491                }
492
493                fn set_movable_by_window_background(&self, value: bool) {
494                    unsafe {
495                        let _: () = $crate::objc2::msg_send![&*self.panel, setMovableByWindowBackground: value];
496                    }
497                }
498
499                fn set_collection_behavior(&self, behavior: $crate::objc2_app_kit::NSWindowCollectionBehavior) {
500                    unsafe {
501                        let _: () = $crate::objc2::msg_send![&*self.panel, setCollectionBehavior: behavior];
502                    }
503                }
504
505                fn content_view(&self) -> $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> {
506                    unsafe {
507                        $crate::objc2::msg_send![&*self.panel, contentView]
508                    }
509                }
510
511                fn resign_main_window(&self) {
512                    unsafe {
513                        let _: () = $crate::objc2::msg_send![&*self.panel, resignMainWindow];
514                    }
515                }
516
517                fn set_style_mask(&self, style_mask: $crate::objc2_app_kit::NSWindowStyleMask) {
518                    unsafe {
519                        let _: () = $crate::objc2::msg_send![&*self.panel, setStyleMask: style_mask];
520                    }
521                }
522
523                fn make_first_responder(&self, responder: Option<&$crate::objc2_app_kit::NSResponder>) -> bool {
524                    unsafe {
525                        let result: bool = match responder {
526                            Some(resp) => $crate::objc2::msg_send![&*self.panel, makeFirstResponder: resp],
527                            None => $crate::objc2::msg_send![&*self.panel, makeFirstResponder: $crate::objc2::ffi::nil],
528                        };
529                        result
530                    }
531                }
532
533                fn set_corner_radius(&self, radius: f64) {
534                    unsafe {
535                        let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> = $crate::objc2::msg_send![&*self.panel, contentView];
536                        let _: () = $crate::objc2::msg_send![&*content_view, setWantsLayer: true];
537                        let content_layer: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![&*content_view, layer];
538                        let _: () = $crate::objc2::msg_send![&*content_layer, setCornerRadius: radius];
539                    }
540                }
541
542                fn set_transparent(&self, transparent: bool) {
543                    unsafe {
544                        if transparent {
545                            let clear_color: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![$crate::objc2::class!(NSColor), clearColor];
546                            let _: () = $crate::objc2::msg_send![&*self.panel, setBackgroundColor: &*clear_color];
547                            let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: false];
548                        } else {
549                            let default_color: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSObject> = $crate::objc2::msg_send![$crate::objc2::class!(NSColor), windowBackgroundColor];
550                            let _: () = $crate::objc2::msg_send![&*self.panel, setBackgroundColor: &*default_color];
551                            let _: () = $crate::objc2::msg_send![&*self.panel, setOpaque: true];
552                        }
553                    }
554                }
555
556            }
557
558            // Implement FromWindow trait
559            impl<R: tauri::Runtime> $crate::FromWindow<R> for $class_name<R> {
560                fn from_window(window: tauri::WebviewWindow<R>, label: String) -> tauri::Result<Self> {
561                    let ns_window = window.ns_window().map_err(|e| {
562                        tauri::Error::Io(std::io::Error::new(
563                            std::io::ErrorKind::Other,
564                            format!("Failed to get NSWindow: {:?}", e),
565                        ))
566                    })?;
567
568                    unsafe {
569                        unsafe extern "C" {
570                            fn object_setClass(
571                                obj: *mut $crate::objc2_foundation::NSObject,
572                                cls: *const $crate::objc2::runtime::AnyClass,
573                            ) -> *const $crate::objc2::runtime::AnyClass;
574
575                            fn object_getClass(
576                                obj: *mut $crate::objc2_foundation::NSObject,
577                            ) -> *const $crate::objc2::runtime::AnyClass;
578                        }
579
580                        let original_class = object_getClass(ns_window as *mut $crate::objc2_foundation::NSObject);
581
582                        // Change the window class to our custom panel class
583                        object_setClass(
584                            ns_window as *mut $crate::objc2_foundation::NSObject,
585                            [<Raw $class_name>]::class(),
586                        );
587
588                        // Now cast to our panel type
589                        let panel_ptr = ns_window as *mut [<Raw $class_name>];
590
591                        // Create a Retained from the raw pointer
592                        let panel = $crate::objc2::rc::Retained::retain(panel_ptr).ok_or_else(|| {
593                            tauri::Error::Io(std::io::Error::new(
594                                std::io::ErrorKind::Other,
595                                "Failed to retain panel",
596                            ))
597                        })?;
598
599                        // Apply instance properties with class-level config after swizzling
600                        // Only for properties that have setter methods available
601                        $($(
602                            Self::apply_instance_property(&panel, stringify!($method), $value);
603                        )*)?
604
605                        // Add tracking area if configured
606                        $($(
607                            Self::add_tracking_area(&panel, $tracking_options, $auto_resize);
608                        )?)?
609
610                        // Enable auto-resizing for all subviews
611                        let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
612                            $crate::objc2::msg_send![&panel, contentView];
613                        let subviews: $crate::objc2::rc::Retained<$crate::objc2_foundation::NSArray<$crate::objc2_app_kit::NSView>> =
614                            $crate::objc2::msg_send![&content_view, subviews];
615                        let count: usize = $crate::objc2::msg_send![&subviews, count];
616
617                        let resize_mask = $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewWidthSizable
618                            | $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewHeightSizable;
619
620                        for i in 0..count {
621                            let view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
622                                $crate::objc2::msg_send![&subviews, objectAtIndex: i];
623                            let _: () = $crate::objc2::msg_send![&view, setAutoresizingMask: resize_mask];
624                        }
625
626                        Ok($class_name::with_label(panel, label, original_class, window.app_handle().clone()))
627                    }
628                }
629            }
630
631            // Helper methods
632            impl<R: tauri::Runtime> $class_name<R> where $class_name<R>: $crate::Panel<R> {
633                #[allow(unused)]
634                fn apply_instance_property(panel: &$crate::objc2_app_kit::NSPanel, method: &str, value: bool) {
635                    unsafe {
636                        match method {
637                            "hides_on_deactivate" | "hidesOnDeactivate" => {
638                                let _: () = $crate::objc2::msg_send![panel, setHidesOnDeactivate: value];
639                            },
640                            "becomes_key_only_if_needed" | "becomesKeyOnlyIfNeeded" => {
641                                let _: () = $crate::objc2::msg_send![panel, setBecomesKeyOnlyIfNeeded: value];
642                            },
643                            "works_when_modal" | "worksWhenModal" => {
644                                let _: () = $crate::objc2::msg_send![panel, setWorksWhenModal: value];
645                            },
646                            "is_floating_panel" | "isFloatingPanel" => {
647                                let _: () = $crate::objc2::msg_send![panel, setFloatingPanel: value];
648                            },
649                            // Properties like can_become_key_window, can_become_main_window don't have setters
650                            // They are read-only and only affect behavior through method overrides
651                            _ => {
652                                // Skip properties without setters
653                            }
654                        }
655                    }
656                }
657
658                #[allow(unused)]
659                fn add_tracking_area(panel: &$crate::objc2_app_kit::NSPanel, options: impl Into<$crate::objc2_app_kit::NSTrackingAreaOptions>, auto_resize: bool) {
660                    unsafe {
661                        let content_view: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSView> =
662                            $crate::objc2::msg_send![panel, contentView];
663                        let bounds: $crate::objc2_foundation::NSRect =
664                            $crate::objc2::msg_send![&content_view, bounds];
665
666                        // Create tracking area
667                        let tracking_area: $crate::objc2::rc::Retained<$crate::objc2_app_kit::NSTrackingArea> = {
668                            let alloc: *mut $crate::objc2_app_kit::NSTrackingArea = $crate::objc2::msg_send![
669                                $crate::objc2_app_kit::NSTrackingArea::class(),
670                                alloc
671                            ];
672                            let area: *mut $crate::objc2_app_kit::NSTrackingArea = $crate::objc2::msg_send![
673                                alloc,
674                                initWithRect: bounds,
675                                options: options.into(),
676                                owner: &*content_view,
677                                userInfo: $crate::objc2::ffi::nil
678                            ];
679                            $crate::objc2::rc::Retained::from_raw(area).unwrap()
680                        };
681
682                        // Set auto-resizing if requested
683                        if auto_resize {
684                            let resize_mask = $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewWidthSizable
685                                | $crate::objc2_app_kit::NSAutoresizingMaskOptions::ViewHeightSizable;
686                            let _: () = $crate::objc2::msg_send![&content_view, setAutoresizingMask: resize_mask];
687                        }
688
689                        // Add tracking area
690                        let _: () = $crate::objc2::msg_send![&content_view, addTrackingArea: &*tracking_area];
691                    }
692                }
693            }
694        }
695    };
696}