401 lines
16 KiB
Rust
401 lines
16 KiB
Rust
//! # iOS / UIKit
|
|
//!
|
|
//! Winit has an OS requirement of iOS 8 or higher, and is regularly tested on
|
|
//! iOS 9.3.
|
|
//!
|
|
//! ## Building app
|
|
//!
|
|
//! To build ios app you will need rustc built for this targets:
|
|
//!
|
|
//! - armv7-apple-ios
|
|
//! - armv7s-apple-ios
|
|
//! - i386-apple-ios
|
|
//! - aarch64-apple-ios
|
|
//! - x86_64-apple-ios
|
|
//!
|
|
//! Then
|
|
//!
|
|
//! ```
|
|
//! cargo build --target=...
|
|
//! ```
|
|
//! The simplest way to integrate your app into xcode environment is to build it
|
|
//! as a static library. Wrap your main function and export it.
|
|
//!
|
|
//! ```rust, ignore
|
|
//! #[no_mangle]
|
|
//! pub extern fn start_winit_app() {
|
|
//! start_inner()
|
|
//! }
|
|
//!
|
|
//! fn start_inner() {
|
|
//! ...
|
|
//! }
|
|
//! ```
|
|
//!
|
|
//! Compile project and then drag resulting .a into Xcode project. Add winit.h to xcode.
|
|
//!
|
|
//! ```ignore
|
|
//! void start_winit_app();
|
|
//! ```
|
|
//!
|
|
//! Use start_winit_app inside your xcode's main function.
|
|
//!
|
|
//!
|
|
//! ## App lifecycle and events
|
|
//!
|
|
//! iOS environment is very different from other platforms and you must be very
|
|
//! careful with it's events. Familiarize yourself with
|
|
//! [app lifecycle](https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIApplicationDelegate_Protocol/).
|
|
//!
|
|
//! This is how those event are represented in winit:
|
|
//!
|
|
//! - applicationDidBecomeActive is Resumed
|
|
//! - applicationWillResignActive is Suspended
|
|
//! - applicationWillTerminate is LoopExiting
|
|
//!
|
|
//! Keep in mind that after LoopExiting event is received every attempt to draw with
|
|
//! opengl will result in segfault.
|
|
//!
|
|
//! Also note that app may not receive the LoopExiting event if suspended; it might be SIGKILL'ed.
|
|
//!
|
|
//! ## Custom `UIApplicationDelegate`
|
|
//!
|
|
//! Winit usually handles everything related to the lifecycle events of the application. Sometimes,
|
|
//! though, you might want to access some of the more niche stuff that [the application
|
|
//! delegate][app-delegate] provides. This functionality is not exposed directly in Winit, since it
|
|
//! would increase the API surface by quite a lot. Instead, Winit guarantees that it will not
|
|
//! register an application delegate, so you can set up a custom one in a nib file instead.
|
|
//!
|
|
//! [app-delegate]: https://developer.apple.com/documentation/uikit/uiapplicationdelegate?language=objc
|
|
|
|
use std::os::raw::c_void;
|
|
|
|
#[cfg(feature = "serde")]
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
use crate::monitor::{MonitorHandle, VideoModeHandle};
|
|
use crate::window::{Window, WindowAttributes};
|
|
|
|
/// Additional methods on [`Window`] that are specific to iOS.
|
|
pub trait WindowExtIOS {
|
|
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
|
///
|
|
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
|
/// this to [`MonitorHandle::scale_factor()`].
|
|
///
|
|
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
|
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
|
fn set_scale_factor(&self, scale_factor: f64);
|
|
|
|
/// Sets the valid orientations for the [`Window`].
|
|
///
|
|
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
|
///
|
|
/// This changes the value returned by
|
|
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc),
|
|
/// and then calls
|
|
/// [`-[UIViewController attemptRotationToDeviceOrientation]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621400-attemptrotationtodeviceorientati?language=objc).
|
|
fn set_valid_orientations(&self, valid_orientations: ValidOrientations);
|
|
|
|
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
|
///
|
|
/// The default is to prefer showing the home indicator.
|
|
///
|
|
/// This changes the value returned by
|
|
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc),
|
|
/// and then calls
|
|
/// [`-[UIViewController setNeedsUpdateOfHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887509-setneedsupdateofhomeindicatoraut?language=objc).
|
|
///
|
|
/// This only has an effect on iOS 11.0+.
|
|
fn set_prefers_home_indicator_hidden(&self, hidden: bool);
|
|
|
|
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
|
/// application's touch handling.
|
|
///
|
|
/// This changes the value returned by
|
|
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc),
|
|
/// and then calls
|
|
/// [`-[UIViewController setNeedsUpdateOfScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887507-setneedsupdateofscreenedgesdefer?language=objc).
|
|
///
|
|
/// This only has an effect on iOS 11.0+.
|
|
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
|
|
|
|
/// Sets whether the [`Window`] prefers the status bar hidden.
|
|
///
|
|
/// The default is to prefer showing the status bar.
|
|
///
|
|
/// This sets the value of the
|
|
/// [`prefersStatusBarHidden`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc)
|
|
/// property.
|
|
///
|
|
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
|
|
/// is also called for you.
|
|
fn set_prefers_status_bar_hidden(&self, hidden: bool);
|
|
|
|
/// Sets the preferred status bar style for the [`Window`].
|
|
///
|
|
/// The default is system-defined.
|
|
///
|
|
/// This sets the value of the
|
|
/// [`preferredStatusBarStyle`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc)
|
|
/// property.
|
|
///
|
|
/// [`setNeedsStatusBarAppearanceUpdate()`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621354-setneedsstatusbarappearanceupdat?language=objc)
|
|
/// is also called for you.
|
|
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle);
|
|
|
|
/// Sets whether the [`Window`] should recognize pinch gestures.
|
|
///
|
|
/// The default is to not recognize gestures.
|
|
fn recognize_pinch_gesture(&self, should_recognize: bool);
|
|
|
|
/// Sets whether the [`Window`] should recognize pan gestures.
|
|
///
|
|
/// The default is to not recognize gestures.
|
|
/// Installs [`UIPanGestureRecognizer`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer) onto view
|
|
///
|
|
/// Set the minimum number of touches required: [`minimumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-minimumnumberoftouches)
|
|
///
|
|
/// Set the maximum number of touches recognized: [`maximumNumberOfTouches`](https://developer.apple.com/documentation/uikit/uipangesturerecognizer/1621208-maximumnumberoftouches)
|
|
fn recognize_pan_gesture(
|
|
&self,
|
|
should_recognize: bool,
|
|
minimum_number_of_touches: u8,
|
|
maximum_number_of_touches: u8,
|
|
);
|
|
|
|
/// Sets whether the [`Window`] should recognize double tap gestures.
|
|
///
|
|
/// The default is to not recognize gestures.
|
|
fn recognize_doubletap_gesture(&self, should_recognize: bool);
|
|
|
|
/// Sets whether the [`Window`] should recognize rotation gestures.
|
|
///
|
|
/// The default is to not recognize gestures.
|
|
fn recognize_rotation_gesture(&self, should_recognize: bool);
|
|
}
|
|
|
|
impl WindowExtIOS for Window {
|
|
#[inline]
|
|
fn set_scale_factor(&self, scale_factor: f64) {
|
|
self.window.maybe_queue_on_main(move |w| w.set_scale_factor(scale_factor))
|
|
}
|
|
|
|
#[inline]
|
|
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
|
|
self.window.maybe_queue_on_main(move |w| w.set_valid_orientations(valid_orientations))
|
|
}
|
|
|
|
#[inline]
|
|
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
|
self.window.maybe_queue_on_main(move |w| w.set_prefers_home_indicator_hidden(hidden))
|
|
}
|
|
|
|
#[inline]
|
|
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
|
self.window.maybe_queue_on_main(move |w| {
|
|
w.set_preferred_screen_edges_deferring_system_gestures(edges)
|
|
})
|
|
}
|
|
|
|
#[inline]
|
|
fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
|
self.window.maybe_queue_on_main(move |w| w.set_prefers_status_bar_hidden(hidden))
|
|
}
|
|
|
|
#[inline]
|
|
fn set_preferred_status_bar_style(&self, status_bar_style: StatusBarStyle) {
|
|
self.window.maybe_queue_on_main(move |w| w.set_preferred_status_bar_style(status_bar_style))
|
|
}
|
|
|
|
#[inline]
|
|
fn recognize_pinch_gesture(&self, should_recognize: bool) {
|
|
self.window.maybe_queue_on_main(move |w| w.recognize_pinch_gesture(should_recognize));
|
|
}
|
|
|
|
#[inline]
|
|
fn recognize_pan_gesture(
|
|
&self,
|
|
should_recognize: bool,
|
|
minimum_number_of_touches: u8,
|
|
maximum_number_of_touches: u8,
|
|
) {
|
|
self.window.maybe_queue_on_main(move |w| {
|
|
w.recognize_pan_gesture(
|
|
should_recognize,
|
|
minimum_number_of_touches,
|
|
maximum_number_of_touches,
|
|
)
|
|
});
|
|
}
|
|
|
|
#[inline]
|
|
fn recognize_doubletap_gesture(&self, should_recognize: bool) {
|
|
self.window.maybe_queue_on_main(move |w| w.recognize_doubletap_gesture(should_recognize));
|
|
}
|
|
|
|
#[inline]
|
|
fn recognize_rotation_gesture(&self, should_recognize: bool) {
|
|
self.window.maybe_queue_on_main(move |w| w.recognize_rotation_gesture(should_recognize));
|
|
}
|
|
}
|
|
|
|
/// Additional methods on [`WindowAttributes`] that are specific to iOS.
|
|
pub trait WindowAttributesExtIOS {
|
|
/// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `scale_factor`.
|
|
///
|
|
/// The default value is device dependent, and it's recommended GLES or Metal applications set
|
|
/// this to [`MonitorHandle::scale_factor()`].
|
|
///
|
|
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
|
|
/// [`contentScaleFactor`]: https://developer.apple.com/documentation/uikit/uiview/1622657-contentscalefactor?language=objc
|
|
fn with_scale_factor(self, scale_factor: f64) -> Self;
|
|
|
|
/// Sets the valid orientations for the [`Window`].
|
|
///
|
|
/// The default value is [`ValidOrientations::LandscapeAndPortrait`].
|
|
///
|
|
/// This sets the initial value returned by
|
|
/// [`-[UIViewController supportedInterfaceOrientations]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621435-supportedinterfaceorientations?language=objc).
|
|
fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> Self;
|
|
|
|
/// Sets whether the [`Window`] prefers the home indicator hidden.
|
|
///
|
|
/// The default is to prefer showing the home indicator.
|
|
///
|
|
/// This sets the initial value returned by
|
|
/// [`-[UIViewController prefersHomeIndicatorAutoHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887510-prefershomeindicatorautohidden?language=objc).
|
|
///
|
|
/// This only has an effect on iOS 11.0+.
|
|
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> Self;
|
|
|
|
/// Sets the screen edges for which the system gestures will take a lower priority than the
|
|
/// application's touch handling.
|
|
///
|
|
/// This sets the initial value returned by
|
|
/// [`-[UIViewController preferredScreenEdgesDeferringSystemGestures]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/2887512-preferredscreenedgesdeferringsys?language=objc).
|
|
///
|
|
/// This only has an effect on iOS 11.0+.
|
|
fn with_preferred_screen_edges_deferring_system_gestures(self, edges: ScreenEdge) -> Self;
|
|
|
|
/// Sets whether the [`Window`] prefers the status bar hidden.
|
|
///
|
|
/// The default is to prefer showing the status bar.
|
|
///
|
|
/// This sets the initial value returned by
|
|
/// [`-[UIViewController prefersStatusBarHidden]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621440-prefersstatusbarhidden?language=objc).
|
|
fn with_prefers_status_bar_hidden(self, hidden: bool) -> Self;
|
|
|
|
/// Sets the style of the [`Window`]'s status bar.
|
|
///
|
|
/// The default is system-defined.
|
|
///
|
|
/// This sets the initial value returned by
|
|
/// [`-[UIViewController preferredStatusBarStyle]`](https://developer.apple.com/documentation/uikit/uiviewcontroller/1621416-preferredstatusbarstyle?language=objc),
|
|
fn with_preferred_status_bar_style(self, status_bar_style: StatusBarStyle) -> Self;
|
|
}
|
|
|
|
impl WindowAttributesExtIOS for WindowAttributes {
|
|
#[inline]
|
|
fn with_scale_factor(mut self, scale_factor: f64) -> Self {
|
|
self.platform_specific.scale_factor = Some(scale_factor);
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
fn with_valid_orientations(mut self, valid_orientations: ValidOrientations) -> Self {
|
|
self.platform_specific.valid_orientations = valid_orientations;
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> Self {
|
|
self.platform_specific.prefers_home_indicator_hidden = hidden;
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
fn with_preferred_screen_edges_deferring_system_gestures(mut self, edges: ScreenEdge) -> Self {
|
|
self.platform_specific.preferred_screen_edges_deferring_system_gestures = edges;
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
fn with_prefers_status_bar_hidden(mut self, hidden: bool) -> Self {
|
|
self.platform_specific.prefers_status_bar_hidden = hidden;
|
|
self
|
|
}
|
|
|
|
#[inline]
|
|
fn with_preferred_status_bar_style(mut self, status_bar_style: StatusBarStyle) -> Self {
|
|
self.platform_specific.preferred_status_bar_style = status_bar_style;
|
|
self
|
|
}
|
|
}
|
|
|
|
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
|
|
pub trait MonitorHandleExtIOS {
|
|
/// Returns a pointer to the [`UIScreen`] that is used by this monitor.
|
|
///
|
|
/// [`UIScreen`]: https://developer.apple.com/documentation/uikit/uiscreen?language=objc
|
|
fn ui_screen(&self) -> *mut c_void;
|
|
|
|
/// Returns the preferred [`VideoModeHandle`] for this monitor.
|
|
///
|
|
/// This translates to a call to [`-[UIScreen preferredMode]`](https://developer.apple.com/documentation/uikit/uiscreen/1617823-preferredmode?language=objc).
|
|
fn preferred_video_mode(&self) -> VideoModeHandle;
|
|
}
|
|
|
|
impl MonitorHandleExtIOS for MonitorHandle {
|
|
#[inline]
|
|
fn ui_screen(&self) -> *mut c_void {
|
|
// SAFETY: The marker is only used to get the pointer of the screen
|
|
let mtm = unsafe { objc2_foundation::MainThreadMarker::new_unchecked() };
|
|
objc2::rc::Retained::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
|
|
}
|
|
|
|
#[inline]
|
|
fn preferred_video_mode(&self) -> VideoModeHandle {
|
|
VideoModeHandle { video_mode: self.inner.preferred_video_mode() }
|
|
}
|
|
}
|
|
|
|
/// Valid orientations for a particular [`Window`].
|
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
pub enum ValidOrientations {
|
|
/// Excludes `PortraitUpsideDown` on iphone
|
|
#[default]
|
|
LandscapeAndPortrait,
|
|
|
|
Landscape,
|
|
|
|
/// Excludes `PortraitUpsideDown` on iphone
|
|
Portrait,
|
|
}
|
|
|
|
bitflags::bitflags! {
|
|
/// The [edges] of a screen.
|
|
///
|
|
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
|
|
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
pub struct ScreenEdge: u8 {
|
|
const NONE = 0;
|
|
const TOP = 1 << 0;
|
|
const LEFT = 1 << 1;
|
|
const BOTTOM = 1 << 2;
|
|
const RIGHT = 1 << 3;
|
|
const ALL = ScreenEdge::TOP.bits() | ScreenEdge::LEFT.bits()
|
|
| ScreenEdge::BOTTOM.bits() | ScreenEdge::RIGHT.bits();
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
|
|
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
|
|
pub enum StatusBarStyle {
|
|
#[default]
|
|
Default,
|
|
LightContent,
|
|
DarkContent,
|
|
}
|