iOS: add support for controlling the home indicator, and Exclusive video mode (#1078)

* iOS: platform specific edge home indicator control

* iOS: exclusive video mode support

* address nits, and linkify all the ios documentation
This commit is contained in:
mtak- 2019-07-30 23:57:31 -07:00 committed by Hal Gentz
parent 5bc3cf18d9
commit 3c27e7d88f
9 changed files with 383 additions and 118 deletions

View file

@ -11,6 +11,9 @@
consists of `Fullscreen::Exclusive(VideoMode)` and consists of `Fullscreen::Exclusive(VideoMode)` and
`Fullscreen::Borderless(MonitorHandle)` variants. `Fullscreen::Borderless(MonitorHandle)` variants.
- Adds support for exclusive fullscreen mode. - Adds support for exclusive fullscreen mode.
- On iOS, add support for hiding the home indicator.
- On iOS, add support for deferring system gestures.
- On iOS, fix a crash that occurred while acquiring a monitor's name.
# 0.20.0 Alpha 2 (2019-07-09) # 0.20.0 Alpha 2 (2019-07-09)

View file

@ -43,7 +43,7 @@ version = "0.1.3"
default_features = false default_features = false
features = ["display_link"] features = ["display_link"]
[target.'cfg(target_os = "windows")'.dependencies] [target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies]
bitflags = "1" bitflags = "1"
[target.'cfg(target_os = "windows")'.dependencies.winapi] [target.'cfg(target_os = "windows")'.dependencies.winapi]

View file

@ -160,7 +160,7 @@ Legend:
|Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** |
|Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ |
|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ | |❌ | |Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ |
|HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ |
|Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ |

View file

@ -123,7 +123,7 @@ extern crate serde;
#[macro_use] #[macro_use]
extern crate derivative; extern crate derivative;
#[macro_use] #[macro_use]
#[cfg(target_os = "windows")] #[cfg(any(target_os = "ios", target_os = "windows"))]
extern crate bitflags; extern crate bitflags;
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
#[macro_use] #[macro_use]

View file

@ -4,13 +4,13 @@ use std::os::raw::c_void;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::MonitorHandle, monitor::{MonitorHandle, VideoMode},
window::{Window, WindowBuilder}, window::{Window, WindowBuilder},
}; };
/// Additional methods on `EventLoop` that are specific to iOS. /// Additional methods on [`EventLoop`] that are specific to iOS.
pub trait EventLoopExtIOS { pub trait EventLoopExtIOS {
/// Returns the idiom (phone/tablet/tv/etc) for the current device. /// Returns the [`Idiom`] (phone/tablet/tv/etc) for the current device.
fn idiom(&self) -> Idiom; fn idiom(&self) -> Idiom;
} }
@ -20,32 +20,66 @@ impl<T: 'static> EventLoopExtIOS for EventLoop<T> {
} }
} }
/// Additional methods on `Window` that are specific to iOS. /// Additional methods on [`Window`] that are specific to iOS.
pub trait WindowExtIOS { pub trait WindowExtIOS {
/// Returns a pointer to the `UIWindow` that is used by this window. /// Returns a pointer to the [`UIWindow`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIWindow`]: https://developer.apple.com/documentation/uikit/uiwindow?language=objc
fn ui_window(&self) -> *mut c_void; fn ui_window(&self) -> *mut c_void;
/// Returns a pointer to the `UIViewController` that is used by this window. /// Returns a pointer to the [`UIViewController`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIViewController`]: https://developer.apple.com/documentation/uikit/uiviewcontroller?language=objc
fn ui_view_controller(&self) -> *mut c_void; fn ui_view_controller(&self) -> *mut c_void;
/// Returns a pointer to the `UIView` that is used by this window. /// Returns a pointer to the [`UIView`] that is used by this window.
/// ///
/// The pointer will become invalid when the `Window` is destroyed. /// The pointer will become invalid when the [`Window`] is destroyed.
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn ui_view(&self) -> *mut c_void; fn ui_view(&self) -> *mut c_void;
/// Sets the HiDpi factor used by this window. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
/// ///
/// This translates to `-[UIWindow setContentScaleFactor:hidpi_factor]`. /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to [`MonitorHandle::hidpi_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_hidpi_factor(&self, hidpi_factor: f64); fn set_hidpi_factor(&self, hidpi_factor: f64);
/// Sets the valid orientations for screens showing this `Window`. /// Sets the valid orientations for the [`Window`].
/// ///
/// On iPhones and iPods upside down portrait is never enabled. /// 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); 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).
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).
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge);
} }
impl WindowExtIOS for Window { impl WindowExtIOS for Window {
@ -73,23 +107,62 @@ impl WindowExtIOS for Window {
fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
self.window.set_valid_orientations(valid_orientations) self.window.set_valid_orientations(valid_orientations)
} }
#[inline]
fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
self.window.set_prefers_home_indicator_hidden(hidden)
} }
/// Additional methods on `WindowBuilder` that are specific to iOS. #[inline]
fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
self.window
.set_preferred_screen_edges_deferring_system_gestures(edges)
}
}
/// Additional methods on [`WindowBuilder`] that are specific to iOS.
pub trait WindowBuilderExtIOS { pub trait WindowBuilderExtIOS {
/// Sets the root view class used by the `Window`, otherwise a barebones `UIView` is provided. /// Sets the root view class used by the [`Window`], otherwise a barebones [`UIView`] is provided.
/// ///
/// The class will be initialized by calling `[root_view initWithFrame:CGRect]` /// An instance of the class will be initialized by calling [`-[UIView initWithFrame:]`](https://developer.apple.com/documentation/uikit/uiview/1622488-initwithframe?language=objc).
///
/// [`UIView`]: https://developer.apple.com/documentation/uikit/uiview?language=objc
fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder; fn with_root_view_class(self, root_view_class: *const c_void) -> WindowBuilder;
/// Sets the `contentScaleFactor` of the underlying `UIWindow` to `hidpi_factor`. /// Sets the [`contentScaleFactor`] of the underlying [`UIWindow`] to `hidpi_factor`.
/// ///
/// The default value is device dependent, and it's recommended GLES or Metal applications set /// The default value is device dependent, and it's recommended GLES or Metal applications set
/// this to `MonitorHandle::hidpi_factor()`. /// this to [`MonitorHandle::hidpi_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_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder; fn with_hidpi_factor(self, hidpi_factor: f64) -> WindowBuilder;
/// Sets the valid orientations for the `Window`. /// 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) -> WindowBuilder; fn with_valid_orientations(self, valid_orientations: ValidOrientations) -> WindowBuilder;
/// 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).
fn with_prefers_home_indicator_hidden(self, hidden: bool) -> WindowBuilder;
/// 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).
fn with_preferred_screen_edges_deferring_system_gestures(
self,
edges: ScreenEdge,
) -> WindowBuilder;
} }
impl WindowBuilderExtIOS for WindowBuilder { impl WindowBuilderExtIOS for WindowBuilder {
@ -110,12 +183,35 @@ impl WindowBuilderExtIOS for WindowBuilder {
self.platform_specific.valid_orientations = valid_orientations; self.platform_specific.valid_orientations = valid_orientations;
self self
} }
#[inline]
fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder {
self.platform_specific.prefers_home_indicator_hidden = hidden;
self
} }
/// Additional methods on `MonitorHandle` that are specific to iOS. #[inline]
fn with_preferred_screen_edges_deferring_system_gestures(
mut self,
edges: ScreenEdge,
) -> WindowBuilder {
self.platform_specific
.preferred_screen_edges_deferring_system_gestures = edges;
self
}
}
/// Additional methods on [`MonitorHandle`] that are specific to iOS.
pub trait MonitorHandleExtIOS { pub trait MonitorHandleExtIOS {
/// Returns a pointer to the `UIScreen` that is used by this monitor. /// 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; fn ui_screen(&self) -> *mut c_void;
/// Returns the preferred [`VideoMode`] 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) -> VideoMode;
} }
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
@ -123,9 +219,14 @@ impl MonitorHandleExtIOS for MonitorHandle {
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _ self.inner.ui_screen() as _
} }
#[inline]
fn preferred_video_mode(&self) -> VideoMode {
self.inner.preferred_video_mode()
}
} }
/// Valid orientations for a particular `Window`. /// Valid orientations for a particular [`Window`].
#[derive(Clone, Copy, Debug)] #[derive(Clone, Copy, Debug)]
pub enum ValidOrientations { pub enum ValidOrientations {
/// Excludes `PortraitUpsideDown` on iphone /// Excludes `PortraitUpsideDown` on iphone
@ -161,3 +262,19 @@ pub enum Idiom {
TV, TV,
CarPlay, CarPlay,
} }
bitflags! {
/// The [edges] of a screen.
///
/// [edges]: https://developer.apple.com/documentation/uikit/uirectedge?language=objc
#[derive(Default)]
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;
}
}

