1pub use objc2_app_kit::{
3 NSAutoresizingMaskOptions, NSTrackingAreaOptions, NSWindowCollectionBehavior, NSWindowStyleMask,
4};
5
6#[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 #[unsafe(method(mouseEntered:))]
139 fn __mouse_entered(&self, event: &$crate::objc2_app_kit::NSEvent) {
140 unsafe {
141 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 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 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 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 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 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 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 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 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 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 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 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 unsafe { &*(&*self.panel as *const [<Raw $class_name>] as *const $crate::objc2_app_kit::NSPanel) }
292 }
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 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 let retained_handler = h.retain();
319 *self.event_handler.borrow_mut() = Some(retained_handler);
320
321 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 *self.event_handler.borrow_mut() = None;
331
332 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 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 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 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 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 object_setClass(
584 ns_window as *mut $crate::objc2_foundation::NSObject,
585 [<Raw $class_name>]::class(),
586 );
587
588 let panel_ptr = ns_window as *mut [<Raw $class_name>];
590
591 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 $($(
602 Self::apply_instance_property(&panel, stringify!($method), $value);
603 )*)?
604
605 $($(
607 Self::add_tracking_area(&panel, $tracking_options, $auto_resize);
608 )?)?
609
610 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 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 _ => {
652 }
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 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 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 let _: () = $crate::objc2::msg_send![&content_view, addTrackingArea: &*tracking_area];
691 }
692 }
693 }
694 }
695 };
696}