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
172pub struct Store<R: Runtime> {
173    panels: HashMap<String, Arc<dyn Panel<R>>>,
174}
175
176impl<R: Runtime> Default for Store<R> {
177    fn default() -> Self {
178        Self {
179            panels: HashMap::new(),
180        }
181    }
182}
183
184pub struct WebviewPanelManager<R: Runtime>(pub Mutex<Store<R>>);
185
186impl<R: Runtime> Default for WebviewPanelManager<R> {
187    fn default() -> Self {
188        Self(Mutex::new(Store::default()))
189    }
190}
191
192pub trait ManagerExt<R: Runtime> {
193    fn get_webview_panel(&self, label: &str) -> Result<Arc<dyn Panel<R>>, Error>;
194    fn remove_webview_panel(&self, label: &str) -> Option<Arc<dyn Panel<R>>>;
195}
196
197#[derive(Debug)]
198pub enum Error {
199    PanelNotFound,
200}
201
202impl<R: Runtime, T: Manager<R>> ManagerExt<R> for T {
203    fn get_webview_panel(&self, label: &str) -> Result<Arc<dyn Panel<R>>, Error> {
204        let manager = self.state::<self::WebviewPanelManager<R>>();
205        let manager = manager.0.lock().unwrap();
206
207        match manager.panels.get(label) {
208            Some(panel) => Ok(panel.clone()),
209            None => Err(Error::PanelNotFound),
210        }
211    }
212
213    fn remove_webview_panel(&self, label: &str) -> Option<Arc<dyn Panel<R>>> {
214        self.state::<self::WebviewPanelManager<R>>()
215            .0
216            .lock()
217            .unwrap()
218            .panels
219            .remove(label)
220    }
221}
222
223pub trait WebviewWindowExt<R: Runtime> {
224    /// Convert window to specific panel type
225    fn to_panel<P: FromWindow<R> + 'static>(&self) -> tauri::Result<Arc<dyn Panel<R>>>;
226}
227
228impl<R: Runtime> WebviewWindowExt<R> for WebviewWindow<R> {
229    fn to_panel<P: FromWindow<R> + 'static>(&self) -> tauri::Result<Arc<dyn Panel<R>>> {
230        let label = self.label().to_string();
231        let panel = P::from_window(self.clone(), label.clone())?;
232        let arc_panel = Arc::new(panel) as Arc<dyn Panel<R>>;
233
234        let manager = self.state::<WebviewPanelManager<R>>();
235        manager
236            .0
237            .lock()
238            .unwrap()
239            .panels
240            .insert(label, arc_panel.clone());
241
242        Ok(arc_panel)
243    }
244}
245
246/// Initializes the plugin.
247pub fn init<R: Runtime>() -> TauriPlugin<R> {
248    Builder::new("nspanel")
249        .setup(|app, _api| {
250            app.manage(self::WebviewPanelManager::<R>::default());
251
252            Ok(())
253        })
254        .build()
255}