tauri_nspanel/
raw_nspanel.rs

1use bitflags::bitflags;
2use cocoa::{
3    appkit::{NSView, NSViewHeightSizable, NSViewWidthSizable, NSWindowCollectionBehavior},
4    base::{id, nil, BOOL, YES},
5    foundation::NSRect,
6};
7use objc::{
8    class,
9    declare::ClassDecl,
10    msg_send,
11    runtime::{self, Class, Object, Sel},
12    sel, sel_impl, Message,
13};
14use objc_foundation::INSObject;
15use objc_id::{Id, ShareId};
16use tauri::{Runtime, WebviewWindow};
17
18bitflags! {
19    struct NSTrackingAreaOptions: u32 {
20        const NSTrackingActiveAlways = 0x80;
21        const NSTrackingMouseEnteredAndExited = 0x01;
22        const NSTrackingMouseMoved = 0x02;
23        const NSTrackingCursorUpdate = 0x04;
24    }
25}
26
27extern "C" {
28    pub fn object_setClass(obj: id, cls: id) -> id;
29}
30
31const CLS_NAME: &str = "RawNSPanel";
32
33pub struct RawNSPanel;
34
35unsafe impl Sync for RawNSPanel {}
36unsafe impl Send for RawNSPanel {}
37
38impl INSObject for RawNSPanel {
39    fn class() -> &'static runtime::Class {
40        Class::get(CLS_NAME).unwrap_or_else(Self::define_class)
41    }
42}
43
44impl RawNSPanel {
45    /// Returns YES to ensure that RawNSPanel can become a key window
46    extern "C" fn can_become_key_window(_: &Object, _: Sel) -> BOOL {
47        YES
48    }
49
50    extern "C" fn dealloc(this: &mut Object, _cmd: Sel) {
51        unsafe {
52            let superclass = class!(NSObject);
53            let dealloc: extern "C" fn(&mut Object, Sel) =
54                msg_send![super(this, superclass), dealloc];
55            dealloc(this, _cmd);
56        }
57    }
58
59    fn define_class() -> &'static Class {
60        let mut cls = ClassDecl::new(CLS_NAME, class!(NSPanel))
61            .unwrap_or_else(|| panic!("Unable to register {} class", CLS_NAME));
62
63        unsafe {
64            cls.add_method(
65                sel!(canBecomeKeyWindow),
66                Self::can_become_key_window as extern "C" fn(&Object, Sel) -> BOOL,
67            );
68
69            cls.add_method(
70                sel!(dealloc),
71                Self::dealloc as extern "C" fn(&mut Object, Sel),
72            );
73        }
74
75        cls.register()
76    }
77
78    pub fn show(&self) {
79        self.make_first_responder(Some(self.content_view()));
80        self.order_front_regardless();
81        self.make_key_window();
82    }
83
84    pub fn is_visible(&self) -> bool {
85        let flag: BOOL = unsafe { msg_send![self, isVisible] };
86        flag == YES
87    }
88
89    pub fn is_floating_panel(&self) -> bool {
90        let flag: BOOL = unsafe { msg_send![self, isFloatingPanel] };
91        flag == YES
92    }
93
94    pub fn make_key_window(&self) {
95        let _: () = unsafe { msg_send![self, makeKeyWindow] };
96    }
97
98    pub fn resign_key_window(&self) {
99        let _: () = unsafe { msg_send![self, resignKeyWindow] };
100    }
101
102    pub fn make_key_and_order_front(&self, sender: Option<id>) {
103        let _: () = unsafe { msg_send![self, makeKeyAndOrderFront: sender.unwrap_or(nil)] };
104    }
105
106    pub fn order_front_regardless(&self) {
107        let _: () = unsafe { msg_send![self, orderFrontRegardless] };
108    }
109
110    pub fn order_out(&self, sender: Option<id>) {
111        let _: () = unsafe { msg_send![self, orderOut: sender.unwrap_or(nil)] };
112    }
113
114    pub fn content_view(&self) -> id {
115        unsafe { msg_send![self, contentView] }
116    }
117
118    pub fn make_first_responder(&self, sender: Option<id>) {
119        if let Some(responder) = sender {
120            let _: () = unsafe { msg_send![self, makeFirstResponder: responder] };
121        } else {
122            let _: () = unsafe { msg_send![self, makeFirstResponder: self] };
123        }
124    }
125
126    pub fn set_level(&self, level: i32) {
127        let _: () = unsafe { msg_send![self, setLevel: level] };
128    }
129
130    pub fn set_alpha_value(&self, value: f64) {
131        let _: () = unsafe { msg_send![self, setAlphaValue: value] };
132    }
133
134    pub fn set_content_size(&self, width: f64, height: f64) {
135        let _: () = unsafe { msg_send![self, setContentSize: (width, height)] };
136    }
137
138    pub fn set_style_mask(&self, style_mask: i32) {
139        let _: () = unsafe { msg_send![self, setStyleMask: style_mask] };
140    }
141
142    pub fn set_collection_behaviour(&self, behaviour: NSWindowCollectionBehavior) {
143        let _: () = unsafe { msg_send![self, setCollectionBehavior: behaviour] };
144    }
145
146    pub fn set_delegate<T>(&self, delegate: Id<T>) {
147        let _: () = unsafe { msg_send![self, setDelegate: delegate] };
148    }
149
150    pub fn set_floating_panel(&self, value: bool) {
151        let _: () = unsafe { msg_send![self, setFloatingPanel: value] };
152    }
153
154    pub fn set_accepts_mouse_moved_events(&self, value: bool) {
155        let _: () = unsafe { msg_send![self, setAcceptsMouseMovedEvents: value] };
156    }
157
158    pub fn set_ignore_mouse_events(&self, value: bool) {
159        let _: () = unsafe { msg_send![self, setIgnoresMouseEvents: value] };
160    }
161
162    pub fn set_hides_on_deactivate(&self, value: bool) {
163        let _: () = unsafe { msg_send![self, setHidesOnDeactivate: value] };
164    }
165
166    pub fn set_moveable_by_window_background(&self, value: bool) {
167        let _: () = unsafe { msg_send![self, setMovableByWindowBackground: value] };
168    }
169
170    pub fn set_becomes_key_only_if_needed(&self, value: bool) {
171        let _: () = unsafe { msg_send![self, setBecomesKeyOnlyIfNeeded: value] };
172    }
173
174    pub fn set_works_when_modal(&self, value: bool) {
175        let _: () = unsafe { msg_send![self, setWorksWhenModal: value] };
176    }
177
178    pub fn set_opaque(&self, value: bool) {
179        let _: () = unsafe { msg_send![self, setOpaque: value] };
180    }
181
182    pub fn set_has_shadow(&self, value: bool) {
183        let _: () = unsafe { msg_send![self, setHasShadow: value] };
184    }
185
186    pub fn set_released_when_closed(&self, value: bool) {
187        let _: () = unsafe { msg_send![self, setReleasedWhenClosed: value] };
188    }
189
190    #[deprecated(
191        since = "2.0.1",
192        note = "Use set_released_when_closed(bool) instead. This method will be removed in a future version."
193    )]
194    pub fn released_when_closed(&self, value: bool) {
195        self.set_released_when_closed(value);
196    }
197
198    pub fn close(&self) {
199        let _: () = unsafe { msg_send![self, close] };
200    }
201
202    pub fn handle(&mut self) -> ShareId<Self> {
203        unsafe { ShareId::from_ptr(self as *mut Self) }
204    }
205
206    fn add_tracking_area(&self) {
207        let view: id = self.content_view();
208        let bounds: NSRect = unsafe { NSView::bounds(view) };
209        let track_view: id = unsafe { msg_send![class!(NSTrackingArea), alloc] };
210        let track_view: id = unsafe {
211            msg_send![
212            track_view,
213            initWithRect: bounds
214            options: NSTrackingAreaOptions::NSTrackingActiveAlways
215            | NSTrackingAreaOptions::NSTrackingMouseEnteredAndExited
216            | NSTrackingAreaOptions::NSTrackingMouseMoved
217            | NSTrackingAreaOptions::NSTrackingCursorUpdate
218            owner: view
219            userInfo: nil
220            ]
221        };
222        let autoresizing_mask = NSViewWidthSizable | NSViewHeightSizable;
223        let () = unsafe { msg_send![view, setAutoresizingMask: autoresizing_mask] };
224        let () = unsafe { msg_send![view, addTrackingArea: track_view] };
225    }
226
227    /// Create an NSPanel from a Tauri Webview Window
228    pub fn from_window<R: Runtime>(window: WebviewWindow<R>) -> Id<Self> {
229        let nswindow: id = window.ns_window().unwrap() as _;
230        let nspanel_class: id = unsafe { msg_send![Self::class(), class] };
231        unsafe {
232            object_setClass(nswindow, nspanel_class);
233            let panel = Id::from_retained_ptr(nswindow as *mut RawNSPanel);
234
235            // Add a tracking area to the panel's content view,
236            // so that we can receive mouse events such as mouseEntered and mouseExited
237            panel.add_tracking_area();
238
239            panel
240        }
241    }
242}
243
244unsafe impl Message for RawNSPanel {}