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