winit/platform/
ios.rs

1//! # iOS / UIKit
2//!
3//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
4//! iOS 9.3.
5//!
6//! iOS's main `UIApplicationMain` does some init work that's required by all
7//! UI-related code (see issue [#1705]). It is best to create your windows
8//! inside `Event::Resumed`.
9//!
10//! [#1705]: https://github.com/rust-windowing/winit/issues/1705
11//!
12//! ## Building app
13//!
14//! To build ios app you will need rustc built for this targets:
15//!
16//!  - armv7-apple-ios
17//!  - armv7s-apple-ios
18//!  - i386-apple-ios
19//!  - aarch64-apple-ios
20//!  - x86_64-apple-ios
21//!
22//! Then
23//!
24//! ```
25//! cargo build --target=...
26//! ```
27//! The simplest way to integrate your app into xcode environment is to build it
28//! as a static library. Wrap your main function and export it.
29//!
30//! ```rust, ignore
31//! #[no_mangle]
32//! pub extern fn start_winit_app() {
33//!     start_inner()
34//! }
35//!
36//! fn start_inner() {
37//!    ...
38//! }
39//! ```
40//!
41//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
42//!
43//! ```ignore
44//! void start_winit_app();
45//! ```
46//!
47//! Use start_winit_app inside your xcode's main function.
48//!
49//!
50//! ## App lifecycle and events
51//!
52//! iOS environment is very different from other platforms and you must be very
53//! careful with it's events. Familiarize yourself with
54//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
55//!
56//! This is how those event are represented in winit:
57//!
58//!  - applicationDidBecomeActive is Resumed
59//!  - applicationWillResignActive is Suspended
60//!  - applicationWillTerminate is LoopExiting
61//!
62//! Keep in mind that after LoopExiting event is received every attempt to draw with
63//! opengl will result in segfault.
64//!
65//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
66
67use std::os::raw::c_void;
68
69use crate::event_loop::EventLoop;
70use crate::monitor::{MonitorHandle, VideoModeHandle};
71use crate::window::{Window, WindowAttributes};
72
73/// Additional methods on [`EventLoop`] that are specific to iOS.
74pub trait EventLoopExtIOS {
75    /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
76    fn idiom(&self) -> Idiom;
77}
78
79impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
80    fn idiom(&self) -> Idiom {
81        self.event_loop.idiom()
82    }
83}
84
85/// Additional methods on [`Window`] that are specific to iOS.
86pub trait WindowExtIOS {
87    /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
88    ///
89    /// The default value is device dependent, and it's recommended GLES or Metal applications set
90    /// this to [`MonitorHandle::scale_factor()`].
91    ///
92    /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
93    /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
94    fn set_scale_factor(&self, scale_factor: f64);
95
96    /// Sets the valid orientations for the [`Window`].
97    ///
98    /// The default value is [`ValidOrientations::LandscapeAndPortrait`].
99    ///
100    /// This changes the value returned by
101    /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
102    /// and then calls
103    /// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
104    fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
105
106    /// Sets whether the [`Window`] prefers the home indicator hidden.
107    ///
108    /// The default is to prefer showing the home indicator.
109    ///
110    /// This changes the value returned by
111    /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
112    /// and then calls
113    /// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
114    ///
115    /// This only has an effect on iOS 11.0+.
116    fn set_prefers_home_indicator_hidden(&self, hidden: bool);
117
118    /// Sets the screen edges for which the system gestures will take a lower priority than the
119    /// application's touch handling.
120    ///
121    /// This changes the value returned by
122    /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
123    /// and then calls
124    /// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
125    ///
126    /// This only has an effect on iOS 11.0+.
127    fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
128
129    /// Sets whether the [`Window`] prefers the status bar hidden.
130    ///
131    /// The default is to prefer showing the status bar.
132    ///
133    /// This sets the value of the
134    /// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
135    /// property.
136    ///
137    /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
138    /// is also called for you.
139    fn set_prefers_status_bar_hidden(&self, hidden: bool);
140
141    /// Sets the preferred status bar style for the [`Window`].
142    ///
143    /// The default is system-defined.
144    ///
145    /// This sets the value of the
146    /// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
147    /// property.
148    ///
149    /// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
150    /// is also called for you.
151    fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
152
153    /// Sets whether the [`Window`] should recognize pinch gestures.
154    ///
155    /// The default is to not recognize gestures.
156    fn recognize_pinch_gesture(&self, should_recognize: bool);
157
158    /// Sets whether the [`Window`] should recognize pan gestures.
159    ///
160    /// The default is to not recognize gestures.
161    /// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view
162    ///
163    /// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches)
164    ///
165    /// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches)
166    fn recognize_pan_gesture(
167        &self,
168        should_recognize: bool,
169        minimum_number_of_touches: u8,
170        maximum_number_of_touches: u8,
171    );
172
173    /// Sets whether the [`Window`] should recognize double tap gestures.
174    ///
175    /// The default is to not recognize gestures.
176    fn recognize_doubletap_gesture(&self, should_recognize: bool);
177
178    /// Sets whether the [`Window`] should recognize rotation gestures.
179    ///
180    /// The default is to not recognize gestures.
181    fn recognize_rotation_gesture(&self, should_recognize: bool);
182}
183
184impl WindowExtIOS for Window {
185    #[inline]
186    fn set_scale_factor(&self, scale_factor: f64) {
187        self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
188    }
189
190    #[inline]
191    fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
192        self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
193    }
194
195    #[inline]
196    fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
197        self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
198    }
199
200    #[inline]
201    fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
202        self.window.maybe_queue_on_main(move |w| {
203            w.set_preferred_screen_edges_deferring_system_gestures(edges)
204        })
205    }
206
207    #[inline]
208    fn set_prefers_status_bar_hidden(&self, hidden: bool) {
209        self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
210    }
211
212    #[inline]
213    fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
214        self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
215    }
216
217    #[inline]
218    fn recognize_pinch_gesture(&self, should_recognize: bool) {
219        self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
220    }
221
222    #[inline]
223    fn recognize_pan_gesture(
224        &self,
225        should_recognize: bool,
226        minimum_number_of_touches: u8,
227        maximum_number_of_touches: u8,
228    ) {
229        self.window.maybe_queue_on_main(move |w| {
230            w.recognize_pan_gesture(
231                should_recognize,
232                minimum_number_of_touches,
233                maximum_number_of_touches,
234            )
235        });
236    }
237
238    #[inline]
239    fn recognize_doubletap_gesture(&self, should_recognize: bool) {
240        self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
241    }
242
243    #[inline]
244    fn recognize_rotation_gesture(&self, should_recognize: bool) {
245        self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
246    }
247}
248
249/// Additional methods on [`WindowAttributes`] that are specific to iOS.
250pub trait WindowAttributesExtIOS {
251    /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
252    ///
253    /// The default value is device dependent, and it's recommended GLES or Metal applications set
254    /// this to [`MonitorHandle::scale_factor()`].
255    ///
256    /// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
257    /// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
258    fn with_scale_factor(self, scale_factor: f64) -> Self;
259
260    /// Sets the valid orientations for the [`Window`].
261    ///
262    /// The default value is [`ValidOrientations::LandscapeAndPortrait`].
263    ///
264    /// This sets the initial value returned by
265    /// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
266    fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
267
268    /// Sets whether the [`Window`] prefers the home indicator hidden.
269    ///
270    /// The default is to prefer showing the home indicator.
271    ///
272    /// This sets the initial value returned by
273    /// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
274    ///
275    /// This only has an effect on iOS 11.0+.
276    fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
277
278    /// Sets the screen edges for which the system gestures will take a lower priority than the
279    /// application's touch handling.
280    ///
281    /// This sets the initial value returned by
282    /// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
283    ///
284    /// This only has an effect on iOS 11.0+.
285    fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
286
287    /// Sets whether the [`Window`] prefers the status bar hidden.
288    ///
289    /// The default is to prefer showing the status bar.
290    ///
291    /// This sets the initial value returned by
292    /// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
293    fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
294
295    /// Sets the style of the [`Window`]'s status bar.
296    ///
297    /// The default is system-defined.
298    ///
299    /// This sets the initial value returned by
300    /// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
301    fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
302}
303
304impl WindowAttributesExtIOS for WindowAttributes {
305    #[inline]
306    fn with_scale_factor(mut self, scale_factor: f64) -> Self {
307        self.platform_specific.scale_factor = Some(scale_factor);
308        self
309    }
310
311    #[inline]
312    fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
313        self.platform_specific.valid_orientations = valid_orientations;
314        self
315    }
316
317    #[inline]
318    fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
319        self.platform_specific.prefers_home_indicator_hidden = hidden;
320        self
321    }
322
323    #[inline]
324    fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
325        self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
326        self
327    }
328
329    #[inline]
330    fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
331        self.platform_specific.prefers_status_bar_hidden = hidden;
332        self
333    }
334
335    #[inline]
336    fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
337        self.platform_specific.preferred_status_bar_style = status_bar_style;
338        self
339    }
340}
341
342/// Additional methods on [`MonitorHandle`] that are specific to iOS.
343pub trait MonitorHandleExtIOS {
344    /// Returns a pointer to the [`UIScreen`] that is used by this monitor.
345    ///
346    /// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
347    fn ui_screen(&self) -> *mut c_void;
348
349    /// Returns the preferred [`VideoModeHandle`] for this monitor.
350    ///
351    /// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
352    fn preferred_video_mode(&self) -> VideoModeHandle;
353}
354
355impl MonitorHandleExtIOS for MonitorHandle {
356    #[inline]
357    fn ui_screen(&self) -> *mut c_void {
358        // SAFETY: The marker is only used to get the pointer of the screen
359        let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
360        objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
361    }
362
363    #[inline]
364    fn preferred_video_mode(&self) -> VideoModeHandle {
365        VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
366    }
367}
368
369/// Valid orientations for a particular [`Window`].
370#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
371pub enum ValidOrientations {
372    /// Excludes `PortraitUpsideDown` on iphone
373    #[default]
374    LandscapeAndPortrait,
375
376    Landscape,
377
378    /// Excludes `PortraitUpsideDown` on iphone
379    Portrait,
380}
381
382/// The device [idiom].
383///
384/// [idiom]: https://developer.apple.com/documentation/uikit/uidevice/1620037-userinterfaceidiom?language=objc
385#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
386pub enum Idiom {
387    Unspecified,
388
389    /// iPhone and iPod touch.
390    Phone,
391
392    /// iPad.
393    Pad,
394
395    /// tvOS and Apple TV.
396    TV,
397    CarPlay,
398}
399
400bitflags::bitflags! {
401    /// The [edges] of a screen.
402    ///
403    /// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
404    #[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
405    pub struct ScreenEdge: u8 {
406        const NONE   = 0;
407        const TOP    = 1 << 0;
408        const LEFT   = 1 << 1;
409        const BOTTOM = 1 << 2;
410        const RIGHT  = 1 << 3;
411        const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
412            | ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
413    }
414}
415
416#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
417pub enum StatusBarStyle {
418    #[default]
419    Default,
420    LightContent,
421    DarkContent,
422}