Reduce amount of unsafe on iOS (#2579)

* Use objc2::foundation CG types

* Add safe abstraction over UIApplication

* Add safe abstraction over UIDevice

* Add safe abstraction over UIScreen

* Add safe abstraction over UIWindow

* Add safe abstraction over UIViewController

* Add safe abstraction over UIView

* Appease clippy
This commit is contained in:
Mads Marquart 2022-12-28 18:36:32 +01:00 committed by GitHub
parent 5e77d70245
commit ee88e38f13
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
20 changed files with 1048 additions and 962 deletions

View file

@ -1,5 +1,7 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use objc2::rc::Id;
use crate::{ use crate::{
event_loop::EventLoop, event_loop::EventLoop,
monitor::{MonitorHandle, VideoMode}, monitor::{MonitorHandle, VideoMode},
@ -239,7 +241,7 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
self.inner.ui_screen() as _ Id::as_ptr(self.inner.ui_screen()) as *mut c_void
} }
#[inline] #[inline]

View file

@ -15,18 +15,19 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
}; };
use objc2::foundation::{NSInteger, NSUInteger}; use objc2::foundation::{CGRect, CGSize, NSInteger};
use objc2::runtime::Object; use objc2::rc::{Id, Shared};
use objc2::{class, msg_send, sel}; use objc2::{class, msg_send, sel};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::view::WinitUIWindow;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event::{Event, StartCause, WindowEvent}, event::{Event, StartCause, WindowEvent},
event_loop::ControlFlow, event_loop::ControlFlow,
platform_impl::platform::{ platform_impl::platform::{
event_loop::{EventHandler, EventProxy, EventWrapper, Never}, event_loop::{EventHandler, EventProxy, EventWrapper, Never},
ffi::{id, CGRect, CGSize, NSOperatingSystemVersion}, ffi::{id, NSOperatingSystemVersion},
}, },
window::WindowId as RootWindowId, window::WindowId as RootWindowId,
}; };
@ -65,25 +66,25 @@ impl Event<'static, Never> {
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] #[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
enum AppStateImpl { enum AppStateImpl {
NotLaunched { NotLaunched {
queued_windows: Vec<id>, queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<id>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
Launching { Launching {
queued_windows: Vec<id>, queued_windows: Vec<Id<WinitUIWindow, Shared>>,
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_event_handler: Box<dyn EventHandler>, queued_event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
ProcessingEvents { ProcessingEvents {
event_handler: Box<dyn EventHandler>, event_handler: Box<dyn EventHandler>,
queued_gpu_redraws: HashSet<id>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
active_control_flow: ControlFlow, active_control_flow: ControlFlow,
}, },
// special state to deal with reentrancy and prevent mutable aliasing. // special state to deal with reentrancy and prevent mutable aliasing.
InUserCallback { InUserCallback {
queued_events: Vec<EventWrapper>, queued_events: Vec<EventWrapper>,
queued_gpu_redraws: HashSet<id>, queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
}, },
ProcessingRedraws { ProcessingRedraws {
event_handler: Box<dyn EventHandler>, event_handler: Box<dyn EventHandler>,
@ -106,28 +107,6 @@ struct AppState {
waker: EventLoopWaker, waker: EventLoopWaker,
} }
impl Drop for AppState {
fn drop(&mut self) {
match self.state_mut() {
&mut AppStateImpl::NotLaunched {
ref mut queued_windows,
..
}
| &mut AppStateImpl::Launching {
ref mut queued_windows,
..
} => {
for &mut window in queued_windows {
unsafe {
let _: () = msg_send![window, release];
}
}
}
_ => {}
}
}
}
impl AppState { impl AppState {
// requires main thread // requires main thread
unsafe fn get_mut() -> RefMut<'static, AppState> { unsafe fn get_mut() -> RefMut<'static, AppState> {
@ -223,7 +202,9 @@ impl AppState {
}); });
} }
fn did_finish_launching_transition(&mut self) -> (Vec<id>, Vec<EventWrapper>) { fn did_finish_launching_transition(
&mut self,
) -> (Vec<Id<WinitUIWindow, Shared>>, Vec<EventWrapper>) {
let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() {
AppStateImpl::Launching { AppStateImpl::Launching {
queued_windows, queued_windows,
@ -380,7 +361,7 @@ impl AppState {
} }
} }
fn main_events_cleared_transition(&mut self) -> HashSet<id> { fn main_events_cleared_transition(&mut self) -> HashSet<Id<WinitUIWindow, Shared>> {
let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() {
AppStateImpl::ProcessingEvents { AppStateImpl::ProcessingEvents {
event_handler, event_handler,
@ -473,17 +454,13 @@ impl AppState {
// requires main thread and window is a UIWindow // requires main thread and window is a UIWindow
// retains window // retains window
pub unsafe fn set_key_window(window: id) { pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
bug_assert!(
msg_send![window, isKindOfClass: class!(UIWindow)],
"set_key_window called with an incorrect type"
);
let mut this = AppState::get_mut(); let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
ref mut queued_windows, ref mut queued_windows,
.. ..
} => return queued_windows.push(msg_send![window, retain]), } => return queued_windows.push(window.clone()),
&mut AppStateImpl::ProcessingEvents { .. } &mut AppStateImpl::ProcessingEvents { .. }
| &mut AppStateImpl::InUserCallback { .. } | &mut AppStateImpl::InUserCallback { .. }
| &mut AppStateImpl::ProcessingRedraws { .. } => {} | &mut AppStateImpl::ProcessingRedraws { .. } => {}
@ -495,16 +472,12 @@ pub unsafe fn set_key_window(window: id) {
} }
} }
drop(this); drop(this);
msg_send![window, makeKeyAndVisible] window.makeKeyAndVisible();
} }
// requires main thread and window is a UIWindow // requires main thread and window is a UIWindow
// retains window // retains window
pub unsafe fn queue_gl_or_metal_redraw(window: id) { pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
bug_assert!(
msg_send![window, isKindOfClass: class!(UIWindow)],
"set_key_window called with an incorrect type"
);
let mut this = AppState::get_mut(); let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
@ -532,8 +505,6 @@ pub unsafe fn queue_gl_or_metal_redraw(window: id) {
panic!("Attempt to create a `Window` after the app has terminated") panic!("Attempt to create a `Window` after the app has terminated")
} }
} }
drop(this);
} }
// requires main thread // requires main thread
@ -560,9 +531,6 @@ pub unsafe fn did_finish_launching() {
drop(this); drop(this);
for window in windows { for window in windows {
let count: NSUInteger = msg_send![window, retainCount];
// make sure the window is still referenced
if count > 1 {
// Do a little screen dance here to account for windows being created before // Do a little screen dance here to account for windows being created before
// `UIApplicationMain` is called. This fixes visual issues such as being // `UIApplicationMain` is called. This fixes visual issues such as being
// offcenter and sized incorrectly. Additionally, to fix orientation issues, we // offcenter and sized incorrectly. Additionally, to fix orientation issues, we
@ -573,17 +541,15 @@ pub unsafe fn did_finish_launching() {
// [ApplicationLifecycle] Windows were created before application initialzation // [ApplicationLifecycle] Windows were created before application initialzation
// completed. This may result in incorrect visual appearance. // completed. This may result in incorrect visual appearance.
// ``` // ```
let screen: id = msg_send![window, screen]; let screen = window.screen();
let _: id = msg_send![screen, retain]; let _: () = msg_send![&window, setScreen: 0 as id];
let _: () = msg_send![window, setScreen:0 as id]; window.setScreen(&screen);
let _: () = msg_send![window, setScreen: screen];
let _: () = msg_send![screen, release]; let controller = window.rootViewController();
let controller: id = msg_send![window, rootViewController]; window.setRootViewController(None);
let _: () = msg_send![window, setRootViewController:ptr::null::<Object>()]; window.setRootViewController(controller.as_deref());
let _: () = msg_send![window, setRootViewController: controller];
let _: () = msg_send![window, makeKeyAndVisible]; window.makeKeyAndVisible();
}
let _: () = msg_send![window, release];
} }
let (windows, events) = AppState::get_mut().did_finish_launching_transition(); let (windows, events) = AppState::get_mut().did_finish_launching_transition();
@ -597,12 +563,7 @@ pub unsafe fn did_finish_launching() {
// the above window dance hack, could possibly trigger new windows to be created. // the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching // we can just set those windows up normally, as they were created after didFinishLaunching
for window in windows { for window in windows {
let count: NSUInteger = msg_send![window, retainCount]; window.makeKeyAndVisible();
// make sure the window is still referenced
if count > 1 {
let _: () = msg_send![window, makeKeyAndVisible];
}
let _: () = msg_send![window, release];
} }
} }
@ -620,12 +581,12 @@ pub unsafe fn handle_wakeup_transition() {
} }
// requires main thread // requires main thread
pub unsafe fn handle_nonuser_event(event: EventWrapper) { pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) {
handle_nonuser_events(std::iter::once(event)) handle_nonuser_events(std::iter::once(event))
} }
// requires main thread // requires main thread
pub unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) { pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) {
let mut this = AppState::get_mut(); let mut this = AppState::get_mut();
let (mut event_handler, active_control_flow, processing_redraws) = let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
@ -803,9 +764,7 @@ pub unsafe fn handle_main_events_cleared() {
let mut redraw_events: Vec<EventWrapper> = this let mut redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition() .main_events_cleared_transition()
.into_iter() .into_iter()
.map(|window| { .map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
})
.collect(); .collect();
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared)); redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
@ -838,13 +797,13 @@ fn handle_event_proxy(
EventProxy::DpiChangedProxy { EventProxy::DpiChangedProxy {
suggested_size, suggested_size,
scale_factor, scale_factor,
window_id, window,
} => handle_hidpi_proxy( } => handle_hidpi_proxy(
event_handler, event_handler,
control_flow, control_flow,
suggested_size, suggested_size,
scale_factor, scale_factor,
window_id, window,
), ),
} }
} }
@ -854,42 +813,36 @@ fn handle_hidpi_proxy(
mut control_flow: ControlFlow, mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>, suggested_size: LogicalSize<f64>,
scale_factor: f64, scale_factor: f64,
window_id: id, window: Id<WinitUIWindow, Shared>,
) { ) {
let mut size = suggested_size.to_physical(scale_factor); let mut size = suggested_size.to_physical(scale_factor);
let new_inner_size = &mut size; let new_inner_size = &mut size;
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: RootWindowId(window_id.into()), window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged { event: WindowEvent::ScaleFactorChanged {
scale_factor, scale_factor,
new_inner_size, new_inner_size,
}, },
}; };
event_handler.handle_nonuser_event(event, &mut control_flow); event_handler.handle_nonuser_event(event, &mut control_flow);
let (view, screen_frame) = get_view_and_screen_frame(window_id); let (view, screen_frame) = get_view_and_screen_frame(&window);
let physical_size = *new_inner_size; let physical_size = *new_inner_size;
let logical_size = physical_size.to_logical(scale_factor); let logical_size = physical_size.to_logical(scale_factor);
let size = CGSize::new(logical_size); let size = CGSize::new(logical_size.width, logical_size.height);
let new_frame: CGRect = CGRect::new(screen_frame.origin, size); let new_frame: CGRect = CGRect::new(screen_frame.origin, size);
unsafe { unsafe {
let _: () = msg_send![view, setFrame: new_frame]; let _: () = msg_send![view, setFrame: new_frame];
} }
} }
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) { fn get_view_and_screen_frame(window: &WinitUIWindow) -> (id, CGRect) {
unsafe { let view_controller = window.rootViewController().unwrap();
let view_controller: id = msg_send![window_id, rootViewController]; let view: id = unsafe { msg_send![&view_controller, view] };
let view: id = msg_send![view_controller, view]; let bounds = window.bounds();
let bounds: CGRect = msg_send![window_id, bounds]; let screen = window.screen();
let screen: id = msg_send![window_id, screen]; let screen_space = screen.coordinateSpace();
let screen_space: id = msg_send![screen, coordinateSpace]; let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
let screen_frame: CGRect = msg_send![
window_id,
convertRect: bounds,
toCoordinateSpace: screen_space,
];
(view, screen_frame) (view, screen_frame)
}
} }
struct EventLoopWaker { struct EventLoopWaker {

View file

@ -14,10 +14,13 @@ use core_foundation::runloop::{
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate, CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use objc2::runtime::Object; use objc2::foundation::MainThreadMarker;
use objc2::{class, msg_send, ClassType}; use objc2::rc::{Id, Shared};
use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle}; use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use super::uikit::{UIApplication, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use crate::{ use crate::{
dpi::LogicalSize, dpi::LogicalSize,
event::Event, event::Event,
@ -29,20 +32,20 @@ use crate::{
use crate::platform_impl::platform::{ use crate::platform_impl::platform::{
app_state, app_state,
ffi::{id, nil, NSStringRust, UIApplicationMain, UIUserInterfaceIdiom}, ffi::{nil, NSStringRust, UIApplicationMain},
monitor, view, MonitorHandle, monitor, view, MonitorHandle,
}; };
#[derive(Debug)] #[derive(Debug)]
pub enum EventWrapper { pub(crate) enum EventWrapper {
StaticEvent(Event<'static, Never>), StaticEvent(Event<'static, Never>),
EventProxy(EventProxy), EventProxy(EventProxy),
} }
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub enum EventProxy { pub(crate) enum EventProxy {
DpiChangedProxy { DpiChangedProxy {
window_id: id, window: Id<WinitUIWindow, Shared>,
suggested_size: LogicalSize<f64>, suggested_size: LogicalSize<f64>,
scale_factor: f64, scale_factor: f64,
}, },
@ -55,15 +58,13 @@ pub struct EventLoopWindowTarget<T: 'static> {
impl<T: 'static> EventLoopWindowTarget<T> { impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
// guaranteed to be on main thread monitor::uiscreens(MainThreadMarker::new().unwrap())
unsafe { monitor::uiscreens() }
} }
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
// guaranteed to be on main thread Some(MonitorHandle::new(UIScreen::main(
let monitor = unsafe { monitor::main_uiscreen() }; MainThreadMarker::new().unwrap(),
)))
Some(monitor)
} }
pub fn raw_display_handle(&self) -> RawDisplayHandle { pub fn raw_display_handle(&self) -> RawDisplayHandle {
@ -113,13 +114,10 @@ impl<T: 'static> EventLoop<T> {
F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow), F: 'static + FnMut(Event<'_, T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{ {
unsafe { unsafe {
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication]; let _application = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
assert_eq!(
application,
ptr::null_mut(),
"\ "\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\
Note: `EventLoop::run` calls `UIApplicationMain` on iOS" Note: `EventLoop::run` calls `UIApplicationMain` on iOS",
); );
app_state::will_launch(Box::new(EventLoopHandler { app_state::will_launch(Box::new(EventLoopHandler {
f: event_handler, f: event_handler,
@ -151,8 +149,9 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS // EventLoopExtIOS
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom { pub fn idiom(&self) -> Idiom {
// guaranteed to be on main thread UIDevice::current(MainThreadMarker::new().unwrap())
unsafe { self::get_idiom() } .userInterfaceIdiom()
.into()
} }
} }
@ -355,10 +354,3 @@ where
} }
} }
} }
// must be called on main thread
pub unsafe fn get_idiom() -> Idiom {
let device: id = msg_send![class!(UIDevice), currentDevice];
let raw_idiom: UIUserInterfaceIdiom = msg_send![device, userInterfaceIdiom];
raw_idiom.into()
}

View file

@ -2,7 +2,6 @@
use std::convert::TryInto; use std::convert::TryInto;
use std::ffi::CString; use std::ffi::CString;
use std::ops::BitOr;
use std::os::raw::{c_char, c_int}; use std::os::raw::{c_char, c_int};
use objc2::encode::{Encode, Encoding}; use objc2::encode::{Encode, Encoding};
@ -10,19 +9,11 @@ use objc2::foundation::{NSInteger, NSUInteger};
use objc2::runtime::Object; use objc2::runtime::Object;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
use crate::{ use crate::platform::ios::{Idiom, ScreenEdge};
dpi::LogicalSize,
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;
#[cfg(target_pointer_width = "32")]
pub type CGFloat = f32;
#[cfg(target_pointer_width = "64")]
pub type CGFloat = f64;
#[repr(C)] #[repr(C)]
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct NSOperatingSystemVersion { pub struct NSOperatingSystemVersion {
@ -42,116 +33,6 @@ unsafe impl Encode for NSOperatingSystemVersion {
); );
} }
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGPoint {
pub x: CGFloat,
pub y: CGFloat,
}
unsafe impl Encode for CGPoint {
const ENCODING: Encoding = Encoding::Struct("CGPoint", &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGSize {
pub width: CGFloat,
pub height: CGFloat,
}
impl CGSize {
pub fn new(size: LogicalSize<f64>) -> CGSize {
CGSize {
width: size.width as _,
height: size.height as _,
}
}
}
unsafe impl Encode for CGSize {
const ENCODING: Encoding = Encoding::Struct("CGSize", &[CGFloat::ENCODING, CGFloat::ENCODING]);
}
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CGRect {
pub origin: CGPoint,
pub size: CGSize,
}
impl CGRect {
pub fn new(origin: CGPoint, size: CGSize) -> CGRect {
CGRect { origin, size }
}
}
unsafe impl Encode for CGRect {
const ENCODING: Encoding = Encoding::Struct("CGRect", &[CGPoint::ENCODING, CGSize::ENCODING]);
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
unsafe impl Encode for UITouchPhase {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
unsafe impl Encode for UIForceTouchCapability {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
unsafe impl Encode for UITouchType {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
unsafe impl Encode for UIEdgeInsets {
const ENCODING: Encoding = Encoding::Struct(
"UIEdgeInsets",
&[
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
],
);
}
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIUserInterfaceIdiom(NSInteger); pub struct UIUserInterfaceIdiom(NSInteger);
@ -192,55 +73,6 @@ impl From<UIUserInterfaceIdiom> for Idiom {
} }
} }
#[repr(transparent)]
#[derive(Clone, Copy, Debug)]
pub struct UIInterfaceOrientationMask(NSUInteger);
unsafe impl Encode for UIInterfaceOrientationMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}
impl UIInterfaceOrientationMask {
pub const Portrait: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 1);
pub const PortraitUpsideDown: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 2);
pub const LandscapeLeft: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 4);
pub const LandscapeRight: UIInterfaceOrientationMask = UIInterfaceOrientationMask(1 << 3);
pub const Landscape: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::LandscapeLeft.0 | Self::LandscapeRight.0);
pub const AllButUpsideDown: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::Landscape.0 | Self::Portrait.0);
pub const All: UIInterfaceOrientationMask =
UIInterfaceOrientationMask(Self::AllButUpsideDown.0 | Self::PortraitUpsideDown.0);
}
impl BitOr for UIInterfaceOrientationMask {
type Output = Self;
fn bitor(self, rhs: Self) -> Self {
UIInterfaceOrientationMask(self.0 | rhs.0)
}
}
impl UIInterfaceOrientationMask {
pub fn from_valid_orientations_idiom(
valid_orientations: ValidOrientations,
idiom: Idiom,
) -> UIInterfaceOrientationMask {
match (valid_orientations, idiom) {
(ValidOrientations::LandscapeAndPortrait, Idiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, Idiom::Phone) => UIInterfaceOrientationMask::Portrait,
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
}
}
}
#[repr(transparent)] #[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)] #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIRectEdge(NSUInteger); pub struct UIRectEdge(NSUInteger);
@ -267,21 +99,6 @@ impl From<UIRectEdge> for ScreenEdge {
} }
} }
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: UIScreenOverscanCompensation = UIScreenOverscanCompensation(0);
pub const InsetBounds: UIScreenOverscanCompensation = UIScreenOverscanCompensation(1);
pub const None: UIScreenOverscanCompensation = UIScreenOverscanCompensation(2);
}
#[link(name = "UIKit", kind = "framework")] #[link(name = "UIKit", kind = "framework")]
extern "C" { extern "C" {
pub fn UIApplicationMain( pub fn UIApplicationMain(

View file

@ -87,18 +87,19 @@ pub(crate) use self::{
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId}, window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
}; };
use self::uikit::UIScreen;
pub(crate) use crate::icon::NoIcon as PlatformIcon; pub(crate) use crate::icon::NoIcon as PlatformIcon;
pub(self) use crate::platform_impl::Fullscreen; pub(self) use crate::platform_impl::Fullscreen;
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct DeviceId { pub struct DeviceId {
uiscreen: ffi::id, uiscreen: *const UIScreen,
} }
impl DeviceId { impl DeviceId {
pub const unsafe fn dummy() -> Self { pub const unsafe fn dummy() -> Self {
DeviceId { DeviceId {
uiscreen: std::ptr::null_mut(), uiscreen: std::ptr::null(),
} }
} }
} }

View file

@ -6,74 +6,43 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use objc2::foundation::{NSInteger, NSUInteger}; use objc2::foundation::{MainThreadMarker, NSInteger};
use objc2::{class, msg_send}; use objc2::rc::{Id, Shared};
use super::uikit::{UIScreen, UIScreenMode};
use crate::{ use crate::{
dpi::{PhysicalPosition, PhysicalSize}, dpi::{PhysicalPosition, PhysicalSize},
monitor::VideoMode as RootVideoMode, monitor::VideoMode as RootVideoMode,
platform_impl::platform::{ platform_impl::platform::app_state,
app_state,
ffi::{id, nil, CGFloat, CGRect, CGSize},
},
}; };
#[derive(Debug, PartialEq, Eq, Hash)] // TODO(madsmtm): Remove or refactor this
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode, Shared>);
unsafe impl Send for ScreenModeSendSync {}
unsafe impl Sync for ScreenModeSendSync {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
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_millihertz: u32, pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: NativeDisplayMode, pub(crate) screen_mode: ScreenModeSendSync,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NativeDisplayMode(pub id);
unsafe impl Send for NativeDisplayMode {}
impl Drop for NativeDisplayMode {
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.0, release];
}
}
}
impl Clone for NativeDisplayMode {
fn clone(&self) -> Self {
unsafe {
let _: id = msg_send![self.0, retain];
}
NativeDisplayMode(self.0)
}
}
impl Clone for VideoMode {
fn clone(&self) -> VideoMode {
VideoMode {
size: self.size,
bit_depth: self.bit_depth,
refresh_rate_millihertz: self.refresh_rate_millihertz,
screen_mode: self.screen_mode.clone(),
monitor: self.monitor.clone(),
}
}
}
impl VideoMode { impl VideoMode {
unsafe fn retained_new(uiscreen: id, screen_mode: id) -> VideoMode { fn new(uiscreen: Id<UIScreen, Shared>, screen_mode: Id<UIScreenMode, Shared>) -> VideoMode {
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); assert_main_thread!("`VideoMode` can only be created on the main thread on iOS");
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen); let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size: CGSize = msg_send![screen_mode, size]; let size = screen_mode.size();
let screen_mode: id = msg_send![screen_mode, retain];
let screen_mode = NativeDisplayMode(screen_mode);
VideoMode { VideoMode {
size: (size.width as u32, size.height as u32), size: (size.width as u32, size.height as u32),
bit_depth: 32, bit_depth: 32,
refresh_rate_millihertz, refresh_rate_millihertz,
screen_mode, screen_mode: ScreenModeSendSync(screen_mode),
monitor: MonitorHandle::retained_new(uiscreen), monitor: MonitorHandle::new(uiscreen),
} }
} }
@ -94,24 +63,29 @@ impl VideoMode {
} }
} }
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)] #[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner { pub struct Inner {
uiscreen: id, uiscreen: Id<UIScreen, Shared>,
} }
impl Drop for Inner { #[derive(Clone, PartialEq, Eq, Hash)]
fn drop(&mut self) {
unsafe {
let _: () = msg_send![self.uiscreen, release];
}
}
}
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct MonitorHandle { pub struct MonitorHandle {
inner: Inner, inner: Inner,
} }
impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for MonitorHandle {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
// TODO: Make a better ordering
(self as *const Self).cmp(&(other as *const Self))
}
}
impl Deref for MonitorHandle { impl Deref for MonitorHandle {
type Target = Inner; type Target = Inner;
@ -131,12 +105,6 @@ impl DerefMut for MonitorHandle {
unsafe impl Send for MonitorHandle {} unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {} unsafe impl Sync for MonitorHandle {}
impl Clone for MonitorHandle {
fn clone(&self) -> MonitorHandle {
MonitorHandle::retained_new(self.uiscreen)
}
}
impl Drop for MonitorHandle { impl Drop for MonitorHandle {
fn drop(&mut self) { fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS"); assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
@ -167,12 +135,9 @@ impl fmt::Debug for MonitorHandle {
} }
impl MonitorHandle { impl MonitorHandle {
pub fn retained_new(uiscreen: id) -> MonitorHandle { pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS"); assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
unsafe { Self {
let _: id = msg_send![uiscreen, retain];
}
MonitorHandle {
inner: Inner { uiscreen }, inner: Inner { uiscreen },
} }
} }
@ -180,70 +145,62 @@ impl MonitorHandle {
impl Inner { impl Inner {
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
unsafe { let main = UIScreen::main(MainThreadMarker::new().unwrap());
let main = main_uiscreen(); if self.uiscreen == main {
if self.uiscreen == main.uiscreen {
Some("Primary".to_string()) Some("Primary".to_string())
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen { } else if self.uiscreen == main.mirroredScreen() {
Some("Mirrored".to_string()) Some("Mirrored".to_string())
} else { } else {
uiscreens() UIScreen::screens(MainThreadMarker::new().unwrap())
.iter() .iter()
.position(|rhs| rhs.uiscreen == self.uiscreen) .position(|rhs| rhs == &*self.uiscreen)
.map(|idx| idx.to_string()) .map(|idx| idx.to_string())
} }
} }
}
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
unsafe { let bounds = self.uiscreen.nativeBounds();
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
} }
}
pub fn position(&self) -> PhysicalPosition<i32> { pub fn position(&self) -> PhysicalPosition<i32> {
unsafe { let bounds = self.uiscreen.nativeBounds();
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
(bounds.origin.x as f64, bounds.origin.y as f64).into() (bounds.origin.x as f64, bounds.origin.y as f64).into()
} }
}
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
unsafe { self.uiscreen.nativeScale() as f64
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
scale as f64
}
} }
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(self.uiscreen)) Some(refresh_rate_millihertz(&self.uiscreen))
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
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 = msg_send![available_modes, objectAtIndex: i];
// Use Ord impl of RootVideoMode // Use Ord impl of RootVideoMode
modes.insert(RootVideoMode { let modes: BTreeSet<_> = self
video_mode: VideoMode::retained_new(self.uiscreen, mode), .uiscreen
}); .availableModes()
} .into_iter()
.map(|mode| {
let mode: *const UIScreenMode = mode;
let mode = unsafe { Id::retain(mode as *mut UIScreenMode).unwrap() };
RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
} }
})
.collect();
modes.into_iter().map(|mode| mode.video_mode) modes.into_iter().map(|mode| mode.video_mode)
} }
} }
fn refresh_rate_millihertz(uiscreen: id) -> u32 { fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
let refresh_rate_millihertz: NSInteger = unsafe { let refresh_rate_millihertz: NSInteger = {
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.maximum_frames_per_second { if os_capabilities.maximum_frames_per_second {
msg_send![uiscreen, maximumFramesPerSecond] uiscreen.maximumFramesPerSecond()
} else { } else {
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html // https://developer.apple.com/library/archive/technotes/tn2460/_index.html
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison // https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
@ -265,41 +222,25 @@ fn refresh_rate_millihertz(uiscreen: id) -> u32 {
// MonitorHandleExtIOS // MonitorHandleExtIOS
impl Inner { impl Inner {
pub fn ui_screen(&self) -> id { pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
self.uiscreen &self.uiscreen
} }
pub fn preferred_video_mode(&self) -> VideoMode { pub fn preferred_video_mode(&self) -> VideoMode {
unsafe { VideoMode::new(
let mode: id = msg_send![self.uiscreen, preferredMode]; self.uiscreen.clone(),
VideoMode::retained_new(self.uiscreen, mode) self.uiscreen.preferredMode().unwrap(),
} )
} }
} }
// requires being run on main thread pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
pub unsafe fn main_uiscreen() -> MonitorHandle { UIScreen::screens(mtm)
let uiscreen: id = msg_send![class!(UIScreen), mainScreen]; .into_iter()
MonitorHandle::retained_new(uiscreen) .map(|screen| {
} let screen: *const UIScreen = screen;
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
// requires being run on main thread MonitorHandle::new(screen)
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle { })
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen]; .collect()
MonitorHandle::retained_new(uiscreen)
}
// requires being run on main thread
pub unsafe fn uiscreens() -> VecDeque<MonitorHandle> {
let screens: id = msg_send![class!(UIScreen), screens];
let count: NSUInteger = msg_send![screens, count];
let mut result = VecDeque::with_capacity(count as _);
let screens_enum: id = msg_send![screens, objectEnumerator];
loop {
let screen: id = msg_send![screens_enum, nextObject];
if screen == nil {
break result;
}
result.push_back(MonitorHandle::retained_new(screen));
}
} }

View file

@ -0,0 +1,30 @@
use objc2::foundation::{CGRect, MainThreadMarker, NSArray, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UIResponder, UIWindow};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIApplication;
unsafe impl ClassType for UIApplication {
#[inherits(NSObject)]
type Super = UIResponder;
}
);
extern_methods!(
unsafe impl UIApplication {
pub fn shared(_mtm: MainThreadMarker) -> Option<Id<Self, Shared>> {
unsafe { msg_send_id![Self::class(), sharedApplication] }
}
pub fn windows(&self) -> Id<NSArray<UIWindow, Shared>, Shared> {
unsafe { msg_send_id![self, windows] }
}
#[sel(statusBarFrame)]
pub fn statusBarFrame(&self) -> CGRect;
}
);

View file

@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UICoordinateSpace;
unsafe impl ClassType for UICoordinateSpace {
type Super = NSObject;
}
);

View file

@ -0,0 +1,25 @@
use objc2::foundation::{MainThreadMarker, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::super::ffi::UIUserInterfaceIdiom;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIDevice;
unsafe impl ClassType for UIDevice {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIDevice {
pub fn current(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), currentDevice] }
}
#[sel(userInterfaceIdiom)]
pub fn userInterfaceIdiom(&self) -> UIUserInterfaceIdiom;
}
);

