rio_window/platform/
macos.rs

1//! # macOS / AppKit
2//!
3//! Winit has an OS requirement of macOS 10.11 or higher (same as Rust
4//! itself), and is regularly tested on macOS 10.14.
5//!
6//! A lot of functionality expects the application to be ready before you
7//! start doing anything; this includes creating windows, fetching monitors,
8//! drawing, and so on, see issues [#2238], [#2051] and [#2087].
9//!
10//! If you encounter problems, you should try doing your initialization inside
11//! `Event::Resumed`.
12//!
13//! [#2238]: https://github.com/rust-windowing/winit/issues/2238
14//! [#2051]: https://github.com/rust-windowing/winit/issues/2051
15//! [#2087]: https://github.com/rust-windowing/winit/issues/2087
16
17use std::os::raw::c_void;
18
19use crate::event_loop::{ActiveEventLoop, EventLoopBuilder};
20use crate::monitor::MonitorHandle;
21use crate::window::{Window, WindowAttributes};
22
23/// Additional methods on [`Window`] that are specific to MacOS.
24pub trait WindowExtMacOS {
25    /// Returns whether or not the window is in simple fullscreen mode.
26    fn simple_fullscreen(&self) -> bool;
27
28    /// Toggles a fullscreen mode that doesn't require a new macOS space.
29    /// Returns a boolean indicating whether the transition was successful (this
30    /// won't work if the window was already in the native fullscreen).
31    ///
32    /// This is how fullscreen used to work on macOS in versions before Lion.
33    /// And allows the user to have a fullscreen window without using another
34    /// space or taking control over the entire monitor.
35    fn set_simple_fullscreen(&self, fullscreen: bool) -> bool;
36
37    /// Returns whether or not the window has shadow.
38    fn has_shadow(&self) -> bool;
39
40    /// Sets whether or not the window has shadow.
41    fn set_has_shadow(&self, has_shadow: bool);
42
43    /// Sets background color.
44    fn set_background_color(&self, _r: f64, _g: f64, _b: f64, _a: f64);
45
46    /// Group windows together by using the same tabbing identifier.
47    ///
48    /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
49    fn set_tabbing_identifier(&self, identifier: &str);
50
51    /// Returns the window's tabbing identifier.
52    fn tabbing_identifier(&self) -> String;
53
54    /// Select next tab.
55    fn select_next_tab(&self);
56
57    /// Select previous tab.
58    fn select_previous_tab(&self);
59
60    /// Select the tab with the given index.
61    ///
62    /// Will no-op when the index is out of bounds.
63    fn select_tab_at_index(&self, index: usize);
64
65    /// Get the number of tabs in the window tab group.
66    fn num_tabs(&self) -> usize;
67
68    /// Get the window's edit state.
69    ///
70    /// # Examples
71    ///
72    /// ```ignore
73    /// WindowEvent::CloseRequested => {
74    ///     if window.is_document_edited() {
75    ///         // Show the user a save pop-up or similar
76    ///     } else {
77    ///         // Close the window
78    ///         drop(window);
79    ///     }
80    /// }
81    /// ```
82    fn is_document_edited(&self) -> bool;
83
84    /// Put the window in a state which indicates a file save is required.
85    fn set_document_edited(&self, edited: bool);
86
87    /// Set option as alt behavior as described in [`OptionAsAlt`].
88    ///
89    /// This will ignore diacritical marks and accent characters from
90    /// being processed as received characters. Instead, the input
91    /// device's raw character will be placed in event queues with the
92    /// Alt modifier set.
93    fn set_option_as_alt(&self, option_as_alt: OptionAsAlt);
94
95    /// Getter for the [`WindowExtMacOS::set_option_as_alt`].
96    fn option_as_alt(&self) -> OptionAsAlt;
97
98    /// Makes the titlebar bigger, effectively adding more space around the
99    /// window controls if the titlebar is invisible.
100    fn set_unified_titlebar(&self, unified_titlebar: bool);
101    /// Getter for the [`WindowExtMacOS::set_unified_titlebar`].
102    fn unified_titlebar(&self) -> bool;
103}
104
105impl WindowExtMacOS for Window {
106    #[inline]
107    fn simple_fullscreen(&self) -> bool {
108        self.window.maybe_wait_on_main(|w| w.simple_fullscreen())
109    }
110
111    #[inline]
112    fn set_simple_fullscreen(&self, fullscreen: bool) -> bool {
113        self.window
114            .maybe_wait_on_main(move |w| w.set_simple_fullscreen(fullscreen))
115    }
116
117    #[inline]
118    fn has_shadow(&self) -> bool {
119        self.window.maybe_wait_on_main(|w| w.has_shadow())
120    }
121
122    #[inline]
123    fn set_has_shadow(&self, has_shadow: bool) {
124        self.window
125            .maybe_queue_on_main(move |w| w.set_has_shadow(has_shadow))
126    }
127
128    #[inline]
129    fn set_background_color(&self, r: f64, g: f64, b: f64, a: f64) {
130        self.window
131            .maybe_queue_on_main(move |w| w.set_background_color(r, g, b, a))
132    }
133
134    #[inline]
135    fn set_tabbing_identifier(&self, identifier: &str) {
136        self.window
137            .maybe_wait_on_main(|w| w.set_tabbing_identifier(identifier))
138    }
139
140    #[inline]
141    fn tabbing_identifier(&self) -> String {
142        self.window.maybe_wait_on_main(|w| w.tabbing_identifier())
143    }
144
145    #[inline]
146    fn select_next_tab(&self) {
147        self.window.maybe_queue_on_main(|w| w.select_next_tab())
148    }
149
150    #[inline]
151    fn select_previous_tab(&self) {
152        self.window.maybe_queue_on_main(|w| w.select_previous_tab())
153    }
154
155    #[inline]
156    fn select_tab_at_index(&self, index: usize) {
157        self.window
158            .maybe_queue_on_main(move |w| w.select_tab_at_index(index))
159    }
160
161    #[inline]
162    fn num_tabs(&self) -> usize {
163        self.window.maybe_wait_on_main(|w| w.num_tabs())
164    }
165
166    #[inline]
167    fn is_document_edited(&self) -> bool {
168        self.window.maybe_wait_on_main(|w| w.is_document_edited())
169    }
170
171    #[inline]
172    fn set_document_edited(&self, edited: bool) {
173        self.window
174            .maybe_queue_on_main(move |w| w.set_document_edited(edited))
175    }
176
177    #[inline]
178    fn set_option_as_alt(&self, option_as_alt: OptionAsAlt) {
179        self.window
180            .maybe_queue_on_main(move |w| w.set_option_as_alt(option_as_alt))
181    }
182
183    #[inline]
184    fn option_as_alt(&self) -> OptionAsAlt {
185        self.window.maybe_wait_on_main(|w| w.option_as_alt())
186    }
187
188    #[inline]
189    fn set_unified_titlebar(&self, unified_titlebar: bool) {
190        self.window
191            .maybe_wait_on_main(|w| w.set_unified_titlebar(unified_titlebar))
192    }
193
194    #[inline]
195    fn unified_titlebar(&self) -> bool {
196        self.window.maybe_wait_on_main(|w| w.unified_titlebar())
197    }
198}
199
200/// Corresponds to `NSApplicationActivationPolicy`.
201#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
202pub enum ActivationPolicy {
203    /// Corresponds to `NSApplicationActivationPolicyRegular`.
204    #[default]
205    Regular,
206
207    /// Corresponds to `NSApplicationActivationPolicyAccessory`.
208    Accessory,
209
210    /// Corresponds to `NSApplicationActivationPolicyProhibited`.
211    Prohibited,
212}
213
214/// Additional methods on [`WindowAttributes`] that are specific to MacOS.
215///
216/// **Note:** Properties dealing with the titlebar will be overwritten by the
217/// [`WindowAttributes::with_decorations`] method:
218/// - `with_titlebar_transparent`
219/// - `with_title_hidden`
220/// - `with_titlebar_hidden`
221/// - `with_titlebar_buttons_hidden`
222/// - `with_fullsize_content_view`
223pub trait WindowAttributesExtMacOS {
224    /// Enables click-and-drag behavior for the entire window, not just the titlebar.
225    fn with_movable_by_window_background(
226        self,
227        movable_by_window_background: bool,
228    ) -> Self;
229    /// Makes the titlebar transparent and allows the content to appear behind it.
230    fn with_titlebar_transparent(self, titlebar_transparent: bool) -> Self;
231    /// Hides the window title.
232    fn with_title_hidden(self, title_hidden: bool) -> Self;
233    /// Hides the window titlebar.
234    fn with_titlebar_hidden(self, titlebar_hidden: bool) -> Self;
235    /// Hides the window titlebar buttons.
236    fn with_titlebar_buttons_hidden(self, titlebar_buttons_hidden: bool) -> Self;
237    /// Makes the window content appear behind the titlebar.
238    fn with_fullsize_content_view(self, fullsize_content_view: bool) -> Self;
239    fn with_disallow_hidpi(self, disallow_hidpi: bool) -> Self;
240    fn with_has_shadow(self, has_shadow: bool) -> Self;
241    /// Window accepts click-through mouse events.
242    fn with_accepts_first_mouse(self, accepts_first_mouse: bool) -> Self;
243    /// Defines the window tabbing identifier.
244    ///
245    /// <https://developer.apple.com/documentation/appkit/nswindow/1644704-tabbingidentifier>
246    fn with_tabbing_identifier(self, identifier: &str) -> Self;
247    /// Set how the <kbd>Option</kbd> keys are interpreted.
248    ///
249    /// See [`WindowExtMacOS::set_option_as_alt`] for details on what this means if set.
250    fn with_option_as_alt(self, option_as_alt: OptionAsAlt) -> Self;
251    /// See [`WindowExtMacOS::set_unified_titlebar`] for details on what this means if set.
252    fn with_unified_titlebar(self, unified_titlebar: bool) -> Self;
253}
254
255impl WindowAttributesExtMacOS for WindowAttributes {
256    #[inline]
257    fn with_movable_by_window_background(
258        mut self,
259        movable_by_window_background: bool,
260    ) -> Self {
261        self.platform_specific.movable_by_window_background =
262            movable_by_window_background;
263        self
264    }
265
266    #[inline]
267    fn with_titlebar_transparent(mut self, titlebar_transparent: bool) -> Self {
268        self.platform_specific.titlebar_transparent = titlebar_transparent;
269        self
270    }
271
272    #[inline]
273    fn with_titlebar_hidden(mut self, titlebar_hidden: bool) -> Self {
274        self.platform_specific.titlebar_hidden = titlebar_hidden;
275        self
276    }
277
278    #[inline]
279    fn with_titlebar_buttons_hidden(mut self, titlebar_buttons_hidden: bool) -> Self {
280        self.platform_specific.titlebar_buttons_hidden = titlebar_buttons_hidden;
281        self
282    }
283
284    #[inline]
285    fn with_title_hidden(mut self, title_hidden: bool) -> Self {
286        self.platform_specific.title_hidden = title_hidden;
287        self
288    }
289
290    #[inline]
291    fn with_fullsize_content_view(mut self, fullsize_content_view: bool) -> Self {
292        self.platform_specific.fullsize_content_view = fullsize_content_view;
293        self
294    }
295
296    #[inline]
297    fn with_disallow_hidpi(mut self, disallow_hidpi: bool) -> Self {
298        self.platform_specific.disallow_hidpi = disallow_hidpi;
299        self
300    }
301
302    #[inline]
303    fn with_has_shadow(mut self, has_shadow: bool) -> Self {
304        self.platform_specific.has_shadow = has_shadow;
305        self
306    }
307
308    #[inline]
309    fn with_accepts_first_mouse(mut self, accepts_first_mouse: bool) -> Self {
310        self.platform_specific.accepts_first_mouse = accepts_first_mouse;
311        self
312    }
313
314    #[inline]
315    fn with_tabbing_identifier(mut self, tabbing_identifier: &str) -> Self {
316        self.platform_specific
317            .tabbing_identifier
318            .replace(tabbing_identifier.to_string());
319        self
320    }
321
322    #[inline]
323    fn with_option_as_alt(mut self, option_as_alt: OptionAsAlt) -> Self {
324        self.platform_specific.option_as_alt = option_as_alt;
325        self
326    }
327
328    #[inline]
329    fn with_unified_titlebar(mut self, unified_titlebar: bool) -> Self {
330        self.platform_specific.unified_titlebar = unified_titlebar;
331        self
332    }
333}
334
335pub trait EventLoopBuilderExtMacOS {
336    /// Sets the activation policy for the application.
337    ///
338    /// It is set to [`ActivationPolicy::Regular`] by default.
339    ///
340    /// # Example
341    ///
342    /// Set the activation policy to "accessory".
343    ///
344    /// ```
345    /// use rio_window::event_loop::EventLoopBuilder;
346    /// #[cfg(target_os = "macos")]
347    /// use rio_window::platform::macos::{ActivationPolicy, EventLoopBuilderExtMacOS};
348    ///
349    /// let mut builder = EventLoopBuilder::new();
350    /// #[cfg(target_os = "macos")]
351    /// builder.with_activation_policy(ActivationPolicy::Accessory);
352    /// # if false { // We can't test this part
353    /// let event_loop = builder.build();
354    /// # }
355    /// ```
356    fn with_activation_policy(
357        &mut self,
358        activation_policy: ActivationPolicy,
359    ) -> &mut Self;
360
361    /// Used to control whether a default menubar menu is created.
362    ///
363    /// Menu creation is enabled by default.
364    ///
365    /// # Example
366    ///
367    /// Disable creating a default menubar.
368    ///
369    /// ```
370    /// use rio_window::event_loop::EventLoopBuilder;
371    /// #[cfg(target_os = "macos")]
372    /// use rio_window::platform::macos::EventLoopBuilderExtMacOS;
373    ///
374    /// let mut builder = EventLoopBuilder::new();
375    /// #[cfg(target_os = "macos")]
376    /// builder.with_default_menu(false);
377    /// # if false { // We can't test this part
378    /// let event_loop = builder.build();
379    /// # }
380    /// ```
381    fn with_default_menu(&mut self, enable: bool) -> &mut Self;
382
383    /// Used to prevent the application from automatically activating when launched if
384    /// another application is already active.
385    ///
386    /// The default behavior is to ignore other applications and activate when launched.
387    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self;
388}
389
390impl<T> EventLoopBuilderExtMacOS for EventLoopBuilder<T> {
391    #[inline]
392    fn with_activation_policy(
393        &mut self,
394        activation_policy: ActivationPolicy,
395    ) -> &mut Self {
396        self.platform_specific.activation_policy = activation_policy;
397        self
398    }
399
400    #[inline]
401    fn with_default_menu(&mut self, enable: bool) -> &mut Self {
402        self.platform_specific.default_menu = enable;
403        self
404    }
405
406    #[inline]
407    fn with_activate_ignoring_other_apps(&mut self, ignore: bool) -> &mut Self {
408        self.platform_specific.activate_ignoring_other_apps = ignore;
409        self
410    }
411}
412
413/// Additional methods on [`MonitorHandle`] that are specific to MacOS.
414pub trait MonitorHandleExtMacOS {
415    /// Returns the identifier of the monitor for Cocoa.
416    fn native_id(&self) -> u32;
417    /// Returns a pointer to the NSScreen representing this monitor.
418    fn ns_screen(&self) -> Option<*mut c_void>;
419}
420
421impl MonitorHandleExtMacOS for MonitorHandle {
422    #[inline]
423    fn native_id(&self) -> u32 {
424        self.inner.native_identifier()
425    }
426
427    fn ns_screen(&self) -> Option<*mut c_void> {
428        // SAFETY: We only use the marker to get a pointer
429        let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
430        self.inner
431            .ns_screen(mtm)
432            .map(|s| objc2::rc::Retained::as_ptr(&s) as _)
433    }
434}
435
436/// Additional methods on [`ActiveEventLoop`] that are specific to macOS.
437pub trait ActiveEventLoopExtMacOS {
438    /// Hide the entire application. In most applications this is typically triggered with
439    /// Command-H.
440    fn hide_application(&self);
441    /// Hide the other applications. In most applications this is typically triggered with
442    /// Command+Option-H.
443    fn hide_other_applications(&self);
444    /// Set whether the system can automatically organize windows into tabs.
445    ///
446    /// <https://developer.apple.com/documentation/appkit/nswindow/1646657-allowsautomaticwindowtabbing>
447    fn set_allows_automatic_window_tabbing(&self, enabled: bool);
448    /// Returns whether the system can automatically organize windows into tabs.
449    fn allows_automatic_window_tabbing(&self) -> bool;
450}
451
452impl ActiveEventLoopExtMacOS for ActiveEventLoop {
453    fn hide_application(&self) {
454        self.p.hide_application()
455    }
456
457    fn hide_other_applications(&self) {
458        self.p.hide_other_applications()
459    }
460
461    fn set_allows_automatic_window_tabbing(&self, enabled: bool) {
462        self.p.set_allows_automatic_window_tabbing(enabled);
463    }
464
465    fn allows_automatic_window_tabbing(&self) -> bool {
466        self.p.allows_automatic_window_tabbing()
467    }
468}
469
470/// Option as alt behavior.
471///
472/// The default is `None`.
473#[derive(Default, Debug, Clone, Copy, PartialEq, Eq)]
474pub enum OptionAsAlt {
475    /// The left `Option` key is treated as `Alt`.
476    OnlyLeft,
477
478    /// The right `Option` key is treated as `Alt`.
479    OnlyRight,
480
481    /// Both `Option` keys are treated as `Alt`.
482    Both,
483
484    /// No special handling is applied for `Option` key.
485    #[default]
486    None,
487}