mirror of
https://github.com/italicsjenga/winit-sonoma-fix.git
synced 2024-12-23 13:51:30 +11:00
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:
parent
5e77d70245
commit
ee88e38f13
|
@ -1,5 +1,7 @@
|
|||
use std::os::raw::c_void;
|
||||
|
||||
use objc2::rc::Id;
|
||||
|
||||
use crate::{
|
||||
event_loop::EventLoop,
|
||||
monitor::{MonitorHandle, VideoMode},
|
||||
|
@ -239,7 +241,7 @@ pub trait MonitorHandleExtIOS {
|
|||
impl MonitorHandleExtIOS for MonitorHandle {
|
||||
#[inline]
|
||||
fn ui_screen(&self) -> *mut c_void {
|
||||
self.inner.ui_screen() as _
|
||||
Id::as_ptr(self.inner.ui_screen()) as *mut c_void
|
||||
}
|
||||
|
||||
#[inline]
|
||||
|
|
|
@ -15,18 +15,19 @@ use core_foundation::runloop::{
|
|||
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
|
||||
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
|
||||
};
|
||||
use objc2::foundation::{NSInteger, NSUInteger};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::foundation::{CGRect, CGSize, NSInteger};
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::{class, msg_send, sel};
|
||||
use once_cell::sync::Lazy;
|
||||
|
||||
use super::view::WinitUIWindow;
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
event_loop::ControlFlow,
|
||||
platform_impl::platform::{
|
||||
event_loop::{EventHandler, EventProxy, EventWrapper, Never},
|
||||
ffi::{id, CGRect, CGSize, NSOperatingSystemVersion},
|
||||
ffi::{id, NSOperatingSystemVersion},
|
||||
},
|
||||
window::WindowId as RootWindowId,
|
||||
};
|
||||
|
@ -65,25 +66,25 @@ impl Event<'static, Never> {
|
|||
#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"]
|
||||
enum AppStateImpl {
|
||||
NotLaunched {
|
||||
queued_windows: Vec<id>,
|
||||
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
},
|
||||
Launching {
|
||||
queued_windows: Vec<id>,
|
||||
queued_windows: Vec<Id<WinitUIWindow, Shared>>,
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
},
|
||||
ProcessingEvents {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
active_control_flow: ControlFlow,
|
||||
},
|
||||
// special state to deal with reentrancy and prevent mutable aliasing.
|
||||
InUserCallback {
|
||||
queued_events: Vec<EventWrapper>,
|
||||
queued_gpu_redraws: HashSet<id>,
|
||||
queued_gpu_redraws: HashSet<Id<WinitUIWindow, Shared>>,
|
||||
},
|
||||
ProcessingRedraws {
|
||||
event_handler: Box<dyn EventHandler>,
|
||||
|
@ -106,28 +107,6 @@ struct AppState {
|
|||
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 {
|
||||
// requires main thread
|
||||
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() {
|
||||
AppStateImpl::Launching {
|
||||
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() {
|
||||
AppStateImpl::ProcessingEvents {
|
||||
event_handler,
|
||||
|
@ -473,17 +454,13 @@ impl AppState {
|
|||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub unsafe fn set_key_window(window: id) {
|
||||
bug_assert!(
|
||||
msg_send![window, isKindOfClass: class!(UIWindow)],
|
||||
"set_key_window called with an incorrect type"
|
||||
);
|
||||
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow, Shared>) {
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&mut AppStateImpl::NotLaunched {
|
||||
ref mut queued_windows,
|
||||
..
|
||||
} => return queued_windows.push(msg_send![window, retain]),
|
||||
} => return queued_windows.push(window.clone()),
|
||||
&mut AppStateImpl::ProcessingEvents { .. }
|
||||
| &mut AppStateImpl::InUserCallback { .. }
|
||||
| &mut AppStateImpl::ProcessingRedraws { .. } => {}
|
||||
|
@ -495,16 +472,12 @@ pub unsafe fn set_key_window(window: id) {
|
|||
}
|
||||
}
|
||||
drop(this);
|
||||
msg_send![window, makeKeyAndVisible]
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
|
||||
// requires main thread and window is a UIWindow
|
||||
// retains window
|
||||
pub unsafe fn queue_gl_or_metal_redraw(window: id) {
|
||||
bug_assert!(
|
||||
msg_send![window, isKindOfClass: class!(UIWindow)],
|
||||
"set_key_window called with an incorrect type"
|
||||
);
|
||||
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow, Shared>) {
|
||||
let mut this = AppState::get_mut();
|
||||
match this.state_mut() {
|
||||
&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")
|
||||
}
|
||||
}
|
||||
|
||||
drop(this);
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
|
@ -560,9 +531,6 @@ pub unsafe fn did_finish_launching() {
|
|||
drop(this);
|
||||
|
||||
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
|
||||
// `UIApplicationMain` is called. This fixes visual issues such as being
|
||||
// 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
|
||||
// completed. This may result in incorrect visual appearance.
|
||||
// ```
|
||||
let screen: id = msg_send![window, screen];
|
||||
let _: id = msg_send![screen, retain];
|
||||
let _: () = msg_send![window, setScreen:0 as id];
|
||||
let _: () = msg_send![window, setScreen: screen];
|
||||
let _: () = msg_send![screen, release];
|
||||
let controller: id = msg_send![window, rootViewController];
|
||||
let _: () = msg_send![window, setRootViewController:ptr::null::<Object>()];
|
||||
let _: () = msg_send![window, setRootViewController: controller];
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let _: () = msg_send![window, release];
|
||||
let screen = window.screen();
|
||||
let _: () = msg_send![&window, setScreen: 0 as id];
|
||||
window.setScreen(&screen);
|
||||
|
||||
let controller = window.rootViewController();
|
||||
window.setRootViewController(None);
|
||||
window.setRootViewController(controller.as_deref());
|
||||
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
|
||||
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.
|
||||
// we can just set those windows up normally, as they were created after didFinishLaunching
|
||||
for window in windows {
|
||||
let count: NSUInteger = msg_send![window, retainCount];
|
||||
// make sure the window is still referenced
|
||||
if count > 1 {
|
||||
let _: () = msg_send![window, makeKeyAndVisible];
|
||||
}
|
||||
let _: () = msg_send![window, release];
|
||||
window.makeKeyAndVisible();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -620,12 +581,12 @@ pub unsafe fn handle_wakeup_transition() {
|
|||
}
|
||||
|
||||
// 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))
|
||||
}
|
||||
|
||||
// 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 event_handler, active_control_flow, processing_redraws) =
|
||||
match this.try_user_callback_transition() {
|
||||
|
@ -803,9 +764,7 @@ pub unsafe fn handle_main_events_cleared() {
|
|||
let mut redraw_events: Vec<EventWrapper> = this
|
||||
.main_events_cleared_transition()
|
||||
.into_iter()
|
||||
.map(|window| {
|
||||
EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.into())))
|
||||
})
|
||||
.map(|window| EventWrapper::StaticEvent(Event::RedrawRequested(RootWindowId(window.id()))))
|
||||
.collect();
|
||||
|
||||
redraw_events.push(EventWrapper::StaticEvent(Event::RedrawEventsCleared));
|
||||
|
@ -838,13 +797,13 @@ fn handle_event_proxy(
|
|||
EventProxy::DpiChangedProxy {
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
window_id,
|
||||
window,
|
||||
} => handle_hidpi_proxy(
|
||||
event_handler,
|
||||
control_flow,
|
||||
suggested_size,
|
||||
scale_factor,
|
||||
window_id,
|
||||
window,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
@ -854,42 +813,36 @@ fn handle_hidpi_proxy(
|
|||
mut control_flow: ControlFlow,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
window_id: id,
|
||||
window: Id<WinitUIWindow, Shared>,
|
||||
) {
|
||||
let mut size = suggested_size.to_physical(scale_factor);
|
||||
let new_inner_size = &mut size;
|
||||
let event = Event::WindowEvent {
|
||||
window_id: RootWindowId(window_id.into()),
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::ScaleFactorChanged {
|
||||
scale_factor,
|
||||
new_inner_size,
|
||||
},
|
||||
};
|
||||
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 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);
|
||||
unsafe {
|
||||
let _: () = msg_send![view, setFrame: new_frame];
|
||||
}
|
||||
}
|
||||
|
||||
fn get_view_and_screen_frame(window_id: id) -> (id, CGRect) {
|
||||
unsafe {
|
||||
let view_controller: id = msg_send![window_id, rootViewController];
|
||||
let view: id = msg_send![view_controller, view];
|
||||
let bounds: CGRect = msg_send![window_id, bounds];
|
||||
let screen: id = msg_send![window_id, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect = msg_send![
|
||||
window_id,
|
||||
convertRect: bounds,
|
||||
toCoordinateSpace: screen_space,
|
||||
];
|
||||
fn get_view_and_screen_frame(window: &WinitUIWindow) -> (id, CGRect) {
|
||||
let view_controller = window.rootViewController().unwrap();
|
||||
let view: id = unsafe { msg_send![&view_controller, view] };
|
||||
let bounds = window.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = window.convertRect_toCoordinateSpace(bounds, &screen_space);
|
||||
(view, screen_frame)
|
||||
}
|
||||
}
|
||||
|
||||
struct EventLoopWaker {
|
||||
|
|
|
@ -14,10 +14,13 @@ use core_foundation::runloop::{
|
|||
CFRunLoopObserverCreate, CFRunLoopObserverRef, CFRunLoopSourceContext, CFRunLoopSourceCreate,
|
||||
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
|
||||
};
|
||||
use objc2::runtime::Object;
|
||||
use objc2::{class, msg_send, ClassType};
|
||||
use objc2::foundation::MainThreadMarker;
|
||||
use objc2::rc::{Id, Shared};
|
||||
use objc2::ClassType;
|
||||
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
|
||||
|
||||
use super::uikit::{UIApplication, UIDevice, UIScreen};
|
||||
use super::view::WinitUIWindow;
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
event::Event,
|
||||
|
@ -29,20 +32,20 @@ use crate::{
|
|||
|
||||
use crate::platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{id, nil, NSStringRust, UIApplicationMain, UIUserInterfaceIdiom},
|
||||
ffi::{nil, NSStringRust, UIApplicationMain},
|
||||
monitor, view, MonitorHandle,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum EventWrapper {
|
||||
pub(crate) enum EventWrapper {
|
||||
StaticEvent(Event<'static, Never>),
|
||||
EventProxy(EventProxy),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum EventProxy {
|
||||
pub(crate) enum EventProxy {
|
||||
DpiChangedProxy {
|
||||
window_id: id,
|
||||
window: Id<WinitUIWindow, Shared>,
|
||||
suggested_size: LogicalSize<f64>,
|
||||
scale_factor: f64,
|
||||
},
|
||||
|
@ -55,15 +58,13 @@ pub struct EventLoopWindowTarget<T: 'static> {
|
|||
|
||||
impl<T: 'static> EventLoopWindowTarget<T> {
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { monitor::uiscreens() }
|
||||
monitor::uiscreens(MainThreadMarker::new().unwrap())
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
// guaranteed to be on main thread
|
||||
let monitor = unsafe { monitor::main_uiscreen() };
|
||||
|
||||
Some(monitor)
|
||||
Some(MonitorHandle::new(UIScreen::main(
|
||||
MainThreadMarker::new().unwrap(),
|
||||
)))
|
||||
}
|
||||
|
||||
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),
|
||||
{
|
||||
unsafe {
|
||||
let application: *mut Object = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert_eq!(
|
||||
application,
|
||||
ptr::null_mut(),
|
||||
let _application = UIApplication::shared(MainThreadMarker::new().unwrap()).expect(
|
||||
"\
|
||||
`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 {
|
||||
f: event_handler,
|
||||
|
@ -151,8 +149,9 @@ impl<T: 'static> EventLoop<T> {
|
|||
// EventLoopExtIOS
|
||||
impl<T: 'static> EventLoop<T> {
|
||||
pub fn idiom(&self) -> Idiom {
|
||||
// guaranteed to be on main thread
|
||||
unsafe { self::get_idiom() }
|
||||
UIDevice::current(MainThreadMarker::new().unwrap())
|
||||
.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()
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
|
||||
use std::convert::TryInto;
|
||||
use std::ffi::CString;
|
||||
use std::ops::BitOr;
|
||||
use std::os::raw::{c_char, c_int};
|
||||
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
|
@ -10,19 +9,11 @@ use objc2::foundation::{NSInteger, NSUInteger};
|
|||
use objc2::runtime::Object;
|
||||
use objc2::{class, msg_send};
|
||||
|
||||
use crate::{
|
||||
dpi::LogicalSize,
|
||||
platform::ios::{Idiom, ScreenEdge, ValidOrientations},
|
||||
};
|
||||
use crate::platform::ios::{Idiom, ScreenEdge};
|
||||
|
||||
pub type id = *mut Object;
|
||||
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)]
|
||||
#[derive(Clone, Debug)]
|
||||
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)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
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)]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
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")]
|
||||
extern "C" {
|
||||
pub fn UIApplicationMain(
|
||||
|
|
|
@ -87,18 +87,19 @@ pub(crate) use self::{
|
|||
window::{PlatformSpecificWindowBuilderAttributes, Window, WindowId},
|
||||
};
|
||||
|
||||
use self::uikit::UIScreen;
|
||||
pub(crate) use crate::icon::NoIcon as PlatformIcon;
|
||||
pub(self) use crate::platform_impl::Fullscreen;
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct DeviceId {
|
||||
uiscreen: ffi::id,
|
||||
uiscreen: *const UIScreen,
|
||||
}
|
||||
|
||||
impl DeviceId {
|
||||
pub const unsafe fn dummy() -> Self {
|
||||
DeviceId {
|
||||
uiscreen: std::ptr::null_mut(),
|
||||
uiscreen: std::ptr::null(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,74 +6,43 @@ use std::{
|
|||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
use objc2::foundation::{NSInteger, NSUInteger};
|
||||
use objc2::{class, msg_send};
|
||||
use objc2::foundation::{MainThreadMarker, NSInteger};
|
||||
use objc2::rc::{Id, Shared};
|
||||
|
||||
use super::uikit::{UIScreen, UIScreenMode};
|
||||
use crate::{
|
||||
dpi::{PhysicalPosition, PhysicalSize},
|
||||
monitor::VideoMode as RootVideoMode,
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
ffi::{id, nil, CGFloat, CGRect, CGSize},
|
||||
},
|
||||
platform_impl::platform::app_state,
|
||||
};
|
||||
|
||||
#[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(crate) size: (u32, u32),
|
||||
pub(crate) bit_depth: u16,
|
||||
pub(crate) refresh_rate_millihertz: u32,
|
||||
pub(crate) screen_mode: NativeDisplayMode,
|
||||
pub(crate) screen_mode: ScreenModeSendSync,
|
||||
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 {
|
||||
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");
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(uiscreen);
|
||||
let size: CGSize = msg_send![screen_mode, size];
|
||||
let screen_mode: id = msg_send![screen_mode, retain];
|
||||
let screen_mode = NativeDisplayMode(screen_mode);
|
||||
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
|
||||
let size = screen_mode.size();
|
||||
VideoMode {
|
||||
size: (size.width as u32, size.height as u32),
|
||||
bit_depth: 32,
|
||||
refresh_rate_millihertz,
|
||||
screen_mode,
|
||||
monitor: MonitorHandle::retained_new(uiscreen),
|
||||
screen_mode: ScreenModeSendSync(screen_mode),
|
||||
monitor: MonitorHandle::new(uiscreen),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -94,24 +63,29 @@ impl VideoMode {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct Inner {
|
||||
uiscreen: id,
|
||||
uiscreen: Id<UIScreen, Shared>,
|
||||
}
|
||||
|
||||
impl Drop for Inner {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.uiscreen, release];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[derive(Clone, PartialEq, Eq, Hash)]
|
||||
pub struct MonitorHandle {
|
||||
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 {
|
||||
type Target = Inner;
|
||||
|
||||
|
@ -131,12 +105,6 @@ impl DerefMut for MonitorHandle {
|
|||
unsafe impl Send for MonitorHandle {}
|
||||
unsafe impl Sync for MonitorHandle {}
|
||||
|
||||
impl Clone for MonitorHandle {
|
||||
fn clone(&self) -> MonitorHandle {
|
||||
MonitorHandle::retained_new(self.uiscreen)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for MonitorHandle {
|
||||
fn drop(&mut self) {
|
||||
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 {
|
||||
pub fn retained_new(uiscreen: id) -> MonitorHandle {
|
||||
assert_main_thread!("`MonitorHandle` can only be cloned on the main thread on iOS");
|
||||
unsafe {
|
||||
let _: id = msg_send![uiscreen, retain];
|
||||
}
|
||||
MonitorHandle {
|
||||
pub(crate) fn new(uiscreen: Id<UIScreen, Shared>) -> Self {
|
||||
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS");
|
||||
Self {
|
||||
inner: Inner { uiscreen },
|
||||
}
|
||||
}
|
||||
|
@ -180,70 +145,62 @@ impl MonitorHandle {
|
|||
|
||||
impl Inner {
|
||||
pub fn name(&self) -> Option<String> {
|
||||
unsafe {
|
||||
let main = main_uiscreen();
|
||||
if self.uiscreen == main.uiscreen {
|
||||
let main = UIScreen::main(MainThreadMarker::new().unwrap());
|
||||
if self.uiscreen == main {
|
||||
Some("Primary".to_string())
|
||||
} else if self.uiscreen == mirrored_uiscreen(&main).uiscreen {
|
||||
} else if self.uiscreen == main.mirroredScreen() {
|
||||
Some("Mirrored".to_string())
|
||||
} else {
|
||||
uiscreens()
|
||||
UIScreen::screens(MainThreadMarker::new().unwrap())
|
||||
.iter()
|
||||
.position(|rhs| rhs.uiscreen == self.uiscreen)
|
||||
.position(|rhs| rhs == &*self.uiscreen)
|
||||
.map(|idx| idx.to_string())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn size(&self) -> PhysicalSize<u32> {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
let bounds = self.uiscreen.nativeBounds();
|
||||
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn position(&self) -> PhysicalPosition<i32> {
|
||||
unsafe {
|
||||
let bounds: CGRect = msg_send![self.ui_screen(), nativeBounds];
|
||||
let bounds = self.uiscreen.nativeBounds();
|
||||
(bounds.origin.x as f64, bounds.origin.y as f64).into()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_factor(&self) -> f64 {
|
||||
unsafe {
|
||||
let scale: CGFloat = msg_send![self.ui_screen(), nativeScale];
|
||||
scale as f64
|
||||
}
|
||||
self.uiscreen.nativeScale() as f64
|
||||
}
|
||||
|
||||
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> {
|
||||
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
|
||||
modes.insert(RootVideoMode {
|
||||
video_mode: VideoMode::retained_new(self.uiscreen, mode),
|
||||
});
|
||||
}
|
||||
let modes: BTreeSet<_> = self
|
||||
.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)
|
||||
}
|
||||
}
|
||||
|
||||
fn refresh_rate_millihertz(uiscreen: id) -> u32 {
|
||||
let refresh_rate_millihertz: NSInteger = unsafe {
|
||||
fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
|
||||
let refresh_rate_millihertz: NSInteger = {
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.maximum_frames_per_second {
|
||||
msg_send![uiscreen, maximumFramesPerSecond]
|
||||
uiscreen.maximumFramesPerSecond()
|
||||
} else {
|
||||
// https://developer.apple.com/library/archive/technotes/tn2460/_index.html
|
||||
// https://en.wikipedia.org/wiki/IPad_Pro#Model_comparison
|
||||
|
@ -265,41 +222,25 @@ fn refresh_rate_millihertz(uiscreen: id) -> u32 {
|
|||
|
||||
// MonitorHandleExtIOS
|
||||
impl Inner {
|
||||
pub fn ui_screen(&self) -> id {
|
||||
self.uiscreen
|
||||
pub(crate) fn ui_screen(&self) -> &Id<UIScreen, Shared> {
|
||||
&self.uiscreen
|
||||
}
|
||||
|
||||
pub fn preferred_video_mode(&self) -> VideoMode {
|
||||
unsafe {
|
||||
let mode: id = msg_send![self.uiscreen, preferredMode];
|
||||
VideoMode::retained_new(self.uiscreen, mode)
|
||||
}
|
||||
VideoMode::new(
|
||||
self.uiscreen.clone(),
|
||||
self.uiscreen.preferredMode().unwrap(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
pub unsafe fn main_uiscreen() -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![class!(UIScreen), mainScreen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
|
||||
// requires being run on main thread
|
||||
unsafe fn mirrored_uiscreen(monitor: &MonitorHandle) -> MonitorHandle {
|
||||
let uiscreen: id = msg_send![monitor.uiscreen, mirroredScreen];
|
||||
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));
|
||||
}
|
||||
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
|
||||
UIScreen::screens(mtm)
|
||||
.into_iter()
|
||||
.map(|screen| {
|
||||
let screen: *const UIScreen = screen;
|
||||
let screen = unsafe { Id::retain(screen as *mut UIScreen).unwrap() };
|
||||
MonitorHandle::new(screen)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
30
src/platform_impl/ios/uikit/application.rs
Normal file
30
src/platform_impl/ios/uikit/application.rs
Normal 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;
|
||||
}
|
||||
);
|
11
src/platform_impl/ios/uikit/coordinate_space.rs
Normal file
11
src/platform_impl/ios/uikit/coordinate_space.rs
Normal 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;
|
||||
}
|
||||
);
|
25
src/platform_impl/ios/uikit/device.rs
Normal file
25
src/platform_impl/ios/uikit/device.rs
Normal 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;
|
||||
}
|
||||
);
|
11
src/platform_impl/ios/uikit/event.rs
Normal file
11
src/platform_impl/ios/uikit/event.rs
Normal 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;
|
||||
}
|
||||
);
|
|
@ -1,11 +1,30 @@
|
|||
#![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 screen;
|
||||
mod screen_mode;
|
||||
mod touch;
|
||||
mod trait_collection;
|
||||
mod view;
|
||||
mod view_controller;
|
||||
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::view::UIView;
|
||||
pub(crate) use self::view_controller::UIViewController;
|
||||
pub(crate) use self::screen::{UIScreen, UIScreenOverscanCompensation};
|
||||
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;
|
||||
|
|
79
src/platform_impl/ios/uikit/screen.rs
Normal file
79
src/platform_impl/ios/uikit/screen.rs
Normal 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);
|
||||
}
|
18
src/platform_impl/ios/uikit/screen_mode.rs
Normal file
18
src/platform_impl/ios/uikit/screen_mode.rs
Normal 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;
|
||||
}
|
||||
);
|
64
src/platform_impl/ios/uikit/touch.rs
Normal file
64
src/platform_impl/ios/uikit/touch.rs
Normal 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;
|
||||
}
|
32
src/platform_impl/ios/uikit/trait_collection.rs
Normal file
32
src/platform_impl/ios/uikit/trait_collection.rs
Normal 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;
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
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!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -12,3 +14,76 @@ extern_class!(
|
|||
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,
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
use objc2::foundation::NSObject;
|
||||
use objc2::{extern_class, ClassType};
|
||||
use objc2::encode::{Encode, Encoding};
|
||||
use objc2::foundation::{NSObject, NSUInteger};
|
||||
use objc2::{extern_class, extern_methods, ClassType};
|
||||
|
||||
use super::UIResponder;
|
||||
use super::{UIResponder, UIView};
|
||||
|
||||
extern_class!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
|
@ -12,3 +13,38 @@ extern_class!(
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,32 @@
|
|||
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!(
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct UIWindow;
|
||||
|
||||
unsafe impl ClassType for UIWindow {
|
||||
#[inherits(NSObject)]
|
||||
type Super = UIResponder;
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
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);
|
||||
}
|
||||
);
|
||||
|
|
|
@ -1,19 +1,24 @@
|
|||
#![allow(clippy::unnecessary_cast)]
|
||||
|
||||
use objc2::foundation::NSObject;
|
||||
use objc2::{class, declare_class, msg_send, ClassType};
|
||||
use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet};
|
||||
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::{
|
||||
dpi::PhysicalPosition,
|
||||
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
|
||||
platform::ios::ValidOrientations,
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{self, EventProxy, EventWrapper},
|
||||
ffi::{
|
||||
id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UITouchPhase, UITouchType,
|
||||
},
|
||||
event_loop::{EventProxy, EventWrapper},
|
||||
ffi::{id, UIRectEdge, UIUserInterfaceIdiom},
|
||||
window::PlatformSpecificWindowBuilderAttributes,
|
||||
DeviceId, Fullscreen,
|
||||
},
|
||||
|
@ -21,7 +26,7 @@ use crate::{
|
|||
};
|
||||
|
||||
declare_class!(
|
||||
struct WinitView {}
|
||||
pub(crate) struct WinitView {}
|
||||
|
||||
unsafe impl ClassType for WinitView {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
|
@ -32,37 +37,30 @@ declare_class!(
|
|||
unsafe impl WinitView {
|
||||
#[sel(drawRect:)]
|
||||
fn draw_rect(&self, rect: CGRect) {
|
||||
let window = self.window().unwrap();
|
||||
unsafe {
|
||||
let window: id = msg_send![self, window];
|
||||
assert!(!window.is_null());
|
||||
app_state::handle_nonuser_events(
|
||||
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
|
||||
RootWindowId(window.into()),
|
||||
RootWindowId(window.id()),
|
||||
)))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::RedrawEventsCleared,
|
||||
))),
|
||||
);
|
||||
let _: () = msg_send![super(self), drawRect: rect];
|
||||
}
|
||||
let _: () = unsafe { msg_send![super(self), drawRect: rect] };
|
||||
}
|
||||
|
||||
#[sel(layoutSubviews)]
|
||||
fn layout_subviews(&self) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(self), layoutSubviews];
|
||||
let _: () = unsafe { msg_send![super(self), layoutSubviews] };
|
||||
|
||||
let window: id = msg_send![self, window];
|
||||
assert!(!window.is_null());
|
||||
let window_bounds: CGRect = msg_send![window, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect = msg_send![
|
||||
self,
|
||||
convertRect: window_bounds,
|
||||
toCoordinateSpace: screen_space,
|
||||
];
|
||||
let scale_factor: CGFloat = msg_send![screen, scale];
|
||||
let window = self.window().unwrap();
|
||||
let window_bounds = window.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = self.convertRect_toCoordinateSpace(window_bounds, &screen_space);
|
||||
let scale_factor = screen.scale();
|
||||
let size = crate::dpi::LogicalSize {
|
||||
width: screen_frame.size.width 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.
|
||||
// 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.
|
||||
let view_frame: CGRect = msg_send![self, frame];
|
||||
let view_frame = self.frame();
|
||||
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 {
|
||||
window_id: RootWindowId(window.into()),
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Resized(size),
|
||||
}));
|
||||
}
|
||||
|
@ -86,20 +85,20 @@ declare_class!(
|
|||
|
||||
#[sel(setContentScaleFactor:)]
|
||||
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
|
||||
unsafe {
|
||||
let _: () = msg_send![super(self), setContentScaleFactor: untrusted_scale_factor];
|
||||
let _: () =
|
||||
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
|
||||
// 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
|
||||
if window.is_null() {
|
||||
return;
|
||||
}
|
||||
let window = match self.window() {
|
||||
Some(window) => window,
|
||||
None => return,
|
||||
};
|
||||
// `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
|
||||
// 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!(
|
||||
!scale_factor.is_nan()
|
||||
&& scale_factor.is_finite()
|
||||
|
@ -107,24 +106,26 @@ declare_class!(
|
|||
&& scale_factor > 0.0,
|
||||
"invalid scale_factor set on UIView",
|
||||
);
|
||||
let bounds: CGRect = msg_send![self, bounds];
|
||||
let screen: id = msg_send![window, screen];
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
let screen_frame: CGRect =
|
||||
msg_send![self, convertRect: bounds, toCoordinateSpace: screen_space];
|
||||
let scale_factor = scale_factor as f64;
|
||||
let bounds = self.bounds();
|
||||
let screen = window.screen();
|
||||
let screen_space = screen.coordinateSpace();
|
||||
let screen_frame = self.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_id: window,
|
||||
window,
|
||||
scale_factor,
|
||||
suggested_size: size,
|
||||
}))
|
||||
.chain(std::iter::once(EventWrapper::StaticEvent(
|
||||
Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
window_id,
|
||||
event: WindowEvent::Resized(size.to_physical(scale_factor)),
|
||||
},
|
||||
))),
|
||||
|
@ -133,53 +134,80 @@ declare_class!(
|
|||
}
|
||||
|
||||
#[sel(touchesBegan:withEvent:)]
|
||||
fn touches_began(&self, touches: id, _: id) {
|
||||
fn touches_began(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesMoved:withEvent:)]
|
||||
fn touches_moved(&self, touches: id, _: id) {
|
||||
fn touches_moved(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesEnded:withEvent:)]
|
||||
fn touches_ended(&self, touches: id, _: id) {
|
||||
fn touches_ended(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
self.handle_touches(touches)
|
||||
}
|
||||
|
||||
#[sel(touchesCancelled:withEvent:)]
|
||||
fn touches_cancelled(&self, touches: id, _: id) {
|
||||
fn touches_cancelled(&self, touches: &NSSet<UITouch>, _event: Option<&UIEvent>) {
|
||||
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 {
|
||||
fn handle_touches(&self, touches: id) {
|
||||
unsafe {
|
||||
let window: id = msg_send![self, window];
|
||||
assert!(!window.is_null());
|
||||
let uiscreen: id = msg_send![window, screen];
|
||||
let touches_enum: id = msg_send![touches, objectEnumerator];
|
||||
pub(crate) fn new(
|
||||
_mtm: MainThreadMarker,
|
||||
_window_attributes: &WindowAttributes,
|
||||
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
) -> 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 os_supports_force = app_state::os_capabilities().force_touch;
|
||||
loop {
|
||||
let touch: id = msg_send![touches_enum, nextObject];
|
||||
if touch == nil {
|
||||
break;
|
||||
}
|
||||
let logical_location: CGPoint = msg_send![touch, locationInView: nil];
|
||||
let touch_type: UITouchType = msg_send![touch, type];
|
||||
for touch in touches {
|
||||
let logical_location = touch.locationInView(None);
|
||||
let touch_type = touch.type_();
|
||||
let force = if os_supports_force {
|
||||
let trait_collection: id = msg_send![self, traitCollection];
|
||||
let touch_capability: UIForceTouchCapability =
|
||||
msg_send![trait_collection, forceTouchCapability];
|
||||
let trait_collection = unsafe { self.traitCollection() };
|
||||
let touch_capability = trait_collection.forceTouchCapability();
|
||||
// Both the OS _and_ the device need to be checked for force touch support.
|
||||
if touch_capability == UIForceTouchCapability::Available {
|
||||
let force: CGFloat = msg_send![touch, force];
|
||||
let max_possible_force: CGFloat = msg_send![touch, maximumPossibleForce];
|
||||
let force = touch.force();
|
||||
let max_possible_force = touch.maximumPossibleForce();
|
||||
let altitude_angle: Option<f64> = if touch_type == UITouchType::Pencil {
|
||||
let angle: CGFloat = msg_send![touch, altitudeAngle];
|
||||
let angle = touch.altitudeAngle();
|
||||
Some(angle as _)
|
||||
} else {
|
||||
None
|
||||
|
@ -195,8 +223,8 @@ impl WinitView {
|
|||
} else {
|
||||
None
|
||||
};
|
||||
let touch_id = touch as u64;
|
||||
let phase: UITouchPhase = msg_send![touch, phase];
|
||||
let touch_id = touch as *const UITouch as u64;
|
||||
let phase = touch.phase();
|
||||
let phase = match phase {
|
||||
UITouchPhase::Began => TouchPhase::Started,
|
||||
UITouchPhase::Moved => TouchPhase::Moved,
|
||||
|
@ -207,16 +235,18 @@ impl WinitView {
|
|||
};
|
||||
|
||||
let physical_location = {
|
||||
let scale_factor: CGFloat = msg_send![self, contentScaleFactor];
|
||||
let scale_factor = self.contentScaleFactor();
|
||||
PhysicalPosition::from_logical::<(f64, f64), f64>(
|
||||
(logical_location.x as _, logical_location.y as _),
|
||||
scale_factor as f64,
|
||||
)
|
||||
};
|
||||
touch_events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Touch(Touch {
|
||||
device_id: RootDeviceId(DeviceId { uiscreen }),
|
||||
device_id: RootDeviceId(DeviceId {
|
||||
uiscreen: Id::as_ptr(&uiscreen),
|
||||
}),
|
||||
id: touch_id,
|
||||
location: physical_location,
|
||||
force,
|
||||
|
@ -224,13 +254,14 @@ impl WinitView {
|
|||
}),
|
||||
}));
|
||||
}
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(touch_events);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
declare_class!(
|
||||
struct WinitViewController {
|
||||
pub(crate) struct WinitViewController {
|
||||
_prefers_status_bar_hidden: bool,
|
||||
_prefers_home_indicator_auto_hidden: bool,
|
||||
_supported_orientations: UIInterfaceOrientationMask,
|
||||
|
@ -259,9 +290,7 @@ declare_class!(
|
|||
#[sel(setPrefersStatusBarHidden:)]
|
||||
fn set_prefers_status_bar_hidden(&mut self, val: bool) {
|
||||
*self._prefers_status_bar_hidden = val;
|
||||
unsafe {
|
||||
let _: () = msg_send![self, setNeedsStatusBarAppearanceUpdate];
|
||||
}
|
||||
self.setNeedsStatusBarAppearanceUpdate();
|
||||
}
|
||||
|
||||
#[sel(prefersHomeIndicatorAutoHidden)]
|
||||
|
@ -274,9 +303,7 @@ declare_class!(
|
|||
*self._prefers_home_indicator_auto_hidden = val;
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.home_indicator_hidden {
|
||||
unsafe {
|
||||
let _: () = msg_send![self, setNeedsUpdateOfHomeIndicatorAutoHidden];
|
||||
}
|
||||
self.setNeedsUpdateOfHomeIndicatorAutoHidden();
|
||||
} else {
|
||||
os_capabilities.home_indicator_hidden_err_msg("ignoring")
|
||||
}
|
||||
|
@ -290,12 +317,7 @@ declare_class!(
|
|||
#[sel(setSupportedInterfaceOrientations:)]
|
||||
fn set_supported_orientations(&mut self, val: UIInterfaceOrientationMask) {
|
||||
*self._supported_orientations = val;
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
UIViewController::class(),
|
||||
attemptRotationToDeviceOrientation
|
||||
];
|
||||
}
|
||||
UIViewController::attemptRotationToDeviceOrientation();
|
||||
}
|
||||
|
||||
#[sel(preferredScreenEdgesDeferringSystemGestures)]
|
||||
|
@ -308,9 +330,7 @@ declare_class!(
|
|||
*self._preferred_screen_edges_deferring_system_gestures = val;
|
||||
let os_capabilities = app_state::os_capabilities();
|
||||
if os_capabilities.defer_system_gestures {
|
||||
unsafe {
|
||||
let _: () = msg_send![self, setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
|
||||
}
|
||||
self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures();
|
||||
} else {
|
||||
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!(
|
||||
struct WinitUIWindow {}
|
||||
#[derive(Debug, PartialEq, Eq, Hash)]
|
||||
pub(crate) struct WinitUIWindow {}
|
||||
|
||||
unsafe impl ClassType for WinitUIWindow {
|
||||
#[inherits(UIResponder, NSObject)]
|
||||
|
@ -331,128 +422,59 @@ declare_class!(
|
|||
fn become_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId((&*****self).into()),
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(true),
|
||||
}));
|
||||
let _: () = msg_send![super(self), becomeKeyWindow];
|
||||
}
|
||||
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
|
||||
}
|
||||
|
||||
#[sel(resignKeyWindow)]
|
||||
fn resign_key_window(&self) {
|
||||
unsafe {
|
||||
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId((&*****self).into()),
|
||||
window_id: RootWindowId(self.id()),
|
||||
event: WindowEvent::Focused(false),
|
||||
}));
|
||||
let _: () = msg_send![super(self), resignKeyWindow];
|
||||
}
|
||||
let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// requires main thread
|
||||
pub(crate) unsafe fn create_view(
|
||||
_window_attributes: &WindowAttributes,
|
||||
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(
|
||||
impl WinitUIWindow {
|
||||
pub(crate) fn new(
|
||||
_mtm: MainThreadMarker,
|
||||
window_attributes: &WindowAttributes,
|
||||
_platform_attributes: &PlatformSpecificWindowBuilderAttributes,
|
||||
frame: CGRect,
|
||||
view_controller: id,
|
||||
) -> id {
|
||||
let window: id = msg_send![WinitUIWindow::class(), alloc];
|
||||
assert!(!window.is_null(), "Failed to create `UIWindow` instance");
|
||||
let window: id = msg_send![window, initWithFrame: frame];
|
||||
assert!(
|
||||
!window.is_null(),
|
||||
"Failed to initialize `UIWindow` instance"
|
||||
);
|
||||
let _: () = msg_send![window, setRootViewController: view_controller];
|
||||
view_controller: &UIViewController,
|
||||
) -> Id<Self, Shared> {
|
||||
let this: Id<Self, Shared> =
|
||||
unsafe { msg_send_id![msg_send_id![Self::class(), alloc], initWithFrame: frame] };
|
||||
|
||||
this.setRootViewController(Some(view_controller));
|
||||
|
||||
match window_attributes.fullscreen {
|
||||
Some(Fullscreen::Exclusive(ref video_mode)) => {
|
||||
let uiscreen = video_mode.monitor().ui_screen() as id;
|
||||
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.screen_mode.0];
|
||||
msg_send![window, setScreen:video_mode.monitor().ui_screen()]
|
||||
let monitor = video_mode.monitor();
|
||||
let screen = monitor.ui_screen();
|
||||
screen.setCurrentMode(Some(&video_mode.screen_mode.0));
|
||||
this.setScreen(screen);
|
||||
}
|
||||
Some(Fullscreen::Borderless(ref monitor)) => {
|
||||
let uiscreen: id = match &monitor {
|
||||
Some(monitor) => monitor.ui_screen() as id,
|
||||
None => {
|
||||
let uiscreen: id = msg_send![window, screen];
|
||||
uiscreen
|
||||
Some(Fullscreen::Borderless(Some(ref monitor))) => {
|
||||
let screen = monitor.ui_screen();
|
||||
this.setScreen(screen);
|
||||
}
|
||||
};
|
||||
|
||||
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!(
|
||||
|
@ -465,7 +487,7 @@ declare_class!(
|
|||
// UIApplicationDelegate protocol
|
||||
unsafe impl WinitApplicationDelegate {
|
||||
#[sel(application:didFinishLaunchingWithOptions:)]
|
||||
fn did_finish_launching(&self, _: id, _: id) -> bool {
|
||||
fn did_finish_launching(&self, _application: &UIApplication, _: id) -> bool {
|
||||
unsafe {
|
||||
app_state::did_finish_launching();
|
||||
}
|
||||
|
@ -473,40 +495,38 @@ declare_class!(
|
|||
}
|
||||
|
||||
#[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)) }
|
||||
}
|
||||
|
||||
#[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)) }
|
||||
}
|
||||
|
||||
#[sel(applicationWillEnterForeground:)]
|
||||
fn will_enter_foreground(&self, _: id) {}
|
||||
fn will_enter_foreground(&self, _application: &UIApplication) {}
|
||||
#[sel(applicationDidEnterBackground:)]
|
||||
fn did_enter_background(&self, _: id) {}
|
||||
fn did_enter_background(&self, _application: &UIApplication) {}
|
||||
|
||||
#[sel(applicationWillTerminate:)]
|
||||
fn will_terminate(&self, _: id) {
|
||||
unsafe {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
let windows: id = msg_send![app, windows];
|
||||
let windows_enum: id = msg_send![windows, objectEnumerator];
|
||||
fn will_terminate(&self, application: &UIApplication) {
|
||||
let mut events = Vec::new();
|
||||
loop {
|
||||
let window: id = msg_send![windows_enum, nextObject];
|
||||
if window == nil {
|
||||
break;
|
||||
}
|
||||
let is_winit_window = msg_send![window, isKindOfClass: WinitUIWindow::class()];
|
||||
if is_winit_window {
|
||||
for window in application.windows().iter() {
|
||||
if window.is_kind_of::<WinitUIWindow>() {
|
||||
// SAFETY: We just checked that the window is a `winit` window
|
||||
let window = unsafe {
|
||||
let ptr: *const UIWindow = window;
|
||||
let ptr: *const WinitUIWindow = ptr.cast();
|
||||
&*ptr
|
||||
};
|
||||
events.push(EventWrapper::StaticEvent(Event::WindowEvent {
|
||||
window_id: RootWindowId(window.into()),
|
||||
window_id: RootWindowId(window.id()),
|
||||
event: WindowEvent::Destroyed,
|
||||
}));
|
||||
}
|
||||
}
|
||||
unsafe {
|
||||
app_state::handle_nonuser_events(events);
|
||||
app_state::terminated();
|
||||
}
|
||||
|
|
|
@ -5,10 +5,14 @@ use std::{
|
|||
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 raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
|
||||
|
||||
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
|
||||
use super::view::{WinitUIWindow, WinitView, WinitViewController};
|
||||
use crate::{
|
||||
dpi::{self, LogicalPosition, LogicalSize, PhysicalPosition, PhysicalSize, Position, Size},
|
||||
error::{ExternalError, NotSupportedError, OsError as RootOsError},
|
||||
|
@ -17,12 +21,9 @@ use crate::{
|
|||
platform::ios::{ScreenEdge, ValidOrientations},
|
||||
platform_impl::platform::{
|
||||
app_state,
|
||||
event_loop::{self, EventProxy, EventWrapper},
|
||||
ffi::{
|
||||
id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask,
|
||||
UIRectEdge, UIScreenOverscanCompensation,
|
||||
},
|
||||
monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
event_loop::{EventProxy, EventWrapper},
|
||||
ffi::{id, UIRectEdge},
|
||||
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
|
||||
},
|
||||
window::{
|
||||
CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons,
|
||||
|
@ -31,29 +32,19 @@ use crate::{
|
|||
};
|
||||
|
||||
pub struct Inner {
|
||||
pub window: id,
|
||||
pub view_controller: id,
|
||||
pub view: id,
|
||||
pub(crate) window: Id<WinitUIWindow, Shared>,
|
||||
pub(crate) view_controller: Id<WinitViewController, Shared>,
|
||||
pub(crate) view: Id<WinitView, Shared>,
|
||||
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 {
|
||||
pub fn set_title(&self, _title: &str) {
|
||||
debug!("`Window::set_title` is ignored on iOS")
|
||||
}
|
||||
|
||||
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> {
|
||||
|
@ -72,9 +63,9 @@ impl Inner {
|
|||
// testing.
|
||||
//
|
||||
// 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 {
|
||||
let _: () = msg_send![self.view, setNeedsDisplay];
|
||||
self.view.setNeedsDisplay();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -116,7 +107,7 @@ impl Inner {
|
|||
size: screen_frame.size,
|
||||
};
|
||||
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 {
|
||||
unsafe {
|
||||
let hidpi: CGFloat = msg_send![self.view, contentScaleFactor];
|
||||
hidpi as _
|
||||
}
|
||||
self.view.contentScaleFactor() as _
|
||||
}
|
||||
|
||||
pub fn set_cursor_icon(&self, _cursor: CursorIcon) {
|
||||
|
@ -230,17 +218,14 @@ impl Inner {
|
|||
}
|
||||
|
||||
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
|
||||
unsafe {
|
||||
let uiscreen = match monitor {
|
||||
let uiscreen = match &monitor {
|
||||
Some(Fullscreen::Exclusive(video_mode)) => {
|
||||
let uiscreen = video_mode.monitor.ui_screen() as id;
|
||||
let _: () = msg_send![uiscreen, setCurrentMode: video_mode.screen_mode.0];
|
||||
uiscreen
|
||||
}
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen() as id,
|
||||
Some(Fullscreen::Borderless(None)) => {
|
||||
self.current_monitor_inner().ui_screen() as id
|
||||
let uiscreen = video_mode.monitor.ui_screen();
|
||||
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0));
|
||||
uiscreen.clone()
|
||||
}
|
||||
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(),
|
||||
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(),
|
||||
None => {
|
||||
warn!("`Window::set_fullscreen(None)` ignored on iOS");
|
||||
return;
|
||||
|
@ -248,22 +233,18 @@ impl Inner {
|
|||
};
|
||||
|
||||
// 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 {
|
||||
let _: () = msg_send![self.window, setScreen: uiscreen];
|
||||
self.window.setScreen(&uiscreen);
|
||||
}
|
||||
|
||||
let bounds: CGRect = msg_send![uiscreen, bounds];
|
||||
let _: () = msg_send![self.window, setFrame: bounds];
|
||||
let bounds = uiscreen.bounds();
|
||||
self.window.setFrame(bounds);
|
||||
|
||||
// For external displays, we must disable overscan compensation or
|
||||
// the displayed image will have giant black bars surrounding it on
|
||||
// each side
|
||||
let _: () = msg_send![
|
||||
uiscreen,
|
||||
setOverscanCompensation: UIScreenOverscanCompensation::None
|
||||
];
|
||||
}
|
||||
uiscreen.setOverscanCompensation(UIScreenOverscanCompensation::None);
|
||||
}
|
||||
|
||||
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
|
||||
|
@ -271,7 +252,7 @@ impl Inner {
|
|||
let monitor = self.current_monitor_inner();
|
||||
let uiscreen = monitor.ui_screen();
|
||||
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
|
||||
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.
|
||||
fn current_monitor_inner(&self) -> MonitorHandle {
|
||||
unsafe {
|
||||
let uiscreen: id = msg_send![self.window, screen];
|
||||
MonitorHandle::retained_new(uiscreen)
|
||||
}
|
||||
MonitorHandle::new(self.window.screen())
|
||||
}
|
||||
|
||||
pub fn current_monitor(&self) -> Option<MonitorHandle> {
|
||||
|
@ -332,23 +310,24 @@ impl Inner {
|
|||
}
|
||||
|
||||
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
|
||||
unsafe { monitor::uiscreens() }
|
||||
monitor::uiscreens(MainThreadMarker::new().unwrap())
|
||||
}
|
||||
|
||||
pub fn primary_monitor(&self) -> Option<MonitorHandle> {
|
||||
let monitor = unsafe { monitor::main_uiscreen() };
|
||||
Some(monitor)
|
||||
Some(MonitorHandle::new(UIScreen::main(
|
||||
MainThreadMarker::new().unwrap(),
|
||||
)))
|
||||
}
|
||||
|
||||
pub fn id(&self) -> WindowId {
|
||||
self.window.into()
|
||||
self.window.id()
|
||||
}
|
||||
|
||||
pub fn raw_window_handle(&self) -> RawWindowHandle {
|
||||
let mut window_handle = UiKitWindowHandle::empty();
|
||||
window_handle.ui_window = self.window as _;
|
||||
window_handle.ui_view = self.view as _;
|
||||
window_handle.ui_view_controller = self.view_controller as _;
|
||||
window_handle.ui_window = Id::as_ptr(&self.window) as _;
|
||||
window_handle.ui_view = Id::as_ptr(&self.view) as _;
|
||||
window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _;
|
||||
RawWindowHandle::UiKit(window_handle)
|
||||
}
|
||||
|
||||
|
@ -407,6 +386,8 @@ impl Window {
|
|||
window_attributes: WindowAttributes,
|
||||
platform_attributes: PlatformSpecificWindowBuilderAttributes,
|
||||
) -> Result<Window, RootOsError> {
|
||||
let mtm = MainThreadMarker::new().unwrap();
|
||||
|
||||
if window_attributes.min_inner_size.is_some() {
|
||||
warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
|
||||
}
|
||||
|
@ -416,20 +397,18 @@ impl Window {
|
|||
|
||||
// TODO: transparency, visible
|
||||
|
||||
unsafe {
|
||||
let main_screen = UIScreen::main(mtm);
|
||||
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(None)) | None => {
|
||||
monitor::main_uiscreen().ui_screen() as id
|
||||
}
|
||||
Some(Fullscreen::Borderless(None)) | None => &main_screen,
|
||||
};
|
||||
|
||||
let screen_bounds: CGRect = msg_send![screen, bounds];
|
||||
let screen_bounds = screen.bounds();
|
||||
|
||||
let frame = match window_attributes.inner_size {
|
||||
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);
|
||||
CGRect {
|
||||
origin: screen_bounds.origin,
|
||||
|
@ -442,173 +421,138 @@ impl Window {
|
|||
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 view_class: *const Class = msg_send![view, class];
|
||||
let layer_class: *const Class = msg_send![view_class, layerClass];
|
||||
let gl_or_metal_backed = unsafe {
|
||||
let layer_class = WinitView::layerClass();
|
||||
let is_metal = msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)];
|
||||
let is_gl = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)];
|
||||
is_metal || is_gl
|
||||
};
|
||||
|
||||
let view_controller =
|
||||
view::create_view_controller(&window_attributes, &platform_attributes, view);
|
||||
let window = view::create_window(
|
||||
WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view);
|
||||
let window = WinitUIWindow::new(
|
||||
mtm,
|
||||
&window_attributes,
|
||||
&platform_attributes,
|
||||
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 {
|
||||
window,
|
||||
view_controller,
|
||||
view,
|
||||
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
|
||||
impl Inner {
|
||||
pub fn ui_window(&self) -> id {
|
||||
self.window
|
||||
Id::as_ptr(&self.window) as 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 {
|
||||
self.view
|
||||
Id::as_ptr(&self.view) as id
|
||||
}
|
||||
|
||||
pub fn set_scale_factor(&self, scale_factor: f64) {
|
||||
unsafe {
|
||||
assert!(
|
||||
dpi::validate_scale_factor(scale_factor),
|
||||
"`WindowExtIOS::set_scale_factor` received an invalid hidpi factor"
|
||||
);
|
||||
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) {
|
||||
unsafe {
|
||||
let idiom = event_loop::get_idiom();
|
||||
let supported_orientations = UIInterfaceOrientationMask::from_valid_orientations_idiom(
|
||||
self.view_controller.set_supported_interface_orientations(
|
||||
MainThreadMarker::new().unwrap(),
|
||||
valid_orientations,
|
||||
idiom,
|
||||
);
|
||||
msg_send![
|
||||
self.view_controller,
|
||||
setSupportedInterfaceOrientations: supported_orientations
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
self.view_controller,
|
||||
setPrefersHomeIndicatorAutoHidden: hidden,
|
||||
];
|
||||
}
|
||||
self.view_controller
|
||||
.setPrefersHomeIndicatorAutoHidden(hidden);
|
||||
}
|
||||
|
||||
pub fn set_preferred_screen_edges_deferring_system_gestures(&self, edges: ScreenEdge) {
|
||||
let edges: UIRectEdge = edges.into();
|
||||
unsafe {
|
||||
let _: () = msg_send![
|
||||
self.view_controller,
|
||||
setPreferredScreenEdgesDeferringSystemGestures: edges
|
||||
];
|
||||
}
|
||||
self.view_controller
|
||||
.setPreferredScreenEdgesDeferringSystemGestures(edges);
|
||||
}
|
||||
|
||||
pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {
|
||||
unsafe {
|
||||
let _: () = msg_send![self.view_controller, setPrefersStatusBarHidden: hidden,];
|
||||
}
|
||||
self.view_controller.setPrefersStatusBarHidden(hidden);
|
||||
}
|
||||
}
|
||||
|
||||
impl Inner {
|
||||
// requires main thread
|
||||
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
|
||||
unsafe fn rect_to_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![
|
||||
self.window,
|
||||
convertRect: rect,
|
||||
toCoordinateSpace: screen_space,
|
||||
]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
let screen_space = self.window.screen().coordinateSpace();
|
||||
self.window
|
||||
.convertRect_toCoordinateSpace(rect, &screen_space)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
unsafe fn rect_from_screen_space(&self, rect: CGRect) -> CGRect {
|
||||
let screen: id = msg_send![self.window, screen];
|
||||
if !screen.is_null() {
|
||||
let screen_space: id = msg_send![screen, coordinateSpace];
|
||||
msg_send![
|
||||
self.window,
|
||||
convertRect: rect,
|
||||
fromCoordinateSpace: screen_space,
|
||||
]
|
||||
} else {
|
||||
rect
|
||||
}
|
||||
let screen_space = self.window.screen().coordinateSpace();
|
||||
self.window
|
||||
.convertRect_fromCoordinateSpace(rect, &screen_space)
|
||||
}
|
||||
|
||||
// requires main thread
|
||||
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 {
|
||||
let safe_area: UIEdgeInsets = msg_send![self.window, safeAreaInsets];
|
||||
let safe_area = self.window.safeAreaInsets();
|
||||
let safe_bounds = CGRect {
|
||||
origin: CGPoint {
|
||||
x: bounds.origin.x + safe_area.left,
|
||||
|
@ -622,13 +566,11 @@ impl Inner {
|
|||
self.rect_to_screen_space(safe_bounds)
|
||||
} else {
|
||||
let screen_frame = self.rect_to_screen_space(bounds);
|
||||
let status_bar_frame: CGRect = {
|
||||
let app: id = msg_send![class!(UIApplication), sharedApplication];
|
||||
assert!(
|
||||
!app.is_null(),
|
||||
"`Window::get_inner_position` cannot be called before `EventLoop::run` on iOS"
|
||||
let status_bar_frame = {
|
||||
let app = UIApplication::shared(MainThreadMarker::new().unwrap_unchecked()).expect(
|
||||
"`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 {
|
||||
(screen_frame.origin.y, screen_frame.size.height)
|
||||
|
|
Loading…
Reference in a new issue