tauri_nspanel/
lib.rs

1pub mod builder;
2pub mod common;
3pub mod event;
4pub mod panel;
5
6// Re-export for macro usage
7#[doc(hidden)]
8pub use objc2;
9#[doc(hidden)]
10pub use objc2_app_kit;
11#[doc(hidden)]
12pub use objc2_foundation;
13#[doc(hidden)]
14pub use pastey;
15
16use std::{
17    any::Any,
18    collections::HashMap,
19    sync::{Arc, Mutex},
20};
21
22use objc2::runtime::ProtocolObject;
23use objc2_app_kit::NSWindowDelegate;
24use tauri::{
25    plugin::{Builder, TauriPlugin},
26    Manager, Runtime, WebviewWindow,
27};
28
29pub use builder::{CollectionBehavior, PanelBuilder, PanelLevel, StyleMask, TrackingAreaOptions};
30
31// Re-export commonly used types for convenience
32pub use objc2::runtime::AnyObject;
33pub use objc2_app_kit::{NSPanel, NSResponder, NSView, NSWindow};
34pub use objc2_foundation::{NSNotification, NSObject, NSPoint, NSRect, NSSize};
35
36/// Trait for event handlers that can be used with panels
37pub trait EventHandler {
38    /// Get the NSWindowDelegate protocol object
39    fn as_delegate(&self) -> ProtocolObject<dyn NSWindowDelegate>;
40}
41
42/// Common trait for all panel types
43pub trait Panel<R: tauri::Runtime = tauri::Wry>: Send + Sync {
44    /// Show the panel
45    fn show(&self);
46
47    /// Hide the panel
48    fn hide(&self);
49
50    /// Convert panel back to a regular Tauri window
51    fn to_window(&self) -> Option<tauri::WebviewWindow<R>>;
52
53    /// Get a reference to the underlying NSPanel
54    fn as_panel(&self) -> &objc2_app_kit::NSPanel;
55
56    /// Get the panel label
57    fn label(&self) -> &str;
58
59    /// Downcast to concrete type
60    fn as_any(&self) -> &dyn Any;
61
62    /// Set the event handler (window delegate)
63    /// Pass `None` to remove the current delegate
64    fn set_event_handler(&self, handler: Option<&ProtocolObject<dyn NSWindowDelegate>>);
65
66    // Query methods
67    /// Check if the panel is visible
68    fn is_visible(&self) -> bool;
69
70    /// Check if this is a floating panel
71    fn is_floating_panel(&self) -> bool;
72
73    /// Check if panel becomes key only if needed
74    fn becomes_key_only_if_needed(&self) -> bool;
75
76    /// Check if panel can become key window
77    fn can_become_key_window(&self) -> bool;
78
79    /// Check if panel can become main window
80    fn can_become_main_window(&self) -> bool;
81
82    /// Check if panel hides on deactivate
83    fn hides_on_deactivate(&self) -> bool;
84
85    // Window state methods
86    /// Make the panel key window
87    fn make_key_window(&self);
88
89    /// Make the panel main window
90    fn make_main_window(&self);
91
92    /// Resign key window status
93    fn resign_key_window(&self);
94
95    /// Make key and order front
96    fn make_key_and_order_front(&self);
97
98    /// Order front regardless
99    fn order_front_regardless(&self);
100
101    /// Show and make key
102    fn show_and_make_key(&self);
103
104    // Configuration methods
105    /// Set the window level
106    fn set_level(&self, level: i64);
107
108    /// Set whether this is a floating panel
109    fn set_floating_panel(&self, value: bool);
110
111    /// Set whether panel becomes key only if needed
112    fn set_becomes_key_only_if_needed(&self, value: bool);
113
114    /// Set whether panel hides on deactivate
115    fn set_hides_on_deactivate(&self, value: bool);
116
117    /// Set whether panel works when modal
118    fn set_works_when_modal(&self, value: bool);
119
120    /// Set the alpha value
121    fn set_alpha_value(&self, value: f64);
122
123    /// Set whether the panel should be released when closed
124    fn set_released_when_closed(&self, released: bool);
125
126    /// Set the content size
127    fn set_content_size(&self, width: f64, height: f64);
128
129    /// Set whether panel has shadow
130    fn set_has_shadow(&self, value: bool);
131
132    /// Set whether panel is opaque
133    fn set_opaque(&self, value: bool);
134
135    /// Set whether panel accepts mouse moved events
136    fn set_accepts_mouse_moved_events(&self, value: bool);
137
138    /// Set whether panel ignores mouse events
139    fn set_ignores_mouse_events(&self, value: bool);
140
141    /// Set whether panel is movable by window background
142    fn set_movable_by_window_background(&self, value: bool);
143
144    /// Set the collection behavior
145    fn set_collection_behavior(&self, behavior: objc2_app_kit::NSWindowCollectionBehavior);
146
147    /// Get the content view
148    fn content_view(&self) -> objc2::rc::Retained<objc2_app_kit::NSView>;
149
150    /// Resign main window status
151    fn resign_main_window(&self);
152
153    /// Set the style mask
154    fn set_style_mask(&self, style_mask: objc2_app_kit::NSWindowStyleMask);
155
156    /// Make a view the first responder
157    fn make_first_responder(&self, responder: Option<&objc2_app_kit::NSResponder>) -> bool;
158
159    /// Set the corner radius for the panel
160    fn set_corner_radius(&self, radius: f64);
161
162    /// Set the panel background to be transparent
163    fn set_transparent(&self, transparent: bool);
164}
165
166/// Trait for panels that can be created from a window
167pub trait FromWindow<R: Runtime>: Panel<R> + Sized {
168    /// Create panel from a Tauri window
169    fn from_window(window: WebviewWindow<R>, label: String) -> tauri::Result<Self>;
170}
171
172/// Type alias for shared panel references
173pub type PanelHandle<R> = Arc<dyn Panel<R>>;
174
175pub struct Store<R: Runtime> {
176    panels: HashMap<String, PanelHandle<R>>,
177}
178
179impl<R: Runtime> Default for Store<R> {
180    fn default() -> Self {
181        Self {
182            panels: HashMap::new(),
183        }
184    }
185}
186
187pub struct WebviewPanelManager<R: Runtime>(pub Mutex<Store<R>>);
188
189impl<R: Runtime> Default for WebviewPanelManager<R> {
190    fn default() -> Self {
191        Self(Mutex::new(Store::default()))
192    }
193}
194
195pub trait ManagerExt<R: Runtime> {
196    fn get_webview_panel(&self, label: &str) -> Result<PanelHandle<R>, Error>;
197    fn remove_webview_panel(&self, label: &str) -> Option<PanelHandle<R>>;
198}
199
200#[derive(Debug)]
201pub enum Error {
202    PanelNotFound,
203}
204
205impl<R: Runtime, T: Manager<R>> ManagerExt<R> for T {
206    fn get_webview_panel(&self, label: &str) -> Result<PanelHandle<R>, Error> {
207        let manager = self.state::<self::WebviewPanelManager<R>>();
208        let manager = manager.0.lock().unwrap();
209
210        match manager.panels.get(label) {
211            Some(panel) => Ok(panel.clone()),
212            None => Err(Error::PanelNotFound),
213        }
214    }
215
216    fn remove_webview_panel(&self, label: &str) -> Option<PanelHandle<R>> {
217        self.state::<self::WebviewPanelManager<R>>()
218            .0
219            .lock()
220            .unwrap()
221            .panels
222            .remove(label)
223    }
224}
225
226pub trait WebviewWindowExt<R: Runtime> {
227    /// Convert window to specific panel type
228    fn to_panel<P: FromWindow<R> + 'static>(&self) -> tauri::Result<PanelHandle<R>>;
229}
230
231impl<R: Runtime> WebviewWindowExt<R> for WebviewWindow<R> {
232    fn to_panel<P: FromWindow<R> + 'static>(&self) -> tauri::Result<PanelHandle<R>> {
233        let label = self.label().to_string();
234        let panel = P::from_window(self.clone(), label.clone())?;
235        let arc_panel = Arc::new(panel) as PanelHandle<R>;
236
237        let manager = self.state::<WebviewPanelManager<R>>();
238        manager
239            .0
240            .lock()
241            .unwrap()
242            .panels
243            .insert(label, arc_panel.clone());
244
245        Ok(arc_panel)
246    }
247}
248
249/// Initializes the plugin.
250pub fn init<R: Runtime>() -> TauriPlugin<R> {
251    Builder::new("nspanel")
252        .setup(|app, _api| {
253            app.manage(self::WebviewPanelManager::<R>::default());
254
255            Ok(())
256        })
257        .build()
258}