diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index bd4a2c1c80..b6e52c9beb 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -33,47 +33,6 @@ unsafe impl Encode for NSOperatingSystemVersion { ); } -#[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(transparent)] #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct UIUserInterfaceIdiom(NSInteger); diff --git a/src/platform_impl/ios/mod.rs b/src/platform_impl/ios/mod.rs index 18362b6996..21990c6ca2 100644 --- a/src/platform_impl/ios/mod.rs +++ b/src/platform_impl/ios/mod.rs @@ -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(), } } } diff --git a/src/platform_impl/ios/uikit/event.rs b/src/platform_impl/ios/uikit/event.rs new file mode 100644 index 0000000000..9ce24261db --- /dev/null +++ b/src/platform_impl/ios/uikit/event.rs @@ -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; + } +); diff --git a/src/platform_impl/ios/uikit/mod.rs b/src/platform_impl/ios/uikit/mod.rs index 3afabc13e2..ffd8ab9117 100644 --- a/src/platform_impl/ios/uikit/mod.rs +++ b/src/platform_impl/ios/uikit/mod.rs @@ -5,9 +5,12 @@ 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; @@ -15,9 +18,12 @@ 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::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}; diff --git a/src/platform_impl/ios/uikit/screen.rs b/src/platform_impl/ios/uikit/screen.rs index 942009ea8f..770b92378a 100644 --- a/src/platform_impl/ios/uikit/screen.rs +++ b/src/platform_impl/ios/uikit/screen.rs @@ -55,10 +55,7 @@ extern_methods!( } #[sel(setOverscanCompensation:)] - pub fn setOverscanCompensation( - &self, - overscanCompensation: UIScreenOverscanCompensation, - ); + pub fn setOverscanCompensation(&self, overscanCompensation: UIScreenOverscanCompensation); pub fn coordinateSpace(&self) -> Id { unsafe { msg_send_id![self, coordinateSpace] } diff --git a/src/platform_impl/ios/uikit/touch.rs b/src/platform_impl/ios/uikit/touch.rs new file mode 100644 index 0000000000..d0c7c2c7d0 --- /dev/null +++ b/src/platform_impl/ios/uikit/touch.rs @@ -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; +} diff --git a/src/platform_impl/ios/uikit/trait_collection.rs b/src/platform_impl/ios/uikit/trait_collection.rs new file mode 100644 index 0000000000..7ae58d2dd2 --- /dev/null +++ b/src/platform_impl/ios/uikit/trait_collection.rs @@ -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; +} diff --git a/src/platform_impl/ios/uikit/view.rs b/src/platform_impl/ios/uikit/view.rs index ad4eb45486..05c229564c 100644 --- a/src/platform_impl/ios/uikit/view.rs +++ b/src/platform_impl/ios/uikit/view.rs @@ -3,7 +3,7 @@ use objc2::foundation::{CGFloat, CGRect, NSObject}; use objc2::rc::{Id, Shared}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; -use super::{UIResponder, UIViewController, UICoordinateSpace}; +use super::{UICoordinateSpace, UIResponder, UIViewController}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] @@ -23,6 +23,21 @@ extern_methods!( #[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> { unsafe { msg_send_id![self, rootViewController] } } @@ -46,6 +61,9 @@ extern_methods!( #[sel(safeAreaInsets)] pub fn safeAreaInsets(&self) -> UIEdgeInsets; + + #[sel(setNeedsDisplay)] + pub fn setNeedsDisplay(&self); } ); diff --git a/src/platform_impl/ios/uikit/view_controller.rs b/src/platform_impl/ios/uikit/view_controller.rs index f2c08b64e0..b366580527 100644 --- a/src/platform_impl/ios/uikit/view_controller.rs +++ b/src/platform_impl/ios/uikit/view_controller.rs @@ -2,7 +2,7 @@ use objc2::encode::{Encode, Encoding}; use objc2::foundation::{NSObject, NSUInteger}; use objc2::{extern_class, extern_methods, ClassType}; -use super::{UIView, UIResponder}; +use super::{UIResponder, UIView}; extern_class!( #[derive(Debug, PartialEq, Eq, Hash)] diff --git a/src/platform_impl/ios/uikit/window.rs b/src/platform_impl/ios/uikit/window.rs index 68a4303aca..190cf3c1f4 100644 --- a/src/platform_impl/ios/uikit/window.rs +++ b/src/platform_impl/ios/uikit/window.rs @@ -1,4 +1,4 @@ -use objc2::foundation::{CGRect, NSObject}; +use objc2::foundation::NSObject; use objc2::rc::{Id, Shared}; use objc2::{extern_class, extern_methods, msg_send_id, ClassType}; @@ -26,9 +26,6 @@ extern_methods!( #[sel(setHidden:)] pub fn setHidden(&self, flag: bool); - #[sel(setFrame:)] - pub fn setFrame(&self, value: CGRect); - #[sel(makeKeyAndVisible)] pub fn makeKeyAndVisible(&self); } diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index e928f9e353..b185c2b6b7 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -1,30 +1,32 @@ #![allow(clippy::unnecessary_cast)] -use objc2::foundation::{CGFloat, CGPoint, CGRect, MainThreadMarker, NSObject}; +use objc2::foundation::{CGFloat, CGRect, MainThreadMarker, NSObject, NSSet}; use objc2::rc::{Id, Shared}; -use objc2::{declare_class, msg_send, msg_send_id, extern_methods, ClassType}; +use objc2::runtime::Class; +use objc2::{declare_class, extern_methods, msg_send, msg_send_id, ClassType}; -use super::uikit::{UIInterfaceOrientationMask, UIApplication, UIDevice, 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::{EventProxy, EventWrapper}, - ffi::{ - id, nil, UIForceTouchCapability, UIRectEdge, UITouchPhase, - UITouchType, UIUserInterfaceIdiom - }, + ffi::{id, UIRectEdge, UIUserInterfaceIdiom}, window::PlatformSpecificWindowBuilderAttributes, DeviceId, Fullscreen, }, - platform::ios::ValidOrientations, window::{WindowAttributes, WindowId as RootWindowId}, }; declare_class!( - struct WinitView {} + pub(crate) struct WinitView {} unsafe impl ClassType for WinitView { #[inherits(UIResponder, NSObject)] @@ -35,53 +37,47 @@ 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 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 size = crate::dpi::LogicalSize { - width: screen_frame.size.width as f64, - height: screen_frame.size.height as f64, - } - .to_physical(scale_factor as f64); - - // 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]; - if view_frame != window_bounds { - let _: () = msg_send![self, setFrame: window_bounds]; - } + let _: () = unsafe { msg_send![super(self), layoutSubviews] }; + + 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, + } + .to_physical(scale_factor as f64); + + // 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 = self.frame(); + if view_frame != 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), })); } @@ -89,38 +85,38 @@ declare_class!( #[sel(setContentScaleFactor:)] fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { + let _: () = + unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] }; + + // `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 + 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 = self.contentScaleFactor(); + assert!( + !scale_factor.is_nan() + && scale_factor.is_finite() + && scale_factor.is_sign_positive() + && scale_factor > 0.0, + "invalid scale_factor set on UIView", + ); + 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 { - let _: () = msg_send![super(self), setContentScaleFactor: untrusted_scale_factor]; - - let window: Option> = msg_send_id![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 - let window = match 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]; - assert!( - !scale_factor.is_nan() - && scale_factor.is_finite() - && scale_factor.is_sign_positive() - && scale_factor > 0.0, - "invalid scale_factor set on UIView", - ); - let bounds: CGRect = msg_send![self, bounds]; - let screen = window.screen(); - let screen_space = screen.coordinateSpace(); - let screen_frame: CGRect = - msg_send![self, convertRect: bounds, toCoordinateSpace: &*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()); app_state::handle_nonuser_events( std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { window, @@ -138,97 +134,127 @@ declare_class!( } #[sel(touchesBegan:withEvent:)] - fn touches_began(&self, touches: id, _: id) { + fn touches_began(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[sel(touchesMoved:withEvent:)] - fn touches_moved(&self, touches: id, _: id) { + fn touches_moved(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[sel(touchesEnded:withEvent:)] - fn touches_ended(&self, touches: id, _: id) { + fn touches_ended(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } #[sel(touchesCancelled:withEvent:)] - fn touches_cancelled(&self, touches: id, _: id) { + fn touches_cancelled(&self, touches: &NSSet, _event: Option<&UIEvent>) { self.handle_touches(touches) } } ); +extern_methods!( + #[allow(non_snake_case)] + unsafe impl WinitView { + fn window(&self) -> Option> { + unsafe { msg_send_id![self, window] } + } + + unsafe fn traitCollection(&self) -> Id { + 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]; - 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]; - let force = if os_supports_force { - let trait_collection: id = msg_send![self, traitCollection]; - let touch_capability: UIForceTouchCapability = - msg_send![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 altitude_angle: Option = if touch_type == UITouchType::Pencil { - let angle: CGFloat = msg_send![touch, altitudeAngle]; - Some(angle as _) - } else { - None - }; - Some(Force::Calibrated { - force: force as _, - max_possible_force: max_possible_force as _, - altitude_angle, - }) + pub(crate) fn new( + _mtm: MainThreadMarker, + _window_attributes: &WindowAttributes, + platform_attributes: &PlatformSpecificWindowBuilderAttributes, + frame: CGRect, + ) -> Id { + let this: Id = + 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) { + 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; + for touch in touches { + let logical_location = touch.locationInView(None); + let touch_type = touch.type_(); + let force = if os_supports_force { + 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 = touch.force(); + let max_possible_force = touch.maximumPossibleForce(); + let altitude_angle: Option = if touch_type == UITouchType::Pencil { + let angle = touch.altitudeAngle(); + Some(angle as _) } else { None - } + }; + Some(Force::Calibrated { + force: force as _, + max_possible_force: max_possible_force as _, + altitude_angle, + }) } else { None - }; - let touch_id = touch as u64; - let phase: UITouchPhase = msg_send![touch, phase]; - let phase = match phase { - UITouchPhase::Began => TouchPhase::Started, - UITouchPhase::Moved => TouchPhase::Moved, - // 2 is UITouchPhase::Stationary and is not expected here - UITouchPhase::Ended => TouchPhase::Ended, - UITouchPhase::Cancelled => TouchPhase::Cancelled, - _ => panic!("unexpected touch phase: {:?}", phase as i32), - }; - - let physical_location = { - let scale_factor: CGFloat = msg_send![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()), - event: WindowEvent::Touch(Touch { - device_id: RootDeviceId(DeviceId { uiscreen }), - id: touch_id, - location: physical_location, - force, - phase, + } + } else { + None + }; + 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, + // 2 is UITouchPhase::Stationary and is not expected here + UITouchPhase::Ended => TouchPhase::Ended, + UITouchPhase::Cancelled => TouchPhase::Cancelled, + _ => panic!("unexpected touch phase: {:?}", phase as i32), + }; + + let physical_location = { + 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.id()), + event: WindowEvent::Touch(Touch { + device_id: RootDeviceId(DeviceId { + uiscreen: Id::as_ptr(&uiscreen), }), - })); - } + id: touch_id, + location: physical_location, + force, + phase, + }), + })); + } + unsafe { app_state::handle_nonuser_events(touch_events); } } @@ -330,8 +356,15 @@ extern_methods!( ); impl WinitViewController { - pub(crate) fn set_supported_interface_orientations(&self, mtm: MainThreadMarker, valid_orientations: ValidOrientations) { - let mask = match (valid_orientations, UIDevice::current(mtm).userInterfaceIdiom()) { + 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 } @@ -354,9 +387,8 @@ impl WinitViewController { platform_attributes: &PlatformSpecificWindowBuilderAttributes, view: &UIView, ) -> Id { - let this: Id = unsafe { - msg_send_id![msg_send_id![Self::class(), alloc], init] - }; + let this: Id = + unsafe { msg_send_id![msg_send_id![Self::class(), alloc], init] }; this.setPrefersStatusBarHidden(platform_attributes.prefers_status_bar_hidden); @@ -367,7 +399,7 @@ impl WinitViewController { this.setPreferredScreenEdgesDeferringSystemGestures( platform_attributes .preferred_screen_edges_deferring_system_gestures - .into() + .into(), ); this.setView(Some(view)); @@ -388,42 +420,28 @@ declare_class!( unsafe impl WinitUIWindow { #[sel(becomeKeyWindow)] fn become_key_window(&self) { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(self.id()), - event: WindowEvent::Focused(true), - })); + unsafe { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(self.id()), + event: WindowEvent::Focused(true), + })); + } let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; } #[sel(resignKeyWindow)] fn resign_key_window(&self) { - app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { - window_id: RootWindowId(self.id()), - event: WindowEvent::Focused(false), - })); + unsafe { + app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { + window_id: RootWindowId(self.id()), + event: WindowEvent::Focused(false), + })); + } 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 -} - impl WinitUIWindow { pub(crate) fn new( _mtm: MainThreadMarker, diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 9a5e4299a4..466dacb677 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -7,14 +7,12 @@ use std::{ use objc2::foundation::{CGFloat, CGPoint, CGRect, CGSize, MainThreadMarker}; use objc2::rc::{Id, Shared}; -use objc2::runtime::{Class, Object}; +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::{WinitViewController, WinitUIWindow}; +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}, @@ -25,7 +23,7 @@ use crate::{ app_state, event_loop::{EventProxy, EventWrapper}, ffi::{id, UIRectEdge}, - monitor, view, EventLoopWindowTarget, Fullscreen, MonitorHandle, + monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle, }, window::{ CursorGrabMode, CursorIcon, Theme, UserAttentionType, WindowAttributes, WindowButtons, @@ -36,18 +34,10 @@ use crate::{ pub struct Inner { pub(crate) window: Id, pub(crate) view_controller: Id, - pub view: id, + pub(crate) view: Id, gl_or_metal_backed: bool, } -impl Drop for Inner { - fn drop(&mut self) { - unsafe { - let _: () = msg_send![self.view, release]; - } - } -} - impl Inner { pub fn set_title(&self, _title: &str) { debug!("`Window::set_title` is ignored on iOS") @@ -75,7 +65,7 @@ impl Inner { // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc app_state::queue_gl_or_metal_redraw(self.window.clone()); } else { - let _: () = msg_send![self.view, setNeedsDisplay]; + self.view.setNeedsDisplay(); } } } @@ -187,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) { @@ -339,7 +326,7 @@ impl Inner { pub fn raw_window_handle(&self) -> RawWindowHandle { let mut window_handle = UiKitWindowHandle::empty(); window_handle.ui_window = Id::as_ptr(&self.window) as _; - window_handle.ui_view = self.view as _; + window_handle.ui_view = Id::as_ptr(&self.view) as _; window_handle.ui_view_controller = Id::as_ptr(&self.view_controller) as _; RawWindowHandle::UiKit(window_handle) } @@ -410,68 +397,66 @@ 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(), - Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(), - Some(Fullscreen::Borderless(None)) | None => &main_screen, - }; + let main_screen = UIScreen::main(mtm); + let screen = match window_attributes.fullscreen { + 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 => &main_screen, + }; - let screen_bounds = screen.bounds(); - - let frame = match window_attributes.inner_size { - Some(dim) => { - let scale_factor = screen.scale(); - let size = dim.to_logical::(scale_factor as f64); - CGRect { - origin: screen_bounds.origin, - size: CGSize { - width: size.width as _, - height: size.height as _, - }, - } + let screen_bounds = screen.bounds(); + + let frame = match window_attributes.inner_size { + Some(dim) => { + let scale_factor = screen.scale(); + let size = dim.to_logical::(scale_factor as f64); + CGRect { + origin: screen_bounds.origin, + size: CGSize { + width: size.width as _, + height: size.height as _, + }, } - None => screen_bounds, - }; + } + None => screen_bounds, + }; - let view = view::create_view(&window_attributes, &platform_attributes, frame); + let view = WinitView::new(mtm, &window_attributes, &platform_attributes, frame); - let gl_or_metal_backed = { - let view_class: *const Class = msg_send![view, class]; - let layer_class: *const Class = msg_send![view_class, 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 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 = - WinitViewController::new(mtm, &window_attributes, &platform_attributes, &*(view as *const _)); - let window = WinitUIWindow::new( - mtm, - &window_attributes, - &platform_attributes, - frame, - &view_controller, - ); - - 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 = window.screen(); - let screen_space = 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 _, - }; - let window_id = RootWindowId(window.id()); + let view_controller = + WinitViewController::new(mtm, &window_attributes, &platform_attributes, &view); + let window = WinitUIWindow::new( + mtm, + &window_attributes, + &platform_attributes, + frame, + &view_controller, + ); + + 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(), @@ -486,16 +471,16 @@ impl Window { ))), ); } - - Ok(Window { - inner: Inner { - window, - view_controller, - view, - gl_or_metal_backed, - }, - }) } + + Ok(Window { + inner: Inner { + window, + view_controller, + view, + gl_or_metal_backed, + }, + }) } } @@ -508,31 +493,34 @@ impl Inner { 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]; - } + assert!( + dpi::validate_scale_factor(scale_factor), + "`WindowExtIOS::set_scale_factor` received an invalid hidpi factor" + ); + let scale_factor = scale_factor as CGFloat; + self.view.setContentScaleFactor(scale_factor); } pub fn set_valid_orientations(&self, valid_orientations: ValidOrientations) { - self.view_controller.set_supported_interface_orientations(MainThreadMarker::new().unwrap(), valid_orientations); + self.view_controller.set_supported_interface_orientations( + MainThreadMarker::new().unwrap(), + valid_orientations, + ); } pub fn set_prefers_home_indicator_hidden(&self, hidden: bool) { - 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(); - self.view_controller.setPreferredScreenEdgesDeferringSystemGestures(edges); + self.view_controller + .setPreferredScreenEdgesDeferringSystemGestures(edges); } pub fn set_prefers_status_bar_hidden(&self, hidden: bool) {