View file

@ -0,0 +1,11 @@
use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIEvent;
unsafe impl ClassType for UIEvent {
type Super = NSObject;
}
);

View file

@ -1,11 +1,30 @@
#![deny(unsafe_op_in_unsafe_fn)] #![deny(unsafe_op_in_unsafe_fn)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
mod application;
mod coordinate_space;
mod device;
mod event;
mod responder; mod responder;
mod screen;
mod screen_mode;
mod touch;
mod trait_collection;
mod view; mod view;
mod view_controller; mod view_controller;
mod window; mod window;
pub(crate) use self::application::UIApplication;
pub(crate) use self::coordinate_space::UICoordinateSpace;
pub(crate) use self::device::UIDevice;
pub(crate) use self::event::UIEvent;
pub(crate) use self::responder::UIResponder; pub(crate) use self::responder::UIResponder;
pub(crate) use self::view::UIView; pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
pub(crate) use self::view_controller::UIViewController; pub(crate) use self::screen_mode::UIScreenMode;
pub(crate) use self::touch::{UITouch, UITouchPhase, UITouchType};
pub(crate) use self::trait_collection::{UIForceTouchCapability, UITraitCollection};
#[allow(unused_imports)]
pub(crate) use self::view::{UIEdgeInsets, UIView};
pub(crate) use self::view_controller::{UIInterfaceOrientationMask, UIViewController};
pub(crate) use self::window::UIWindow; pub(crate) use self::window::UIWindow;

