Clean up UIView override declaration (#2578)

This commit is contained in:
Mads Marquart 2022-12-01 09:32:44 +01:00 committed by GitHub
parent 2e4d79f57a
commit 4ed4e918f3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 124 additions and 140 deletions

View file

@ -6,7 +6,6 @@ mod view_controller;
mod window;
pub(crate) use self::responder::UIResponder;
#[allow(unused)]
pub(crate) use self::view::UIView;
pub(crate) use self::view_controller::UIViewController;
pub(crate) use self::window::UIWindow;

View file

@ -1,9 +1,7 @@
use objc2::declare::ClassBuilder;
use objc2::foundation::NSObject;
use objc2::runtime::{Bool, Class, Object, Sel};
use objc2::{class, declare_class, msg_send, sel, ClassType};
use objc2::{class, declare_class, msg_send, ClassType};
use super::uikit::{UIResponder, UIViewController, UIWindow};
use super::uikit::{UIResponder, UIView, UIViewController, UIWindow};
use crate::{
dpi::PhysicalPosition,
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
@ -20,13 +18,20 @@ use crate::{
window::{WindowAttributes, WindowId as RootWindowId},
};
// requires main thread
unsafe fn get_view_class() -> &'static Class {
static mut CLASS: Option<&'static Class> = None;
if CLASS.is_none() {
extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) {
declare_class!(
struct WinitView {}
unsafe impl ClassType for WinitView {
#[inherits(UIResponder, NSObject)]
type Super = UIView;
const NAME: &'static str = "WinitUIView";
}
unsafe impl WinitView {
#[sel(drawRect:)]
fn draw_rect(&self, rect: CGRect) {
unsafe {
let window: id = msg_send![object, window];
let window: id = msg_send![self, window];
assert!(!window.is_null());
app_state::handle_nonuser_events(
std::iter::once(EventWrapper::StaticEvent(Event::RedrawRequested(
@ -36,23 +41,22 @@ unsafe fn get_view_class() -> &'static Class {
Event::RedrawEventsCleared,
))),
);
let superclass: &'static Class = msg_send![object, superclass];
let _: () = msg_send![super(object, superclass), drawRect: rect];
let _: () = msg_send![super(self), drawRect: rect];
}
}
extern "C" fn layout_subviews(object: &Object, _: Sel) {
#[sel(layoutSubviews)]
fn layout_subviews(&self) {
unsafe {
let superclass: &'static Class = msg_send![object, superclass];
let _: () = msg_send![super(object, superclass), layoutSubviews];
let _: () = msg_send![super(self), layoutSubviews];
let window: id = msg_send![object, window];
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![
object,
self,
convertRect: window_bounds,
toCoordinateSpace: screen_space,
];
@ -66,9 +70,9 @@ unsafe fn get_view_class() -> &'static 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![object, frame];
let view_frame: CGRect = msg_send![self, frame];
if view_frame != window_bounds {
let _: () = msg_send![object, setFrame: window_bounds];
let _: () = msg_send![self, setFrame: window_bounds];
}
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent {
@ -78,20 +82,12 @@ unsafe fn get_view_class() -> &'static Class {
}
}
extern "C" fn set_content_scale_factor(
object: &mut Object,
_: Sel,
untrusted_scale_factor: CGFloat,
) {
#[sel(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
unsafe {
let superclass: &'static Class = msg_send![&*object, superclass];
let _: () = msg_send![
super(&mut *object, superclass),
setContentScaleFactor: untrusted_scale_factor
];
let object = &*object; // Immutable for rest of method
let _: () = msg_send![super(self), setContentScaleFactor: untrusted_scale_factor];
let window: id = msg_send![object, window];
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
@ -101,7 +97,7 @@ unsafe fn get_view_class() -> &'static Class {
// `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![object, contentScaleFactor];
let scale_factor: CGFloat = msg_send![self, contentScaleFactor];
assert!(
!scale_factor.is_nan()
&& scale_factor.is_finite()
@ -110,11 +106,11 @@ unsafe fn get_view_class() -> &'static Class {
"invalid scale_factor set on UIView",
);
let scale_factor = scale_factor as f64;
let bounds: CGRect = msg_send![object, bounds];
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![object, convertRect: bounds, toCoordinateSpace: screen_space];
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 _,
@ -135,109 +131,101 @@ unsafe fn get_view_class() -> &'static Class {
}
}
extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) {
unsafe {
let window: id = msg_send![object, 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![object, 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<f64> = 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,
})
} else {
None
}
} 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![object, 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,
}),
}));
}
app_state::handle_nonuser_events(touch_events);
}
#[sel(touchesBegan:withEvent:)]
fn touches_began(&self, touches: id, _: id) {
self.handle_touches(touches)
}
let mut decl = ClassBuilder::new("WinitUIView", class!(UIView))
.expect("Failed to declare class `WinitUIView`");
decl.add_method(sel!(drawRect:), draw_rect as extern "C" fn(_, _, _));
decl.add_method(sel!(layoutSubviews), layout_subviews as extern "C" fn(_, _));
decl.add_method(
sel!(setContentScaleFactor:),
set_content_scale_factor as extern "C" fn(_, _, _),
);
#[sel(touchesMoved:withEvent:)]
fn touches_moved(&self, touches: id, _: id) {
self.handle_touches(touches)
}
decl.add_method(
sel!(touchesBegan:withEvent:),
handle_touches as extern "C" fn(_, _, _, _),
);
decl.add_method(
sel!(touchesMoved:withEvent:),
handle_touches as extern "C" fn(_, _, _, _),
);
decl.add_method(
sel!(touchesEnded:withEvent:),
handle_touches as extern "C" fn(_, _, _, _),
);
decl.add_method(
sel!(touchesCancelled:withEvent:),
handle_touches as extern "C" fn(_, _, _, _),
);
#[sel(touchesEnded:withEvent:)]
fn touches_ended(&self, touches: id, _: id) {
self.handle_touches(touches)
}
CLASS = Some(decl.register());
#[sel(touchesCancelled:withEvent:)]
fn touches_cancelled(&self, touches: id, _: id) {
self.handle_touches(touches)
}
}
);
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<f64> = 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,
})
} else {
None
}
} 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,
}),
}));
}
app_state::handle_nonuser_events(touch_events);
}
}
CLASS.unwrap()
}
declare_class!(
@ -368,13 +356,11 @@ pub(crate) unsafe fn create_view(
platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect,
) -> id {
let class = get_view_class();
let view: id = msg_send![class, alloc];
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: Bool::YES];
let _: () = msg_send![view, setMultipleTouchEnabled: true];
if let Some(scale_factor) = platform_attributes.scale_factor {
let _: () = msg_send![view, setContentScaleFactor: scale_factor as CGFloat];
}
@ -400,14 +386,13 @@ pub(crate) unsafe fn create_view_controller(
!view_controller.is_null(),
"Failed to initialize `UIViewController` instance"
);
let status_bar_hidden = Bool::new(platform_attributes.prefers_status_bar_hidden);
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 =
Bool::new(platform_attributes.prefers_home_indicator_hidden);
let prefers_home_indicator_hidden = platform_attributes.prefers_home_indicator_hidden;
let edges: UIRectEdge = platform_attributes
.preferred_screen_edges_deferring_system_gestures
.into();