diff --git a/CHANGELOG.md b/CHANGELOG.md index b8ec9346..98faa296 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ consists of `Fullscreen::Exclusive(VideoMode)` and `Fullscreen::Borderless(MonitorHandle)` variants. - 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) diff --git a/Cargo.toml b/Cargo.toml index e205b9a2..9c1678ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -43,7 +43,7 @@ version = "0.1.3" default_features = false features = ["display_link"] -[target.'cfg(target_os = "windows")'.dependencies] +[target.'cfg(any(target_os = "ios", target_os = "windows"))'.dependencies] bitflags = "1" [target.'cfg(target_os = "windows")'.dependencies.winapi] diff --git a/FEATURES.md b/FEATURES.md index 73994604..7e697570 100644 --- a/FEATURES.md +++ b/FEATURES.md @@ -160,7 +160,7 @@ Legend: |Window maximization toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|**N/A** | |Fullscreen |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | |Fullscreen toggle |✔️ |✔️ |✔️ |✔️ |**N/A**|**N/A**|❌ | -|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |❌ |❌ | +|Exclusive fullscreen |✔️ |✔️ |✔️ |**N/A** |❌ |✔️ |❌ | |HiDPI support |✔️ |✔️ |✔️ |✔️ |▢[#721]|✔️ |✔️ | |Popup windows |❌ |❌ |❌ |❌ |❌ |❌ |❌ | diff --git a/src/lib.rs b/src/lib.rs index a4a65f54..4a85a2ca 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -123,7 +123,7 @@ extern crate serde; #[macro_use] extern crate derivative; #[macro_use] -#[cfg(target_os = "windows")] +#[cfg(any(target_os = "ios", target_os = "windows"))] extern crate bitflags; #[cfg(any(target_os = "macos", target_os = "ios"))] #[macro_use] diff --git a/src/platform/ios.rs b/src/platform/ios.rs index 8c1034a4..a2a06a65 100644 --- a/src/platform/ios.rs +++ b/src/platform/ios.rs @@ -4,13 +4,13 @@ use std::os::raw::c_void; use crate::{ event_loop::EventLoop, - monitor::MonitorHandle, + monitor::{MonitorHandle, VideoMode}, window::{Window, WindowBuilder}, }; -/// Additional methods on `EventLoop` that are specific to iOS. +/// Additional methods on [`EventLoop`] that are specific to iOS. 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; } @@ -20,32 +20,66 @@ impl EventLoopExtIOS for EventLoop { } } -/// Additional methods on `Window` that are specific to iOS. +/// Additional methods on [`Window`] that are specific to iOS. 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; - /// 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; - /// 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; - /// 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); - /// 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); + + /// 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 { @@ -73,23 +107,62 @@ impl WindowExtIOS for Window { fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { 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) + } + + #[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. +/// Additional methods on [`WindowBuilder`] that are specific to iOS. 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; - /// 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 - /// 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; - /// 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; + + /// 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 { @@ -110,12 +183,35 @@ impl WindowBuilderExtIOS for WindowBuilder { self.platform_specific.valid_orientations = valid_orientations; self } + + #[inline] + fn with_prefers_home_indicator_hidden(mut self, hidden: bool) -> WindowBuilder { + self.platform_specific.prefers_home_indicator_hidden = hidden; + self + } + + #[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. +/// Additional methods on [`MonitorHandle`] that are specific to iOS. 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; + + /// 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 { @@ -123,9 +219,14 @@ impl MonitorHandleExtIOS for MonitorHandle { fn ui_screen(&self) -> *mut c_void { 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)] pub enum ValidOrientations { /// Excludes `PortraitUpsideDown` on iphone @@ -161,3 +262,19 @@ pub enum Idiom { TV, 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; + } +} diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index 9b0c26be..4782cf1a 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -1,10 +1,10 @@ #![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 crate::platform::ios::{Idiom, ValidOrientations}; +use crate::platform::ios::{Idiom, ScreenEdge, ValidOrientations}; pub type id = *mut Object; 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 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 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 = "CoreFoundation", kind = "framework")] extern "C" { diff --git a/src/platform_impl/ios/monitor.rs b/src/platform_impl/ios/monitor.rs index 90257077..96b6f7eb 100644 --- a/src/platform_impl/ios/monitor.rs +++ b/src/platform_impl/ios/monitor.rs @@ -10,15 +10,50 @@ use crate::{ 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(crate) size: (u32, u32), pub(crate) bit_depth: u16, pub(crate) refresh_rate: u16, + pub(crate) screen_mode: id, 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 { + 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 { self.size.into() } @@ -133,9 +168,10 @@ impl MonitorHandle { impl Inner { pub fn name(&self) -> Option { unsafe { - if self.uiscreen == main_uiscreen().uiscreen { + let main = main_uiscreen(); + if self.uiscreen == main.uiscreen { Some("Primary".to_string()) - } else if self.uiscreen == mirrored_uiscreen().uiscreen { + } else if self.uiscreen == mirrored_uiscreen(&main).uiscreen { Some("Mirrored".to_string()) } else { uiscreens() @@ -168,24 +204,17 @@ impl Inner { } pub fn video_modes(&self) -> impl Iterator { - 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(); + 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 { - let mode: id = unsafe { msg_send![available_modes, objectAtIndex: i] }; - let size: CGSize = unsafe { msg_send![mode, size] }; - modes.insert(RootVideoMode { - video_mode: VideoMode { - size: (size.width as u32, size.height as u32), - bit_depth: 32, - refresh_rate: refresh_rate as u16, - monitor: MonitorHandle::retained_new(self.uiscreen), - }, - }); + for i in 0..available_mode_count { + let mode: id = msg_send![available_modes, objectAtIndex: i]; + modes.insert(RootVideoMode { + video_mode: VideoMode::retained_new(self.uiscreen, mode), + }); + } } modes.into_iter() @@ -197,6 +226,15 @@ impl Inner { pub fn ui_screen(&self) -> id { 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 @@ -206,8 +244,8 @@ pub unsafe fn main_uiscreen() -> MonitorHandle { } // requires being run on main thread -unsafe fn mirrored_uiscreen() -> MonitorHandle { - let uiscreen: id = msg_send![class!(UIScreen), mirroredScreen]; +unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle { + let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen]; MonitorHandle::retained_new(uiscreen) } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index 95047814..79211404 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -11,13 +11,48 @@ use crate::{ platform_impl::platform::{ app_state::AppState, event_loop, - ffi::{id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UITouchPhase}, + ffi::{ + id, nil, CGFloat, CGPoint, CGRect, UIInterfaceOrientationMask, UIRectEdge, UITouchPhase, + }, window::PlatformSpecificWindowBuilderAttributes, DeviceId, }, 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 unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { static mut CLASSES: Option> = None; @@ -91,67 +126,56 @@ unsafe fn get_view_controller_class() -> &'static Class { if CLASS.is_none() { let uiviewcontroller_class = class!(UIViewController); - extern "C" fn set_prefers_status_bar_hidden(object: &mut Object, _: Sel, hidden: BOOL) { - unsafe { - object.set_ivar::("_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::("_prefers_status_bar_hidden") } - } - - extern "C" fn set_supported_orientations( - object: &mut Object, - _: Sel, - orientations: UIInterfaceOrientationMask, - ) { - unsafe { - object.set_ivar::( - "_supported_orientations", - orientations, - ); - let () = msg_send![class!(UIViewController), attemptRotationToDeviceOrientation]; - } - } - - extern "C" fn supported_orientations( - object: &Object, - _: Sel, - ) -> UIInterfaceOrientationMask { - unsafe { *object.get_ivar::("_supported_orientations") } - } - extern "C" fn should_autorotate(_: &Object, _: Sel) -> BOOL { YES } let mut decl = ClassDecl::new("WinitUIViewController", uiviewcontroller_class) .expect("Failed to declare class `WinitUIViewController`"); - decl.add_ivar::("_prefers_status_bar_hidden"); - decl.add_ivar::("_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( sel!(shouldAutorotate), 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.unwrap() @@ -333,6 +357,14 @@ pub unsafe fn create_view_controller( platform_attributes.valid_orientations, 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![ view_controller, setPrefersStatusBarHidden: status_bar_hidden @@ -341,6 +373,14 @@ pub unsafe fn create_view_controller( view_controller, 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]; view_controller } @@ -366,7 +406,11 @@ pub unsafe fn create_window( let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; } 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)) => { msg_send![window, setScreen:monitor.ui_screen()] } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 05b68ea4..62f00281 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -10,11 +10,14 @@ use crate::{ error::{ExternalError, NotSupportedError, OsError as RootOsError}, icon::Icon, monitor::MonitorHandle as RootMonitorHandle, - platform::ios::{MonitorHandleExtIOS, ValidOrientations}, + platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ app_state::AppState, event_loop, - ffi::{id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask}, + ffi::{ + id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, + UIRectEdge, + }, monitor, view, EventLoopWindowTarget, MonitorHandle, }, window::{CursorIcon, Fullscreen, WindowAttributes}, @@ -159,21 +162,27 @@ impl Inner { pub fn set_fullscreen(&self, monitor: Option) { unsafe { - match monitor { - Some(Fullscreen::Exclusive(_)) => unimplemented!("exclusive fullscreen on iOS"), // TODO - Some(Fullscreen::Borderless(monitor)) => { - let uiscreen = monitor.ui_screen() as id; - let current: id = msg_send![self.window, screen]; - let bounds: CGRect = msg_send![uiscreen, bounds]; - - // this is pretty slow on iOS, so avoid doing it if we can - if uiscreen != current { - let () = msg_send![self.window, setScreen: uiscreen]; - } - let () = msg_send![self.window, setFrame: bounds]; + let uiscreen = match monitor { + Some(Fullscreen::Exclusive(video_mode)) => { + let uiscreen = video_mode.video_mode.monitor.ui_screen() as id; + let () = msg_send![uiscreen, setCurrentMode: video_mode.video_mode.screen_mode]; + uiscreen } - None => warn!("`Window::set_fullscreen(None)` ignored on iOS"), + 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 bounds: CGRect = msg_send![uiscreen, bounds]; + + // this is pretty slow on iOS, so avoid doing it if we can + if uiscreen != current { + let () = msg_send![self.window, setScreen: uiscreen]; } + let () = msg_send![self.window, setFrame: bounds]; } } @@ -295,7 +304,9 @@ impl Window { unsafe { 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, 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 { @@ -496,6 +527,8 @@ pub struct PlatformSpecificWindowBuilderAttributes { pub root_view_class: &'static Class, pub hidpi_factor: Option, pub valid_orientations: ValidOrientations, + pub prefers_home_indicator_hidden: bool, + pub preferred_screen_edges_deferring_system_gestures: ScreenEdge, } impl Default for PlatformSpecificWindowBuilderAttributes { @@ -504,6 +537,8 @@ impl Default for PlatformSpecificWindowBuilderAttributes { root_view_class: class!(UIView), hidpi_factor: None, valid_orientations: Default::default(), + prefers_home_indicator_hidden: false, + preferred_screen_edges_deferring_system_gestures: Default::default(), } } }