View file

@ -0,0 +1,79 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSArray, NSInteger, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::{UICoordinateSpace, UIScreenMode};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreen;
unsafe impl ClassType for UIScreen {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreen {
pub fn main(_mtm: MainThreadMarker) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mainScreen] }
}
pub fn screens(_mtm: MainThreadMarker) -> Id<NSArray<Self, Shared>, Shared> {
unsafe { msg_send_id![Self::class(), screens] }
}
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(scale)]
pub fn scale(&self) -> CGFloat;
#[sel(nativeBounds)]
pub fn nativeBounds(&self) -> CGRect;
#[sel(nativeScale)]
pub fn nativeScale(&self) -> CGFloat;
#[sel(maximumFramesPerSecond)]
pub fn maximumFramesPerSecond(&self) -> NSInteger;
pub fn mirroredScreen(&self) -> Id<Self, Shared> {
unsafe { msg_send_id![Self::class(), mirroredScreen] }
}
pub fn preferredMode(&self) -> Option<Id<UIScreenMode, Shared>> {
unsafe { msg_send_id![self, preferredMode] }
}
#[sel(setCurrentMode:)]
pub fn setCurrentMode(&self, mode: Option<&UIScreenMode>);
pub fn availableModes(&self) -> Id<NSArray<UIScreenMode, Shared>, Shared> {
unsafe { msg_send_id![self, availableModes] }
}
#[sel(setOverscanCompensation:)]
pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation);
pub fn coordinateSpace(&self) -> Id<UICoordinateSpace, Shared> {
unsafe { msg_send_id![self, coordinateSpace] }
}
}
);
#[repr(transparent)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct UIScreenOverscanCompensation(NSInteger);
unsafe impl Encode for UIScreenOverscanCompensation {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[allow(dead_code)]
impl UIScreenOverscanCompensation {
pub const Scale: Self = Self(0);
pub const InsetBounds: Self = Self(1);
pub const None: Self = Self(2);
}

View file

@ -0,0 +1,18 @@
use objc2::foundation::{CGSize, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIScreenMode;
unsafe impl ClassType for UIScreenMode {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UIScreenMode {
#[sel(size)]
pub fn size(&self) -> CGSize;
}
);

View file

@ -0,0 +1,64 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{CGFloat, CGPoint, NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
use super::UIView;
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITouch;
unsafe impl ClassType for UITouch {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITouch {
#[sel(locationInView:)]
pub fn locationInView(&self, view: Option<&UIView>) -> CGPoint;
#[sel(type)]
pub fn type_(&self) -> UITouchType;
#[sel(force)]
pub fn force(&self) -> CGFloat;
#[sel(maximumPossibleForce)]
pub fn maximumPossibleForce(&self) -> CGFloat;
#[sel(altitudeAngle)]
pub fn altitudeAngle(&self) -> CGFloat;
#[sel(phase)]
pub fn phase(&self) -> UITouchPhase;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchType {
Direct = 0,
Indirect,
Pencil,
}
unsafe impl Encode for UITouchType {
const ENCODING: Encoding = NSInteger::ENCODING;
}
#[derive(Debug)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UITouchPhase {
Began = 0,
Moved,
Stationary,
Ended,
Cancelled,
}
unsafe impl Encode for UITouchPhase {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View file

@ -0,0 +1,32 @@
use objc2::encode::{Encode, Encoding};
use objc2::foundation::{NSInteger, NSObject};
use objc2::{extern_class, extern_methods, ClassType};
extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UITraitCollection;
unsafe impl ClassType for UITraitCollection {
type Super = NSObject;
}
);
extern_methods!(
unsafe impl UITraitCollection {
#[sel(forceTouchCapability)]
pub fn forceTouchCapability(&self) -> UIForceTouchCapability;
}
);
#[derive(Debug, PartialEq, Eq)]
#[allow(dead_code)]
#[repr(isize)]
pub enum UIForceTouchCapability {
Unknown = 0,
Unavailable,
Available,
}
unsafe impl Encode for UIForceTouchCapability {
const ENCODING: Encoding = NSInteger::ENCODING;
}

View file

@ -1,7 +1,9 @@
use objc2::foundation::NSObject; use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, ClassType}; use objc2::foundation::{CGFloat, CGRect, NSObject};
use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::UIResponder; use super::{UICoordinateSpace, UIResponder, UIViewController};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -12,3 +14,76 @@ extern_class!(
type Super = UIResponder; type Super = UIResponder;
} }
); );
extern_methods!(
unsafe impl UIView {
#[sel(bounds)]
pub fn bounds(&self) -> CGRect;
#[sel(setBounds:)]
pub fn setBounds(&self, value: CGRect);
#[sel(frame)]
pub fn frame(&self) -> CGRect;
#[sel(setFrame:)]
pub fn setFrame(&self, value: CGRect);
#[sel(contentScaleFactor)]
pub fn contentScaleFactor(&self) -> CGFloat;
#[sel(setContentScaleFactor:)]
pub fn setContentScaleFactor(&self, val: CGFloat);
#[sel(setMultipleTouchEnabled:)]
pub fn setMultipleTouchEnabled(&self, val: bool);
pub fn rootViewController(&self) -> Option<Id<UIViewController, Shared>> {
unsafe { msg_send_id![self, rootViewController] }
}
#[sel(setRootViewController:)]
pub fn setRootViewController(&self, rootViewController: Option<&UIViewController>);
#[sel(convertRect:toCoordinateSpace:)]
pub fn convertRect_toCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(convertRect:fromCoordinateSpace:)]
pub fn convertRect_fromCoordinateSpace(
&self,
rect: CGRect,
coordinateSpace: &UICoordinateSpace,
) -> CGRect;
#[sel(safeAreaInsets)]
pub fn safeAreaInsets(&self) -> UIEdgeInsets;
#[sel(setNeedsDisplay)]
pub fn setNeedsDisplay(&self);
}
);
#[repr(C)]
#[derive(Debug, Clone)]
pub struct UIEdgeInsets {
pub top: CGFloat,
pub left: CGFloat,
pub bottom: CGFloat,
pub right: CGFloat,
}
unsafe impl Encode for UIEdgeInsets {
const ENCODING: Encoding = Encoding::Struct(
"UIEdgeInsets",
&[
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
CGFloat::ENCODING,
],
);
}

