use std::ffi::c_void;
use std::ptr;
use std::sync::Arc;
use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{ClassType, MainThreadMarker, msg_send};
use objc2_core_foundation::{
CFIndex, CFRunLoop, CFRunLoopActivity, CFRunLoopObserver, kCFRunLoopDefaultMode,
};
use objc2_foundation::{NSNotificationCenter, NSObjectProtocol};
use objc2_ui_kit::{
UIApplication, UIApplicationDidBecomeActiveNotification,
UIApplicationDidEnterBackgroundNotification, UIApplicationDidFinishLaunchingNotification,
UIApplicationDidReceiveMemoryWarningNotification, UIApplicationWillEnterForegroundNotification,
UIApplicationWillResignActiveNotification, UIApplicationWillTerminateNotification, UIScreen,
};
use rwh_06::HasDisplayHandle;
use winit_core::application::ApplicationHandler;
use winit_core::cursor::{CustomCursor, CustomCursorSource};
use winit_core::error::{EventLoopError, NotSupportedError, RequestError};
use winit_core::event_loop::{
ActiveEventLoop as RootActiveEventLoop, ControlFlow, DeviceEvents,
EventLoopProxy as CoreEventLoopProxy, OwnedDisplayHandle as CoreOwnedDisplayHandle,
};
use winit_core::monitor::MonitorHandle as CoreMonitorHandle;
use winit_core::window::{Theme, Window as CoreWindow};
use super::app_state::{AppState, send_occluded_event_for_all_windows};
use super::notification_center::create_observer;
use crate::monitor::MonitorHandle;
use crate::window::Window;
use crate::{app_state, monitor};
#[derive(Debug)]
pub struct ActiveEventLoop {
pub(super) mtm: MainThreadMarker,
}
impl RootActiveEventLoop for ActiveEventLoop {
fn create_proxy(&self) -> CoreEventLoopProxy {
CoreEventLoopProxy::new(AppState::get(self.mtm).event_loop_proxy().clone())
}
fn create_window(
&self,
window_attributes: winit_core::window::WindowAttributes,
) -> Result<Box<dyn CoreWindow>, RequestError> {
Ok(Box::new(Window::new(self, window_attributes)?))
}
fn create_custom_cursor(
&self,
_source: CustomCursorSource,
) -> Result<CustomCursor, RequestError> {
Err(NotSupportedError::new("create_custom_cursor is not supported").into())
}
fn available_monitors(&self) -> Box<dyn Iterator<Item = CoreMonitorHandle>> {
Box::new(
monitor::uiscreens(self.mtm)
.into_iter()
.map(|monitor| CoreMonitorHandle(Arc::new(monitor))),
)
}
fn primary_monitor(&self) -> Option<winit_core::monitor::MonitorHandle> {
#[allow(deprecated)]
let monitor = MonitorHandle::new(UIScreen::mainScreen(self.mtm));
Some(CoreMonitorHandle(Arc::new(monitor)))
}
fn listen_device_events(&self, _allowed: DeviceEvents) {}
fn set_control_flow(&self, control_flow: ControlFlow) {
AppState::get(self.mtm).set_control_flow(control_flow)
}
fn system_theme(&self) -> Option<Theme> {
None
}
fn control_flow(&self) -> ControlFlow {
AppState::get(self.mtm).control_flow()
}
fn exit(&self) {
tracing::warn!("`ControlFlow::Exit` ignored on iOS");
}
fn exiting(&self) -> bool {
false
}
fn owned_display_handle(&self) -> CoreOwnedDisplayHandle {
CoreOwnedDisplayHandle::new(Arc::new(OwnedDisplayHandle))
}
fn rwh_06_handle(&self) -> &dyn rwh_06::HasDisplayHandle {
self
}
}
impl rwh_06::HasDisplayHandle for ActiveEventLoop {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Clone, PartialEq, Eq)]
pub(crate) struct OwnedDisplayHandle;
impl HasDisplayHandle for OwnedDisplayHandle {
fn display_handle(&self) -> Result<rwh_06::DisplayHandle<'_>, rwh_06::HandleError> {
let raw = rwh_06::RawDisplayHandle::UiKit(rwh_06::UiKitDisplayHandle::new());
unsafe { Ok(rwh_06::DisplayHandle::borrow_raw(raw)) }
}
}
#[derive(Debug)]
pub struct EventLoop {
mtm: MainThreadMarker,
window_target: ActiveEventLoop,
_did_finish_launching_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_become_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_resign_active_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_enter_foreground_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_enter_background_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_will_terminate_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
_did_receive_memory_warning_observer: Retained<ProtocolObject<dyn NSObjectProtocol>>,
}
#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct PlatformSpecificEventLoopAttributes {}
impl EventLoop {
pub fn new(_: &PlatformSpecificEventLoopAttributes) -> Result<EventLoop, EventLoopError> {
let mtm = MainThreadMarker::new()
.expect("On iOS, `EventLoop` must be created on the main thread");
if !AppState::setup_global(mtm) {
return Err(EventLoopError::RecreationAttempt);
}
setup_control_flow_observers();
let center = NSNotificationCenter::defaultCenter();
let _did_finish_launching_observer = create_observer(
¢er,
unsafe { UIApplicationDidFinishLaunchingNotification },
move |_| {
app_state::did_finish_launching(mtm);
},
);
let _did_become_active_observer = create_observer(
¢er,
unsafe { UIApplicationDidBecomeActiveNotification },
move |_| app_state::handle_resumed(mtm),
);
let _will_resign_active_observer = create_observer(
¢er,
unsafe { UIApplicationWillResignActiveNotification },
move |_| app_state::handle_suspended(mtm),
);
let _will_enter_foreground_observer = create_observer(
¢er,
unsafe { UIApplicationWillEnterForegroundNotification },
move |notification| {
let app = notification.object().expect(
"UIApplicationWillEnterForegroundNotification to have application object",
);
let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, false);
},
);
let _did_enter_background_observer = create_observer(
¢er,
unsafe { UIApplicationDidEnterBackgroundNotification },
move |notification| {
let app = notification.object().expect(
"UIApplicationDidEnterBackgroundNotification to have application object",
);
let app = app.downcast::<UIApplication>().unwrap();
send_occluded_event_for_all_windows(&app, true);
},
);
let _will_terminate_observer = create_observer(
¢er,
unsafe { UIApplicationWillTerminateNotification },
move |notification| {
let app = notification
.object()
.expect("UIApplicationWillTerminateNotification to have application object");
let app = app.downcast::<UIApplication>().unwrap();
app_state::terminated(&app);
},
);
let _did_receive_memory_warning_observer = create_observer(
¢er,
unsafe { UIApplicationDidReceiveMemoryWarningNotification },
move |_| app_state::handle_memory_warning(mtm),
);
Ok(EventLoop {
mtm,
window_target: ActiveEventLoop { mtm },
_did_finish_launching_observer,
_did_become_active_observer,
_will_resign_active_observer,
_will_enter_foreground_observer,
_did_enter_background_observer,
_will_terminate_observer,
_did_receive_memory_warning_observer,
})
}
pub fn run_app_never_return<A: ApplicationHandler + 'static>(self, app: A) -> ! {
let application: Option<Retained<UIApplication>> =
unsafe { msg_send![UIApplication::class(), sharedApplication] };
assert!(
application.is_none(),
"\
`EventLoop` cannot be `run` after a call to `UIApplicationMain` on iOS\nNote: \
`EventLoop::run_app` calls `UIApplicationMain` on iOS",
);
app_state::launch(self.mtm, app, || UIApplication::main(None, None, self.mtm))
}
pub fn window_target(&self) -> &dyn RootActiveEventLoop {
&self.window_target
}
}
fn setup_control_flow_observers() {
unsafe {
extern "C-unwind" fn control_flow_begin_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::AfterWaiting => app_state::handle_wakeup_transition(mtm),
_ => unreachable!(),
}
}
extern "C-unwind" fn control_flow_main_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_main_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, _ => unreachable!(),
}
}
extern "C-unwind" fn control_flow_end_handler(
_: *mut CFRunLoopObserver,
activity: CFRunLoopActivity,
_: *mut c_void,
) {
let mtm = MainThreadMarker::new().unwrap();
#[allow(non_upper_case_globals)]
match activity {
CFRunLoopActivity::BeforeWaiting => app_state::handle_events_cleared(mtm),
CFRunLoopActivity::Exit => {}, _ => unreachable!(),
}
}
let main_loop = CFRunLoop::main().unwrap();
let begin_observer = CFRunLoopObserver::new(
None,
CFRunLoopActivity::AfterWaiting.0,
true,
CFIndex::MIN,
Some(control_flow_begin_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&begin_observer), kCFRunLoopDefaultMode);
let main_end_observer = CFRunLoopObserver::new(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
0, Some(control_flow_main_end_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&main_end_observer), kCFRunLoopDefaultMode);
let end_observer = CFRunLoopObserver::new(
None,
(CFRunLoopActivity::Exit | CFRunLoopActivity::BeforeWaiting).0,
true,
CFIndex::MAX,
Some(control_flow_end_handler),
ptr::null_mut(),
)
.unwrap();
main_loop.add_observer(Some(&end_observer), kCFRunLoopDefaultMode);
}
}