View file

@ -1,10 +1,10 @@
#![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)] #![allow(non_camel_case_types, non_snake_case, non_upper_case_globals)]
use std::{ffi::CString, ops::BitOr, os::raw::*}; use std::{convert::TryInto, ffi::CString, ops::BitOr, os::raw::*};
use objc::{runtime::Object, Encode, Encoding}; use objc::{runtime::Object, Encode, Encoding};
use crate::platform::ios::{Idiom, ValidOrientations}; use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations};
pub type id = *mut Object; pub type id = *mut Object;
pub const nil: id = 0 as id; pub const nil: id = 0 as id;
@ -173,6 +173,34 @@ impl UIInterfaceOrientationMask {
} }
} }
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger);
unsafe impl Encode for UIRectEdge {
fn encode() -> Encoding {
NSUInteger::encode()
}
}
impl From<ScreenEdge> for UIRectEdge {
fn from(screen_edge: ScreenEdge) -> UIRectEdge {
assert_eq!(
screen_edge.bits() & !ScreenEdge::ALL.bits(),
0,
"invalid `ScreenEdge`"
);
UIRectEdge(screen_edge.bits().into())
}
}
impl Into<ScreenEdge> for UIRectEdge {
fn into(self) -> ScreenEdge {
let bits: u8 = self.0.try_into().expect("invalid `UIRectEdge`");
ScreenEdge::from_bits(bits).expect("invalid `ScreenEdge`")
}
}
#[link(name = "UIKit", kind = "framework")] #[link(name = "UIKit", kind = "framework")]
#[link(name = "CoreFoundation", kind = "framework")] #[link(name = "CoreFoundation", kind = "framework")]
extern "C" { extern "C" {

View file

@ -10,15 +10,50 @@ use crate::{
platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger}, platform_impl::platform::ffi::{id, nil, CGFloat, CGRect, CGSize, NSInteger, NSUInteger},
}; };
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub struct VideoMode { pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16, pub(crate) bit_depth: u16,
pub(crate) refresh_rate: u16, pub(crate) refresh_rate: u16,
pub(crate) screen_mode: id,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate: self.refresh_rate,
screen_mode: unsafe { msg_send![self.screen_mode, retain] },
monitor: self.monitor.clone(),
}
}
}
impl Drop for VideoMode {
fn drop(&mut self) {
unsafe {
assert_main_thread!("`VideoMode` can only be dropped on the main thread on iOS");
msg_send![self.screen_mode, release];
}
}
}
impl VideoMode { impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate: NSInteger = msg_send![uiscreen, maximumFramesPerSecond];
let size: CGSize = msg_send![screen_mode, size];
VideoMode {
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
screen_mode: msg_send![screen_mode, retain],
monitor: MonitorHandle::retained_new(uiscreen),
}
}
pub fn size(&self) -> PhysicalSize { pub fn size(&self) -> PhysicalSize {
self.size.into() self.size.into()
} }
@ -133,9 +168,10 @@ impl MonitorHandle {
impl Inner { impl Inner {
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
unsafe { unsafe {
if self.uiscreen == main_uiscreen().uiscreen { let main = main_uiscreen();
if self.uiscreen == main.uiscreen {
Some("Primary".to_string()) Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen().uiscreen { } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
Some("Mirrored".to_string()) Some("Mirrored".to_string())
} else { } else {
uiscreens() uiscreens()
@ -168,25 +204,18 @@ impl Inner {
} }
pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = RootVideoMode> {
let refresh_rate: NSInteger = unsafe { msg_send![self.uiscreen, maximumFramesPerSecond] };
let available_modes: id = unsafe { msg_send![self.uiscreen, availableModes] };
let available_mode_count: NSUInteger = unsafe { msg_send![available_modes, count] };
let mut modes = BTreeSet::new(); let mut modes = BTreeSet::new();
unsafe {
let available_modes: id = msg_send![self.uiscreen, availableModes];
let available_mode_count: NSUInteger = msg_send![available_modes, count];
for i in 0..available_mode_count { for i in 0..available_mode_count {
let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; let mode: id = msg_send![available_modes, objectAtIndex: i];
let size: CGSize = unsafe { msg_send![mode, size] };
modes.insert(RootVideoMode { modes.insert(RootVideoMode {
video_mode: VideoMode { video_mode: VideoMode::retained_new(self.uiscreen, mode),
size: (size.width as u32, size.height as u32),
bit_depth: 32,
refresh_rate: refresh_rate as u16,
monitor: MonitorHandle::retained_new(self.uiscreen),
},
}); });
} }
}
modes.into_iter() modes.into_iter()
} }
@ -197,6 +226,15 @@ impl Inner {
pub fn ui_screen(&self) -> id { pub fn ui_screen(&self) -> id {
self.uiscreen self.uiscreen
} }
pub fn preferred_video_mode(&self) -> RootVideoMode {
unsafe {
let mode: id = msg_send![self.uiscreen, preferredMode];
RootVideoMode {
video_mode: VideoMode::retained_new(self.uiscreen, mode),
}
}
}
} }
// requires being run on main thread // requires being run on main thread
@ -206,8 +244,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle {
} }
// requires being run on main thread // requires being run on main thread
unsafe fn mirrored_uiscreen() -> MonitorHandle { unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen]; let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
MonitorHandle::retained_new(uiscreen) MonitorHandle::retained_new(uiscreen)
} }

View file

@ -11,13 +11,48 @@ use crate::{
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
event_loop, event_loop,
ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, ffi::{
id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase,
},
window::PlatformSpecificWindowBuilderAttributes, window::PlatformSpecificWindowBuilderAttributes,
DeviceId, DeviceId,
}, },
window::{Fullscreen, WindowAttributes, WindowId as RootWindowId}, window::{Fullscreen, WindowAttributes, WindowId as RootWindowId},
}; };
macro_rules! add_property {
(
$decl:ident,
$name:ident: $t:ty,
$setter_name:ident: |$object:ident| $after_set:expr,
$getter_name:ident,
) => {
{
const VAR_NAME: &'static str = concat!("_", stringify!($name));
$decl.add_ivar::<$t>(VAR_NAME);
#[allow(non_snake_case)]
extern "C" fn $setter_name($object: &mut Object, _: Sel, value: $t) {
unsafe {
$object.set_ivar::<$t>(VAR_NAME, value);
}
$after_set
}
#[allow(non_snake_case)]
extern "C" fn $getter_name($object: &Object, _: Sel) -> $t {
unsafe { *$object.get_ivar::<$t>(VAR_NAME) }
}
$decl.add_method(
sel!($setter_name:),
$setter_name as extern "C" fn(&mut Object, Sel, $t),
);
$decl.add_method(
sel!($getter_name),
$getter_name as extern "C" fn(&Object, Sel) -> $t,
);
}
};
}
// requires main thread // requires main thread
unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class {
static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None; static mut CLASSES: Option<HashMap<*const Class, &'static Class>> = None;
@ -91,67 +126,56 @@ unsafe fn get_view_controller_class() -> &'static Class {
if CLASS.is_none() { if CLASS.is_none() {
let uiviewcontroller_class = class!(UIViewController); let uiviewcontroller_class = class!(UIViewController);
extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) {
unsafe {
object.set_ivar::<BOOL>("_prefers_status_bar_hidden", hidden);
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
}
extern "C" fn prefers_status_bar_hidden(object: &Object, _: Sel) -> BOOL {
unsafe { *object.get_ivar::<BOOL>("_prefers_status_bar_hidden") }
}
extern "C" fn set_supported_orientations(
object: &mut Object,
_: Sel,
orientations: UIInterfaceOrientationMask,
) {
unsafe {
object.set_ivar::<UIInterfaceOrientationMask>(
"_supported_orientations",
orientations,
);
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
}
extern "C" fn supported_orientations(
object: &Object,
_: Sel,
) -> UIInterfaceOrientationMask {
unsafe { *object.get_ivar::<UIInterfaceOrientationMask>("_supported_orientations") }
}
extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL {
YES YES
} }
let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class) let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class)
.expect("Failed to declare class `WinitUIViewController`"); .expect("Failed to declare class `WinitUIViewController`");
decl.add_ivar::<BOOL>("_prefers_status_bar_hidden");
decl.add_ivar::<UIInterfaceOrientationMask>("_supported_orientations");
decl.add_method(
sel!(setPrefersStatusBarHidden:),
set_prefers_status_bar_hidden as extern "C" fn(&mut Object, Sel, BOOL),
);
decl.add_method(
sel!(prefersStatusBarHidden),
prefers_status_bar_hidden as extern "C" fn(&Object, Sel) -> BOOL,
);
decl.add_method(
sel!(setSupportedInterfaceOrientations:),
set_supported_orientations
as extern "C" fn(&mut Object, Sel, UIInterfaceOrientationMask),
);
decl.add_method(
sel!(supportedInterfaceOrientations),
supported_orientations as extern "C" fn(&Object, Sel) -> UIInterfaceOrientationMask,
);
decl.add_method( decl.add_method(
sel!(shouldAutorotate), sel!(shouldAutorotate),
should_autorotate as extern "C" fn(&Object, Sel) -> BOOL, should_autorotate as extern "C" fn(&Object, Sel) -> BOOL,
); );
add_property! {
decl,
prefers_status_bar_hidden: BOOL,
setPrefersStatusBarHidden: |object| {
unsafe {
let () = msg_send![object, setNeedsStatusBarAppearanceUpdate];
}
},
prefersStatusBarHidden,
}
add_property! {
decl,
prefers_home_indicator_auto_hidden: BOOL,
setPrefersHomeIndicatorAutoHidden: |object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
},
prefersHomeIndicatorAutoHidden,
}
add_property! {
decl,
supported_orientations: UIInterfaceOrientationMask,
setSupportedInterfaceOrientations: |object| {
unsafe {
let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation];
}
},
supportedInterfaceOrientations,
}
add_property! {
decl,
preferred_screen_edges_deferring_system_gestures: UIRectEdge,
setPreferredScreenEdgesDeferringSystemGestures: |object| {
unsafe {
let () = msg_send![object, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
},
preferredScreenEdgesDeferringSystemGestures,
}
CLASS = Some(decl.register()); CLASS = Some(decl.register());
} }
CLASS.unwrap() CLASS.unwrap()
@ -333,6 +357,14 @@ pub unsafe fn create_view_controller(
platform_attributes.valid_orientations, platform_attributes.valid_orientations,
idiom, idiom,
); );
let prefers_home_indicator_hidden = if platform_attributes.prefers_home_indicator_hidden {
YES
} else {
NO
};
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let () = msg_send![ let () = msg_send![
view_controller, view_controller,
setPrefersStatusBarHidden: status_bar_hidden setPrefersStatusBarHidden: status_bar_hidden
@ -341,6 +373,14 @@ pub unsafe fn create_view_controller(
view_controller, view_controller,
setSupportedInterfaceOrientations: supported_orientations setSupportedInterfaceOrientations: supported_orientations
]; ];
let () = msg_send![
view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
let () = msg_send![
view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
let () = msg_send![view_controller, setView: view]; let () = msg_send![view_controller, setView: view];
view_controller view_controller
} }
@ -366,7 +406,11 @@ pub unsafe fn create_window(
let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat];
} }
match window_attributes.fullscreen { match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => unimplemented!(), Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id;
let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
}
Some(Fullscreen::Borderless(ref monitor)) => { Some(Fullscreen::Borderless(ref monitor)) => {
msg_send![window, setScreen:monitor.ui_screen()] msg_send![window, setScreen:monitor.ui_screen()]
} }