View file

@ -1,7 +1,8 @@
use objc2::foundation::NSObject; use objc2::encode::{Encode, Encoding};
use objc2::{extern_class, ClassType}; use objc2::foundation::{NSObject, NSUInteger};
use objc2::{extern_class, extern_methods, ClassType};
use super::UIResponder; use super::{UIResponder, UIView};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
@ -12,3 +13,38 @@ extern_class!(
type Super = UIResponder; type Super = UIResponder;
} }
); );
extern_methods!(
unsafe impl UIViewController {
#[sel(attemptRotationToDeviceOrientation)]
pub fn attemptRotationToDeviceOrientation();
#[sel(setNeedsStatusBarAppearanceUpdate)]
pub fn setNeedsStatusBarAppearanceUpdate(&self);
#[sel(setNeedsUpdateOfHomeIndicatorAutoHidden)]
pub fn setNeedsUpdateOfHomeIndicatorAutoHidden(&self);
#[sel(setNeedsUpdateOfScreenEdgesDeferringSystemGestures)]
pub fn setNeedsUpdateOfScreenEdgesDeferringSystemGestures(&self);
#[sel(setView:)]
pub fn setView(&self, view: Option<&UIView>);
}
);
bitflags! {
pub struct UIInterfaceOrientationMask: NSUInteger {
const Portrait = 1 << 1;
const PortraitUpsideDown = 1 << 2;
const LandscapeRight = 1 << 3;
const LandscapeLeft = 1 << 4;
const Landscape = Self::LandscapeLeft.bits() | Self::LandscapeRight.bits();
const AllButUpsideDown = Self::Landscape.bits() | Self::Portrait.bits();
const All = Self::AllButUpsideDown.bits() | Self::PortraitUpsideDown.bits();
}
}
unsafe impl Encode for UIInterfaceOrientationMask {
const ENCODING: Encoding = NSUInteger::ENCODING;
}

