2019-05-26 11:10:41 +10:00
use std ::{
collections ::VecDeque ,
ops ::{ Deref , DerefMut } ,
} ;
2019-06-22 01:33:15 +10:00
use objc ::runtime ::{ Class , Object , NO , YES } ;
use crate ::{
dpi ::{ self , LogicalPosition , LogicalSize } ,
error ::{ ExternalError , NotSupportedError , OsError as RootOsError } ,
icon ::Icon ,
monitor ::MonitorHandle as RootMonitorHandle ,
platform ::ios ::{ MonitorHandleExtIOS , ValidOrientations } ,
platform_impl ::platform ::{
2019-05-26 11:10:41 +10:00
app_state ::AppState ,
event_loop ,
2019-06-22 01:33:15 +10:00
ffi ::{ id , CGFloat , CGPoint , CGRect , CGSize , UIEdgeInsets , UIInterfaceOrientationMask } ,
monitor , view , EventLoopWindowTarget , MonitorHandle ,
2019-05-26 11:10:41 +10:00
} ,
2019-07-30 04:16:14 +10:00
window ::{ CursorIcon , Fullscreen , WindowAttributes } ,
2019-05-26 11:10:41 +10:00
} ;
pub struct Inner {
pub window : id ,
pub view_controller : id ,
pub view : id ,
supports_safe_area : bool ,
}
impl Drop for Inner {
fn drop ( & mut self ) {
unsafe {
let ( ) = msg_send! [ self . view , release ] ;
let ( ) = msg_send! [ self . view_controller , release ] ;
let ( ) = msg_send! [ self . window , release ] ;
}
}
}
impl Inner {
pub fn set_title ( & self , _title : & str ) {
debug! ( " `Window::set_title` is ignored on iOS " )
}
2019-05-30 11:29:54 +10:00
pub fn set_visible ( & self , visible : bool ) {
match visible {
true = > unsafe {
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . window , setHidden : NO ] ;
2019-05-30 11:29:54 +10:00
} ,
false = > unsafe {
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . window , setHidden : YES ] ;
2019-05-30 11:29:54 +10:00
} ,
2019-05-26 11:10:41 +10:00
}
}
pub fn request_redraw ( & self ) {
unsafe {
let ( ) = msg_send! [ self . view , setNeedsDisplay ] ;
}
}
2019-05-30 11:29:54 +10:00
pub fn inner_position ( & self ) -> Result < LogicalPosition , NotSupportedError > {
2019-05-26 11:10:41 +10:00
unsafe {
let safe_area = self . safe_area_screen_space ( ) ;
2019-05-30 11:29:54 +10:00
Ok ( LogicalPosition {
2019-05-26 11:10:41 +10:00
x : safe_area . origin . x ,
y : safe_area . origin . y ,
} )
}
}
2019-05-30 11:29:54 +10:00
pub fn outer_position ( & self ) -> Result < LogicalPosition , NotSupportedError > {
2019-05-26 11:10:41 +10:00
unsafe {
let screen_frame = self . screen_frame ( ) ;
2019-05-30 11:29:54 +10:00
Ok ( LogicalPosition {
2019-05-26 11:10:41 +10:00
x : screen_frame . origin . x ,
y : screen_frame . origin . y ,
} )
}
}
2019-05-30 11:29:54 +10:00
pub fn set_outer_position ( & self , position : LogicalPosition ) {
2019-05-26 11:10:41 +10:00
unsafe {
let screen_frame = self . screen_frame ( ) ;
let new_screen_frame = CGRect {
origin : CGPoint {
x : position . x as _ ,
y : position . y as _ ,
} ,
size : screen_frame . size ,
} ;
let bounds = self . from_screen_space ( new_screen_frame ) ;
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . window , setBounds : bounds ] ;
2019-05-26 11:10:41 +10:00
}
}
2019-05-30 11:29:54 +10:00
pub fn inner_size ( & self ) -> LogicalSize {
2019-05-26 11:10:41 +10:00
unsafe {
let safe_area = self . safe_area_screen_space ( ) ;
2019-05-30 11:29:54 +10:00
LogicalSize {
2019-05-26 11:10:41 +10:00
width : safe_area . size . width ,
height : safe_area . size . height ,
2019-05-30 11:29:54 +10:00
}
2019-05-26 11:10:41 +10:00
}
}
2019-05-30 11:29:54 +10:00
pub fn outer_size ( & self ) -> LogicalSize {
2019-05-26 11:10:41 +10:00
unsafe {
let screen_frame = self . screen_frame ( ) ;
2019-05-30 11:29:54 +10:00
LogicalSize {
2019-05-26 11:10:41 +10:00
width : screen_frame . size . width ,
height : screen_frame . size . height ,
2019-05-30 11:29:54 +10:00
}
2019-05-26 11:10:41 +10:00
}
}
pub fn set_inner_size ( & self , _size : LogicalSize ) {
unimplemented! ( " not clear what `Window::set_inner_size` means on iOS " ) ;
}
2019-05-30 11:29:54 +10:00
pub fn set_min_inner_size ( & self , _dimensions : Option < LogicalSize > ) {
warn! ( " `Window::set_min_inner_size` is ignored on iOS " )
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn set_max_inner_size ( & self , _dimensions : Option < LogicalSize > ) {
warn! ( " `Window::set_max_inner_size` is ignored on iOS " )
2019-05-26 11:10:41 +10:00
}
pub fn set_resizable ( & self , _resizable : bool ) {
warn! ( " `Window::set_resizable` is ignored on iOS " )
}
2019-05-30 11:29:54 +10:00
pub fn hidpi_factor ( & self ) -> f64 {
2019-05-26 11:10:41 +10:00
unsafe {
let hidpi : CGFloat = msg_send! [ self . view , contentScaleFactor ] ;
hidpi as _
}
}
2019-05-30 11:29:54 +10:00
pub fn set_cursor_icon ( & self , _cursor : CursorIcon ) {
debug! ( " `Window::set_cursor_icon` ignored on iOS " )
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn set_cursor_position ( & self , _position : LogicalPosition ) -> Result < ( ) , ExternalError > {
Err ( ExternalError ::NotSupported ( NotSupportedError ::new ( ) ) )
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn set_cursor_grab ( & self , _grab : bool ) -> Result < ( ) , ExternalError > {
Err ( ExternalError ::NotSupported ( NotSupportedError ::new ( ) ) )
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn set_cursor_visible ( & self , _visible : bool ) {
debug! ( " `Window::set_cursor_visible` is ignored on iOS " )
2019-05-26 11:10:41 +10:00
}
pub fn set_maximized ( & self , _maximized : bool ) {
warn! ( " `Window::set_maximized` is ignored on iOS " )
}
2019-07-30 04:16:14 +10:00
pub fn set_fullscreen ( & self , monitor : Option < Fullscreen > ) {
2019-05-26 11:10:41 +10:00
unsafe {
match monitor {
2019-07-30 04:16:14 +10:00
Some ( Fullscreen ::Exclusive ( _ ) ) = > unimplemented! ( " exclusive fullscreen on iOS " ) , // TODO
Some ( Fullscreen ::Borderless ( monitor ) ) = > {
2019-05-30 11:29:54 +10:00
let uiscreen = monitor . ui_screen ( ) as id ;
2019-05-26 11:10:41 +10:00
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 {
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . window , setScreen : uiscreen ] ;
2019-05-26 11:10:41 +10:00
}
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . window , setFrame : bounds ] ;
2019-06-25 02:14:55 +10:00
}
2019-05-26 11:10:41 +10:00
None = > warn! ( " `Window::set_fullscreen(None)` ignored on iOS " ) ,
}
}
}
2019-07-30 04:16:14 +10:00
pub fn fullscreen ( & self ) -> Option < Fullscreen > {
2019-05-26 11:10:41 +10:00
unsafe {
2019-05-30 11:29:54 +10:00
let monitor = self . current_monitor ( ) ;
let uiscreen = monitor . inner . ui_screen ( ) ;
2019-05-26 11:10:41 +10:00
let screen_space_bounds = self . screen_frame ( ) ;
let screen_bounds : CGRect = msg_send! [ uiscreen , bounds ] ;
// TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds . origin . x = = screen_bounds . origin . x
& & screen_space_bounds . origin . y = = screen_bounds . origin . y
& & screen_space_bounds . size . width = = screen_bounds . size . width
& & screen_space_bounds . size . height = = screen_bounds . size . height
{
2019-07-30 04:16:14 +10:00
Some ( Fullscreen ::Borderless ( monitor ) )
2019-05-26 11:10:41 +10:00
} else {
None
}
}
}
pub fn set_decorations ( & self , decorations : bool ) {
unsafe {
let status_bar_hidden = if decorations { NO } else { YES } ;
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [
self . view_controller ,
setPrefersStatusBarHidden : status_bar_hidden
] ;
2019-05-26 11:10:41 +10:00
}
}
pub fn set_always_on_top ( & self , _always_on_top : bool ) {
warn! ( " `Window::set_always_on_top` is ignored on iOS " )
}
pub fn set_window_icon ( & self , _icon : Option < Icon > ) {
warn! ( " `Window::set_window_icon` is ignored on iOS " )
}
2019-05-30 11:29:54 +10:00
pub fn set_ime_position ( & self , _position : LogicalPosition ) {
warn! ( " `Window::set_ime_position` is ignored on iOS " )
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn current_monitor ( & self ) -> RootMonitorHandle {
2019-05-26 11:10:41 +10:00
unsafe {
let uiscreen : id = msg_send! [ self . window , screen ] ;
2019-06-22 01:33:15 +10:00
RootMonitorHandle {
inner : MonitorHandle ::retained_new ( uiscreen ) ,
}
2019-05-26 11:10:41 +10:00
}
}
2019-05-30 11:29:54 +10:00
pub fn available_monitors ( & self ) -> VecDeque < MonitorHandle > {
2019-06-22 01:33:15 +10:00
unsafe { monitor ::uiscreens ( ) }
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
pub fn primary_monitor ( & self ) -> MonitorHandle {
2019-06-22 01:33:15 +10:00
unsafe { monitor ::main_uiscreen ( ) }
2019-05-26 11:10:41 +10:00
}
pub fn id ( & self ) -> WindowId {
self . window . into ( )
}
}
pub struct Window {
pub inner : Inner ,
}
impl Drop for Window {
fn drop ( & mut self ) {
unsafe {
assert_main_thread! ( " `Window::drop` can only be run on the main thread on iOS " ) ;
}
}
}
unsafe impl Send for Window { }
unsafe impl Sync for Window { }
impl Deref for Window {
type Target = Inner ;
fn deref ( & self ) -> & Inner {
unsafe {
assert_main_thread! ( " `Window` methods can only be run on the main thread on iOS " ) ;
}
& self . inner
}
}
impl DerefMut for Window {
fn deref_mut ( & mut self ) -> & mut Inner {
unsafe {
assert_main_thread! ( " `Window` methods can only be run on the main thread on iOS " ) ;
}
& mut self . inner
}
}
impl Window {
pub fn new < T > (
event_loop : & EventLoopWindowTarget < T > ,
window_attributes : WindowAttributes ,
platform_attributes : PlatformSpecificWindowBuilderAttributes ,
2019-05-30 11:29:54 +10:00
) -> Result < Window , RootOsError > {
if let Some ( _ ) = window_attributes . min_inner_size {
warn! ( " `WindowAttributes::min_inner_size` is ignored on iOS " ) ;
2019-05-26 11:10:41 +10:00
}
2019-05-30 11:29:54 +10:00
if let Some ( _ ) = window_attributes . max_inner_size {
warn! ( " `WindowAttributes::max_inner_size` is ignored on iOS " ) ;
2019-05-26 11:10:41 +10:00
}
if window_attributes . always_on_top {
warn! ( " `WindowAttributes::always_on_top` is unsupported on iOS " ) ;
}
// TODO: transparency, visible
unsafe {
2019-07-30 04:16:14 +10:00
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 ::Borderless ( ref monitor ) ) = > monitor . ui_screen ( ) as id ,
None = > monitor ::main_uiscreen ( ) . ui_screen ( ) ,
} ;
2019-05-26 11:10:41 +10:00
let screen_bounds : CGRect = msg_send! [ screen , bounds ] ;
2019-05-30 11:29:54 +10:00
let frame = match window_attributes . inner_size {
2019-06-25 02:14:55 +10:00
Some ( dim ) = > CGRect {
origin : screen_bounds . origin ,
size : CGSize {
width : dim . width ,
height : dim . height ,
} ,
2019-05-26 11:10:41 +10:00
} ,
None = > screen_bounds ,
} ;
let view = view ::create_view ( & window_attributes , & platform_attributes , frame . clone ( ) ) ;
2019-06-22 01:33:15 +10:00
let view_controller =
view ::create_view_controller ( & window_attributes , & platform_attributes , view ) ;
let window = view ::create_window (
& window_attributes ,
& platform_attributes ,
frame ,
view_controller ,
) ;
2019-05-26 11:10:41 +10:00
let supports_safe_area = event_loop . capabilities ( ) . supports_safe_area ;
let result = Window {
inner : Inner {
window ,
view_controller ,
view ,
supports_safe_area ,
} ,
} ;
AppState ::set_key_window ( window ) ;
Ok ( result )
}
}
}
// WindowExtIOS
impl Inner {
2019-06-22 01:33:15 +10:00
pub fn ui_window ( & self ) -> id {
self . window
}
pub fn ui_view_controller ( & self ) -> id {
self . view_controller
}
pub fn ui_view ( & self ) -> id {
self . view
}
2019-05-26 11:10:41 +10:00
pub fn set_hidpi_factor ( & self , hidpi_factor : f64 ) {
unsafe {
2019-06-22 01:33:15 +10:00
assert! (
dpi ::validate_hidpi_factor ( hidpi_factor ) ,
" `WindowExtIOS::set_hidpi_factor` received an invalid hidpi factor "
) ;
2019-05-26 11:10:41 +10:00
let hidpi_factor = hidpi_factor as CGFloat ;
2019-06-22 01:33:15 +10:00
let ( ) = msg_send! [ self . view , setContentScaleFactor : hidpi_factor ] ;
2019-05-26 11:10:41 +10:00
}
}
pub fn set_valid_orientations ( & self , valid_orientations : ValidOrientations ) {
unsafe {
let idiom = event_loop ::get_idiom ( ) ;
2019-06-22 01:33:15 +10:00
let supported_orientations = UIInterfaceOrientationMask ::from_valid_orientations_idiom (
valid_orientations ,
idiom ,
) ;
msg_send! [
self . view_controller ,
setSupportedInterfaceOrientations : supported_orientations
] ;
2019-05-26 11:10:41 +10:00
}
}
}
impl Inner {
// requires main thread
unsafe fn screen_frame ( & self ) -> CGRect {
self . to_screen_space ( msg_send! [ self . window , bounds ] )
}
// requires main thread
unsafe fn to_screen_space ( & self , rect : CGRect ) -> CGRect {
let screen : id = msg_send! [ self . window , screen ] ;
if ! screen . is_null ( ) {
let screen_space : id = msg_send! [ screen , coordinateSpace ] ;
msg_send! [ self . window , convertRect :rect toCoordinateSpace :screen_space ]
} else {
rect
}
}
// requires main thread
unsafe fn from_screen_space ( & self , rect : CGRect ) -> CGRect {
let screen : id = msg_send! [ self . window , screen ] ;
if ! screen . is_null ( ) {
let screen_space : id = msg_send! [ screen , coordinateSpace ] ;
msg_send! [ self . window , convertRect :rect fromCoordinateSpace :screen_space ]
} else {
rect
}
}
// requires main thread
unsafe fn safe_area_screen_space ( & self ) -> CGRect {
let bounds : CGRect = msg_send! [ self . window , bounds ] ;
if self . supports_safe_area {
let safe_area : UIEdgeInsets = msg_send! [ self . window , safeAreaInsets ] ;
let safe_bounds = CGRect {
origin : CGPoint {
x : bounds . origin . x + safe_area . left ,
y : bounds . origin . y + safe_area . top ,
} ,
size : CGSize {
width : bounds . size . width - safe_area . left - safe_area . right ,
height : bounds . size . height - safe_area . top - safe_area . bottom ,
} ,
} ;
self . to_screen_space ( safe_bounds )
} else {
let screen_frame = self . to_screen_space ( bounds ) ;
let status_bar_frame : CGRect = {
let app : id = msg_send! [ class! ( UIApplication ) , sharedApplication ] ;
2019-06-22 01:33:15 +10:00
assert! (
! app . is_null ( ) ,
" `Window::get_inner_position` cannot be called before `EventLoop::run` on iOS "
) ;
2019-05-26 11:10:41 +10:00
msg_send! [ app , statusBarFrame ]
} ;
let ( y , height ) = if screen_frame . origin . y > status_bar_frame . size . height {
( screen_frame . origin . y , screen_frame . size . height )
} else {
let y = status_bar_frame . size . height ;
2019-06-22 01:33:15 +10:00
let height = screen_frame . size . height
- ( status_bar_frame . size . height - screen_frame . origin . y ) ;
2019-05-26 11:10:41 +10:00
( y , height )
} ;
CGRect {
origin : CGPoint {
x : screen_frame . origin . x ,
y ,
} ,
size : CGSize {
width : screen_frame . size . width ,
height ,
2019-06-22 01:33:15 +10:00
} ,
2019-05-26 11:10:41 +10:00
}
}
}
}
#[ derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash) ]
pub struct WindowId {
window : id ,
}
impl WindowId {
pub unsafe fn dummy ( ) -> Self {
WindowId {
window : std ::ptr ::null_mut ( ) ,
}
}
}
unsafe impl Send for WindowId { }
unsafe impl Sync for WindowId { }
impl From < & Object > for WindowId {
fn from ( window : & Object ) -> WindowId {
2019-06-22 01:33:15 +10:00
WindowId {
window : window as * const _ as _ ,
}
2019-05-26 11:10:41 +10:00
}
}
impl From < & mut Object > for WindowId {
fn from ( window : & mut Object ) -> WindowId {
2019-06-22 01:33:15 +10:00
WindowId {
window : window as _ ,
}
2019-05-26 11:10:41 +10:00
}
}
impl From < id > for WindowId {
fn from ( window : id ) -> WindowId {
WindowId { window }
}
}
#[ derive(Clone) ]
pub struct PlatformSpecificWindowBuilderAttributes {
pub root_view_class : & 'static Class ,
pub hidpi_factor : Option < f64 > ,
pub valid_orientations : ValidOrientations ,
}
impl Default for PlatformSpecificWindowBuilderAttributes {
fn default ( ) -> PlatformSpecificWindowBuilderAttributes {
PlatformSpecificWindowBuilderAttributes {
root_view_class : class ! ( UIView ) ,
hidpi_factor : None ,
valid_orientations : Default ::default ( ) ,
}
}
}