View file

@ -10,11 +10,14 @@ use crate::{
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
icon::Icon, icon::Icon,
monitor::MonitorHandle as RootMonitorHandle, monitor::MonitorHandle as RootMonitorHandle,
platform::ios::{MonitorHandleExtIOS, ValidOrientations}, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
event_loop, event_loop,
ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, ffi::{
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
UIRectEdge,
},
monitor, view, EventLoopWindowTarget, MonitorHandle, monitor, view, EventLoopWindowTarget, MonitorHandle,
}, },
window::{CursorIcon, Fullscreen, WindowAttributes}, window::{CursorIcon, Fullscreen, WindowAttributes},
@ -159,10 +162,19 @@ impl Inner {
pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) { pub fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe { unsafe {
match monitor { let uiscreen = match monitor {
Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO Some(Fullscreen::Exclusive(video_mode)) => {
Some(Fullscreen::Borderless(monitor)) => { let uiscreen = video_mode.video_mode.monitor.ui_screen() as id;
let uiscreen = monitor.ui_screen() as id; let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode];
uiscreen
}
Some(Fullscreen::Borderless(monitor)) => monitor.ui_screen() as id,
None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS");
return;
}
};
let current: id = msg_send![self.window, screen]; let current: id = msg_send![self.window, screen];
let bounds: CGRect = msg_send![uiscreen, bounds]; let bounds: CGRect = msg_send![uiscreen, bounds];
@ -172,9 +184,6 @@ impl Inner {
} }
let () = msg_send![self.window, setFrame: bounds]; let () = msg_send![self.window, setFrame: bounds];
} }
None => warn!("`Window::set_fullscreen(None)` ignored on iOS"),
}
}
} }
pub fn fullscreen(&self) -> Option<Fullscreen> { pub fn fullscreen(&self) -> Option<Fullscreen> {
@ -295,7 +304,9 @@ impl Window {
unsafe { unsafe {
let screen = match window_attributes.fullscreen { let screen = match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO: do we set the frame to video mode bounds instead of screen bounds? Some(Fullscreen::Exclusive(ref video_mode)) => {
video_mode.video_mode.monitor.ui_screen() as id
}
Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id, Some(Fullscreen::Borderless(ref monitor)) => monitor.ui_screen() as id,
None => monitor::main_uiscreen().ui_screen(), None => monitor::main_uiscreen().ui_screen(),
}; };
@ -375,6 +386,26 @@ impl Inner {
]; ];
} }
} }
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe {
let prefers_home_indicator_hidden = if hidden { NO } else { YES };
let () = msg_send![
self.view_controller,
setPrefersHomeIndicatorAutoHidden: prefers_home_indicator_hidden
];
}
}
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into();
unsafe {
let () = msg_send![
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
}
} }
impl Inner { impl Inner {
@ -496,6 +527,8 @@ pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class: &'static Class, pub root_view_class: &'static Class,
pub hidpi_factor: Option<f64>, pub hidpi_factor: Option<f64>,
pub valid_orientations: ValidOrientations, pub valid_orientations: ValidOrientations,
pub prefers_home_indicator_hidden: bool,
pub preferred_screen_edges_deferring_system_gestures: ScreenEdge,
} }
impl Default for PlatformSpecificWindowBuilderAttributes { impl Default for PlatformSpecificWindowBuilderAttributes {
@ -504,6 +537,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes {
root_view_class: class!(UIView), root_view_class: class!(UIView),
hidpi_factor: None, hidpi_factor: None,
valid_orientations: Default::default(), valid_orientations: Default::default(),
prefers_home_indicator_hidden: false,
preferred_screen_edges_deferring_system_gestures: Default::default(),
} }
} }
} }