use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::rc::Weak;
use std::sync::{Arc, Mutex};
use std::time::Instant;
use objc2::rc::Retained;
use objc2::runtime::AnyObject;
use objc2::{declare_class, msg_send_id, mutability, ClassType, DeclaredClass};
use objc2_app_kit::{NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate};
use objc2_foundation::{MainThreadMarker, NSArray, NSObject, NSObjectProtocol, NSSize, NSURL};
use super::event_handler::EventHandler;
use super::event_loop::{stop_app_immediately, ActiveEventLoop, PanicInfo};
use super::observer::{EventLoopWaker, RunLoop};
use super::window::WinitWindow;
use super::{menu, WindowId, DEVICE_ID};
use crate::dpi::PhysicalSize;
use crate::event::{DeviceEvent, Event, InnerSizeWriter, StartCause, WindowEvent};
use crate::event_loop::{ActiveEventLoop as RootActiveEventLoop, ControlFlow};
use crate::window::WindowId as RootWindowId;
#[derive(Debug)]
struct Policy(NSApplicationActivationPolicy);
impl Default for Policy {
fn default() -> Self {
Self(NSApplicationActivationPolicy::Regular)
}
}
#[derive(Debug, Default)]
pub(super) struct State {
activation_policy: Policy,
default_menu: bool,
activate_ignoring_other_apps: bool,
event_handler: EventHandler,
stop_on_launch: Cell<bool>,
stop_before_wait: Cell<bool>,
stop_after_wait: Cell<bool>,
stop_on_redraw: Cell<bool>,
is_launched: Cell<bool>,
is_running: Cell<bool>,
exit: Cell<bool>,
control_flow: Cell<ControlFlow>,
waker: RefCell<EventLoopWaker>,
start_time: Cell<Option<Instant>>,
wait_timeout: Cell<Option<Instant>>,
pending_events: RefCell<VecDeque<QueuedEvent>>,
pending_redraw: RefCell<Vec<WindowId>>,
}
declare_class!(
#[derive(Debug)]
pub(super) struct ApplicationDelegate;
unsafe impl ClassType for ApplicationDelegate {
type Super = NSObject;
type Mutability = mutability::MainThreadOnly;
const NAME: &'static str = "WinitApplicationDelegate";
}
impl DeclaredClass for ApplicationDelegate {
type Ivars = State;
}
unsafe impl NSObjectProtocol for ApplicationDelegate {}
unsafe impl NSApplicationDelegate for ApplicationDelegate {
#[method(applicationDidFinishLaunching:)]
fn did_finish_launching(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationDidFinishLaunching:");
self.ivars().is_launched.set(true);
let mtm = MainThreadMarker::from(self);
let app = NSApplication::sharedApplication(mtm);
app.setActivationPolicy(self.ivars().activation_policy.0);
window_activation_hack(&app);
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.ivars().activate_ignoring_other_apps);
if self.ivars().default_menu {
menu::initialize(&app);
}
self.ivars().waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
if self.ivars().stop_on_launch.get() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
#[method(applicationWillTerminate:)]
fn will_terminate(&self, _sender: Option<&AnyObject>) {
trace_scope!("applicationWillTerminate:");
self.internal_exit();
}
#[method(application:openURLs:)]
fn application_open_urls(&self, _application: &NSApplication, urls: &NSArray<NSURL>) {
trace_scope!("Trigger `application:openURLs:`");
let mut open_urls: Vec<String> = Vec::new();
urls.into_iter().for_each(|url| {
unsafe {
if let Some(ns_str) = url.absoluteString() {
open_urls.push(ns_str.to_string());
}
}
});
self.open_urls(open_urls);
trace_scope!("Completed `application:openURLs:`");
}
}
);
impl ApplicationDelegate {
pub(super) fn new(
mtm: MainThreadMarker,
activation_policy: NSApplicationActivationPolicy,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Retained<Self> {
let this = mtm.alloc().set_ivars(State {
activation_policy: Policy(activation_policy),
default_menu,
activate_ignoring_other_apps,
..Default::default()
});
unsafe { msg_send_id![super(this), init] }
}
pub fn get(mtm: MainThreadMarker) -> Retained<Self> {
let app = NSApplication::sharedApplication(mtm);
let delegate =
unsafe { app.delegate() }.expect("a delegate was not configured on the application");
if delegate.is_kind_of::<Self>() {
unsafe { Retained::cast(delegate) }
} else {
panic!("tried to get a delegate that was not the one Winit has registered")
}
}
pub fn set_event_handler<R>(
&self,
handler: impl FnMut(Event<HandlePendingUserEvents>, &RootActiveEventLoop),
closure: impl FnOnce() -> R,
) -> R {
self.ivars().event_handler.set(handler, closure)
}
pub fn set_stop_on_launch(&self) {
self.ivars().stop_on_launch.set(true);
}
pub fn set_stop_before_wait(&self, value: bool) {
self.ivars().stop_before_wait.set(value)
}
pub fn set_stop_after_wait(&self, value: bool) {
self.ivars().stop_after_wait.set(value)
}
pub fn set_stop_on_redraw(&self, value: bool) {
self.ivars().stop_on_redraw.set(value)
}
pub fn set_wait_timeout(&self, value: Option<Instant>) {
self.ivars().wait_timeout.set(value)
}
pub fn internal_exit(&self) {
self.handle_event(Event::LoopExiting);
self.set_is_running(false);
self.set_stop_on_redraw(false);
self.set_stop_before_wait(false);
self.set_stop_after_wait(false);
self.set_wait_timeout(None);
}
pub fn is_launched(&self) -> bool {
self.ivars().is_launched.get()
}
pub fn set_is_running(&self, value: bool) {
self.ivars().is_running.set(value)
}
pub fn is_running(&self) -> bool {
self.ivars().is_running.get()
}
pub fn exit(&self) {
self.ivars().exit.set(true)
}
pub fn clear_exit(&self) {
self.ivars().exit.set(false)
}
pub fn exiting(&self) -> bool {
self.ivars().exit.get()
}
pub fn set_control_flow(&self, value: ControlFlow) {
self.ivars().control_flow.set(value)
}
pub fn control_flow(&self) -> ControlFlow {
self.ivars().control_flow.get()
}
pub fn queue_window_event(&self, window_id: WindowId, event: WindowEvent) {
self.ivars()
.pending_events
.borrow_mut()
.push_back(QueuedEvent::WindowEvent(window_id, event));
}
pub fn queue_device_event(&self, event: DeviceEvent) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::DeviceEvent(event));
}
pub fn queue_static_scale_factor_changed_event(
&self,
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
) {
self.ivars().pending_events.borrow_mut().push_back(QueuedEvent::ScaleFactorChanged {
window,
suggested_size,
scale_factor,
});
}
pub fn handle_redraw(&self, window_id: WindowId) {
let mtm = MainThreadMarker::from(self);
if !self.ivars().event_handler.in_use() {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
if self.ivars().stop_on_redraw.get() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
}
}
pub fn queue_redraw(&self, window_id: WindowId) {
let mut pending_redraw = self.ivars().pending_redraw.borrow_mut();
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
unsafe { RunLoop::get() }.wakeup();
}
fn handle_event(&self, event: Event<HandlePendingUserEvents>) {
self.ivars().event_handler.handle_event(event, &ActiveEventLoop::new_root(self.retain()))
}
pub fn dispatch_init_events(&self) {
self.handle_event(Event::NewEvents(StartCause::Init));
self.handle_event(Event::Resumed);
}
pub fn open_urls(&self, urls: Vec<String>) {
self.handle_event(Event::Opened { urls });
}
pub fn wakeup(&self, panic_info: Weak<PanicInfo>) {
let mtm = MainThreadMarker::from(self);
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
if self.ivars().stop_after_wait.get() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
let start = self.ivars().start_time.get().unwrap();
let cause = match self.control_flow() {
ControlFlow::Poll => StartCause::Poll,
ControlFlow::Wait => StartCause::WaitCancelled { start, requested_resume: None },
ControlFlow::WaitUntil(requested_resume) => {
if Instant::now() >= requested_resume {
StartCause::ResumeTimeReached { start, requested_resume }
} else {
StartCause::WaitCancelled { start, requested_resume: Some(requested_resume) }
}
},
};
self.handle_event(Event::NewEvents(cause));
}
pub fn cleared(&self, panic_info: Weak<PanicInfo>) {
let mtm = MainThreadMarker::from(self);
let panic_info = panic_info
.upgrade()
.expect("The panic info must exist here. This failure indicates a developer error.");
if panic_info.is_panicking() || !self.ivars().event_handler.ready() || !self.is_running() {
return;
}
self.handle_event(Event::UserEvent(HandlePendingUserEvents));
let events = mem::take(&mut *self.ivars().pending_events.borrow_mut());
for event in events {
match event {
QueuedEvent::WindowEvent(window_id, event) => {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event,
});
},
QueuedEvent::DeviceEvent(event) => {
self.handle_event(Event::DeviceEvent { device_id: DEVICE_ID, event });
},
QueuedEvent::ScaleFactorChanged { window, suggested_size, scale_factor } => {
let new_inner_size = Arc::new(Mutex::new(suggested_size));
let scale_factor_changed_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::ScaleFactorChanged {
scale_factor,
inner_size_writer: InnerSizeWriter::new(Arc::downgrade(
&new_inner_size,
)),
},
};
self.handle_event(scale_factor_changed_event);
let physical_size = *new_inner_size.lock().unwrap();
drop(new_inner_size);
if physical_size != suggested_size {
let logical_size = physical_size.to_logical(scale_factor);
let size = NSSize::new(logical_size.width, logical_size.height);
window.setContentSize(size);
}
let resized_event = Event::WindowEvent {
window_id: RootWindowId(window.id()),
event: WindowEvent::Resized(physical_size),
};
self.handle_event(resized_event);
},
}
}
let redraw = mem::take(&mut *self.ivars().pending_redraw.borrow_mut());
for window_id in redraw {
self.handle_event(Event::WindowEvent {
window_id: RootWindowId(window_id),
event: WindowEvent::RedrawRequested,
});
}
self.handle_event(Event::AboutToWait);
if self.exiting() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
if self.ivars().stop_before_wait.get() {
let app = NSApplication::sharedApplication(mtm);
stop_app_immediately(&app);
}
self.ivars().start_time.set(Some(Instant::now()));
let wait_timeout = self.ivars().wait_timeout.get(); let app_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
self.ivars().waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
}
}
#[derive(Debug)]
pub(crate) enum QueuedEvent {
WindowEvent(WindowId, WindowEvent),
DeviceEvent(DeviceEvent),
ScaleFactorChanged {
window: Retained<WinitWindow>,
suggested_size: PhysicalSize<u32>,
scale_factor: f64,
},
}
#[derive(Debug)]
pub(crate) struct HandlePendingUserEvents;
fn min_timeout(a: Option<Instant>, b: Option<Instant>) -> Option<Instant> {
a.map_or(b, |a_timeout| b.map_or(Some(a_timeout), |b_timeout| Some(a_timeout.min(b_timeout))))
}
fn window_activation_hack(app: &NSApplication) {
app.windows().into_iter().for_each(|window| {
if window.isVisible() {
tracing::trace!("Activating visible window");
window.makeKeyAndOrderFront(None);
} else {
tracing::trace!("Skipping activating invisible window");
}
})
}