Make iOS fully thread safe (#3045)

* macOS & iOS: Refactor EventWrapper

* macOS & iOS: Make EventLoopWindowTarget independent of the user event

* iOS: Use MainThreadMarker instead of marking functions unsafe

* Make iOS thread safe
This commit is contained in:
Mads Marquart 2023-08-27 17:04:39 +02:00 committed by GitHub
parent d9f04780cc
commit 86baa1c99a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
14 changed files with 457 additions and 445 deletions

View file

@ -8,6 +8,7 @@ And please only add new entries to the top of this list, right below the `# Unre
# Unreleased # Unreleased
- Make iOS `MonitorHandle` and `VideoMode` usable from other threads.
- Fix window size sometimes being invalid when resizing on macOS. - Fix window size sometimes being invalid when resizing on macOS.
- On Web, `ControlFlow::Poll` and `ControlFlow::WaitUntil` are now using the Prioritized Task Scheduling API. `setTimeout()` with a trick to circumvent throttling to 4ms is used as a fallback. - On Web, `ControlFlow::Poll` and `ControlFlow::WaitUntil` are now using the Prioritized Task Scheduling API. `setTimeout()` with a trick to circumvent throttling to 4ms is used as a fallback.
- On Web, never return a `MonitorHandle`. - On Web, never return a `MonitorHandle`.

View file

@ -1,5 +1,6 @@
use std::os::raw::c_void; use std::os::raw::c_void;
use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id; use objc2::rc::Id;
use crate::{ use crate::{
@ -210,7 +211,9 @@ pub trait MonitorHandleExtIOS {
impl MonitorHandleExtIOS for MonitorHandle { impl MonitorHandleExtIOS for MonitorHandle {
#[inline] #[inline]
fn ui_screen(&self) -> *mut c_void { fn ui_screen(&self) -> *mut c_void {
Id::as_ptr(self.inner.ui_screen()) as *mut c_void // SAFETY: The marker is only used to get the pointer of the screen
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.inner.ui_screen(mtm)) as *mut c_void
} }
#[inline] #[inline]

View file

@ -16,19 +16,21 @@ use core_foundation::runloop::{
kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate, kCFRunLoopCommonModes, CFRunLoopAddTimer, CFRunLoopGetMain, CFRunLoopRef, CFRunLoopTimerCreate,
CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate, CFRunLoopTimerInvalidate, CFRunLoopTimerRef, CFRunLoopTimerSetNextFireDate,
}; };
use icrate::Foundation::{CGRect, CGSize, NSInteger, NSOperatingSystemVersion, NSProcessInfo}; use icrate::Foundation::{
CGRect, CGSize, MainThreadMarker, NSInteger, NSOperatingSystemVersion, NSProcessInfo,
};
use objc2::rc::Id; use objc2::rc::Id;
use objc2::runtime::AnyObject; use objc2::runtime::AnyObject;
use objc2::{msg_send, sel}; use objc2::{msg_send, sel};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::event_loop::{EventHandler, Never};
use super::uikit::UIView; use super::uikit::UIView;
use super::view::WinitUIWindow; use super::view::WinitUIWindow;
use crate::{ use crate::{
dpi::LogicalSize, dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::ControlFlow, event_loop::ControlFlow,
platform_impl::platform::event_loop::{EventHandler, EventProxy, EventWrapper, Never},
window::WindowId as RootWindowId, window::WindowId as RootWindowId,
}; };
@ -44,6 +46,19 @@ macro_rules! bug_assert {
}; };
} }
#[derive(Debug)]
pub enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged(ScaleFactorChanged),
}
#[derive(Debug)]
pub struct ScaleFactorChanged {
pub(super) window: Id<WinitUIWindow>,
pub(super) suggested_size: PhysicalSize<u32>,
pub(super) scale_factor: f64,
}
enum UserCallbackTransitionResult<'a> { enum UserCallbackTransitionResult<'a> {
Success { Success {
event_handler: Box<dyn EventHandler>, event_handler: Box<dyn EventHandler>,
@ -114,24 +129,18 @@ struct AppState {
} }
impl AppState { impl AppState {
// requires main thread fn get_mut(_mtm: MainThreadMarker) -> RefMut<'static, AppState> {
unsafe fn get_mut() -> RefMut<'static, AppState> {
// basically everything in UIKit requires the main thread, so it's pointless to use the // basically everything in UIKit requires the main thread, so it's pointless to use the
// std::sync APIs. // std::sync APIs.
// must be mut because plain `static` requires `Sync` // must be mut because plain `static` requires `Sync`
static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None); static mut APP_STATE: RefCell<Option<AppState>> = RefCell::new(None);
if cfg!(debug_assertions) { let mut guard = unsafe { APP_STATE.borrow_mut() };
assert_main_thread!(
"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() { if guard.is_none() {
#[inline(never)] #[inline(never)]
#[cold] #[cold]
unsafe fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) { fn init_guard(guard: &mut RefMut<'static, Option<AppState>>) {
let waker = EventLoopWaker::new(CFRunLoopGetMain()); let waker = EventLoopWaker::new(unsafe { CFRunLoopGetMain() });
**guard = Some(AppState { **guard = Some(AppState {
app_state: Some(AppStateImpl::NotLaunched { app_state: Some(AppStateImpl::NotLaunched {
queued_windows: Vec::new(), queued_windows: Vec::new(),
@ -142,7 +151,7 @@ impl AppState {
waker, waker,
}); });
} }
init_guard(&mut guard) init_guard(&mut guard);
} }
RefMut::map(guard, |state| state.as_mut().unwrap()) RefMut::map(guard, |state| state.as_mut().unwrap())
} }
@ -451,10 +460,8 @@ impl AppState {
} }
} }
// requires main thread and window is a UIWindow pub(crate) fn set_key_window(mtm: MainThreadMarker, window: &Id<WinitUIWindow>) {
// retains window let mut this = AppState::get_mut(mtm);
pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
ref mut queued_windows, ref mut queued_windows,
@ -474,10 +481,8 @@ pub(crate) unsafe fn set_key_window(window: &Id<WinitUIWindow>) {
window.makeKeyAndVisible(); window.makeKeyAndVisible();
} }
// requires main thread and window is a UIWindow pub(crate) fn queue_gl_or_metal_redraw(mtm: MainThreadMarker, window: Id<WinitUIWindow>) {
// retains window let mut this = AppState::get_mut(mtm);
pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
let mut this = AppState::get_mut();
match this.state_mut() { match this.state_mut() {
&mut AppStateImpl::NotLaunched { &mut AppStateImpl::NotLaunched {
ref mut queued_gpu_redraws, ref mut queued_gpu_redraws,
@ -506,14 +511,12 @@ pub(crate) unsafe fn queue_gl_or_metal_redraw(window: Id<WinitUIWindow>) {
} }
} }
// requires main thread pub fn will_launch(mtm: MainThreadMarker, queued_event_handler: Box<dyn EventHandler>) {
pub unsafe fn will_launch(queued_event_handler: Box<dyn EventHandler>) { AppState::get_mut(mtm).will_launch_transition(queued_event_handler)
AppState::get_mut().will_launch_transition(queued_event_handler)
} }
// requires main thread pub fn did_finish_launching(mtm: MainThreadMarker) {
pub unsafe fn did_finish_launching() { let mut this = AppState::get_mut(mtm);
let mut this = AppState::get_mut();
let windows = match this.state_mut() { let windows = match this.state_mut() {
AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows), AppStateImpl::Launching { queued_windows, .. } => mem::take(queued_windows),
s => bug!("unexpected state {:?}", s), s => bug!("unexpected state {:?}", s),
@ -541,7 +544,7 @@ pub unsafe fn did_finish_launching() {
// completed. This may result in incorrect visual appearance. // completed. This may result in incorrect visual appearance.
// ``` // ```
let screen = window.screen(); let screen = window.screen();
let _: () = msg_send![&window, setScreen: ptr::null::<AnyObject>()]; let _: () = unsafe { msg_send![&window, setScreen: ptr::null::<AnyObject>()] };
window.setScreen(&screen); window.setScreen(&screen);
let controller = window.rootViewController(); let controller = window.rootViewController();
@ -551,13 +554,13 @@ pub unsafe fn did_finish_launching() {
window.makeKeyAndVisible(); window.makeKeyAndVisible();
} }
let (windows, events) = AppState::get_mut().did_finish_launching_transition(); let (windows, events) = AppState::get_mut(mtm).did_finish_launching_transition();
let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents( let events = std::iter::once(EventWrapper::StaticEvent(Event::NewEvents(
StartCause::Init, StartCause::Init,
))) )))
.chain(events); .chain(events);
handle_nonuser_events(events); handle_nonuser_events(mtm, events);
// the above window dance hack, could possibly trigger new windows to be created. // the above window dance hack, could possibly trigger new windows to be created.
// we can just set those windows up normally, as they were created after didFinishLaunching // we can just set those windows up normally, as they were created after didFinishLaunching
@ -566,27 +569,27 @@ pub unsafe fn did_finish_launching() {
} }
} }
// requires main thread
// AppState::did_finish_launching handles the special transition `Init` // AppState::did_finish_launching handles the special transition `Init`
pub unsafe fn handle_wakeup_transition() { pub fn handle_wakeup_transition(mtm: MainThreadMarker) {
let mut this = AppState::get_mut(); let mut this = AppState::get_mut(mtm);
let wakeup_event = match this.wakeup_transition() { let wakeup_event = match this.wakeup_transition() {
None => return, None => return,
Some(wakeup_event) => wakeup_event, Some(wakeup_event) => wakeup_event,
}; };
drop(this); drop(this);
handle_nonuser_event(wakeup_event) handle_nonuser_event(mtm, wakeup_event)
} }
// requires main thread pub(crate) fn handle_nonuser_event(mtm: MainThreadMarker, event: EventWrapper) {
pub(crate) unsafe fn handle_nonuser_event(event: EventWrapper) { handle_nonuser_events(mtm, std::iter::once(event))
handle_nonuser_events(std::iter::once(event))
} }
// requires main thread pub(crate) fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(
pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>(events: I) { mtm: MainThreadMarker,
let mut this = AppState::get_mut(); events: I,
) {
let mut this = AppState::get_mut(mtm);
let (mut event_handler, active_control_flow, processing_redraws) = let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => { UserCallbackTransitionResult::ReentrancyPrevented { queued_events } => {
@ -615,14 +618,14 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
} }
event_handler.handle_nonuser_event(event, &mut control_flow) event_handler.handle_nonuser_event(event, &mut control_flow)
} }
EventWrapper::EventProxy(proxy) => { EventWrapper::ScaleFactorChanged(event) => {
handle_event_proxy(&mut event_handler, control_flow, proxy) handle_hidpi_proxy(&mut event_handler, control_flow, event)
} }
} }
} }
loop { loop {
let mut this = AppState::get_mut(); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback {
ref mut queued_events, ref mut queued_events,
@ -672,17 +675,16 @@ pub(crate) unsafe fn handle_nonuser_events<I: IntoIterator<Item = EventWrapper>>
} }
event_handler.handle_nonuser_event(event, &mut control_flow) event_handler.handle_nonuser_event(event, &mut control_flow)
} }
EventWrapper::EventProxy(proxy) => { EventWrapper::ScaleFactorChanged(event) => {
handle_event_proxy(&mut event_handler, control_flow, proxy) handle_hidpi_proxy(&mut event_handler, control_flow, event)
} }
} }
} }
} }
} }
// requires main thread fn handle_user_events(mtm: MainThreadMarker) {
unsafe fn handle_user_events() { let mut this = AppState::get_mut(mtm);
let mut this = AppState::get_mut();
let mut control_flow = this.control_flow; let mut control_flow = this.control_flow;
let (mut event_handler, active_control_flow, processing_redraws) = let (mut event_handler, active_control_flow, processing_redraws) =
match this.try_user_callback_transition() { match this.try_user_callback_transition() {
@ -703,7 +705,7 @@ unsafe fn handle_user_events() {
event_handler.handle_user_events(&mut control_flow); event_handler.handle_user_events(&mut control_flow);
loop { loop {
let mut this = AppState::get_mut(); let mut this = AppState::get_mut(mtm);
let queued_events = match this.state_mut() { let queued_events = match this.state_mut() {
&mut AppStateImpl::InUserCallback { &mut AppStateImpl::InUserCallback {
ref mut queued_events, ref mut queued_events,
@ -734,8 +736,8 @@ unsafe fn handle_user_events() {
EventWrapper::StaticEvent(event) => { EventWrapper::StaticEvent(event) => {
event_handler.handle_nonuser_event(event, &mut control_flow) event_handler.handle_nonuser_event(event, &mut control_flow)
} }
EventWrapper::EventProxy(proxy) => { EventWrapper::ScaleFactorChanged(event) => {
handle_event_proxy(&mut event_handler, control_flow, proxy) handle_hidpi_proxy(&mut event_handler, control_flow, event)
} }
} }
} }
@ -743,9 +745,8 @@ unsafe fn handle_user_events() {
} }
} }
// requires main thread pub fn handle_main_events_cleared(mtm: MainThreadMarker) {
pub unsafe fn handle_main_events_cleared() { let mut this = AppState::get_mut(mtm);
let mut this = AppState::get_mut();
if !this.has_launched() { if !this.has_launched() {
return; return;
} }
@ -755,9 +756,9 @@ pub unsafe fn handle_main_events_cleared() {
}; };
drop(this); drop(this);
handle_user_events(); handle_user_events(mtm);
let mut this = AppState::get_mut(); let mut this = AppState::get_mut(mtm);
let redraw_events: Vec<EventWrapper> = this let redraw_events: Vec<EventWrapper> = this
.main_events_cleared_transition() .main_events_cleared_transition()
.into_iter() .into_iter()
@ -770,18 +771,16 @@ pub unsafe fn handle_main_events_cleared() {
.collect(); .collect();
drop(this); drop(this);
handle_nonuser_events(redraw_events); handle_nonuser_events(mtm, redraw_events);
handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait)); handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::AboutToWait));
} }
// requires main thread pub fn handle_events_cleared(mtm: MainThreadMarker) {
pub unsafe fn handle_events_cleared() { AppState::get_mut(mtm).events_cleared_transition();
AppState::get_mut().events_cleared_transition();
} }
// requires main thread pub fn terminated(mtm: MainThreadMarker) {
pub unsafe fn terminated() { let mut this = AppState::get_mut(mtm);
let mut this = AppState::get_mut();
let mut event_handler = this.terminated_transition(); let mut event_handler = this.terminated_transition();
let mut control_flow = this.control_flow; let mut control_flow = this.control_flow;
drop(this); drop(this);
@ -789,34 +788,17 @@ pub unsafe fn terminated() {
event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow) event_handler.handle_nonuser_event(Event::LoopExiting, &mut control_flow)
} }
fn handle_event_proxy(
event_handler: &mut Box<dyn EventHandler>,
control_flow: ControlFlow,
proxy: EventProxy,
) {
match proxy {
EventProxy::DpiChangedProxy {
suggested_size,
scale_factor,
window,
} => handle_hidpi_proxy(
event_handler,
control_flow,
suggested_size,
scale_factor,
window,
),
}
}
fn handle_hidpi_proxy( fn handle_hidpi_proxy(
event_handler: &mut Box<dyn EventHandler>, event_handler: &mut Box<dyn EventHandler>,
mut control_flow: ControlFlow, mut control_flow: ControlFlow,
suggested_size: LogicalSize<f64>, event: ScaleFactorChanged,
scale_factor: f64,
window: Id<WinitUIWindow>,
) { ) {
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor))); let ScaleFactorChanged {
suggested_size,
scale_factor,
window,
} = event;
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let event = Event::WindowEvent { let event = Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged { event: WindowEvent::ScaleFactorChanged {

View file

@ -15,12 +15,10 @@ use core_foundation::runloop::{
CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp, CFRunLoopSourceInvalidate, CFRunLoopSourceRef, CFRunLoopSourceSignal, CFRunLoopWakeUp,
}; };
use icrate::Foundation::{MainThreadMarker, NSString}; use icrate::Foundation::{MainThreadMarker, NSString};
use objc2::rc::Id;
use objc2::ClassType; use objc2::ClassType;
use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle}; use raw_window_handle::{RawDisplayHandle, UiKitDisplayHandle};
use crate::{ use crate::{
dpi::LogicalSize,
error::EventLoopError, error::EventLoopError,
event::Event, event::Event,
event_loop::{ event_loop::{
@ -30,38 +28,21 @@ use crate::{
}; };
use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen}; use super::uikit::{UIApplication, UIApplicationMain, UIDevice, UIScreen};
use super::view::WinitUIWindow;
use super::{app_state, monitor, view, MonitorHandle}; use super::{app_state, monitor, view, MonitorHandle};
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<Never>),
EventProxy(EventProxy),
}
#[derive(Debug, PartialEq)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitUIWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
receiver: Receiver<T>, pub(super) mtm: MainThreadMarker,
sender_to_clone: Sender<T>, p: PhantomData<T>,
} }
impl<T: 'static> EventLoopWindowTarget<T> { impl<T: 'static> EventLoopWindowTarget<T> {
pub fn available_monitors(&self) -> VecDeque<MonitorHandle> { pub fn available_monitors(&self) -> VecDeque<MonitorHandle> {
monitor::uiscreens(MainThreadMarker::new().unwrap()) monitor::uiscreens(self.mtm)
} }
pub fn primary_monitor(&self) -> Option<MonitorHandle> { pub fn primary_monitor(&self) -> Option<MonitorHandle> {
Some(MonitorHandle::new(UIScreen::main( Some(MonitorHandle::new(UIScreen::main(self.mtm)))
MainThreadMarker::new().unwrap(),
)))
} }
pub fn raw_display_handle(&self) -> RawDisplayHandle { pub fn raw_display_handle(&self) -> RawDisplayHandle {
@ -70,6 +51,9 @@ impl<T: 'static> EventLoopWindowTarget<T> {
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
mtm: MainThreadMarker,
sender: Sender<T>,
receiver: Receiver<T>,
window_target: RootEventLoopWindowTarget<T>, window_target: RootEventLoopWindowTarget<T>,
} }
@ -80,7 +64,8 @@ impl<T: 'static> EventLoop<T> {
pub(crate) fn new( pub(crate) fn new(
_: &PlatformSpecificEventLoopAttributes, _: &PlatformSpecificEventLoopAttributes,
) -> Result<EventLoop<T>, EventLoopError> { ) -> Result<EventLoop<T>, EventLoopError> {
assert_main_thread!("`EventLoop` can only be created on the main thread on iOS"); let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
static mut SINGLETON_INIT: bool = false; static mut SINGLETON_INIT: bool = false;
unsafe { unsafe {
@ -92,16 +77,19 @@ impl<T: 'static> EventLoop<T> {
SINGLETON_INIT = true; SINGLETON_INIT = true;
} }
let (sender_to_clone, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
// this line sets up the main run loop before `UIApplicationMain` // this line sets up the main run loop before `UIApplicationMain`
setup_control_flow_observers(); setup_control_flow_observers();
Ok(EventLoop { Ok(EventLoop {
mtm,
sender,
receiver,
window_target: RootEventLoopWindowTarget { window_target: RootEventLoopWindowTarget {
p: EventLoopWindowTarget { p: EventLoopWindowTarget {
receiver, mtm,
sender_to_clone, p: PhantomData,
}, },
_marker: PhantomData, _marker: PhantomData,
}, },
@ -113,7 +101,7 @@ impl<T: 'static> EventLoop<T> {
F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow), F: FnMut(Event<T>, &RootEventLoopWindowTarget<T>, &mut ControlFlow),
{ {
unsafe { unsafe {
let application = UIApplication::shared(MainThreadMarker::new().unwrap()); let application = UIApplication::shared(self.mtm);
assert!( assert!(
application.is_none(), application.is_none(),
"\ "\
@ -128,10 +116,11 @@ impl<T: 'static> EventLoop<T> {
let handler = EventLoopHandler { let handler = EventLoopHandler {
f: event_handler, f: event_handler,
receiver: self.receiver,
event_loop: self.window_target, event_loop: self.window_target,
}; };
app_state::will_launch(Box::new(handler)); app_state::will_launch(self.mtm, Box::new(handler));
// Ensure application delegate is initialized // Ensure application delegate is initialized
view::WinitApplicationDelegate::class(); view::WinitApplicationDelegate::class();
@ -147,7 +136,7 @@ impl<T: 'static> EventLoop<T> {
} }
pub fn create_proxy(&self) -> EventLoopProxy<T> { pub fn create_proxy(&self) -> EventLoopProxy<T> {
EventLoopProxy::new(self.window_target.p.sender_to_clone.clone()) EventLoopProxy::new(self.sender.clone())
} }
pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> { pub fn window_target(&self) -> &RootEventLoopWindowTarget<T> {
@ -158,9 +147,7 @@ impl<T: 'static> EventLoop<T> {
// EventLoopExtIOS // EventLoopExtIOS
impl<T: 'static> EventLoop<T> { impl<T: 'static> EventLoop<T> {
pub fn idiom(&self) -> Idiom { pub fn idiom(&self) -> Idiom {
UIDevice::current(MainThreadMarker::new().unwrap()) UIDevice::current(self.mtm).userInterfaceIdiom().into()
.userInterfaceIdiom()
.into()
} }
} }
@ -238,12 +225,11 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
unsafe { let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(), kCFRunLoopAfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
@ -263,13 +249,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
unsafe { let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(), kCFRunLoopBeforeWaiting => app_state::handle_main_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
@ -279,13 +264,12 @@ fn setup_control_flow_observers() {
activity: CFRunLoopActivity, activity: CFRunLoopActivity,
_: *mut c_void, _: *mut c_void,
) { ) {
unsafe { let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)] #[allow(non_upper_case_globals)]
match activity { match activity {
kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(), kCFRunLoopBeforeWaiting => app_state::handle_events_cleared(mtm),
kCFRunLoopExit => unimplemented!(), // not expected to ever happen kCFRunLoopExit => unimplemented!(), // not expected to ever happen
_ => unreachable!(), _ => unreachable!(),
}
} }
} }
@ -336,6 +320,7 @@ pub trait EventHandler: Debug {
struct EventLoopHandler<T: 'static> { struct EventLoopHandler<T: 'static> {
f: Box<EventHandlerCallback<T>>, f: Box<EventHandlerCallback<T>>,
receiver: Receiver<T>,
event_loop: RootEventLoopWindowTarget<T>, event_loop: RootEventLoopWindowTarget<T>,
} }
@ -357,7 +342,7 @@ impl<T: 'static> EventHandler for EventLoopHandler<T> {
} }
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
for event in self.event_loop.p.receiver.try_iter() { for event in self.receiver.try_iter() {
(self.f)(Event::UserEvent(event), &self.event_loop, control_flow); (self.f)(Event::UserEvent(event), &self.event_loop, control_flow);
} }
} }

View file

@ -58,17 +58,6 @@
#![cfg(ios_platform)] #![cfg(ios_platform)]
#![allow(clippy::let_unit_value)] #![allow(clippy::let_unit_value)]
// TODO: (mtak-) UIKit requires main thread for virtually all function/method calls. This could be
// worked around in the future by using GCD (grand central dispatch) and/or caching of values like
// window size/position.
macro_rules! assert_main_thread {
($($t:tt)*) => {
if !::icrate::Foundation::is_main_thread() {
panic!($($t)*);
}
};
}
mod app_state; mod app_state;
mod event_loop; mod event_loop;
mod ffi; mod ffi;

View file

@ -2,11 +2,11 @@
use std::{ use std::{
collections::{BTreeSet, VecDeque}, collections::{BTreeSet, VecDeque},
fmt, fmt, hash, ptr,
ops::{Deref, DerefMut},
}; };
use icrate::Foundation::{MainThreadMarker, NSInteger}; use icrate::Foundation::{MainThreadBound, MainThreadMarker, NSInteger};
use objc2::mutability::IsRetainable;
use objc2::rc::Id; use objc2::rc::Id;
use super::uikit::{UIScreen, UIScreenMode}; use super::uikit::{UIScreen, UIScreenMode};
@ -16,32 +16,59 @@ use crate::{
platform_impl::platform::app_state, platform_impl::platform::app_state,
}; };
// TODO(madsmtm): Remove or refactor this // Workaround for `MainThreadBound` implementing almost no traits
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug)]
pub(crate) struct ScreenModeSendSync(pub(crate) Id<UIScreenMode>); struct MainThreadBoundDelegateImpls<T>(MainThreadBound<Id<T>>);
unsafe impl Send for ScreenModeSendSync {} impl<T: IsRetainable> Clone for MainThreadBoundDelegateImpls<T> {
unsafe impl Sync for ScreenModeSendSync {} fn clone(&self) -> Self {
Self(
self.0
.get_on_main(|inner, mtm| MainThreadBound::new(Id::clone(inner), mtm)),
)
}
}
impl<T: IsRetainable> hash::Hash for MainThreadBoundDelegateImpls<T> {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)).hash(state);
}
}
impl<T: IsRetainable> PartialEq for MainThreadBoundDelegateImpls<T> {
fn eq(&self, other: &Self) -> bool {
// SAFETY: Marker only used to get the pointer
let mtm = unsafe { MainThreadMarker::new_unchecked() };
Id::as_ptr(self.0.get(mtm)) == Id::as_ptr(other.0.get(mtm))
}
}
impl<T: IsRetainable> Eq for MainThreadBoundDelegateImpls<T> {}
#[derive(Debug, PartialEq, Eq, Hash, Clone)] #[derive(Debug, PartialEq, Eq, Hash, Clone)]
pub struct VideoMode { pub struct VideoMode {
pub(crate) size: (u32, u32), pub(crate) size: (u32, u32),
pub(crate) bit_depth: u16, pub(crate) bit_depth: u16,
pub(crate) refresh_rate_millihertz: u32, pub(crate) refresh_rate_millihertz: u32,
pub(crate) screen_mode: ScreenModeSendSync, screen_mode: MainThreadBoundDelegateImpls<UIScreenMode>,
pub(crate) monitor: MonitorHandle, pub(crate) monitor: MonitorHandle,
} }
impl VideoMode { impl VideoMode {
fn new(uiscreen: Id<UIScreen>, screen_mode: Id<UIScreenMode>) -> VideoMode { fn new(
assert_main_thread!("`VideoMode` can only be created on the main thread on iOS"); uiscreen: Id<UIScreen>,
screen_mode: Id<UIScreenMode>,
mtm: MainThreadMarker,
) -> VideoMode {
let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen); let refresh_rate_millihertz = refresh_rate_millihertz(&uiscreen);
let size = screen_mode.size(); let size = screen_mode.size();
VideoMode { VideoMode {
size: (size.width as u32, size.height as u32), size: (size.width as u32, size.height as u32),
bit_depth: 32, bit_depth: 32,
refresh_rate_millihertz, refresh_rate_millihertz,
screen_mode: ScreenModeSendSync(screen_mode), screen_mode: MainThreadBoundDelegateImpls(MainThreadBound::new(screen_mode, mtm)),
monitor: MonitorHandle::new(uiscreen), monitor: MonitorHandle::new(uiscreen),
} }
} }
@ -61,18 +88,40 @@ impl VideoMode {
pub fn monitor(&self) -> MonitorHandle { pub fn monitor(&self) -> MonitorHandle {
self.monitor.clone() self.monitor.clone()
} }
pub(super) fn screen_mode(&self, mtm: MainThreadMarker) -> &Id<UIScreenMode> {
self.screen_mode.0.get(mtm)
}
} }
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Inner {
uiscreen: Id<UIScreen>,
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MonitorHandle { pub struct MonitorHandle {
inner: Inner, ui_screen: MainThreadBound<Id<UIScreen>>,
} }
impl Clone for MonitorHandle {
fn clone(&self) -> Self {
Self {
ui_screen: self
.ui_screen
.get_on_main(|inner, mtm| MainThreadBound::new(inner.clone(), mtm)),
}
}
}
impl hash::Hash for MonitorHandle {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
(self as *const Self).hash(state);
}
}
impl PartialEq for MonitorHandle {
fn eq(&self, other: &Self) -> bool {
ptr::eq(self, other)
}
}
impl Eq for MonitorHandle {}
impl PartialOrd for MonitorHandle { impl PartialOrd for MonitorHandle {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other)) Some(self.cmp(other))
@ -86,31 +135,6 @@ impl Ord for MonitorHandle {
} }
} }
impl Deref for MonitorHandle {
type Target = Inner;
fn deref(&self) -> &Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&self.inner
}
}
impl DerefMut for MonitorHandle {
fn deref_mut(&mut self) -> &mut Inner {
assert_main_thread!("`MonitorHandle` methods can only be run on the main thread on iOS");
&mut self.inner
}
}
unsafe impl Send for MonitorHandle {}
unsafe impl Sync for MonitorHandle {}
impl Drop for MonitorHandle {
fn drop(&mut self) {
assert_main_thread!("`MonitorHandle` can only be dropped on the main thread on iOS");
}
}
impl fmt::Debug for MonitorHandle { impl fmt::Debug for MonitorHandle {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
// TODO: Do this using the proper fmt API // TODO: Do this using the proper fmt API
@ -135,59 +159,80 @@ impl fmt::Debug for MonitorHandle {
} }
impl MonitorHandle { impl MonitorHandle {
pub(crate) fn new(uiscreen: Id<UIScreen>) -> Self { pub(crate) fn new(ui_screen: Id<UIScreen>) -> Self {
assert_main_thread!("`MonitorHandle` can only be created on the main thread on iOS"); // Holding `Id<UIScreen>` implies we're on the main thread.
let mtm = MainThreadMarker::new().unwrap();
Self { Self {
inner: Inner { uiscreen }, ui_screen: MainThreadBound::new(ui_screen, mtm),
} }
} }
}
impl Inner {
pub fn name(&self) -> Option<String> { pub fn name(&self) -> Option<String> {
let main = UIScreen::main(MainThreadMarker::new().unwrap()); self.ui_screen.get_on_main(|ui_screen, mtm| {
if self.uiscreen == main { let main = UIScreen::main(mtm);
Some("Primary".to_string()) if *ui_screen == main {
} else if self.uiscreen == main.mirroredScreen() { Some("Primary".to_string())
Some("Mirrored".to_string()) } else if *ui_screen == main.mirroredScreen() {
} else { Some("Mirrored".to_string())
UIScreen::screens(MainThreadMarker::new().unwrap()) } else {
.iter() UIScreen::screens(mtm)
.position(|rhs| rhs == &*self.uiscreen) .iter()
.map(|idx| idx.to_string()) .position(|rhs| rhs == &**ui_screen)
} .map(|idx| idx.to_string())
}
})
} }
pub fn size(&self) -> PhysicalSize<u32> { pub fn size(&self) -> PhysicalSize<u32> {
let bounds = self.uiscreen.nativeBounds(); let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32) PhysicalSize::new(bounds.size.width as u32, bounds.size.height as u32)
} }
pub fn position(&self) -> PhysicalPosition<i32> { pub fn position(&self) -> PhysicalPosition<i32> {
let bounds = self.uiscreen.nativeBounds(); let bounds = self
.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeBounds());
(bounds.origin.x as f64, bounds.origin.y as f64).into() (bounds.origin.x as f64, bounds.origin.y as f64).into()
} }
pub fn scale_factor(&self) -> f64 { pub fn scale_factor(&self) -> f64 {
self.uiscreen.nativeScale() as f64 self.ui_screen
.get_on_main(|ui_screen, _| ui_screen.nativeScale()) as f64
} }
pub fn refresh_rate_millihertz(&self) -> Option<u32> { pub fn refresh_rate_millihertz(&self) -> Option<u32> {
Some(refresh_rate_millihertz(&self.uiscreen)) Some(
self.ui_screen
.get_on_main(|ui_screen, _| refresh_rate_millihertz(ui_screen)),
)
} }
pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> { pub fn video_modes(&self) -> impl Iterator<Item = VideoMode> {
// Use Ord impl of RootVideoMode self.ui_screen.get_on_main(|ui_screen, mtm| {
let modes: BTreeSet<_> = self // Use Ord impl of RootVideoMode
.uiscreen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(self.uiscreen.clone(), mode),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode) let modes: BTreeSet<_> = ui_screen
.availableModes()
.into_iter()
.map(|mode| RootVideoMode {
video_mode: VideoMode::new(ui_screen.clone(), mode, mtm),
})
.collect();
modes.into_iter().map(|mode| mode.video_mode)
})
}
pub(crate) fn ui_screen(&self, mtm: MainThreadMarker) -> &Id<UIScreen> {
self.ui_screen.get(mtm)
}
pub fn preferred_video_mode(&self) -> VideoMode {
self.ui_screen.get_on_main(|ui_screen, mtm| {
VideoMode::new(ui_screen.clone(), ui_screen.preferredMode().unwrap(), mtm)
})
} }
} }
@ -215,20 +260,6 @@ fn refresh_rate_millihertz(uiscreen: &UIScreen) -> u32 {
refresh_rate_millihertz as u32 * 1000 refresh_rate_millihertz as u32 * 1000
} }
// MonitorHandleExtIOS
impl Inner {
pub(crate) fn ui_screen(&self) -> &Id<UIScreen> {
&self.uiscreen
}
pub fn preferred_video_mode(&self) -> VideoMode {
VideoMode::new(
self.uiscreen.clone(),
self.uiscreen.preferredMode().unwrap(),
)
}
}
pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> { pub fn uiscreens(mtm: MainThreadMarker) -> VecDeque<MonitorHandle> {
UIScreen::screens(mtm) UIScreen::screens(mtm)
.into_iter() .into_iter()

View file

@ -8,6 +8,7 @@ use objc2::rc::Id;
use objc2::runtime::AnyClass; use objc2::runtime::AnyClass;
use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType}; use objc2::{declare_class, extern_methods, msg_send, msg_send_id, mutability, ClassType};
use super::app_state::{self, EventWrapper};
use super::uikit::{ use super::uikit::{
UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask, UIApplication, UIDevice, UIEvent, UIForceTouchCapability, UIInterfaceOrientationMask,
UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController, UIResponder, UITouch, UITouchPhase, UITouchType, UITraitCollection, UIView, UIViewController,
@ -19,8 +20,6 @@ use crate::{
event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent}, event::{DeviceId as RootDeviceId, Event, Force, Touch, TouchPhase, WindowEvent},
platform::ios::ValidOrientations, platform::ios::ValidOrientations,
platform_impl::platform::{ platform_impl::platform::{
app_state,
event_loop::{EventProxy, EventWrapper},
ffi::{UIRectEdge, UIUserInterfaceIdiom}, ffi::{UIRectEdge, UIUserInterfaceIdiom},
window::PlatformSpecificWindowBuilderAttributes, window::PlatformSpecificWindowBuilderAttributes,
DeviceId, Fullscreen, DeviceId, Fullscreen,
@ -41,18 +40,21 @@ declare_class!(
unsafe impl WinitView { unsafe impl WinitView {
#[method(drawRect:)] #[method(drawRect:)]
fn draw_rect(&self, rect: CGRect) { fn draw_rect(&self, rect: CGRect) {
let mtm = MainThreadMarker::new().unwrap();
let window = self.window().unwrap(); let window = self.window().unwrap();
unsafe { app_state::handle_nonuser_event(
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::RedrawRequested, event: WindowEvent::RedrawRequested,
})); }),
} );
let _: () = unsafe { msg_send![super(self), drawRect: rect] }; let _: () = unsafe { msg_send![super(self), drawRect: rect] };
} }
#[method(layoutSubviews)] #[method(layoutSubviews)]
fn layout_subviews(&self) { fn layout_subviews(&self) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = unsafe { msg_send![super(self), layoutSubviews] }; let _: () = unsafe { msg_send![super(self), layoutSubviews] };
let window = self.window().unwrap(); let window = self.window().unwrap();
@ -75,16 +77,18 @@ declare_class!(
self.setFrame(window_bounds); self.setFrame(window_bounds);
} }
unsafe { app_state::handle_nonuser_event(
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(window.id()), window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(size), event: WindowEvent::Resized(size),
})); }),
} );
} }
#[method(setContentScaleFactor:)] #[method(setContentScaleFactor:)]
fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) { fn set_content_scale_factor(&self, untrusted_scale_factor: CGFloat) {
let mtm = MainThreadMarker::new().unwrap();
let _: () = let _: () =
unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] }; unsafe { msg_send![super(self), setContentScaleFactor: untrusted_scale_factor] };
@ -112,25 +116,26 @@ declare_class!(
let screen_space = screen.coordinateSpace(); let screen_space = screen.coordinateSpace();
let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space); let screen_frame = self.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _, width: screen_frame.size.width as f64,
height: screen_frame.size.height as _, height: screen_frame.size.height as f64,
}; };
let window_id = RootWindowId(window.id()); let window_id = RootWindowId(window.id());
unsafe { app_state::handle_nonuser_events(
app_state::handle_nonuser_events( mtm,
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window, window,
scale_factor, scale_factor,
suggested_size: size, suggested_size: size.to_physical(scale_factor),
})) },
.chain(std::iter::once(EventWrapper::StaticEvent( ))
Event::WindowEvent { .chain(std::iter::once(EventWrapper::StaticEvent(
window_id, Event::WindowEvent {
event: WindowEvent::Resized(size.to_physical(scale_factor)), window_id,
}, event: WindowEvent::Resized(size.to_physical(scale_factor)),
))), },
); ))),
} );
} }
#[method(touchesBegan:withEvent:)] #[method(touchesBegan:withEvent:)]
@ -255,9 +260,8 @@ impl WinitView {
}), }),
})); }));
} }
unsafe { let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(touch_events); app_state::handle_nonuser_events(mtm, touch_events);
}
} }
} }
@ -431,23 +435,27 @@ declare_class!(
unsafe impl WinitUIWindow { unsafe impl WinitUIWindow {
#[method(becomeKeyWindow)] #[method(becomeKeyWindow)]
fn become_key_window(&self) { fn become_key_window(&self) {
unsafe { let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()), window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(true), event: WindowEvent::Focused(true),
})); }),
} );
let _: () = unsafe { msg_send![super(self), becomeKeyWindow] }; let _: () = unsafe { msg_send![super(self), becomeKeyWindow] };
} }
#[method(resignKeyWindow)] #[method(resignKeyWindow)]
fn resign_key_window(&self) { fn resign_key_window(&self) {
unsafe { let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { app_state::handle_nonuser_event(
mtm,
EventWrapper::StaticEvent(Event::WindowEvent {
window_id: RootWindowId(self.id()), window_id: RootWindowId(self.id()),
event: WindowEvent::Focused(false), event: WindowEvent::Focused(false),
})); }),
} );
let _: () = unsafe { msg_send![super(self), resignKeyWindow] }; let _: () = unsafe { msg_send![super(self), resignKeyWindow] };
} }
} }
@ -455,7 +463,7 @@ declare_class!(
impl WinitUIWindow { impl WinitUIWindow {
pub(crate) fn new( pub(crate) fn new(
_mtm: MainThreadMarker, mtm: MainThreadMarker,
window_attributes: &WindowAttributes, window_attributes: &WindowAttributes,
_platform_attributes: &PlatformSpecificWindowBuilderAttributes, _platform_attributes: &PlatformSpecificWindowBuilderAttributes,
frame: CGRect, frame: CGRect,
@ -468,12 +476,12 @@ impl WinitUIWindow {
match window_attributes.fullscreen.clone().map(Into::into) { match window_attributes.fullscreen.clone().map(Into::into) {
Some(Fullscreen::Exclusive(ref video_mode)) => { Some(Fullscreen::Exclusive(ref video_mode)) => {
let monitor = video_mode.monitor(); let monitor = video_mode.monitor();
let screen = monitor.ui_screen(); let screen = monitor.ui_screen(mtm);
screen.setCurrentMode(Some(&video_mode.screen_mode.0)); screen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
this.setScreen(screen); this.setScreen(screen);
} }
Some(Fullscreen::Borderless(Some(ref monitor))) => { Some(Fullscreen::Borderless(Some(ref monitor))) => {
let screen = monitor.ui_screen(); let screen = monitor.ui_screen(mtm);
this.setScreen(screen); this.setScreen(screen);
} }
_ => (), _ => (),
@ -500,20 +508,20 @@ declare_class!(
unsafe impl WinitApplicationDelegate { unsafe impl WinitApplicationDelegate {
#[method(application:didFinishLaunchingWithOptions:)] #[method(application:didFinishLaunchingWithOptions:)]
fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool { fn did_finish_launching(&self, _application: &UIApplication, _: *mut NSObject) -> bool {
unsafe { app_state::did_finish_launching(MainThreadMarker::new().unwrap());
app_state::did_finish_launching();
}
true true
} }
#[method(applicationDidBecomeActive:)] #[method(applicationDidBecomeActive:)]
fn did_become_active(&self, _application: &UIApplication) { fn did_become_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)) } let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Resumed))
} }
#[method(applicationWillResignActive:)] #[method(applicationWillResignActive:)]
fn will_resign_active(&self, _application: &UIApplication) { fn will_resign_active(&self, _application: &UIApplication) {
unsafe { app_state::handle_nonuser_event(EventWrapper::StaticEvent(Event::Suspended)) } let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_event(mtm, EventWrapper::StaticEvent(Event::Suspended))
} }
#[method(applicationWillEnterForeground:)] #[method(applicationWillEnterForeground:)]
@ -539,10 +547,9 @@ declare_class!(
})); }));
} }
} }
unsafe { let mtm = MainThreadMarker::new().unwrap();
app_state::handle_nonuser_events(events); app_state::handle_nonuser_events(mtm, events);
app_state::terminated(); app_state::terminated(mtm);
}
} }
} }
); );

