tauri_nspanel/
raw_nspanel.rs

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