View file

@ -1,14 +1,32 @@
use objc2::foundation::NSObject; use objc2::foundation::NSObject;
use objc2::{extern_class, ClassType}; use objc2::rc::{Id, Shared};
use objc2::{extern_class, extern_methods, msg_send_id, ClassType};
use super::UIResponder; use super::{UIResponder, UIScreen, UIView};
extern_class!( extern_class!(
#[derive(Debug, PartialEq, Eq, Hash)] #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct UIWindow; pub(crate) struct UIWindow;
unsafe impl ClassType for UIWindow { unsafe impl ClassType for UIWindow {
#[inherits(NSObject)] #[inherits(UIResponder, NSObject)]
type Super = UIResponder; type Super = UIView;
}
);
extern_methods!(
unsafe impl UIWindow {
pub fn screen(&self) -> Id<UIScreen, Shared> {
unsafe { msg_send_id![self, screen] }
}
#[sel(setScreen:)]
pub fn setScreen(&self, screen: &UIScreen);
#[sel(setHidden:)]
pub fn setHidden(&self, flag: bool);
#[sel(makeKeyAndVisible)]
pub fn makeKeyAndVisible(&self);
} }
); );

View file

@ -1,19 +1,24 @@
#![allow(clippy::unnecessary_cast)] #![allow(clippy::unnecessary_cast)]
use objc2::foundation::NSObject; use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
use objc2::{class, declare_class, msg_send, ClassType}; use objc2::rc::{Id, Shared};
use objc2::runtime::Class;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType};
use super::uikit::{UIResponder, UIView, UIViewController, UIWindow}; use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
UIWindow,
};
use super::window::WindowId;
use crate::{ use crate::{
dpi::PhysicalPosition, dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations,
platform_impl::platform::{ platform_impl::platform::{
app_state, app_state,
event_loop::{self, EventProxy, EventWrapper}, event_loop::{EventProxy, EventWrapper},
ffi::{ ffi::{id, UIRectEdge, UIUserInterfaceIdiom},
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
UIRectEdge, UITouchPhase, UITouchType,
},
window::PlatformSpecificWindowBuilderAttributes, window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen, DeviceId, Fullscreen,
}, },
@ -21,7 +26,7 @@ use crate::{
}; };
declare_class!( declare_class!(
struct WinitView {} pub(crate) struct WinitView {}
unsafe impl ClassType for WinitView { unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
@ -32,37 +37,30 @@ declare_class!(
unsafe impl WinitView { unsafe impl WinitView {
#[sel(drawRect:)] #[sel(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
let window = self.window().unwrap();
unsafe { unsafe {
let window: id = msg_send![self, window];
assert!(!window.is_null());
app_state::handle_nonuser_events( app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested( std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
RootWindowId(window.into()), RootWindowId(window.id()),
))) )))
.chain(std::iter::once(EventWrapper::StaticEvent( .chain(std::iter::once(EventWrapper::StaticEvent(
Event::RedrawEventsCleared, Event::RedrawEventsCleared,
))), ))),
); );
let _: () = msg_send![super(self), drawRect: rect];
} }
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
} }
#[sel(layoutSubviews)] #[sel(layoutSubviews)]
fn layout_subviews(&self) { fn layout_subviews(&self) {
unsafe { let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let _: () = msg_send![super(self), layoutSubviews];
let window: id = msg_send![self, window]; let window = self.window().unwrap();
assert!(!window.is_null()); let window_bounds = window.bounds();
let window_bounds: CGRect = msg_send![window, bounds]; let screen = window.screen();
let screen: id = msg_send![window, screen]; let screen_space = screen.coordinateSpace();
let screen_space: id = msg_send![screen, coordinateSpace]; let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
let screen_frame: CGRect = msg_send![ let scale_factor = screen.scale();
self,
convertRect: window_bounds,
toCoordinateSpace: screen_space,
];
let scale_factor: CGFloat = msg_send![screen, scale];
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as f64, width: screen_frame.size.width as f64,
height: screen_frame.size.height as f64, height: screen_frame.size.height as f64,
@ -72,13 +70,14 @@ declare_class!(
// If the app is started in landscape, the view frame and window bounds can be mismatched. // If the app is started in landscape, the view frame and window bounds can be mismatched.
// The view frame will be in portrait and the window bounds in landscape. So apply the // The view frame will be in portrait and the window bounds in landscape. So apply the
// window bounds to the view frame to make it consistent. // window bounds to the view frame to make it consistent.
let view_frame: CGRect = msg_send![self, frame]; let view_frame = self.frame();
if view_frame != window_bounds { if view_frame != window_bounds {
let _: () = msg_send![self, setFrame: window_bounds]; self.setFrame(window_bounds);
} }
unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size), event: WindowEvent::Resized(size),
})); }));
} }
@ -86,20 +85,20 @@ declare_class!(
#[sel(setContentScaleFactor:)] #[sel(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
unsafe { let _: () =
let _: () = msg_send![super(self), setContentScaleFactor: untrusted_scale_factor]; unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
let window: id = msg_send![self, window];
// `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow // `window` is null when `setContentScaleFactor` is invoked prior to `[UIWindow
// makeKeyAndVisible]` at window creation time (either manually or internally by // makeKeyAndVisible]` at window creation time (either manually or internally by
// UIKit when the `UIView` is first created), in which case we send no events here // UIKit when the `UIView` is first created), in which case we send no events here
if window.is_null() { let window = match self.window() {
return; Some(window) => window,
} None => return,
};
// `setContentScaleFactor` may be called with a value of 0, which means "reset the // `setContentScaleFactor` may be called with a value of 0, which means "reset the
// content scale factor to a device-specific default value", so we can't use the // content scale factor to a device-specific default value", so we can't use the
// parameter here. We can query the actual factor using the getter // parameter here. We can query the actual factor using the getter
let scale_factor: CGFloat = msg_send![self, contentScaleFactor]; let scale_factor = self.contentScaleFactor();
assert!( assert!(
!scale_factor.is_nan() !scale_factor.is_nan()
&& scale_factor.is_finite() && scale_factor.is_finite()
@ -107,24 +106,26 @@ declare_class!(
&& scale_factor > 0.0, && scale_factor > 0.0,
"invalid scale_factor set on UIView", "invalid scale_factor set on UIView",
); );
let bounds: CGRect = msg_send![self, bounds]; let scale_factor = scale_factor as f64;
let screen: id = msg_send![window, screen]; let bounds = self.bounds();
let screen_space: id = msg_send![screen, coordinateSpace]; let screen = window.screen();
let screen_frame: CGRect = let screen_space = screen.coordinateSpace();
msg_send![self, convertRect: bounds, toCoordinateSpace: screen_space]; let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _, width: screen_frame.size.width as _,
height: screen_frame.size.height as _, height: screen_frame.size.height as _,
}; };
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events( app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window, window,
scale_factor, scale_factor,
suggested_size: size, suggested_size: size,
})) }))
.chain(std::iter::once(EventWrapper::StaticEvent( .chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent { Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)), event: WindowEvent::Resized(size.to_physical(scale_factor)),
}, },
))), ))),
@ -133,53 +134,80 @@ declare_class!(
} }
#[sel(touchesBegan:withEvent:)] #[sel(touchesBegan:withEvent:)]
fn touches_began(&self, touches: id, _: id) { fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[sel(touchesMoved:withEvent:)] #[sel(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: id, _: id) { fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[sel(touchesEnded:withEvent:)] #[sel(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: id, _: id) { fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
#[sel(touchesCancelled:withEvent:)] #[sel(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: id, _: id) { fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
self.handle_touches(touches) self.handle_touches(touches)
} }
} }
); );
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitView {
fn window(&self) -> Option<Id<WinitUIWindow, Shared>> {
unsafe { msg_send_id![self, window] }
}
unsafe fn traitCollection(&self) -> Id<UITraitCollection, Shared> {
msg_send_id![self, traitCollection]
}
// TODO: Allow the user to customize this
#[sel(layerClass)]
pub(crate) fn layerClass() -> &'static Class;
}
);
impl WinitView { impl WinitView {
fn handle_touches(&self, touches: id) { pub(crate) fn new(
unsafe { _mtm: MainThreadMarker,
let window: id = msg_send![self, window]; _window_attributes: &WindowAttributes,
assert!(!window.is_null()); platform_attributes: &PlatformSpecificWindowBuilderAttributes,
let uiscreen: id = msg_send![window, screen]; frame: CGRect,
let touches_enum: id = msg_send![touches, objectEnumerator]; ) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
this.setMultipleTouchEnabled(true);
if let Some(scale_factor) = platform_attributes.scale_factor {
this.setContentScaleFactor(scale_factor as _);
}
this
}
fn handle_touches(&self, touches: &NSSet<UITouch>) {
let window = self.window().unwrap();
let uiscreen = window.screen();
let mut touch_events = Vec::new(); let mut touch_events = Vec::new();
let os_supports_force = app_state::os_capabilities().force_touch; let os_supports_force = app_state::os_capabilities().force_touch;
loop { for touch in touches {
let touch: id = msg_send![touches_enum, nextObject]; let logical_location = touch.locationInView(None);
if touch == nil { let touch_type = touch.type_();
break;
}
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
let touch_type: UITouchType = msg_send![touch, type];
let force = if os_supports_force { let force = if os_supports_force {
let trait_collection: id = msg_send![self, traitCollection]; let trait_collection = unsafe { self.traitCollection() };
let touch_capability: UIForceTouchCapability = let touch_capability = trait_collection.forceTouchCapability();
msg_send![trait_collection, forceTouchCapability];
// Both the OS _and_ the device need to be checked for force touch support. // Both the OS _and_ the device need to be checked for force touch support.
if touch_capability == UIForceTouchCapability::Available { if touch_capability == UIForceTouchCapability::Available {
let force: CGFloat = msg_send![touch, force]; let force = touch.force();
let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce]; let max_possible_force = touch.maximumPossibleForce();
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil { let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
let angle: CGFloat = msg_send![touch, altitudeAngle]; let angle = touch.altitudeAngle();
Some(angle as _) Some(angle as _)
} else { } else {
None None
@ -195,8 +223,8 @@ impl WinitView {
} else { } else {
None None
}; };
let touch_id = touch as u64; let touch_id = touch as *const UITouch as u64;
let phase: UITouchPhase = msg_send![touch, phase]; let phase = touch.phase();
let phase = match phase { let phase = match phase {
UITouchPhase::Began => TouchPhase::Started, UITouchPhase::Began => TouchPhase::Started,
UITouchPhase::Moved => TouchPhase::Moved, UITouchPhase::Moved => TouchPhase::Moved,
@ -207,16 +235,18 @@ impl WinitView {
}; };
let physical_location = { let physical_location = {
let scale_factor: CGFloat = msg_send![self, contentScaleFactor]; let scale_factor = self.contentScaleFactor();
PhysicalPosition::from_logical::<(f64, f64), f64>( PhysicalPosition::from_logical::<(f64, f64), f64>(
(logical_location.x as _, logical_location.y as _), (logical_location.x as _, logical_location.y as _),
scale_factor as f64, scale_factor as f64,
) )
}; };
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent { touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id: RootWindowId(window.id()),
event: WindowEvent::Touch(Touch { event: WindowEvent::Touch(Touch {
device_id: RootDeviceId(DeviceId { uiscreen }), device_id: RootDeviceId(DeviceId {
uiscreen: Id::as_ptr(&uiscreen),
}),
id: touch_id, id: touch_id,
location: physical_location, location: physical_location,
force, force,
@ -224,13 +254,14 @@ impl WinitView {
}), }),
})); }));
} }
unsafe {
app_state::handle_nonuser_events(touch_events); app_state::handle_nonuser_events(touch_events);
} }
} }
} }
declare_class!( declare_class!(
struct WinitViewController { pub(crate) struct WinitViewController {
_prefers_status_bar_hidden: bool, _prefers_status_bar_hidden: bool,
_prefers_home_indicator_auto_hidden: bool, _prefers_home_indicator_auto_hidden: bool,
_supported_orientations: UIInterfaceOrientationMask, _supported_orientations: UIInterfaceOrientationMask,
@ -259,9 +290,7 @@ declare_class!(
#[sel(setPrefersStatusBarHidden:)] #[sel(setPrefersStatusBarHidden:)]
fn set_prefers_status_bar_hidden(&mut self, val: bool) { fn set_prefers_status_bar_hidden(&mut self, val: bool) {
*self._prefers_status_bar_hidden = val; *self._prefers_status_bar_hidden = val;
unsafe { self.setNeedsStatusBarAppearanceUpdate();
let _: () = msg_send![self, setNeedsStatusBarAppearanceUpdate];
}
} }
#[sel(prefersHomeIndicatorAutoHidden)] #[sel(prefersHomeIndicatorAutoHidden)]
@ -274,9 +303,7 @@ declare_class!(
*self._prefers_home_indicator_auto_hidden = val; *self._prefers_home_indicator_auto_hidden = val;
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.home_indicator_hidden { if os_capabilities.home_indicator_hidden {
unsafe { self.setNeedsUpdateOfHomeIndicatorAutoHidden();
let _: () = msg_send![self, setNeedsUpdateOfHomeIndicatorAutoHidden];
}
} else { } else {
os_capabilities.home_indicator_hidden_err_msg("ignoring") os_capabilities.home_indicator_hidden_err_msg("ignoring")
} }
@ -290,12 +317,7 @@ declare_class!(
#[sel(setSupportedInterfaceOrientations:)] #[sel(setSupportedInterfaceOrientations:)]
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) { fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
*self._supported_orientations = val; *self._supported_orientations = val;
unsafe { UIViewController::attemptRotationToDeviceOrientation();
let _: () = msg_send![
UIViewController::class(),
attemptRotationToDeviceOrientation
];
}
} }
#[sel(preferredScreenEdgesDeferringSystemGestures)] #[sel(preferredScreenEdgesDeferringSystemGestures)]
@ -308,9 +330,7 @@ declare_class!(
*self._preferred_screen_edges_deferring_system_gestures = val; *self._preferred_screen_edges_deferring_system_gestures = val;
let os_capabilities = app_state::os_capabilities(); let os_capabilities = app_state::os_capabilities();
if os_capabilities.defer_system_gestures { if os_capabilities.defer_system_gestures {
unsafe { self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
let _: () = msg_send![self, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
} else { } else {
os_capabilities.defer_system_gestures_err_msg("ignoring") os_capabilities.defer_system_gestures_err_msg("ignoring")
} }
@ -318,8 +338,79 @@ declare_class!(
} }
); );
extern_methods!(
#[allow(non_snake_case)]
unsafe impl WinitViewController {
#[sel(setPrefersStatusBarHidden:)]
pub(crate) fn setPrefersStatusBarHidden(&self, flag: bool);
#[sel(setSupportedInterfaceOrientations:)]
pub(crate) fn setSupportedInterfaceOrientations(&self, val: UIInterfaceOrientationMask);
#[sel(setPrefersHomeIndicatorAutoHidden:)]
pub(crate) fn setPrefersHomeIndicatorAutoHidden(&self, val: bool);
#[sel(setPreferredScreenEdgesDeferringSystemGestures:)]
pub(crate) fn setPreferredScreenEdgesDeferringSystemGestures(&self, val: UIRectEdge);
}
);
impl WinitViewController {
pub(crate) fn set_supported_interface_orientations(
&self,
mtm: MainThreadMarker,
valid_orientations: ValidOrientations,
) {
let mask = match (
valid_orientations,
UIDevice::current(mtm).userInterfaceIdiom(),
) {
(ValidOrientations::LandscapeAndPortrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::AllButUpsideDown
}
(ValidOrientations::LandscapeAndPortrait, _) => UIInterfaceOrientationMask::All,
(ValidOrientations::Landscape, _) => UIInterfaceOrientationMask::Landscape,
(ValidOrientations::Portrait, UIUserInterfaceIdiom::Phone) => {
UIInterfaceOrientationMask::Portrait
}
(ValidOrientations::Portrait, _) => {
UIInterfaceOrientationMask::Portrait
| UIInterfaceOrientationMask::PortraitUpsideDown
}
};
self.setSupportedInterfaceOrientations(mask);
}
pub(crate) fn new(
mtm: MainThreadMarker,
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: &UIView,
) -> Id<Self, Shared> {
let this: Id<Self, Shared> =
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] };
this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden);
this.set_supported_interface_orientations(mtm, platform_attributes.valid_orientations);
this.setPrefersHomeIndicatorAutoHidden(platform_attributes.prefers_home_indicator_hidden);
this.setPreferredScreenEdgesDeferringSystemGestures(
platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into(),
);
this.setView(Some(view));
this
}
}
declare_class!( declare_class!(
struct WinitUIWindow {} #[derive(Debug, PartialEq, Eq, Hash)]
pub(crate) struct WinitUIWindow {}
unsafe impl ClassType for WinitUIWindow { unsafe impl ClassType for WinitUIWindow {
#[inherits(UIResponder, NSObject)] #[inherits(UIResponder, NSObject)]
@ -331,128 +422,59 @@ declare_class!(
fn become_key_window(&self) { fn become_key_window(&self) {
unsafe { unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId((&*****self).into()), window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true), event: WindowEvent::Focused(true),
})); }));
let _: () = msg_send![super(self), becomeKeyWindow];
} }
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
} }
#[sel(resignKeyWindow)] #[sel(resignKeyWindow)]
fn resign_key_window(&self) { fn resign_key_window(&self) {
unsafe { unsafe {
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId((&*****self).into()), window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false), event: WindowEvent::Focused(false),
})); }));
let _: () = msg_send![super(self), resignKeyWindow];
} }
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
} }
} }
); );
// requires main thread impl WinitUIWindow {
pub(crate) unsafe fn create_view( pub(crate) fn new(
_window_attributes: &WindowAttributes, _mtm: MainThreadMarker,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> id {
let view: id = msg_send![WinitView::class(), alloc];
assert!(!view.is_null(), "Failed to create `UIView` instance");
let view: id = msg_send![view, initWithFrame: frame];
assert!(!view.is_null(), "Failed to initialize `UIView` instance");
let _: () = msg_send![view, setMultipleTouchEnabled: true];
if let Some(scale_factor) = platform_attributes.scale_factor {
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
}
view
}
// requires main thread
pub(crate) unsafe fn create_view_controller(
_window_attributes: &WindowAttributes,
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
view: id,
) -> id {
let class = WinitViewController::class();
let view_controller: id = msg_send![class, alloc];
assert!(
!view_controller.is_null(),
"Failed to create `UIViewController` instance"
);
let view_controller: id = msg_send![view_controller, init];
assert!(
!view_controller.is_null(),
"Failed to initialize `UIViewController` instance"
);
let status_bar_hidden = platform_attributes.prefers_status_bar_hidden;
let idiom = event_loop::get_idiom();
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
platform_attributes.valid_orientations,
idiom,
);
let prefers_home_indicator_hidden = platform_attributes.prefers_home_indicator_hidden;
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();
let _: () = msg_send![
view_controller,
setPrefersStatusBarHidden: status_bar_hidden
];
let _: () = msg_send![
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
}
// requires main thread
pub(crate) unsafe fn create_window(
window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
view_controller: id, view_controller: &UIViewController,
) -> id { ) -> Id<Self, Shared> {
let window: id = msg_send![WinitUIWindow::class(), alloc]; let this: Id<Self, Shared> =
assert!(!window.is_null(), "Failed to create `UIWindow` instance"); unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
let window: id = msg_send![window, initWithFrame: frame];
assert!( this.setRootViewController(Some(view_controller));
!window.is_null(),
"Failed to initialize `UIWindow` instance"
);
let _: () = msg_send![window, setRootViewController: view_controller];
match window_attributes.fullscreen { match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => { Some(Fullscreen::Exclusive(ref video_mode)) => {
let uiscreen = video_mode.monitor().ui_screen() as id; let monitor = video_mode.monitor();
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.screen_mode.0]; let screen = monitor.ui_screen();
msg_send![window, setScreen:video_mode.monitor().ui_screen()] screen.setCurrentMode(Some(&video_mode.screen_mode.0));
this.setScreen(screen);
} }
Some(Fullscreen::Borderless(ref monitor)) => { Some(Fullscreen::Borderless(Some(ref monitor))) => {
let uiscreen: id = match &monitor { let screen = monitor.ui_screen();
Some(monitor) => monitor.ui_screen() as id, this.setScreen(screen);
None => {
let uiscreen: id = msg_send![window, screen];
uiscreen
} }
}; _ => (),
msg_send![window, setScreen: uiscreen]
}
None => (),
} }
window this
}
pub(crate) fn id(&self) -> WindowId {
(self as *const Self as usize as u64).into()
}
} }
declare_class!( declare_class!(
@ -465,7 +487,7 @@ declare_class!(
// UIApplicationDelegate protocol // UIApplicationDelegate protocol
unsafe impl WinitApplicationDelegate { unsafe impl WinitApplicationDelegate {
#[sel(application:didFinishLaunchingWithOptions:)] #[sel(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _: id, _: id) -> bool { fn did_finish_launching(&self, _application: &UIApplication, _: id) -> bool {
unsafe { unsafe {
app_state::did_finish_launching(); app_state::did_finish_launching();
} }
@ -473,40 +495,38 @@ declare_class!(
} }
#[sel(applicationDidBecomeActive:)] #[sel(applicationDidBecomeActive:)]
fn did_become_active(&self, _: id) { fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) }
} }
#[sel(applicationWillResignActive:)] #[sel(applicationWillResignActive:)]
fn will_resign_active(&self, _: id) { fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) }
} }
#[sel(applicationWillEnterForeground:)] #[sel(applicationWillEnterForeground:)]
fn will_enter_foreground(&self, _: id) {} fn will_enter_foreground(&self, _application: &UIApplication) {}
#[sel(applicationDidEnterBackground:)] #[sel(applicationDidEnterBackground:)]
fn did_enter_background(&self, _: id) {} fn did_enter_background(&self, _application: &UIApplication) {}
#[sel(applicationWillTerminate:)] #[sel(applicationWillTerminate:)]
fn will_terminate(&self, _: id) { fn will_terminate(&self, application: &UIApplication) {
unsafe {
let app: id = msg_send![class!(UIApplication), sharedApplication];
let windows: id = msg_send![app, windows];
let windows_enum: id = msg_send![windows, objectEnumerator];
let mut events = Vec::new(); let mut events = Vec::new();
loop { for window in application.windows().iter() {
let window: id = msg_send![windows_enum, nextObject]; if window.is_kind_of::<WinitUIWindow>() {
if window == nil { // SAFETY: We just checked that the window is a `winit` window
break; let window = unsafe {
} let ptr: *const UIWindow = window;
let is_winit_window = msg_send![window, isKindOfClass: WinitUIWindow::class()]; let ptr: *const WinitUIWindow = ptr.cast();
if is_winit_window { &*ptr
};
events.push(EventWrapper::StaticEvent(Event::WindowEvent { events.push(EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.into()), window_id: RootWindowId(window.id()),
event: WindowEvent::Destroyed, event: WindowEvent::Destroyed,
})); }));
} }
} }
unsafe {
app_state::handle_nonuser_events(events); app_state::handle_nonuser_events(events);
app_state::terminated(); app_state::terminated();
} }