View file

@ -8,6 +8,7 @@ use objc2::runtime::AnyObject;
use objc2::{class, msg_send}; use objc2::{class, msg_send};
use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle}; use raw_window_handle::{RawDisplayHandle, RawWindowHandle, UiKitDisplayHandle, UiKitWindowHandle};
use super::app_state::EventWrapper;
use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation}; use super::uikit::{UIApplication, UIScreen, UIScreenOverscanCompensation};
use super::view::{WinitUIWindow, WinitView, WinitViewController}; use super::view::{WinitUIWindow, WinitView, WinitViewController};
use crate::{ use crate::{
@ -17,9 +18,7 @@ use crate::{
icon::Icon, icon::Icon,
platform::ios::{ScreenEdge, ValidOrientations}, platform::ios::{ScreenEdge, ValidOrientations},
platform_impl::platform::{ platform_impl::platform::{
app_state, app_state, monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
event_loop::{EventProxy, EventWrapper},
monitor, EventLoopWindowTarget, Fullscreen, MonitorHandle,
}, },
window::{ window::{
CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType, CursorGrabMode, CursorIcon, ImePurpose, ResizeDirection, Theme, UserAttentionType,
@ -53,20 +52,19 @@ impl Inner {
} }
pub fn request_redraw(&self) { pub fn request_redraw(&self) {
unsafe { if self.gl_or_metal_backed {
if self.gl_or_metal_backed { let mtm = MainThreadMarker::new().unwrap();
// `setNeedsDisplay` does nothing on UIViews which are directly backed by CAEAGLLayer or CAMetalLayer. // `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 // 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. // 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 // The docs for `setNeedsDisplay` don't mention `CAMetalLayer`; however, this has been confirmed via
// testing. // testing.
// //
// https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc // https://developer.apple.com/documentation/uikit/uiview/1622437-setneedsdisplay?language=objc
app_state::queue_gl_or_metal_redraw(self.window.clone()); app_state::queue_gl_or_metal_redraw(mtm, self.window.clone());
} else { } else {
self.view.setNeedsDisplay(); self.view.setNeedsDisplay();
}
} }
} }
@ -219,14 +217,17 @@ impl Inner {
} }
pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) { pub(crate) fn set_fullscreen(&self, monitor: Option<Fullscreen>) {
let mtm = MainThreadMarker::new().unwrap();
let uiscreen = match &monitor { let uiscreen = match &monitor {
Some(Fullscreen::Exclusive(video_mode)) => { Some(Fullscreen::Exclusive(video_mode)) => {
let uiscreen = video_mode.monitor.ui_screen(); let uiscreen = video_mode.monitor.ui_screen(mtm);
uiscreen.setCurrentMode(Some(&video_mode.screen_mode.0)); uiscreen.setCurrentMode(Some(video_mode.screen_mode(mtm)));
uiscreen.clone() uiscreen.clone()
} }
Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen().clone(), Some(Fullscreen::Borderless(Some(monitor))) => monitor.ui_screen(mtm).clone(),
Some(Fullscreen::Borderless(None)) => self.current_monitor_inner().ui_screen().clone(), Some(Fullscreen::Borderless(None)) => {
self.current_monitor_inner().ui_screen(mtm).clone()
}
None => { None => {
warn!("`Window::set_fullscreen(None)` ignored on iOS"); warn!("`Window::set_fullscreen(None)` ignored on iOS");
return; return;
@ -249,8 +250,9 @@ impl Inner {
} }
pub(crate) fn fullscreen(&self) -> Option<Fullscreen> { pub(crate) fn fullscreen(&self) -> Option<Fullscreen> {
let mtm = MainThreadMarker::new().unwrap();
let monitor = self.current_monitor_inner(); let monitor = self.current_monitor_inner();
let uiscreen = monitor.ui_screen(); let uiscreen = monitor.ui_screen(mtm);
let screen_space_bounds = self.screen_frame(); let screen_space_bounds = self.screen_frame();
let screen_bounds = uiscreen.bounds(); let screen_bounds = uiscreen.bounds();
@ -365,11 +367,11 @@ pub struct Window {
impl Window { impl Window {
pub(crate) fn new<T>( pub(crate) fn new<T>(
_event_loop: &EventLoopWindowTarget<T>, event_loop: &EventLoopWindowTarget<T>,
window_attributes: WindowAttributes, window_attributes: WindowAttributes,
platform_attributes: PlatformSpecificWindowBuilderAttributes, platform_attributes: PlatformSpecificWindowBuilderAttributes,
) -> Result<Window, RootOsError> { ) -> Result<Window, RootOsError> {
let mtm = MainThreadMarker::new().unwrap(); let mtm = event_loop.mtm;
if window_attributes.min_inner_size.is_some() { if window_attributes.min_inner_size.is_some() {
warn!("`WindowAttributes::min_inner_size` is ignored on iOS"); warn!("`WindowAttributes::min_inner_size` is ignored on iOS");
@ -383,8 +385,8 @@ impl Window {
let main_screen = UIScreen::main(mtm); let main_screen = UIScreen::main(mtm);
let fullscreen = window_attributes.fullscreen.clone().map(Into::into); let fullscreen = window_attributes.fullscreen.clone().map(Into::into);
let screen = match fullscreen { let screen = match fullscreen {
Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(), Some(Fullscreen::Exclusive(ref video_mode)) => video_mode.monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(), Some(Fullscreen::Borderless(Some(ref monitor))) => monitor.ui_screen(mtm),
Some(Fullscreen::Borderless(None)) | None => &main_screen, Some(Fullscreen::Borderless(None)) | None => &main_screen,
}; };
@ -424,7 +426,7 @@ impl Window {
&view_controller, &view_controller,
); );
unsafe { app_state::set_key_window(&window) }; app_state::set_key_window(mtm, &window);
// Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized` // Like the Windows and macOS backends, we send a `ScaleFactorChanged` and `Resized`
// event on window creation if the DPI factor != 1.0 // event on window creation if the DPI factor != 1.0
@ -436,25 +438,26 @@ impl Window {
let screen_space = screen.coordinateSpace(); let screen_space = screen.coordinateSpace();
let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space); let screen_frame = view.convertRect_toCoordinateSpace(bounds, &screen_space);
let size = crate::dpi::LogicalSize { let size = crate::dpi::LogicalSize {
width: screen_frame.size.width as _, width: screen_frame.size.width as f64,
height: screen_frame.size.height as _, height: screen_frame.size.height as f64,
}; };
let window_id = RootWindowId(window.id()); let window_id = RootWindowId(window.id());
unsafe { app_state::handle_nonuser_events(
app_state::handle_nonuser_events( mtm,
std::iter::once(EventWrapper::EventProxy(EventProxy::DpiChangedProxy { std::iter::once(EventWrapper::ScaleFactorChanged(
app_state::ScaleFactorChanged {
window: window.clone(), window: window.clone(),
scale_factor, scale_factor,
suggested_size: size, suggested_size: size.to_physical(scale_factor),
})) },
.chain(std::iter::once(EventWrapper::StaticEvent( ))
Event::WindowEvent { .chain(std::iter::once(EventWrapper::StaticEvent(
window_id, Event::WindowEvent {
event: WindowEvent::Resized(size.to_physical(scale_factor)), window_id,
}, event: WindowEvent::Resized(size.to_physical(scale_factor)),
))), },
); ))),
} );
} }
let inner = Inner { let inner = Inner {

View file

@ -4,7 +4,7 @@ use icrate::Foundation::NSObject;
use objc2::{declare_class, msg_send, mutability, ClassType}; use objc2::{declare_class, msg_send, mutability, ClassType};
use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder}; use super::appkit::{NSApplication, NSEvent, NSEventModifierFlags, NSEventType, NSResponder};
use super::{app_state::AppState, event::EventWrapper, DEVICE_ID}; use super::{app_state::AppState, DEVICE_ID};
use crate::event::{DeviceEvent, ElementState, Event}; use crate::event::{DeviceEvent, ElementState, Event};
declare_class!( declare_class!(
@ -96,5 +96,5 @@ fn queue_device_event(event: DeviceEvent) {
device_id: DEVICE_ID, device_id: DEVICE_ID,
event, event,
}; };
AppState::queue_event(EventWrapper::StaticEvent(event)); AppState::queue_event(event);
} }

View file

@ -6,29 +6,24 @@ use std::{
rc::{Rc, Weak}, rc::{Rc, Weak},
sync::{ sync::{
atomic::{AtomicBool, Ordering}, atomic::{AtomicBool, Ordering},
Arc, Mutex, MutexGuard, mpsc, Arc, Mutex, MutexGuard,
}, },
time::Instant, time::Instant,
}; };
use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp}; use core_foundation::runloop::{CFRunLoopGetMain, CFRunLoopWakeUp};
use icrate::Foundation::{is_main_thread, NSSize}; use icrate::Foundation::{is_main_thread, NSSize};
use objc2::rc::autoreleasepool; use objc2::rc::{autoreleasepool, Id};
use once_cell::sync::Lazy; use once_cell::sync::Lazy;
use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent}; use super::appkit::{NSApp, NSApplication, NSApplicationActivationPolicy, NSEvent};
use super::{
event_loop::PanicInfo, menu, observer::EventLoopWaker, util::Never, window::WinitWindow,
};
use crate::{ use crate::{
dpi::LogicalSize, dpi::PhysicalSize,
event::{Event, InnerSizeWriter, StartCause, WindowEvent}, event::{Event, InnerSizeWriter, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget}, event_loop::{ControlFlow, EventLoopWindowTarget as RootWindowTarget},
platform_impl::platform::{
event::{EventProxy, EventWrapper},
event_loop::PanicInfo,
menu,
observer::EventLoopWaker,
util::Never,
window::WinitWindow,
},
window::WindowId, window::WindowId,
}; };
@ -54,6 +49,7 @@ pub(crate) type Callback<T> = RefCell<dyn FnMut(Event<T>, &RootWindowTarget<T>,
struct EventLoopHandler<T: 'static> { struct EventLoopHandler<T: 'static> {
callback: Weak<Callback<T>>, callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
} }
impl<T> EventLoopHandler<T> { impl<T> EventLoopHandler<T> {
@ -103,7 +99,7 @@ impl<T> EventHandler for EventLoopHandler<T> {
fn handle_user_events(&mut self, control_flow: &mut ControlFlow) { fn handle_user_events(&mut self, control_flow: &mut ControlFlow) {
self.with_callback(|this, mut callback| { self.with_callback(|this, mut callback| {
for event in this.window_target.p.receiver.try_iter() { for event in this.receiver.try_iter() {
if let ControlFlow::ExitWithCode(code) = *control_flow { if let ControlFlow::ExitWithCode(code) = *control_flow {
// XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode? // XXX: why isn't event dispatching simply skipped after control_flow = ExitWithCode?
let dummy = &mut ControlFlow::ExitWithCode(code); let dummy = &mut ControlFlow::ExitWithCode(code);
@ -116,6 +112,16 @@ impl<T> EventHandler for EventLoopHandler<T> {
} }
} }
#[derive(Debug)]
enum EventWrapper {
StaticEvent(Event<Never>),
ScaleFactorChanged {
window: Id<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Default)] #[derive(Default)]
struct Handler { struct Handler {
stop_app_on_launch: AtomicBool, stop_app_on_launch: AtomicBool,
@ -313,14 +319,9 @@ impl Handler {
self.callback.lock().unwrap().is_some() self.callback.lock().unwrap().is_some()
} }
fn handle_nonuser_event(&self, wrapper: EventWrapper) { fn handle_nonuser_event(&self, event: Event<Never>) {
if let Some(ref mut callback) = *self.callback.lock().unwrap() { if let Some(ref mut callback) = *self.callback.lock().unwrap() {
match wrapper { callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap())
EventWrapper::StaticEvent(event) => {
callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap())
}
EventWrapper::EventProxy(proxy) => self.handle_proxy(proxy, callback),
}
} }
} }
@ -332,41 +333,27 @@ impl Handler {
fn handle_scale_factor_changed_event( fn handle_scale_factor_changed_event(
&self, &self,
callback: &mut Box<dyn EventHandler + 'static>,
window: &WinitWindow, window: &WinitWindow,
suggested_size: LogicalSize<f64>, suggested_size: PhysicalSize<u32>,
scale_factor: f64, scale_factor: f64,
) { ) {
let new_inner_size = Arc::new(Mutex::new(suggested_size.to_physical(scale_factor))); if let Some(ref mut callback) = *self.callback.lock().unwrap() {
let event = Event::WindowEvent { let new_inner_size = Arc::new(Mutex::new(suggested_size));
window_id: WindowId(window.id()), let event = Event::WindowEvent {
event: WindowEvent::ScaleFactorChanged { window_id: WindowId(window.id()),
scale_factor, event: WindowEvent::ScaleFactorChanged {
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)), scale_factor,
}, inner_size_writer: InnerSizeWriter::new(Arc::downgrade(&new_inner_size)),
}; },
};
callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap()); callback.handle_nonuser_event(event, &mut self.control_flow.lock().unwrap());
let physical_size = *new_inner_size.lock().unwrap(); let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size); drop(new_inner_size);
let logical_size = physical_size.to_logical(scale_factor); let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height); let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size); window.setContentSize(size);
}
fn handle_proxy(&self, proxy: EventProxy, callback: &mut Box<dyn EventHandler + 'static>) {
match proxy {
EventProxy::DpiChangedProxy {
window,
suggested_size,
scale_factor,
} => self.handle_scale_factor_changed_event(
callback,
&window,
suggested_size,
scale_factor,
),
} }
} }
} }
@ -387,10 +374,12 @@ impl AppState {
pub unsafe fn set_callback<T>( pub unsafe fn set_callback<T>(
callback: Weak<Callback<T>>, callback: Weak<Callback<T>>,
window_target: Rc<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
receiver: Rc<mpsc::Receiver<T>>,
) { ) {
*HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler { *HANDLER.callback.lock().unwrap() = Some(Box::new(EventLoopHandler {
callback, callback,
window_target, window_target,
receiver,
})); }));
} }
@ -435,7 +424,7 @@ impl AppState {
pub fn exit() -> i32 { pub fn exit() -> i32 {
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::LoopExiting)); HANDLER.handle_nonuser_event(Event::LoopExiting);
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
HANDLER.exit(); HANDLER.exit();
Self::clear_callback(); Self::clear_callback();
@ -448,12 +437,10 @@ impl AppState {
pub fn dispatch_init_events() { pub fn dispatch_init_events() {
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents( HANDLER.handle_nonuser_event(Event::NewEvents(StartCause::Init));
StartCause::Init,
)));
// NB: For consistency all platforms must emit a 'resumed' event even though macOS // NB: For consistency all platforms must emit a 'resumed' event even though macOS
// applications don't themselves have a formal suspend/resume lifecycle. // applications don't themselves have a formal suspend/resume lifecycle.
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::Resumed)); HANDLER.handle_nonuser_event(Event::Resumed);
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
} }
@ -544,7 +531,7 @@ impl AppState {
ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"), ControlFlow::ExitWithCode(_) => StartCause::Poll, //panic!("unexpected `ControlFlow::Exit`"),
}; };
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::NewEvents(cause))); HANDLER.handle_nonuser_event(Event::NewEvents(cause));
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
} }
@ -564,10 +551,10 @@ impl AppState {
// Redraw request might come out of order from the OS. // Redraw request might come out of order from the OS.
// -> Don't go back into the callback when our callstack originates from there // -> Don't go back into the callback when our callstack originates from there
if !HANDLER.in_callback.swap(true, Ordering::AcqRel) { if !HANDLER.in_callback.swap(true, Ordering::AcqRel) {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id, window_id,
event: WindowEvent::RedrawRequested, event: WindowEvent::RedrawRequested,
})); });
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
// `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events // `pump_events` will request to stop immediately _after_ dispatching RedrawRequested events
@ -578,11 +565,25 @@ impl AppState {
} }
} }
pub fn queue_event(wrapper: EventWrapper) { pub fn queue_event(event: Event<Never>) {
if !is_main_thread() { if !is_main_thread() {
panic!("Event queued from different thread: {wrapper:#?}"); panic!("Event queued from different thread: {event:#?}");
} }
HANDLER.events().push_back(wrapper); HANDLER.events().push_back(EventWrapper::StaticEvent(event));
}
pub fn queue_static_scale_factor_changed_event(
window: Id<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
HANDLER
.events()
.push_back(EventWrapper::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
} }
pub fn stop() { pub fn stop() {
@ -614,17 +615,32 @@ impl AppState {
HANDLER.set_in_callback(true); HANDLER.set_in_callback(true);
HANDLER.handle_user_events(); HANDLER.handle_user_events();
for event in HANDLER.take_events() { for event in HANDLER.take_events() {
HANDLER.handle_nonuser_event(event); match event {
EventWrapper::StaticEvent(event) => {
HANDLER.handle_nonuser_event(event);
}
EventWrapper::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
} => {
HANDLER.handle_scale_factor_changed_event(
&window,
suggested_size,
scale_factor,
);
}
}
} }
for window_id in HANDLER.should_redraw() { for window_id in HANDLER.should_redraw() {
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::WindowEvent { HANDLER.handle_nonuser_event(Event::WindowEvent {
window_id, window_id,
event: WindowEvent::RedrawRequested, event: WindowEvent::RedrawRequested,
})); });
} }
HANDLER.handle_nonuser_event(EventWrapper::StaticEvent(Event::AboutToWait)); HANDLER.handle_nonuser_event(Event::AboutToWait);
HANDLER.set_in_callback(false); HANDLER.set_in_callback(false);
if HANDLER.should_exit() { if HANDLER.should_exit() {

View file

@ -5,15 +5,11 @@ use core_foundation::{
data::{CFDataGetBytePtr, CFDataRef}, data::{CFDataGetBytePtr, CFDataRef},
}; };
use icrate::Foundation::MainThreadMarker; use icrate::Foundation::MainThreadMarker;
use objc2::rc::Id;
use smol_str::SmolStr; use smol_str::SmolStr;
use super::appkit::{NSEvent, NSEventModifierFlags}; use super::appkit::{NSEvent, NSEventModifierFlags};
use super::util::Never;
use super::window::WinitWindow;
use crate::{ use crate::{
dpi::LogicalSize, event::{ElementState, KeyEvent, Modifiers},
event::{ElementState, Event, KeyEvent, Modifiers},
keyboard::{ keyboard::{
Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode, Key, KeyCode, KeyLocation, ModifiersKeys, ModifiersState, NativeKey, NativeKeyCode,
}, },
@ -21,21 +17,6 @@ use crate::{
platform_impl::platform::ffi, platform_impl::platform::ffi,
}; };
#[derive(Debug)]
pub(crate) enum EventWrapper {
StaticEvent(Event<Never>),
EventProxy(EventProxy),
}
#[derive(Debug)]
pub(crate) enum EventProxy {
DpiChangedProxy {
window: Id<WinitWindow>,
suggested_size: LogicalSize<f64>,
scale_factor: f64,
},
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct KeyEventExtra { pub struct KeyEventExtra {
pub text_with_all_modifiers: Option<SmolStr>, pub text_with_all_modifiers: Option<SmolStr>,

View file

@ -65,9 +65,10 @@ impl PanicInfo {
} }
} }
#[derive(Debug)]
pub struct EventLoopWindowTarget<T: 'static> { pub struct EventLoopWindowTarget<T: 'static> {
pub receiver: mpsc::Receiver<T>,
mtm: MainThreadMarker, mtm: MainThreadMarker,
p: PhantomData<T>,
} }
impl<T: 'static> EventLoopWindowTarget<T> { impl<T: 'static> EventLoopWindowTarget<T> {
@ -107,14 +108,18 @@ impl<T> EventLoopWindowTarget<T> {
} }
pub struct EventLoop<T: 'static> { pub struct EventLoop<T: 'static> {
/// Store a reference to the application for convenience.
app: Id<WinitApplication>,
/// The delegate is only weakly referenced by NSApplication, so we keep /// The delegate is only weakly referenced by NSApplication, so we keep
/// it around here as well. /// it around here as well.
_delegate: Id<ApplicationDelegate>, _delegate: Id<ApplicationDelegate>,
// Event sender and receiver, used for EventLoopProxy.
sender: mpsc::Sender<T>, sender: mpsc::Sender<T>,
receiver: Rc<mpsc::Receiver<T>>,
window_target: Rc<RootWindowTarget<T>>, window_target: Rc<RootWindowTarget<T>>,
panic_info: Rc<PanicInfo>, panic_info: Rc<PanicInfo>,
mtm: MainThreadMarker,
/// We make sure that the callback closure is dropped during a panic /// We make sure that the callback closure is dropped during a panic
/// by making the event loop own it. /// by making the event loop own it.
@ -177,13 +182,17 @@ impl<T> EventLoop<T> {
let (sender, receiver) = mpsc::channel(); let (sender, receiver) = mpsc::channel();
Ok(EventLoop { Ok(EventLoop {
app,
_delegate: delegate, _delegate: delegate,
sender, sender,
receiver: Rc::new(receiver),
window_target: Rc::new(RootWindowTarget { window_target: Rc::new(RootWindowTarget {
p: EventLoopWindowTarget { receiver, mtm }, p: EventLoopWindowTarget {
mtm,
p: PhantomData,
},
_marker: PhantomData, _marker: PhantomData,
}), }),
mtm,
panic_info, panic_info,
_callback: None, _callback: None,
}) })
@ -231,8 +240,6 @@ impl<T> EventLoop<T> {
self._callback = Some(Rc::clone(&callback)); self._callback = Some(Rc::clone(&callback));
let exit_code = autoreleasepool(|_| { let exit_code = autoreleasepool(|_| {
let app = NSApplication::shared(self.mtm);
// A bit of juggling with the callback references to make sure // A bit of juggling with the callback references to make sure
// that `self.callback` is the only owner of the callback. // that `self.callback` is the only owner of the callback.
let weak_cb: Weak<_> = Rc::downgrade(&callback); let weak_cb: Weak<_> = Rc::downgrade(&callback);
@ -241,7 +248,11 @@ impl<T> EventLoop<T> {
// # Safety // # Safety
// We make sure to call `AppState::clear_callback` before returning // We make sure to call `AppState::clear_callback` before returning
unsafe { unsafe {
AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); AppState::set_callback(
weak_cb,
Rc::clone(&self.window_target),
Rc::clone(&self.receiver),
);
} }
// catch panics to make sure we can't unwind without clearing the set callback // catch panics to make sure we can't unwind without clearing the set callback
@ -257,7 +268,7 @@ impl<T> EventLoop<T> {
debug_assert!(!AppState::is_running()); debug_assert!(!AppState::is_running());
AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed` AppState::start_running(); // Set is_running = true + dispatch `NewEvents(Init)` + `Resumed`
} }
unsafe { app.run() }; unsafe { self.app.run() };
// While the app is running it's possible that we catch a panic // While the app is running it's possible that we catch a panic
// to avoid unwinding across an objective-c ffi boundary, which // to avoid unwinding across an objective-c ffi boundary, which
@ -326,7 +337,11 @@ impl<T> EventLoop<T> {
// to ensure that we don't hold on to the callback beyond its (erased) // to ensure that we don't hold on to the callback beyond its (erased)
// lifetime // lifetime
unsafe { unsafe {
AppState::set_callback(weak_cb, Rc::clone(&self.window_target)); AppState::set_callback(
weak_cb,
Rc::clone(&self.window_target),
Rc::clone(&self.receiver),
);
} }
// catch panics to make sure we can't unwind without clearing the set callback // catch panics to make sure we can't unwind without clearing the set callback

View file

@ -31,7 +31,7 @@ use crate::{
platform::scancode::KeyCodeExtScancode, platform::scancode::KeyCodeExtScancode,
platform_impl::platform::{ platform_impl::platform::{
app_state::AppState, app_state::AppState,
event::{create_key_event, event_mods, EventWrapper}, event::{create_key_event, event_mods},
util, util,
window::WinitWindow, window::WinitWindow,
DEVICE_ID, DEVICE_ID,
@ -826,7 +826,7 @@ impl WinitView {
window_id: self.window_id(), window_id: self.window_id(),
event, event,
}; };
AppState::queue_event(EventWrapper::StaticEvent(event)); AppState::queue_event(event);
} }
fn queue_device_event(&self, event: DeviceEvent) { fn queue_device_event(&self, event: DeviceEvent) {
@ -834,7 +834,7 @@ impl WinitView {
device_id: DEVICE_ID, device_id: DEVICE_ID,
event, event,
}; };
AppState::queue_event(EventWrapper::StaticEvent(event)); AppState::queue_event(event);
} }
fn scale_factor(&self) -> f64 { fn scale_factor(&self) -> f64 {

View file

@ -11,16 +11,15 @@ use objc2::{class, declare_class, msg_send, msg_send_id, mutability, sel, ClassT
use super::appkit::{ use super::appkit::{
NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState, NSApplicationPresentationOptions, NSFilenamesPboardType, NSPasteboard, NSWindowOcclusionState,
}; };
use super::{
app_state::AppState,
util,
window::{get_ns_theme, WinitWindow},
Fullscreen,
};
use crate::{ use crate::{
dpi::{LogicalPosition, LogicalSize}, dpi::{LogicalPosition, LogicalSize},
event::{Event, WindowEvent}, event::{Event, WindowEvent},
platform_impl::platform::{
app_state::AppState,
event::{EventProxy, EventWrapper},
util,
window::{get_ns_theme, WinitWindow},
Fullscreen,
},
window::WindowId, window::WindowId,
}; };
@ -447,7 +446,7 @@ impl WinitWindowDelegate {
window_id: WindowId(self.window.id()), window_id: WindowId(self.window.id()),
event, event,
}; };
AppState::queue_event(EventWrapper::StaticEvent(event)); AppState::queue_event(event);
} }
fn queue_static_scale_factor_changed_event(&self) { fn queue_static_scale_factor_changed_event(&self) {
@ -457,12 +456,12 @@ impl WinitWindowDelegate {
}; };
self.state.previous_scale_factor.set(scale_factor); self.state.previous_scale_factor.set(scale_factor);
let wrapper = EventWrapper::EventProxy(EventProxy::DpiChangedProxy { let suggested_size = self.view_size();
window: self.window.clone(), AppState::queue_static_scale_factor_changed_event(
suggested_size: self.view_size(), self.window.clone(),
suggested_size.to_physical(scale_factor),
scale_factor, scale_factor,
}); );
AppState::queue_event(wrapper);
} }
fn emit_move_event(&self) { fn emit_move_event(&self) {