From bfcd85ab150d088be254f43870e7d485f6f95348 Mon Sep 17 00:00:00 2001 From: mtak- Date: Wed, 4 Sep 2019 14:23:11 -0700 Subject: [PATCH] [ios] Groundwork for new Redraw API, refactoring AppState, and bugfixes (#1133) * fix #1087. the CFRunLoopTimer was never started if the user never changed the controlflow. * RedrawRequested ordering matches the new redraw api consistent asserts lots of appstate refactoring to rely less on unsafe, and hopefully make it easier to maintain * ios: dpi bugfix. inputs to setContentScaleFactor are not to be trusted as iOS uses 0.0 as a sentinel value for "default device dpi". the fix is to always go through the getter. * move touch handling onto uiview * update changelog * rustfmt weirdness * fix use option around nullable function pointers in ffi * Document why gl and metal views don't use setNeedsDisplay * change main events cleared observer priority to 0 instead of magic number log when processing non-redraw events when we expect to only be processing redraw events --- CHANGELOG.md | 4 + src/platform_impl/ios/app_state.rs | 1041 +++++++++++++++++---------- src/platform_impl/ios/event_loop.rs | 49 +- src/platform_impl/ios/ffi.rs | 16 +- src/platform_impl/ios/view.rs | 288 ++++---- src/platform_impl/ios/window.rs | 33 +- 6 files changed, 880 insertions(+), 551 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e9a4f05e..8dbb42c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -33,6 +33,10 @@ - On iOS, add touch pressure information for touch events. - Implement `raw_window_handle::HasRawWindowHandle` for `Window` type on all supported platforms. - On macOS, fix the signature of `-[NSView drawRect:]`. +- On iOS, fix the behavior of `ControlFlow::Poll`. It wasn't polling if that was the only mode ever used by the application. +- On iOS, fix DPI sent out by views on creation was `0.0` - now it gives a reasonable number. +- On iOS, RedrawRequested now works for gl/metal backed views. +- On iOS, RedrawRequested is generally ordered after EventsCleared. # 0.20.0 Alpha 2 (2019-07-09) diff --git a/src/platform_impl/ios/app_state.rs b/src/platform_impl/ios/app_state.rs index e8987e54..5b5b726b 100644 --- a/src/platform_impl/ios/app_state.rs +++ b/src/platform_impl/ios/app_state.rs @@ -1,6 +1,9 @@ +#![deny(unused_results)] + use std::{ cell::{RefCell, RefMut}, - mem::{self, ManuallyDrop}, + collections::HashSet, + mem, os::raw::c_void, ptr, time::Instant, @@ -9,45 +12,85 @@ use std::{ use objc::runtime::{BOOL, YES}; use crate::{ - event::{Event, StartCause}, + event::{Event, StartCause, WindowEvent}, event_loop::ControlFlow, -}; - -use crate::platform_impl::platform::{ - event_loop::{EventHandler, Never}, - ffi::{ - id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, - CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, - CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, - NSUInteger, + platform_impl::platform::{ + event_loop::{EventHandler, Never}, + ffi::{ + id, kCFRunLoopCommonModes, CFAbsoluteTimeGetCurrent, CFRelease, CFRunLoopAddTimer, + CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, CFRunLoopTimerInvalidate, + CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, NSInteger, NSOperatingSystemVersion, + NSUInteger, + }, }, + window::WindowId as RootWindowId, }; macro_rules! bug { - ($msg:expr) => { - panic!("winit iOS bug, file an issue: {}", $msg) + ($($msg:tt)*) => { + panic!("winit iOS bug, file an issue: {}", format!($($msg)*)) }; } +macro_rules! bug_assert { + ($test:expr, $($msg:tt)*) => { + assert!($test, "winit iOS bug, file an issue: {}", format!($($msg)*)) + }; +} + +enum UserCallbackTransitionResult<'a> { + Success { + event_handler: Box, + active_control_flow: ControlFlow, + processing_redraws: bool, + }, + ReentrancyPrevented { + queued_events: &'a mut Vec>, + }, +} + +impl Event { + fn is_redraw(&self) -> bool { + if let Event::WindowEvent { + window_id: _, + event: WindowEvent::RedrawRequested, + } = self + { + true + } else { + false + } + } +} + // this is the state machine for the app lifecycle #[derive(Debug)] +#[must_use = "dropping `AppStateImpl` without inspecting it is probably a bug"] enum AppStateImpl { NotLaunched { queued_windows: Vec, queued_events: Vec>, + queued_gpu_redraws: HashSet, }, Launching { queued_windows: Vec, queued_events: Vec>, queued_event_handler: Box, + queued_gpu_redraws: HashSet, }, ProcessingEvents { event_handler: Box, + queued_gpu_redraws: HashSet, active_control_flow: ControlFlow, }, // special state to deal with reentrancy and prevent mutable aliasing. InUserCallback { queued_events: Vec>, + queued_gpu_redraws: HashSet, + }, + ProcessingRedraws { + event_handler: Box, + active_control_flow: ControlFlow, }, Waiting { waiting_event_handler: Box, @@ -59,9 +102,16 @@ enum AppStateImpl { Terminated, } -impl Drop for AppStateImpl { +struct AppState { + // This should never be `None`, except for briefly during a state transition. + app_state: Option, + control_flow: ControlFlow, + waker: EventLoopWaker, +} + +impl Drop for AppState { fn drop(&mut self) { - match self { + match self.state_mut() { &mut AppStateImpl::NotLaunched { ref mut queued_windows, .. @@ -69,22 +119,18 @@ impl Drop for AppStateImpl { | &mut AppStateImpl::Launching { ref mut queued_windows, .. - } => unsafe { + } => { for &mut window in queued_windows { - let () = msg_send![window, release]; + unsafe { + let () = msg_send![window, release]; + } } - }, + } _ => {} } } } -pub struct AppState { - app_state: AppStateImpl, - control_flow: ControlFlow, - waker: EventLoopWaker, -} - impl AppState { // requires main thread unsafe fn get_mut() -> RefMut<'static, AppState> { @@ -98,7 +144,6 @@ impl AppState { "bug in winit: `AppState::get_mut()` can only be called on the main thread" ); } - let mut guard = APP_STATE.borrow_mut(); if guard.is_none() { #[inline(never)] @@ -106,10 +151,11 @@ impl AppState { unsafe fn init_guard(guard: &mut RefMut<'static, Option>) { let waker = EventLoopWaker::new(CFRunLoopGetMain()); **guard = Some(AppState { - app_state: AppStateImpl::NotLaunched { + app_state: Some(AppStateImpl::NotLaunched { queued_windows: Vec::new(), queued_events: Vec::new(), - }, + queued_gpu_redraws: HashSet::new(), + }), control_flow: ControlFlow::default(), waker, }); @@ -119,233 +165,153 @@ impl AppState { RefMut::map(guard, |state| state.as_mut().unwrap()) } - // requires main thread and window is a UIWindow - // retains window - pub unsafe fn set_key_window(window: id) { - let mut this = AppState::get_mut(); - match &mut this.app_state { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - .. - } => { - queued_windows.push(window); - let _: id = msg_send![window, retain]; - return; - } - &mut AppStateImpl::ProcessingEvents { .. } => {} - &mut AppStateImpl::InUserCallback { .. } => {} - &mut AppStateImpl::Terminated => panic!( - "Attempt to create a `Window` \ - after the app has terminated" - ), - app_state => unreachable!("unexpected state: {:#?}", app_state), /* all other cases should be impossible */ + fn state(&self) -> &AppStateImpl { + match &self.app_state { + Some(ref state) => state, + None => bug!("`AppState` previously failed a state transition"), } - drop(this); - msg_send![window, makeKeyAndVisible] } - // requires main thread - pub unsafe fn will_launch(queued_event_handler: Box) { - let mut this = AppState::get_mut(); - let (queued_windows, queued_events) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { - ref mut queued_windows, - ref mut queued_events, - } => { - let windows = ptr::read(queued_windows); - let events = ptr::read(queued_events); - (windows, events) - } - _ => panic!( - "winit iOS expected the app to be in a `NotLaunched` \ - state, but was not - please file an issue" - ), + fn state_mut(&mut self) -> &mut AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn take_state(&mut self) -> AppStateImpl { + match self.app_state.take() { + Some(state) => state, + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn set_state(&mut self, new_state: AppStateImpl) { + bug_assert!( + self.app_state.is_none(), + "attempted to set an `AppState` without calling `take_state` first {:?}", + self.app_state + ); + self.app_state = Some(new_state) + } + + fn replace_state(&mut self, new_state: AppStateImpl) -> AppStateImpl { + match &mut self.app_state { + Some(ref mut state) => mem::replace(state, new_state), + None => bug!("`AppState` previously failed a state transition"), + } + } + + fn has_launched(&self) -> bool { + match self.state() { + &AppStateImpl::NotLaunched { .. } | &AppStateImpl::Launching { .. } => false, + _ => true, + } + } + + fn will_launch_transition(&mut self, queued_event_handler: Box) { + let (queued_windows, queued_events, queued_gpu_redraws) = match self.take_state() { + AppStateImpl::NotLaunched { + queued_windows, + queued_events, + queued_gpu_redraws, + } => (queued_windows, queued_events, queued_gpu_redraws), + s => bug!("unexpected state {:?}", s), }; - ptr::write( - &mut this.app_state, + self.set_state(AppStateImpl::Launching { + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + }); + } + + fn did_finish_launching_transition(&mut self) -> (Vec, Vec>) { + let (windows, events, event_handler, queued_gpu_redraws) = match self.take_state() { AppStateImpl::Launching { queued_windows, queued_events, queued_event_handler, - }, - ); + queued_gpu_redraws, + } => ( + queued_windows, + queued_events, + queued_event_handler, + queued_gpu_redraws, + ), + s => bug!("unexpected state {:?}", s), + }; + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + active_control_flow: ControlFlow::Poll, + queued_gpu_redraws, + }); + (windows, events) } - // requires main thread - pub unsafe fn did_finish_launching() { - let mut this = AppState::get_mut(); - let windows = match &mut this.app_state { - &mut AppStateImpl::Launching { - ref mut queued_windows, - .. - } => mem::replace(queued_windows, Vec::new()), - _ => panic!( - "winit iOS expected the app to be in a `Launching` \ - state, but was not - please file an issue" - ), - }; - // have to drop RefMut because the window setup code below can trigger new events - 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 - // gotta reset the `rootViewController`. - // - // relevant iOS log: - // ``` - // [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::<()>()]; - let () = msg_send![window, setRootViewController: controller]; - let () = msg_send![window, makeKeyAndVisible]; - } - let () = msg_send![window, release]; + fn wakeup_transition(&mut self) -> Option> { + // before `AppState::did_finish_launching` is called, pretend there is no running + // event loop. + if !self.has_launched() { + return None; } - let mut this = AppState::get_mut(); - let (windows, events, event_handler) = match &mut this.app_state { - &mut AppStateImpl::Launching { - ref mut queued_windows, - ref mut queued_events, - ref mut queued_event_handler, - } => { - let windows = ptr::read(queued_windows); - let events = ptr::read(queued_events); - let event_handler = ptr::read(queued_event_handler); - (windows, events, event_handler) - } - _ => panic!( - "winit iOS expected the app to be in a `Launching` \ - state, but was not - please file an issue" + let (event_handler, event) = match (self.control_flow, self.take_state()) { + ( + ControlFlow::Poll, + AppStateImpl::PollFinished { + waiting_event_handler, + }, + ) => (waiting_event_handler, Event::NewEvents(StartCause::Poll)), + ( + ControlFlow::Wait, + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => ( + waiting_event_handler, + Event::NewEvents(StartCause::WaitCancelled { + start, + requested_resume: None, + }), ), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Poll, - }, - ); - drop(this); - - let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); - AppState::handle_nonuser_events(events); - - // 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]; - } - } - - // requires main thread - // AppState::did_finish_launching handles the special transition `Init` - pub unsafe fn handle_wakeup_transition() { - let mut this = AppState::get_mut(); - let event = - match this.control_flow { - ControlFlow::Poll => { - let event_handler = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::PollFinished { - ref mut waiting_event_handler, - } => ptr::read(waiting_event_handler), - _ => bug!("`EventHandler` unexpectedly started polling"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Poll, - }, - ); - Event::NewEvents(StartCause::Poll) - } - ControlFlow::Wait => { - let (event_handler, start) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::Waiting { - ref mut waiting_event_handler, - ref mut start, - } => (ptr::read(waiting_event_handler), *start), - _ => bug!("`EventHandler` unexpectedly woke up"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::Wait, - }, - ); + ( + ControlFlow::WaitUntil(requested_resume), + AppStateImpl::Waiting { + waiting_event_handler, + start, + }, + ) => { + let event = if Instant::now() >= requested_resume { + Event::NewEvents(StartCause::ResumeTimeReached { + start, + requested_resume, + }) + } else { Event::NewEvents(StartCause::WaitCancelled { start, - requested_resume: None, + requested_resume: Some(requested_resume), }) - } - ControlFlow::WaitUntil(requested_resume) => { - let (event_handler, start) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } - | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::Waiting { - ref mut waiting_event_handler, - ref mut start, - } => (ptr::read(waiting_event_handler), *start), - _ => bug!("`EventHandler` unexpectedly woke up"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow: ControlFlow::WaitUntil(requested_resume), - }, - ); - if Instant::now() >= requested_resume { - Event::NewEvents(StartCause::ResumeTimeReached { - start, - requested_resume, - }) - } else { - Event::NewEvents(StartCause::WaitCancelled { - start, - requested_resume: Some(requested_resume), - }) - } - } - ControlFlow::Exit => bug!("unexpected controlflow `Exit`"), - }; - drop(this); - AppState::handle_nonuser_event(event) + }; + (waiting_event_handler, event) + } + (ControlFlow::Exit, _) => bug!("unexpected `ControlFlow` `Exit`"), + s => bug!("`EventHandler` unexpectedly woke up {:?}", s), + }; + + self.set_state(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws: Default::default(), + active_control_flow: self.control_flow, + }); + Some(event) } - // requires main thread - pub unsafe fn handle_nonuser_event(event: Event) { - AppState::handle_nonuser_events(std::iter::once(event)) - } - - // requires main thread - pub unsafe fn handle_nonuser_events>>(events: I) { - let mut this = AppState::get_mut(); - let mut control_flow = this.control_flow; - let (mut event_handler, active_control_flow) = match &mut this.app_state { + fn try_user_callback_transition(&mut self) -> UserCallbackTransitionResult<'_> { + // If we're not able to process an event due to recursion or `Init` not having been sent out + // yet, then queue the events up. + match self.state_mut() { &mut AppStateImpl::Launching { ref mut queued_events, .. @@ -358,211 +324,488 @@ impl AppState { ref mut queued_events, .. } => { - queued_events.extend(events); - return; + // A lifetime cast: early returns are not currently handled well with NLL, but + // polonius handles them well. This transmute is a safe workaround. + return unsafe { + mem::transmute::< + UserCallbackTransitionResult<'_>, + UserCallbackTransitionResult<'_>, + >(UserCallbackTransitionResult::ReentrancyPrevented { + queued_events, + }) + }; } - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => (ptr::read(event_handler), *active_control_flow), - &mut AppStateImpl::PollFinished { .. } - | &mut AppStateImpl::Waiting { .. } - | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), - }; - ptr::write( - &mut this.app_state, - AppStateImpl::InUserCallback { - queued_events: Vec::new(), - }, - ); - drop(this); - for event in events { - event_handler.handle_nonuser_event(event, &mut control_flow) + &mut AppStateImpl::ProcessingEvents { .. } + | &mut AppStateImpl::ProcessingRedraws { .. } => {} + + s @ &mut AppStateImpl::PollFinished { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::Terminated => { + bug!("unexpected attempted to process an event {:?}", s) + } } - loop { - let mut this = AppState::get_mut(); - let queued_events = match &mut this.app_state { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - } => mem::replace(queued_events, Vec::new()), - _ => bug!("unexpected `AppStateImpl`"), - }; - if queued_events.is_empty() { - this.app_state = AppStateImpl::ProcessingEvents { + + let (event_handler, queued_gpu_redraws, active_control_flow, processing_redraws) = + match self.take_state() { + AppStateImpl::Launching { .. } + | AppStateImpl::NotLaunched { .. } + | AppStateImpl::InUserCallback { .. } => unreachable!(), + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => ( + event_handler, + queued_gpu_redraws, + active_control_flow, + false, + ), + AppStateImpl::ProcessingRedraws { event_handler, active_control_flow, - }; - this.control_flow = control_flow; - break; - } - drop(this); - for event in queued_events { - event_handler.handle_nonuser_event(event, &mut control_flow) - } + } => (event_handler, Default::default(), active_control_flow, true), + AppStateImpl::PollFinished { .. } + | AppStateImpl::Waiting { .. } + | AppStateImpl::Terminated => unreachable!(), + }; + self.set_state(AppStateImpl::InUserCallback { + queued_events: Vec::new(), + queued_gpu_redraws, + }); + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, } } - // requires main thread - pub unsafe fn handle_user_events() { - let mut this = AppState::get_mut(); - let mut control_flow = this.control_flow; - let (mut event_handler, active_control_flow) = match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => (ptr::read(event_handler), *active_control_flow), - &mut AppStateImpl::InUserCallback { .. } - | &mut AppStateImpl::PollFinished { .. } - | &mut AppStateImpl::Waiting { .. } - | &mut AppStateImpl::Terminated => bug!("unexpected attempted to process an event"), + fn main_events_cleared_transition(&mut self) -> HashSet { + let (event_handler, queued_gpu_redraws, active_control_flow) = match self.take_state() { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } => (event_handler, queued_gpu_redraws, active_control_flow), + s => bug!("unexpected state {:?}", s), }; - ptr::write( - &mut this.app_state, - AppStateImpl::InUserCallback { - queued_events: Vec::new(), - }, - ); - drop(this); - - event_handler.handle_user_events(&mut control_flow); - loop { - let mut this = AppState::get_mut(); - let queued_events = match &mut this.app_state { - &mut AppStateImpl::InUserCallback { - ref mut queued_events, - } => mem::replace(queued_events, Vec::new()), - _ => bug!("unexpected `AppStateImpl`"), - }; - if queued_events.is_empty() { - this.app_state = AppStateImpl::ProcessingEvents { - event_handler, - active_control_flow, - }; - this.control_flow = control_flow; - break; - } - drop(this); - for event in queued_events { - event_handler.handle_nonuser_event(event, &mut control_flow) - } - event_handler.handle_user_events(&mut control_flow); - } + self.set_state(AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + }); + queued_gpu_redraws } - // requires main thread - pub unsafe fn handle_events_cleared() { - let mut this = AppState::get_mut(); - match &mut this.app_state { - &mut AppStateImpl::NotLaunched { .. } | &mut AppStateImpl::Launching { .. } => return, - &mut AppStateImpl::ProcessingEvents { .. } => {} - _ => unreachable!(), - }; - drop(this); - - AppState::handle_user_events(); - AppState::handle_nonuser_event(Event::EventsCleared); - - let mut this = AppState::get_mut(); - let (event_handler, old) = match &mut this.app_state { - &mut AppStateImpl::ProcessingEvents { - ref mut event_handler, - ref mut active_control_flow, - } => ( - ManuallyDrop::new(ptr::read(event_handler)), - *active_control_flow, - ), - _ => unreachable!(), + fn events_cleared_transition(&mut self) { + if !self.has_launched() { + return; + } + let (waiting_event_handler, old) = match self.take_state() { + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } => (event_handler, active_control_flow), + s => bug!("unexpected state {:?}", s), }; - let new = this.control_flow; + let new = self.control_flow; match (old, new) { - (ControlFlow::Poll, ControlFlow::Poll) => ptr::write( - &mut this.app_state, - AppStateImpl::PollFinished { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - }, - ), + (ControlFlow::Poll, ControlFlow::Poll) => self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }), (ControlFlow::Wait, ControlFlow::Wait) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); } (ControlFlow::WaitUntil(old_instant), ControlFlow::WaitUntil(new_instant)) if old_instant == new_instant => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); } (_, ControlFlow::Wait) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ); - this.waker.stop() + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.stop() } (_, ControlFlow::WaitUntil(new_instant)) => { let start = Instant::now(); - ptr::write( - &mut this.app_state, - AppStateImpl::Waiting { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - start, - }, - ); - this.waker.start_at(new_instant) + self.set_state(AppStateImpl::Waiting { + waiting_event_handler, + start, + }); + self.waker.start_at(new_instant) } (_, ControlFlow::Poll) => { - ptr::write( - &mut this.app_state, - AppStateImpl::PollFinished { - waiting_event_handler: ManuallyDrop::into_inner(event_handler), - }, - ); - this.waker.start() + self.set_state(AppStateImpl::PollFinished { + waiting_event_handler, + }); + self.waker.start() } (_, ControlFlow::Exit) => { // https://developer.apple.com/library/archive/qa/qa1561/_index.html // it is not possible to quit an iOS app gracefully and programatically warn!("`ControlFlow::Exit` ignored on iOS"); - this.control_flow = old + self.control_flow = old } } } - pub fn terminated() { - let mut this = unsafe { AppState::get_mut() }; - let mut old = mem::replace(&mut this.app_state, AppStateImpl::Terminated); - let mut control_flow = this.control_flow; - if let AppStateImpl::ProcessingEvents { - ref mut event_handler, - .. - } = old - { - drop(this); - event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) - } else { - bug!("`LoopDestroyed` happened while not processing events") + fn terminated_transition(&mut self) -> Box { + match self.replace_state(AppStateImpl::Terminated) { + AppStateImpl::ProcessingRedraws { event_handler, .. } => event_handler, + s => bug!( + "`LoopDestroyed` happened while not processing events {:?}", + s + ), } } } +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn set_key_window(window: id) { + bug_assert!( + { + let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)]; + is_window == YES + }, + "set_key_window called with an incorrect type" + ); + 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]), + &mut AppStateImpl::ProcessingEvents { .. } + | &mut AppStateImpl::InUserCallback { .. } + | &mut AppStateImpl::ProcessingRedraws { .. } => {} + s @ &mut AppStateImpl::Launching { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); + msg_send![window, makeKeyAndVisible] +} + +// requires main thread and window is a UIWindow +// retains window +pub unsafe fn queue_gl_or_metal_redraw(window: id) { + bug_assert!( + { + let is_window: BOOL = msg_send![window, isKindOfClass: class!(UIWindow)]; + is_window == YES + }, + "set_key_window called with an incorrect type" + ); + let mut this = AppState::get_mut(); + match this.state_mut() { + &mut AppStateImpl::NotLaunched { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::Launching { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::ProcessingEvents { + ref mut queued_gpu_redraws, + .. + } + | &mut AppStateImpl::InUserCallback { + ref mut queued_gpu_redraws, + .. + } => drop(queued_gpu_redraws.insert(window)), + s @ &mut AppStateImpl::ProcessingRedraws { .. } + | s @ &mut AppStateImpl::Waiting { .. } + | s @ &mut AppStateImpl::PollFinished { .. } => bug!("unexpected state {:?}", s), + &mut AppStateImpl::Terminated => { + panic!("Attempt to create a `Window` after the app has terminated") + } + } + drop(this); +} + +// requires main thread +pub unsafe fn will_launch(queued_event_handler: Box) { + AppState::get_mut().will_launch_transition(queued_event_handler) +} + +// requires main thread +pub unsafe fn did_finish_launching() { + let mut this = AppState::get_mut(); + let windows = match this.state_mut() { + AppStateImpl::Launching { queued_windows, .. } => mem::replace(queued_windows, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + + // start waking up the event loop now! + bug_assert!( + this.control_flow == ControlFlow::Poll, + "unexpectedly not setup to `Poll` on launch!" + ); + this.waker.start(); + + // have to drop RefMut because the window setup code below can trigger new events + 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 + // gotta reset the `rootViewController`. + // + // relevant iOS log: + // ``` + // [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::<()>()]; + let () = msg_send![window, setRootViewController: controller]; + let () = msg_send![window, makeKeyAndVisible]; + } + let () = msg_send![window, release]; + } + + let (windows, events) = AppState::get_mut().did_finish_launching_transition(); + + let events = std::iter::once(Event::NewEvents(StartCause::Init)).chain(events); + handle_nonuser_events(events); + + // 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]; + } +} + +// requires main thread +// AppState::did_finish_launching handles the special transition `Init` +pub unsafe fn handle_wakeup_transition() { + let mut this = AppState::get_mut(); + let wakeup_event = match this.wakeup_transition() { + None => return, + Some(wakeup_event) => wakeup_event, + }; + drop(this); + + handle_nonuser_event(wakeup_event) +} + +// requires main thread +pub unsafe fn handle_nonuser_event(event: Event) { + handle_nonuser_events(std::iter::once(event)) +} + +// requires main thread +pub unsafe fn handle_nonuser_events>>(events: I) { + let mut this = AppState::get_mut(); + let (mut event_handler, active_control_flow, processing_redraws) = + match this.try_user_callback_transition() { + UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { + queued_events.extend(events); + return; + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + let mut control_flow = this.control_flow; + drop(this); + + for event in events { + if !processing_redraws && event.is_redraw() { + log::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + log::warn!( + "processing non `RedrawRequested` event after the main event loop: {:#?}", + event + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::replace(queued_events, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(if processing_redraws { + bug_assert!( + queued_gpu_redraws.is_empty(), + "redraw queued while processing redraws" + ); + AppStateImpl::ProcessingRedraws { + event_handler, + active_control_flow, + } + } else { + AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + } + }); + this.control_flow = control_flow; + break; + } + drop(this); + + for event in queued_events { + if !processing_redraws && event.is_redraw() { + log::info!("processing `RedrawRequested` during the main event loop"); + } else if processing_redraws && !event.is_redraw() { + log::warn!( + "processing non-`RedrawRequested` event after the main event loop: {:#?}", + event + ); + } + event_handler.handle_nonuser_event(event, &mut control_flow) + } + } +} + +// requires main thread +unsafe fn handle_user_events() { + let mut this = AppState::get_mut(); + let mut control_flow = this.control_flow; + let (mut event_handler, active_control_flow, processing_redraws) = + match this.try_user_callback_transition() { + UserCallbackTransitionResult::ReentrancyPrevented { .. } => { + bug!("unexpected attempted to process an event") + } + UserCallbackTransitionResult::Success { + event_handler, + active_control_flow, + processing_redraws, + } => (event_handler, active_control_flow, processing_redraws), + }; + if processing_redraws { + bug!("user events attempted to be sent out while `ProcessingRedraws`"); + } + drop(this); + + event_handler.handle_user_events(&mut control_flow); + + loop { + let mut this = AppState::get_mut(); + let queued_events = match this.state_mut() { + &mut AppStateImpl::InUserCallback { + ref mut queued_events, + queued_gpu_redraws: _, + } => mem::replace(queued_events, Vec::new()), + s => bug!("unexpected state {:?}", s), + }; + if queued_events.is_empty() { + let queued_gpu_redraws = match this.take_state() { + AppStateImpl::InUserCallback { + queued_events: _, + queued_gpu_redraws, + } => queued_gpu_redraws, + _ => unreachable!(), + }; + this.app_state = Some(AppStateImpl::ProcessingEvents { + event_handler, + queued_gpu_redraws, + active_control_flow, + }); + this.control_flow = control_flow; + break; + } + drop(this); + + for event in queued_events { + event_handler.handle_nonuser_event(event, &mut control_flow) + } + event_handler.handle_user_events(&mut control_flow); + } +} + +// requires main thread +pub unsafe fn handle_main_events_cleared() { + let mut this = AppState::get_mut(); + if !this.has_launched() { + return; + } + match this.state_mut() { + &mut AppStateImpl::ProcessingEvents { .. } => {} + _ => bug!("`ProcessingRedraws` happened unexpectedly"), + }; + drop(this); + + // User events are always sent out at the end of the "MainEventLoop" + handle_user_events(); + handle_nonuser_event(Event::EventsCleared); + + let mut this = AppState::get_mut(); + let redraw_events = this + .main_events_cleared_transition() + .into_iter() + .map(|window| Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::RedrawRequested, + }); + drop(this); + + handle_nonuser_events(redraw_events); +} + +// requires main thread +pub unsafe fn handle_events_cleared() { + AppState::get_mut().events_cleared_transition(); +} + +// requires main thread +pub unsafe fn terminated() { + let mut this = AppState::get_mut(); + let mut event_handler = this.terminated_transition(); + let mut control_flow = this.control_flow; + drop(this); + + event_handler.handle_nonuser_event(Event::LoopDestroyed, &mut control_flow) +} + struct EventLoopWaker { timer: CFRunLoopTimerRef, } diff --git a/src/platform_impl/ios/event_loop.rs b/src/platform_impl/ios/event_loop.rs index b5b56739..b26b1eb6 100644 --- a/src/platform_impl/ios/event_loop.rs +++ b/src/platform_impl/ios/event_loop.rs @@ -16,7 +16,7 @@ use crate::{ }; use crate::platform_impl::platform::{ - app_state::AppState, + app_state, ffi::{ id, kCFRunLoopAfterWaiting, kCFRunLoopBeforeWaiting, kCFRunLoopCommonModes, kCFRunLoopDefaultMode, kCFRunLoopEntry, kCFRunLoopExit, nil, CFIndex, CFRelease, @@ -80,7 +80,7 @@ impl EventLoop { `EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\n\ Note: `EventLoop::run` calls `UIApplicationMain` on iOS" ); - AppState::will_launch(Box::new(EventLoopHandler { + app_state::will_launch(Box::new(EventLoopHandler { f: event_handler, event_loop: self.window_target, })); @@ -155,7 +155,7 @@ impl EventLoopProxy { let rl = CFRunLoopGetMain(); // we want all the members of context to be zero/null, except one let mut context: CFRunLoopSourceContext = mem::zeroed(); - context.perform = event_loop_proxy_handler; + context.perform = Some(event_loop_proxy_handler); let source = CFRunLoopSourceCreate(ptr::null_mut(), CFIndex::max_value() - 1, &mut context); CFRunLoopAddSource(rl, source, kCFRunLoopCommonModes); @@ -188,15 +188,40 @@ fn setup_control_flow_observers() { unsafe { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopAfterWaiting => AppState::handle_wakeup_transition(), + kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), kCFRunLoopEntry => unimplemented!(), // not expected to ever happen _ => unreachable!(), } } } + // Core Animation registers its `CFRunLoopObserver` that performs drawing operations in + // `CA::Transaction::ensure_implicit` with a priority of `0x1e8480`. We set the main_end + // priority to be 0, in order to send EventsCleared before RedrawRequested. This value was + // chosen conservatively to guard against apple using different priorities for their redraw + // observers in different OS's or on different devices. If it so happens that it's too + // conservative, the main symptom would be non-redraw events coming in after `EventsCleared`. + // + // The value of `0x1e8480` was determined by inspecting stack traces and the associated + // registers for every `CFRunLoopAddObserver` call on an iPad Air 2 running iOS 11.4. + // + // Also tested to be `0x1e8480` on iPhone 8, iOS 13 beta 4. + extern "C" fn control_flow_main_end_handler( + _: CFRunLoopObserverRef, + activity: CFRunLoopActivity, + _: *mut c_void, + ) { + unsafe { + #[allow(non_upper_case_globals)] + match activity { + kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), + kCFRunLoopExit => unimplemented!(), // not expected to ever happen + _ => unreachable!(), + } + } + } + // end is queued with the lowest priority to ensure it is processed after other observers - // without that, LoopDestroyed will get sent after EventsCleared extern "C" fn control_flow_end_handler( _: CFRunLoopObserverRef, activity: CFRunLoopActivity, @@ -205,7 +230,7 @@ fn setup_control_flow_observers() { unsafe { #[allow(non_upper_case_globals)] match activity { - kCFRunLoopBeforeWaiting => AppState::handle_events_cleared(), + kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), kCFRunLoopExit => unimplemented!(), // not expected to ever happen _ => unreachable!(), } @@ -213,6 +238,7 @@ fn setup_control_flow_observers() { } let main_loop = CFRunLoopGetMain(); + let begin_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopEntry | kCFRunLoopAfterWaiting, @@ -222,6 +248,17 @@ fn setup_control_flow_observers() { ptr::null_mut(), ); CFRunLoopAddObserver(main_loop, begin_observer, kCFRunLoopDefaultMode); + + let main_end_observer = CFRunLoopObserverCreate( + ptr::null_mut(), + kCFRunLoopExit | kCFRunLoopBeforeWaiting, + 1, // repeat = true + 0, // see comment on `control_flow_main_end_handler` + control_flow_main_end_handler, + ptr::null_mut(), + ); + CFRunLoopAddObserver(main_loop, main_end_observer, kCFRunLoopDefaultMode); + let end_observer = CFRunLoopObserverCreate( ptr::null_mut(), kCFRunLoopExit | kCFRunLoopBeforeWaiting, diff --git a/src/platform_impl/ios/ffi.rs b/src/platform_impl/ios/ffi.rs index fcaf395e..4ecd47f7 100644 --- a/src/platform_impl/ios/ffi.rs +++ b/src/platform_impl/ios/ffi.rs @@ -331,14 +331,14 @@ pub enum CFRunLoopTimerContext {} pub struct CFRunLoopSourceContext { pub version: CFIndex, pub info: *mut c_void, - pub retain: extern "C" fn(*const c_void) -> *const c_void, - pub release: extern "C" fn(*const c_void), - pub copyDescription: extern "C" fn(*const c_void) -> CFStringRef, - pub equal: extern "C" fn(*const c_void, *const c_void) -> Boolean, - pub hash: extern "C" fn(*const c_void) -> CFHashCode, - pub schedule: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), - pub cancel: extern "C" fn(*mut c_void, CFRunLoopRef, CFRunLoopMode), - pub perform: extern "C" fn(*mut c_void), + pub retain: Option *const c_void>, + pub release: Option, + pub copyDescription: Option CFStringRef>, + pub equal: Option Boolean>, + pub hash: Option CFHashCode>, + pub schedule: Option, + pub cancel: Option, + pub perform: Option, } pub trait NSString: Sized { diff --git a/src/platform_impl/ios/view.rs b/src/platform_impl/ios/view.rs index e871ab46..8c6ba1e7 100644 --- a/src/platform_impl/ios/view.rs +++ b/src/platform_impl/ios/view.rs @@ -9,7 +9,7 @@ use crate::{ event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, platform::ios::MonitorHandleExtIOS, platform_impl::platform::{ - app_state::{self, AppState, OSCapabilities}, + app_state::{self, OSCapabilities}, event_loop, ffi::{ id, nil, CGFloat, CGPoint, CGRect, UIForceTouchCapability, UIInterfaceOrientationMask, @@ -101,7 +101,7 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn draw_rect(object: &Object, _: Sel, rect: CGRect) { unsafe { let window: id = msg_send![object, window]; - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::RedrawRequested, }); @@ -112,6 +112,9 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { extern "C" fn layout_subviews(object: &Object, _: Sel) { unsafe { + let superclass: &'static Class = msg_send![object, superclass]; + let () = msg_send![super(object, superclass), layoutSubviews]; + let window: id = msg_send![object, window]; let bounds: CGRect = msg_send![window, bounds]; let screen: id = msg_send![window, screen]; @@ -122,12 +125,127 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { width: screen_frame.size.width as _, height: screen_frame.size.height as _, }; - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(window.into()), event: WindowEvent::Resized(size), }); + } + } + + extern "C" fn set_content_scale_factor( + object: &mut Object, + _: Sel, + untrusted_hidpi_factor: CGFloat, + ) { + unsafe { let superclass: &'static Class = msg_send![object, superclass]; - let () = msg_send![super(object, superclass), layoutSubviews]; + let () = msg_send![ + super(object, superclass), + setContentScaleFactor: untrusted_hidpi_factor + ]; + + // On launch, iOS sets the contentScaleFactor to 0.0. This is a sentinel value that + // iOS appears to use to "reset" the contentScaleFactor to the device specific + // default value. + // + // The workaround is to not trust the value received by this function, and always + // go through the getter. + let hidpi_factor: CGFloat = msg_send![object, contentScaleFactor]; + assert!( + !hidpi_factor.is_nan() + && hidpi_factor.is_finite() + && hidpi_factor.is_sign_positive() + && hidpi_factor > 0.0, + "invalid hidpi_factor set on UIWindow", + ); + + let window: id = msg_send![object, window]; + + let bounds: CGRect = msg_send![object, 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]; + 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(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), + }) + .chain(std::iter::once(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Resized(size), + })), + ); + } + } + + extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { + unsafe { + let window: id = msg_send![object, window]; + 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 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 = 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), + }; + + touch_events.push(Event::WindowEvent { + window_id: RootWindowId(window.into()), + event: WindowEvent::Touch(Touch { + device_id: RootDeviceId(DeviceId { uiscreen }), + id: touch_id, + location: (location.x as f64, location.y as f64).into(), + force, + phase, + }), + }); + } + app_state::handle_nonuser_events(touch_events); } } @@ -142,6 +260,28 @@ unsafe fn get_view_class(root_view_class: &'static Class) -> &'static Class { sel!(layoutSubviews), layout_subviews as extern "C" fn(&Object, Sel), ); + decl.add_method( + sel!(setContentScaleFactor:), + set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat), + ); + + decl.add_method( + sel!(touchesBegan:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesMoved:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesEnded:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.add_method( + sel!(touchesCancelled:withEvent:), + handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), + ); + decl.register() }) } @@ -223,7 +363,7 @@ unsafe fn get_window_class() -> &'static Class { extern "C" fn become_key_window(object: &Object, _: Sel) { unsafe { - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(true), }); @@ -233,7 +373,7 @@ unsafe fn get_window_class() -> &'static Class { extern "C" fn resign_key_window(object: &Object, _: Sel) { unsafe { - AppState::handle_nonuser_event(Event::WindowEvent { + app_state::handle_nonuser_event(Event::WindowEvent { window_id: RootWindowId(object.into()), event: WindowEvent::Focused(false), }); @@ -241,102 +381,6 @@ unsafe fn get_window_class() -> &'static Class { } } - extern "C" fn handle_touches(object: &Object, _: Sel, touches: id, _: id) { - unsafe { - let uiscreen = msg_send![object, 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 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 = 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), - }; - - touch_events.push(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Touch(Touch { - device_id: RootDeviceId(DeviceId { uiscreen }), - id: touch_id, - location: (location.x as f64, location.y as f64).into(), - force, - phase, - }), - }); - } - AppState::handle_nonuser_events(touch_events); - } - } - - extern "C" fn set_content_scale_factor(object: &mut Object, _: Sel, hidpi_factor: CGFloat) { - unsafe { - let () = msg_send![ - super(object, class!(UIWindow)), - setContentScaleFactor: hidpi_factor - ]; - let view_controller: id = msg_send![object, rootViewController]; - let view: id = msg_send![view_controller, view]; - let () = msg_send![view, setContentScaleFactor: hidpi_factor]; - let bounds: CGRect = msg_send![object, bounds]; - let screen: id = msg_send![object, screen]; - let screen_space: id = msg_send![screen, coordinateSpace]; - let screen_frame: CGRect = - msg_send![object, convertRect:bounds toCoordinateSpace:screen_space]; - let size = crate::dpi::LogicalSize { - width: screen_frame.size.width as _, - height: screen_frame.size.height as _, - }; - AppState::handle_nonuser_events( - std::iter::once(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::HiDpiFactorChanged(hidpi_factor as _), - }) - .chain(std::iter::once(Event::WindowEvent { - window_id: RootWindowId(object.into()), - event: WindowEvent::Resized(size), - })), - ); - } - } - let mut decl = ClassDecl::new("WinitUIWindow", uiwindow_class) .expect("Failed to declare class `WinitUIWindow`"); decl.add_method( @@ -348,28 +392,6 @@ unsafe fn get_window_class() -> &'static Class { resign_key_window as extern "C" fn(&Object, Sel), ); - decl.add_method( - sel!(touchesBegan:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesMoved:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesEnded:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - decl.add_method( - sel!(touchesCancelled:withEvent:), - handle_touches as extern "C" fn(this: &Object, _: Sel, _: id, _: id), - ); - - decl.add_method( - sel!(setContentScaleFactor:), - set_content_scale_factor as extern "C" fn(&mut Object, Sel, CGFloat), - ); - CLASS = Some(decl.register()); } CLASS.unwrap() @@ -388,6 +410,9 @@ pub unsafe fn create_view( let view: id = msg_send![view, initWithFrame: frame]; assert!(!view.is_null(), "Failed to initialize `UIView` instance"); let () = msg_send![view, setMultipleTouchEnabled: YES]; + if let Some(hidpi_factor) = platform_attributes.hidpi_factor { + let () = msg_send![view, setContentScaleFactor: hidpi_factor as CGFloat]; + } view } @@ -451,7 +476,7 @@ pub unsafe fn create_view_controller( // requires main thread pub unsafe fn create_window( window_attributes: &WindowAttributes, - platform_attributes: &PlatformSpecificWindowBuilderAttributes, + _platform_attributes: &PlatformSpecificWindowBuilderAttributes, frame: CGRect, view_controller: id, ) -> id { @@ -465,9 +490,6 @@ pub unsafe fn create_window( "Failed to initialize `UIWindow` instance" ); let () = msg_send![window, setRootViewController: view_controller]; - if let Some(hidpi_factor) = platform_attributes.hidpi_factor { - let () = msg_send![window, setContentScaleFactor: hidpi_factor as CGFloat]; - } match window_attributes.fullscreen { Some(Fullscreen::Exclusive(ref video_mode)) => { let uiscreen = video_mode.monitor().ui_screen() as id; @@ -486,17 +508,17 @@ pub unsafe fn create_window( pub fn create_delegate_class() { extern "C" fn did_finish_launching(_: &mut Object, _: Sel, _: id, _: id) -> BOOL { unsafe { - AppState::did_finish_launching(); + app_state::did_finish_launching(); } YES } extern "C" fn did_become_active(_: &Object, _: Sel, _: id) { - unsafe { AppState::handle_nonuser_event(Event::Resumed) } + unsafe { app_state::handle_nonuser_event(Event::Resumed) } } extern "C" fn will_resign_active(_: &Object, _: Sel, _: id) { - unsafe { AppState::handle_nonuser_event(Event::Suspended) } + unsafe { app_state::handle_nonuser_event(Event::Suspended) } } extern "C" fn will_enter_foreground(_: &Object, _: Sel, _: id) {} @@ -521,8 +543,8 @@ pub fn create_delegate_class() { }); } } - AppState::handle_nonuser_events(events); - AppState::terminated(); + app_state::handle_nonuser_events(events); + app_state::terminated(); } } diff --git a/src/platform_impl/ios/window.rs b/src/platform_impl/ios/window.rs index 0655add1..0b609c60 100644 --- a/src/platform_impl/ios/window.rs +++ b/src/platform_impl/ios/window.rs @@ -4,7 +4,7 @@ use std::{ ops::{Deref, DerefMut}, }; -use objc::runtime::{Class, Object, NO, YES}; +use objc::runtime::{Class, Object, BOOL, NO, YES}; use crate::{ dpi::{self, LogicalPosition, LogicalSize}, @@ -13,8 +13,7 @@ use crate::{ monitor::MonitorHandle as RootMonitorHandle, platform::ios::{MonitorHandleExtIOS, ScreenEdge, ValidOrientations}, platform_impl::platform::{ - app_state::{self, AppState}, - event_loop, + app_state, event_loop, ffi::{ id, CGFloat, CGPoint, CGRect, CGSize, UIEdgeInsets, UIInterfaceOrientationMask, UIRectEdge, UIScreenOverscanCompensation, @@ -28,6 +27,7 @@ pub struct Inner { pub window: id, pub view_controller: id, pub view: id, + gl_or_metal_backed: bool, } impl Drop for Inner { @@ -58,7 +58,19 @@ impl Inner { pub fn request_redraw(&self) { unsafe { - let () = msg_send![self.view, setNeedsDisplay]; + if self.gl_or_metal_backed { + // `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. + // Ordinarily the OS sets up a bunch of UIKit state before calling drawRect: on a UIView, but when using + // raw or gl/metal for drawing this work is completely avoided. + // + // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via + // testing. + // + // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc + app_state::queue_gl_or_metal_redraw(self.window); + } else { + let () = msg_send![self.view, setNeedsDisplay]; + } } } @@ -337,6 +349,16 @@ impl Window { }; let view = view::create_view(&window_attributes, &platform_attributes, frame.clone()); + + let gl_or_metal_backed = { + let view_class: id = msg_send![view, class]; + let layer_class: id = msg_send![view_class, layerClass]; + let is_metal: BOOL = + msg_send![layer_class, isSubclassOfClass: class!(CAMetalLayer)]; + let is_gl: BOOL = msg_send![layer_class, isSubclassOfClass: class!(CAEAGLLayer)]; + is_metal == YES || is_gl == YES + }; + let view_controller = view::create_view_controller(&window_attributes, &platform_attributes, view); let window = view::create_window( @@ -351,9 +373,10 @@ impl Window { window, view_controller, view, + gl_or_metal_backed, }, }; - AppState::set_key_window(window); + app_state::set_key_window(window); Ok(result) } }