1use std::sync::Arc;
2
3use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy};
4use objc2_foundation::MainThreadMarker;
5use tauri::{AppHandle, Position, Runtime, Size, WebviewUrl, WebviewWindowBuilder};
6
7use crate::{FromWindow, Panel, WebviewWindowExt};
8
9type WindowConfigFn<'a, R> = Box<
11 dyn FnOnce(
12 WebviewWindowBuilder<'a, R, AppHandle<R>>,
13 ) -> WebviewWindowBuilder<'a, R, AppHandle<R>>,
14>;
15
16#[derive(Debug, Clone, Copy, PartialEq)]
19pub enum PanelLevel {
20 Normal,
22 Submenu,
24 TornOffMenu,
26 Floating,
28 ModalPanel,
30 Utility,
32 Dock,
34 MainMenu,
36 Status,
38 PopUpMenu,
40 ScreenSaver,
42 Custom(i32),
44}
45
46impl PanelLevel {
47 pub fn value(&self) -> i64 {
49 match self {
50 PanelLevel::Normal => 0,
51 PanelLevel::Submenu => 3,
52 PanelLevel::TornOffMenu => 3,
53 PanelLevel::Floating => 4,
54 PanelLevel::ModalPanel => 8,
55 PanelLevel::Utility => 19,
56 PanelLevel::Dock => 20,
57 PanelLevel::MainMenu => 24,
58 PanelLevel::Status => 25,
59 PanelLevel::PopUpMenu => 101,
60 PanelLevel::ScreenSaver => 1000,
61 PanelLevel::Custom(value) => *value as i64,
62 }
63 }
64}
65
66impl From<PanelLevel> for i64 {
67 fn from(level: PanelLevel) -> Self {
68 level.value()
69 }
70}
71
72impl From<i32> for PanelLevel {
73 fn from(value: i32) -> Self {
74 PanelLevel::Custom(value)
75 }
76}
77
78impl From<i64> for PanelLevel {
79 fn from(value: i64) -> Self {
80 PanelLevel::Custom(value as i32)
81 }
82}
83
84#[derive(Debug, Clone, Copy, PartialEq)]
104pub struct CollectionBehavior(objc2_app_kit::NSWindowCollectionBehavior);
105
106impl CollectionBehavior {
107 pub fn new() -> Self {
109 Self(objc2_app_kit::NSWindowCollectionBehavior::empty())
110 }
111
112 pub fn can_join_all_spaces(mut self) -> Self {
114 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::CanJoinAllSpaces;
115 self
116 }
117
118 pub fn move_to_active_space(mut self) -> Self {
120 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::MoveToActiveSpace;
121 self
122 }
123
124 pub fn managed(mut self) -> Self {
126 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::Managed;
127 self
128 }
129
130 pub fn transient(mut self) -> Self {
132 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::Transient;
133 self
134 }
135
136 pub fn stationary(mut self) -> Self {
138 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::Stationary;
139 self
140 }
141
142 pub fn participates_in_cycle(mut self) -> Self {
144 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::ParticipatesInCycle;
145 self
146 }
147
148 pub fn ignores_cycle(mut self) -> Self {
150 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::IgnoresCycle;
151 self
152 }
153
154 pub fn full_screen_primary(mut self) -> Self {
156 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::FullScreenPrimary;
157 self
158 }
159
160 pub fn full_screen_auxiliary(mut self) -> Self {
162 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::FullScreenAuxiliary;
163 self
164 }
165
166 pub fn full_screen_none(mut self) -> Self {
168 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::FullScreenNone;
169 self
170 }
171
172 pub fn full_screen_allows_tiling(mut self) -> Self {
174 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::FullScreenAllowsTiling;
175 self
176 }
177
178 pub fn full_screen_disallows_tiling(mut self) -> Self {
180 self.0 |= objc2_app_kit::NSWindowCollectionBehavior::FullScreenDisallowsTiling;
181 self
182 }
183
184 pub fn from_raw(flags: objc2_app_kit::NSWindowCollectionBehavior) -> Self {
186 Self(flags)
187 }
188
189 pub fn value(&self) -> objc2_app_kit::NSWindowCollectionBehavior {
191 self.0
192 }
193}
194
195impl Default for CollectionBehavior {
196 fn default() -> Self {
197 Self::new()
198 }
199}
200
201impl From<CollectionBehavior> for objc2_app_kit::NSWindowCollectionBehavior {
202 fn from(behavior: CollectionBehavior) -> Self {
203 behavior.0
204 }
205}
206
207impl From<objc2_app_kit::NSWindowCollectionBehavior> for CollectionBehavior {
208 fn from(value: objc2_app_kit::NSWindowCollectionBehavior) -> Self {
209 CollectionBehavior(value)
210 }
211}
212
213#[derive(Debug, Clone, Copy, PartialEq)]
244pub struct TrackingAreaOptions(objc2_app_kit::NSTrackingAreaOptions);
245
246impl TrackingAreaOptions {
247 pub fn new() -> Self {
249 Self(objc2_app_kit::NSTrackingAreaOptions::empty())
250 }
251
252 pub fn mouse_moved(mut self) -> Self {
254 self.0 |= objc2_app_kit::NSTrackingAreaOptions::MouseMoved;
255 self
256 }
257
258 pub fn mouse_entered_and_exited(mut self) -> Self {
260 self.0 |= objc2_app_kit::NSTrackingAreaOptions::MouseEnteredAndExited;
261 self
262 }
263
264 pub fn active_always(mut self) -> Self {
266 self.0 |= objc2_app_kit::NSTrackingAreaOptions::ActiveAlways;
267 self
268 }
269
270 pub fn active_in_active_app(mut self) -> Self {
272 self.0 |= objc2_app_kit::NSTrackingAreaOptions::ActiveInActiveApp;
273 self
274 }
275
276 pub fn active_in_key_window(mut self) -> Self {
278 self.0 |= objc2_app_kit::NSTrackingAreaOptions::ActiveInKeyWindow;
279 self
280 }
281
282 pub fn active_when_first_responder(mut self) -> Self {
284 self.0 |= objc2_app_kit::NSTrackingAreaOptions::ActiveWhenFirstResponder;
285 self
286 }
287
288 pub fn assume_inside(mut self) -> Self {
290 self.0 |= objc2_app_kit::NSTrackingAreaOptions::AssumeInside;
291 self
292 }
293
294 pub fn in_visible_rect(mut self) -> Self {
296 self.0 |= objc2_app_kit::NSTrackingAreaOptions::InVisibleRect;
297 self
298 }
299
300 pub fn cursor_update(mut self) -> Self {
302 self.0 |= objc2_app_kit::NSTrackingAreaOptions::CursorUpdate;
303 self
304 }
305
306 pub fn from_raw(flags: objc2_app_kit::NSTrackingAreaOptions) -> Self {
308 Self(flags)
309 }
310
311 pub fn value(&self) -> objc2_app_kit::NSTrackingAreaOptions {
313 self.0
314 }
315}
316
317impl Default for TrackingAreaOptions {
318 fn default() -> Self {
319 Self::new()
320 }
321}
322
323impl From<TrackingAreaOptions> for objc2_app_kit::NSTrackingAreaOptions {
324 fn from(options: TrackingAreaOptions) -> Self {
325 options.0
326 }
327}
328
329impl From<objc2_app_kit::NSTrackingAreaOptions> for TrackingAreaOptions {
330 fn from(value: objc2_app_kit::NSTrackingAreaOptions) -> Self {
331 TrackingAreaOptions(value)
332 }
333}
334
335#[derive(Debug, Clone, Copy, PartialEq)]
355pub struct StyleMask(objc2_app_kit::NSWindowStyleMask);
356
357impl StyleMask {
358 pub fn new() -> Self {
360 Self(
361 objc2_app_kit::NSWindowStyleMask::Titled
362 | objc2_app_kit::NSWindowStyleMask::Closable
363 | objc2_app_kit::NSWindowStyleMask::Miniaturizable
364 | objc2_app_kit::NSWindowStyleMask::Resizable,
365 )
366 }
367
368 pub fn empty() -> Self {
370 Self(objc2_app_kit::NSWindowStyleMask::empty())
371 }
372
373 pub fn titled(mut self) -> Self {
375 self.0 |= objc2_app_kit::NSWindowStyleMask::Titled;
376 self
377 }
378
379 pub fn closable(mut self) -> Self {
381 self.0 |= objc2_app_kit::NSWindowStyleMask::Closable;
382 self
383 }
384
385 pub fn miniaturizable(mut self) -> Self {
387 self.0 |= objc2_app_kit::NSWindowStyleMask::Miniaturizable;
388 self
389 }
390
391 pub fn resizable(mut self) -> Self {
393 self.0 |= objc2_app_kit::NSWindowStyleMask::Resizable;
394 self
395 }
396
397 pub fn unified_title_and_toolbar(mut self) -> Self {
399 self.0 |= objc2_app_kit::NSWindowStyleMask::UnifiedTitleAndToolbar;
400 self
401 }
402
403 pub fn full_size_content_view(mut self) -> Self {
405 self.0 |= objc2_app_kit::NSWindowStyleMask::FullSizeContentView;
406 self
407 }
408
409 pub fn utility_window(mut self) -> Self {
411 self.0 |= objc2_app_kit::NSWindowStyleMask::UtilityWindow;
412 self
413 }
414
415 pub fn hud_window(mut self) -> Self {
417 self.0 |= objc2_app_kit::NSWindowStyleMask::HUDWindow;
418 self
419 }
420
421 pub fn nonactivating_panel(mut self) -> Self {
423 self.0 |= objc2_app_kit::NSWindowStyleMask::NonactivatingPanel;
424 self
425 }
426
427 pub fn borderless(mut self) -> Self {
429 self.0 = objc2_app_kit::NSWindowStyleMask::Borderless;
430 self
431 }
432
433 pub fn from_raw(flags: objc2_app_kit::NSWindowStyleMask) -> Self {
435 Self(flags)
436 }
437
438 pub fn value(&self) -> objc2_app_kit::NSWindowStyleMask {
440 self.0
441 }
442}
443
444impl Default for StyleMask {
445 fn default() -> Self {
446 Self::new()
447 }
448}
449
450impl From<StyleMask> for objc2_app_kit::NSWindowStyleMask {
451 fn from(mask: StyleMask) -> Self {
452 mask.0
453 }
454}
455
456impl From<objc2_app_kit::NSWindowStyleMask> for StyleMask {
457 fn from(value: objc2_app_kit::NSWindowStyleMask) -> Self {
458 StyleMask(value)
459 }
460}
461
462#[derive(Default)]
463pub(crate) struct PanelConfig {
464 pub floating: Option<bool>,
465 pub level: Option<PanelLevel>,
466 pub has_shadow: Option<bool>,
467 pub opaque: Option<bool>,
468 pub alpha_value: Option<f64>,
469 pub hides_on_deactivate: Option<bool>,
470 pub becomes_key_only_if_needed: Option<bool>,
471 pub accepts_mouse_moved_events: Option<bool>,
472 pub ignores_mouse_events: Option<bool>,
473 pub movable_by_window_background: Option<bool>,
474 pub released_when_closed: Option<bool>,
475 pub works_when_modal: Option<bool>,
476 pub content_size: Option<Size>,
477 pub style_mask: Option<StyleMask>,
478 pub collection_behavior: Option<CollectionBehavior>,
479 pub no_activate: Option<bool>,
480 pub corner_radius: Option<f64>,
481 pub transparent: Option<bool>,
482}
483
484pub struct PanelBuilder<'a, R: Runtime, T: FromWindow<R> + 'static> {
518 handle: &'a AppHandle<R>,
519 label: String,
520 url: Option<WebviewUrl>,
521 title: Option<String>,
522 position: Option<Position>,
523 size: Option<Size>,
524 pub(crate) panel_config: PanelConfig,
525 window_fn: Option<WindowConfigFn<'a, R>>,
526 _phantom: std::marker::PhantomData<T>,
527}
528
529impl<'a, R: Runtime + 'a, T: FromWindow<R> + 'static> PanelBuilder<'a, R, T> {
530 pub fn new(handle: &'a AppHandle<R>, label: impl Into<String>) -> Self {
532 Self {
533 handle,
534 label: label.into(),
535 url: None,
536 title: None,
537 position: None,
538 size: None,
539 panel_config: PanelConfig::default(),
540 window_fn: None,
541 _phantom: std::marker::PhantomData,
542 }
543 }
544
545 pub fn url(mut self, url: WebviewUrl) -> Self {
547 self.url = Some(url);
548 self
549 }
550
551 pub fn title(mut self, title: impl Into<String>) -> Self {
553 self.title = Some(title.into());
554 self
555 }
556
557 pub fn position(mut self, position: Position) -> Self {
559 self.position = Some(position);
560 self
561 }
562
563 pub fn size(mut self, size: Size) -> Self {
565 self.size = Some(size);
566 self
567 }
568
569 pub fn floating(mut self, floating: bool) -> Self {
571 self.panel_config.floating = Some(floating);
572 self
573 }
574
575 pub fn level(mut self, level: PanelLevel) -> Self {
594 self.panel_config.level = Some(level);
595 self
596 }
597
598 pub fn has_shadow(mut self, has_shadow: bool) -> Self {
600 self.panel_config.has_shadow = Some(has_shadow);
601 self
602 }
603
604 pub fn opaque(mut self, opaque: bool) -> Self {
606 self.panel_config.opaque = Some(opaque);
607 self
608 }
609
610 pub fn alpha_value(mut self, alpha: f64) -> Self {
612 self.panel_config.alpha_value = Some(alpha);
613 self
614 }
615
616 pub fn hides_on_deactivate(mut self, hides: bool) -> Self {
618 self.panel_config.hides_on_deactivate = Some(hides);
619 self
620 }
621
622 pub fn becomes_key_only_if_needed(mut self, value: bool) -> Self {
624 self.panel_config.becomes_key_only_if_needed = Some(value);
625 self
626 }
627
628 pub fn accepts_mouse_moved_events(mut self, value: bool) -> Self {
630 self.panel_config.accepts_mouse_moved_events = Some(value);
631 self
632 }
633
634 pub fn ignores_mouse_events(mut self, value: bool) -> Self {
636 self.panel_config.ignores_mouse_events = Some(value);
637 self
638 }
639
640 pub fn movable_by_window_background(mut self, value: bool) -> Self {
642 self.panel_config.movable_by_window_background = Some(value);
643 self
644 }
645
646 pub fn released_when_closed(mut self, value: bool) -> Self {
648 self.panel_config.released_when_closed = Some(value);
649 self
650 }
651
652 pub fn works_when_modal(mut self, value: bool) -> Self {
654 self.panel_config.works_when_modal = Some(value);
655 self
656 }
657
658 pub fn content_size(mut self, size: Size) -> Self {
660 self.panel_config.content_size = Some(size);
661 self
662 }
663
664 pub fn style_mask(mut self, style_mask: StyleMask) -> Self {
687 self.panel_config.style_mask = Some(style_mask);
688 self
689 }
690
691 pub fn collection_behavior(mut self, behavior: CollectionBehavior) -> Self {
709 self.panel_config.collection_behavior = Some(behavior);
710 self
711 }
712
713 pub fn no_activate(mut self, no_activate: bool) -> Self {
735 self.panel_config.no_activate = Some(no_activate);
736 self
737 }
738
739 pub fn corner_radius(mut self, radius: f64) -> Self {
754 self.panel_config.corner_radius = Some(radius);
755 self
756 }
757
758 pub fn transparent(mut self, transparent: bool) -> Self {
773 self.panel_config.transparent = Some(transparent);
774 self
775 }
776
777 pub fn with_window<F>(mut self, f: F) -> Self
801 where
802 F: FnOnce(
803 WebviewWindowBuilder<'a, R, AppHandle<R>>,
804 ) -> WebviewWindowBuilder<'a, R, AppHandle<R>>
805 + 'static,
806 {
807 self.window_fn = Some(Box::new(f) as WindowConfigFn<'a, R>);
808 self
809 }
810
811 pub fn build(self) -> tauri::Result<Arc<dyn Panel<R>>> {
816 let original_policy = if self.panel_config.no_activate.unwrap_or(false) {
818 MainThreadMarker::new().map(|mtm| unsafe {
819 let app = NSApplication::sharedApplication(mtm);
820 let current_policy = app.activationPolicy();
821 let _success = app.setActivationPolicy(NSApplicationActivationPolicy::Prohibited);
822 current_policy
823 })
824 } else {
825 None
826 };
827
828 let mut window_builder = WebviewWindowBuilder::new(
830 self.handle,
831 &self.label,
832 self.url.unwrap_or(WebviewUrl::App("index.html".into())),
833 );
834
835 if let Some(title) = self.title {
836 window_builder = window_builder.title(title);
837 }
838
839 if let Some(position) = self.position {
840 match position {
841 Position::Physical(pos) => {
842 window_builder = window_builder.position(pos.x as f64, pos.y as f64);
843 }
844 Position::Logical(pos) => {
845 window_builder = window_builder.position(pos.x, pos.y);
846 }
847 }
848 }
849
850 if let Some(size) = self.size {
851 match size {
852 Size::Physical(s) => {
853 window_builder = window_builder.inner_size(s.width as f64, s.height as f64);
854 }
855 Size::Logical(s) => {
856 window_builder = window_builder.inner_size(s.width, s.height);
857 }
858 }
859 }
860
861 if let Some(window_fn) = self.window_fn {
863 window_builder = window_fn(window_builder);
864 }
865
866 let window = window_builder.build()?;
868
869 let panel = window.to_panel::<T>().unwrap();
871
872 if let Some(floating) = self.panel_config.floating {
874 panel.set_floating_panel(floating);
875 }
876 if let Some(level) = self.panel_config.level {
877 panel.set_level(level.value());
878 }
879 if let Some(has_shadow) = self.panel_config.has_shadow {
880 panel.set_has_shadow(has_shadow);
881 }
882 if let Some(opaque) = self.panel_config.opaque {
883 panel.set_opaque(opaque);
884 }
885 if let Some(alpha_value) = self.panel_config.alpha_value {
886 panel.set_alpha_value(alpha_value);
887 }
888 if let Some(hides) = self.panel_config.hides_on_deactivate {
889 panel.set_hides_on_deactivate(hides);
890 }
891 if let Some(value) = self.panel_config.becomes_key_only_if_needed {
892 panel.set_becomes_key_only_if_needed(value);
893 }
894 if let Some(value) = self.panel_config.accepts_mouse_moved_events {
895 panel.set_accepts_mouse_moved_events(value);
896 }
897 if let Some(value) = self.panel_config.ignores_mouse_events {
898 panel.set_ignores_mouse_events(value);
899 }
900 if let Some(value) = self.panel_config.movable_by_window_background {
901 panel.set_movable_by_window_background(value);
902 }
903 if let Some(value) = self.panel_config.released_when_closed {
904 panel.set_released_when_closed(value);
905 }
906 if let Some(value) = self.panel_config.works_when_modal {
907 panel.set_works_when_modal(value);
908 }
909 if let Some(style_mask) = self.panel_config.style_mask {
910 panel.set_style_mask(style_mask.0);
911 }
912 if let Some(behavior) = self.panel_config.collection_behavior {
913 panel.set_collection_behavior(behavior.0);
914 }
915 if let Some(radius) = self.panel_config.corner_radius {
916 panel.set_corner_radius(radius);
917 }
918 if let Some(transparent) = self.panel_config.transparent {
919 panel.set_transparent(transparent);
920 }
921
922 if let Some(policy) = original_policy {
924 if let Some(mtm) = MainThreadMarker::new() {
925 let app = NSApplication::sharedApplication(mtm);
926 let _success = app.setActivationPolicy(policy);
927 }
928 }
929
930 Ok(panel)
931 }
932}