View file

@ -5,10 +5,14 @@ use std::{
ops::{Deref, DerefMut}, ops::{Deref, DerefMut},
}; };
use objc2::runtime::{Class, Object}; use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker};
use objc2::rc::{Id, Shared};
use objc2::runtime::Object;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{ use crate::{
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size}, dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
error::{ExternalError, NotSupportedError, OsError as RootOsError}, error::{ExternalError, NotSupportedError, OsError as RootOsError},
@ -17,12 +21,9 @@ use crate::{
platform::ios::{ScreenEdge, ValidOrientations}, platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state, app_state,
event_loop::{self, EventProxy, EventWrapper}, event_loop::{EventProxy, EventWrapper},
ffi::{ ffi::{id, UIRectEdge},
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
UIRectEdge, UIScreenOverscanCompensation,
},
monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons,
@ -31,29 +32,19 @@ use crate::{
}; };
pub struct Inner { pub struct Inner {
pub window: id, pub(crate) window: Id<WinitUIWindow, Shared>,
pub view_controller: id, pub(crate) view_controller: Id<WinitViewController, Shared>,
pub view: id, pub(crate) view: Id<WinitView, Shared>,
gl_or_metal_backed: bool, gl_or_metal_backed: 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 { impl Inner {
pub fn set_title(&self, _title: &str) { pub fn set_title(&self, _title: &str) {
debug!("`Window::set_title` is ignored on iOS") debug!("`Window::set_title` is ignored on iOS")
} }
pub fn set_visible(&self, visible: bool) { pub fn set_visible(&self, visible: bool) {
unsafe { msg_send![self.window, setHidden: !visible] } self.window.setHidden(!visible)
} }
pub fn is_visible(&self) -> Option<bool> { pub fn is_visible(&self) -> Option<bool> {
@ -72,9 +63,9 @@ impl Inner {
// testing. // testing.
// //
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window); app_state::queue_gl_or_metal_redraw(self.window.clone());
} else { } else {
let _: () = msg_send![self.view, setNeedsDisplay]; self.view.setNeedsDisplay();
} }
} }
} }
@ -116,7 +107,7 @@ impl Inner {
size: screen_frame.size, size: screen_frame.size,
}; };
let bounds = self.rect_from_screen_space(new_screen_frame); let bounds = self.rect_from_screen_space(new_screen_frame);
let _: () = msg_send![self.window, setBounds: bounds]; self.window.setBounds(bounds);
} }
} }
@ -186,10 +177,7 @@ impl Inner {
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
unsafe { self.view.contentScaleFactor() as _
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
hidpi as _
}
} }
pub fn set_cursor_icon(&self, _cursor: CursorIcon) { pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
@ -230,17 +218,14 @@ impl Inner {
} }
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) { pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
unsafe { let uiscreen = match &monitor {
let uiscreen = match monitor {
Some(Fullscreen::Exclusive(video_mode)) => { Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen() as id; let uiscreen = video_mode.monitor.ui_screen();
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.screen_mode.0]; uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
uiscreen uiscreen.clone()
}
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen() as id,
Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen() as id
} }
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
None => { None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS"); warn!("`Window::set_fullscreen(None)` ignored on iOS");
return; return;
@ -248,22 +233,18 @@ impl Inner {
}; };
// this is pretty slow on iOS, so avoid doing it if we can // this is pretty slow on iOS, so avoid doing it if we can
let current: id = msg_send![self.window, screen]; let current = self.window.screen();
if uiscreen != current { if uiscreen != current {
let _: () = msg_send![self.window, setScreen: uiscreen]; self.window.setScreen(&uiscreen);
} }
let bounds: CGRect = msg_send![uiscreen, bounds]; let bounds = uiscreen.bounds();
let _: () = msg_send![self.window, setFrame: bounds]; self.window.setFrame(bounds);
// For external displays, we must disable overscan compensation or // For external displays, we must disable overscan compensation or
// the displayed image will have giant black bars surrounding it on // the displayed image will have giant black bars surrounding it on
// each side // each side
let _: () = msg_send![ uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None);
uiscreen,
setOverscanCompensation: UIScreenOverscanCompensation::None
];
}
} }
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
@ -271,7 +252,7 @@ impl Inner {
let monitor = self.current_monitor_inner(); let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen(); let uiscreen = monitor.ui_screen();
let screen_space_bounds = self.screen_frame(); let screen_space_bounds = self.screen_frame();
let screen_bounds: CGRect = msg_send![uiscreen, bounds]; let screen_bounds = uiscreen.bounds();
// TODO: track fullscreen instead of relying on brittle float comparisons // TODO: track fullscreen instead of relying on brittle float comparisons
if screen_space_bounds.origin.x == screen_bounds.origin.x if screen_space_bounds.origin.x == screen_bounds.origin.x
@ -321,10 +302,7 @@ impl Inner {
// Allow directly accessing the current monitor internally without unwrapping. // Allow directly accessing the current monitor internally without unwrapping.
fn current_monitor_inner(&self) -> MonitorHandle { fn current_monitor_inner(&self) -> MonitorHandle {
unsafe { MonitorHandle::new(self.window.screen())
let uiscreen: id = msg_send![self.window, screen];
MonitorHandle::retained_new(uiscreen)
}
} }
pub fn current_monitor(&self) -> Option<MonitorHandle> { pub fn current_monitor(&self) -> Option<MonitorHandle> {
@ -332,23 +310,24 @@ impl Inner {
} }
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
unsafe { monitor::uiscreens() } monitor::uiscreens(MainThreadMarker::new().unwrap())
} }
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
let monitor = unsafe { monitor::main_uiscreen() }; Some(MonitorHandle::new(UIScreen::main(
Some(monitor) MainThreadMarker::new().unwrap(),
)))
} }
pub fn id(&self) -> WindowId { pub fn id(&self) -> WindowId {
self.window.into() self.window.id()
} }
pub fn raw_window_handle(&self) -> RawWindowHandle { pub fn raw_window_handle(&self) -> RawWindowHandle {
let mut window_handle = UiKitWindowHandle::empty(); let mut window_handle = UiKitWindowHandle::empty();
window_handle.ui_window = self.window as _; window_handle.ui_window = Id::as_ptr(&self.window) as _;
window_handle.ui_view = self.view as _; window_handle.ui_view = Id::as_ptr(&self.view) as _;
window_handle.ui_view_controller = self.view_controller as _; window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
RawWindowHandle::UiKit(window_handle) RawWindowHandle::UiKit(window_handle)
} }
@ -407,6 +386,8 @@ impl Window {
window_attributes: WindowAttributes, window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> { ) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap();
if window_attributes.min_inner_size.is_some() { if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
} }
@ -416,20 +397,18 @@ impl Window {
// TODO: transparency, visible // TODO: transparency, visible
unsafe { let main_screen = UIScreen::main(mtm);
let screen = match window_attributes.fullscreen { let screen = match window_attributes.fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen() as id, Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(),
Some(Fullscreen::Borderless(None)) | None => { Some(Fullscreen::Borderless(None)) | None => &main_screen,
monitor::main_uiscreen().ui_screen() as id
}
}; };
let screen_bounds: CGRect = msg_send![screen, bounds]; let screen_bounds = screen.bounds();
let frame = match window_attributes.inner_size { let frame = match window_attributes.inner_size {
Some(dim) => { Some(dim) => {
let scale_factor: CGFloat = msg_send![screen, scale]; let scale_factor = screen.scale();
let size = dim.to_logical::<f64>(scale_factor as f64); let size = dim.to_logical::<f64>(scale_factor as f64);
CGRect { CGRect {
origin: screen_bounds.origin, origin: screen_bounds.origin,
@ -442,173 +421,138 @@ impl Window {
None => screen_bounds, None => screen_bounds,
}; };
let view = view::create_view(&window_attributes, &platform_attributes, frame); let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame);
let gl_or_metal_backed = { let gl_or_metal_backed = unsafe {
let view_class: *const Class = msg_send![view, class]; let layer_class = WinitView::layerClass();
let layer_class: *const Class = msg_send![view_class, layerClass];
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
is_metal || is_gl is_metal || is_gl
}; };
let view_controller = let view_controller =
view::create_view_controller(&window_attributes, &platform_attributes, view); WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
let window = view::create_window( let window = WinitUIWindow::new(
mtm,
&window_attributes, &window_attributes,
&platform_attributes, &platform_attributes,
frame, frame,
view_controller, &view_controller,
); );
let result = Window { unsafe { app_state::set_key_window(&window) };
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor = view.contentScaleFactor();
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds = view.bounds();
let screen = window.screen();
let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
let window_id = RootWindowId(window.id());
unsafe {
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window: window.clone(),
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id,
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
}
Ok(Window {
inner: Inner { inner: Inner {
window, window,
view_controller, view_controller,
view, view,
gl_or_metal_backed, gl_or_metal_backed,
}, },
}; })
app_state::set_key_window(window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0
let scale_factor: CGFloat = msg_send![view, contentScaleFactor];
let scale_factor = scale_factor as f64;
if scale_factor != 1.0 {
let bounds: CGRect = msg_send![view, bounds];
let screen: id = msg_send![window, screen];
let screen_space: id = msg_send![screen, coordinateSpace];
let screen_frame: CGRect =
msg_send![view, convertRect: bounds, toCoordinateSpace: screen_space];
let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _,
height: screen_frame.size.height as _,
};
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy {
window_id: window,
scale_factor,
suggested_size: size,
}))
.chain(std::iter::once(EventWrapper::StaticEvent(
Event::WindowEvent {
window_id: RootWindowId(window.into()),
event: WindowEvent::Resized(size.to_physical(scale_factor)),
},
))),
);
}
Ok(result)
}
} }
} }
// WindowExtIOS // WindowExtIOS
impl Inner { impl Inner {
pub fn ui_window(&self) -> id { pub fn ui_window(&self) -> id {
self.window Id::as_ptr(&self.window) as id
} }
pub fn ui_view_controller(&self) -> id { pub fn ui_view_controller(&self) -> id {
self.view_controller Id::as_ptr(&self.view_controller) as id
} }
pub fn ui_view(&self) -> id { pub fn ui_view(&self) -> id {
self.view Id::as_ptr(&self.view) as id
} }
pub fn set_scale_factor(&self, scale_factor: f64) { pub fn set_scale_factor(&self, scale_factor: f64) {
unsafe {
assert!( assert!(
dpi::validate_scale_factor(scale_factor), dpi::validate_scale_factor(scale_factor),
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
); );
let scale_factor = scale_factor as CGFloat; let scale_factor = scale_factor as CGFloat;
let _: () = msg_send![self.view, setContentScaleFactor: scale_factor]; self.view.setContentScaleFactor(scale_factor);
}
} }
pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) {
unsafe { self.view_controller.set_supported_interface_orientations(
let idiom = event_loop::get_idiom(); MainThreadMarker::new().unwrap(),
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
valid_orientations, valid_orientations,
idiom,
); );
msg_send![
self.view_controller,
setSupportedInterfaceOrientations: supported_orientations
]
}
} }
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
unsafe { self.view_controller
let _: () = msg_send![ .setPrefersHomeIndicatorAutoHidden(hidden);
self.view_controller,
setPrefersHomeIndicatorAutoHidden: hidden,
];
}
} }
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) { pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
let edges: UIRectEdge = edges.into(); let edges: UIRectEdge = edges.into();
unsafe { self.view_controller
let _: () = msg_send![ .setPreferredScreenEdgesDeferringSystemGestures(edges);
self.view_controller,
setPreferredScreenEdgesDeferringSystemGestures: edges
];
}
} }
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) { pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
unsafe { self.view_controller.setPrefersStatusBarHidden(hidden);
let _: () = msg_send![self.view_controller, setPrefersStatusBarHidden: hidden,];
}
} }
} }
impl Inner { impl Inner {
// requires main thread // requires main thread
unsafe fn screen_frame(&self) -> CGRect { unsafe fn screen_frame(&self) -> CGRect {
self.rect_to_screen_space(msg_send![self.window, bounds]) self.rect_to_screen_space(self.window.bounds())
} }
// requires main thread // requires main thread
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect { unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen]; let screen_space = self.window.screen().coordinateSpace();
if !screen.is_null() { self.window
let screen_space: id = msg_send![screen, coordinateSpace]; .convertRect_toCoordinateSpace(rect, &screen_space)
msg_send![
self.window,
convertRect: rect,
toCoordinateSpace: screen_space,
]
} else {
rect
}
} }
// requires main thread // requires main thread
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect { unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
let screen: id = msg_send![self.window, screen]; let screen_space = self.window.screen().coordinateSpace();
if !screen.is_null() { self.window
let screen_space: id = msg_send![screen, coordinateSpace]; .convertRect_fromCoordinateSpace(rect, &screen_space)
msg_send![
self.window,
convertRect: rect,
fromCoordinateSpace: screen_space,
]
} else {
rect
}
} }
// requires main thread // requires main thread
unsafe fn safe_area_screen_space(&self) -> CGRect { unsafe fn safe_area_screen_space(&self) -> CGRect {
let bounds: CGRect = msg_send![self.window, bounds]; let bounds = self.window.bounds();
if app_state::os_capabilities().safe_area { if app_state::os_capabilities().safe_area {
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets]; let safe_area = self.window.safeAreaInsets();
let safe_bounds = CGRect { let safe_bounds = CGRect {
origin: CGPoint { origin: CGPoint {
x: bounds.origin.x + safe_area.left, x: bounds.origin.x + safe_area.left,
@ -622,13 +566,11 @@ impl Inner {
self.rect_to_screen_space(safe_bounds) self.rect_to_screen_space(safe_bounds)
} else { } else {
let screen_frame = self.rect_to_screen_space(bounds); let screen_frame = self.rect_to_screen_space(bounds);
let status_bar_frame: CGRect = { let status_bar_frame = {
let app: id = msg_send![class!(UIApplication), sharedApplication]; let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
assert!( "`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS",
!app.is_null(),
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
); );
msg_send![app, statusBarFrame] app.statusBarFrame()
}; };
let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height { let (y, height) = if screen_frame.origin.y > status_bar_frame.size.height {
(screen_frame.origin.y, screen_frame.size.height) (screen_frame.origin.y, screen_frame.size.height)