use std::cell::{Cell, OnceCell, RefCell};
use std::collections::HashMap;
use std::mem;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Instant;
use dispatch2::MainThreadBound;
use objc2::MainThreadMarker;
use objc2::rc::{Retained, Weak};
use objc2_app_kit::{
NSApplication, NSApplicationActivationPolicy, NSDragOperation, NSRunningApplication,
};
use objc2_foundation::NSNotification;
use winit_common::core_foundation::{EventLoopProxy, MainRunLoop};
use winit_common::event_handler::EventHandler;
use winit_core::application::ApplicationHandler;
use winit_core::data_transfer::DataTransferId;
use winit_core::event::{StartCause, WindowEvent};
use winit_core::event_loop::{ControlFlow, DndAction};
use winit_core::window::WindowId;
use super::event_loop::{ActiveEventLoop, notify_windows_of_exit, stop_app_immediately};
use super::menu;
use super::observer::EventLoopWaker;
use crate::dnd::Pasteboards;
use crate::window_delegate::WindowDelegate;
#[derive(Debug)]
pub(super) struct AppState {
mtm: MainThreadMarker,
drag_state: RefCell<Option<DragState>>,
pasteboards: Pasteboards,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
run_loop: MainRunLoop,
event_loop_proxy: Arc<EventLoopProxy>,
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_redraw: RefCell<Vec<WindowId>>,
windows: RefCell<HashMap<WindowId, MainThreadBound<Weak<WindowDelegate>>>>,
}
#[derive(Debug)]
pub(crate) struct DragState {
pub id: DataTransferId,
pub valid_actions: Vec<DndAction>,
}
static GLOBAL: MainThreadBound<OnceCell<Rc<AppState>>> =
MainThreadBound::new(OnceCell::new(), unsafe { MainThreadMarker::new_unchecked() });
impl AppState {
pub(super) fn setup_global(
mtm: MainThreadMarker,
activation_policy: Option<NSApplicationActivationPolicy>,
default_menu: bool,
activate_ignoring_other_apps: bool,
) -> Option<Rc<Self>> {
let event_loop_proxy = Arc::new(EventLoopProxy::new(mtm, move || {
Self::get(mtm).with_handler(|app, event_loop| app.proxy_wake_up(event_loop));
}));
let this = Rc::new(Self {
mtm,
pasteboards: Default::default(),
drag_state: Default::default(),
activation_policy,
default_menu,
activate_ignoring_other_apps,
run_loop: MainRunLoop::get(mtm),
event_loop_proxy,
event_handler: EventHandler::new(),
stop_on_launch: Cell::new(false),
stop_before_wait: Cell::new(false),
stop_after_wait: Cell::new(false),
stop_on_redraw: Cell::new(false),
is_launched: Cell::new(false),
is_running: Cell::new(false),
exit: Cell::new(false),
control_flow: Cell::new(ControlFlow::default()),
waker: RefCell::new(EventLoopWaker::new()),
start_time: Cell::new(None),
wait_timeout: Cell::new(None),
windows: Default::default(),
pending_redraw: RefCell::new(vec![]),
});
GLOBAL.get(mtm).set(this.clone()).ok().and(Some(this))
}
pub fn get(mtm: MainThreadMarker) -> Rc<Self> {
GLOBAL
.get(mtm)
.get()
.expect("tried to get application state before it was registered")
.clone()
}
pub fn with_window_delegate_on_main<F, R>(&self, id: WindowId, func: F) -> Option<R>
where
F: FnOnce(Retained<WindowDelegate>) -> R + Send,
R: Send,
{
self.windows.borrow_mut().get(&id)?.get_on_main(move |delegate| delegate.load().map(func))
}
pub fn register_window(&self, window: &Retained<WindowDelegate>, mtm: MainThreadMarker) {
let id = window.id();
let window_downgraded = Weak::from_retained(window);
self.windows.borrow_mut().insert(id, MainThreadBound::new(window_downgraded, mtm));
}
pub fn did_finish_launching(self: &Rc<Self>, _notification: &NSNotification) {
self.is_launched.set(true);
let app = NSApplication::sharedApplication(self.mtm);
if let Some(activation_policy) = self.activation_policy {
app.setActivationPolicy(activation_policy);
} else {
let is_bundled =
NSRunningApplication::currentApplication().bundleIdentifier().is_some();
if !is_bundled {
app.setActivationPolicy(NSApplicationActivationPolicy::Regular);
}
}
#[allow(deprecated)]
app.activateIgnoringOtherApps(self.activate_ignoring_other_apps);
if self.default_menu {
menu::initialize(&app);
}
self.waker.borrow_mut().start();
self.set_is_running(true);
self.dispatch_init_events();
if self.stop_on_launch.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
pub fn will_terminate(self: &Rc<Self>, _notification: &NSNotification) {
let app = NSApplication::sharedApplication(self.mtm);
notify_windows_of_exit(&app);
self.event_handler.terminate();
self.internal_exit();
}
pub fn set_event_handler<R>(
&self,
handler: impl ApplicationHandler,
closure: impl FnOnce() -> R,
) -> R {
self.event_handler.set(Box::new(handler), closure)
}
pub fn event_loop_proxy(&self) -> &Arc<EventLoopProxy> {
&self.event_loop_proxy
}
pub fn set_stop_on_launch(&self) {
self.stop_on_launch.set(true);
}
pub fn set_stop_before_wait(&self, value: bool) {
self.stop_before_wait.set(value)
}
pub fn set_stop_after_wait(&self, value: bool) {
self.stop_after_wait.set(value)
}
pub fn set_stop_on_redraw(&self, value: bool) {
self.stop_on_redraw.set(value)
}
pub fn set_wait_timeout(&self, value: Option<Instant>) {
self.wait_timeout.set(value)
}
pub fn internal_exit(self: &Rc<Self>) {
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.is_launched.get()
}
pub fn set_is_running(&self, value: bool) {
self.is_running.set(value)
}
pub fn is_running(&self) -> bool {
self.is_running.get()
}
pub fn exit(&self) {
self.exit.set(true)
}
pub fn clear_exit(&self) {
self.exit.set(false)
}
pub fn exiting(&self) -> bool {
self.exit.get()
}
pub fn set_control_flow(&self, value: ControlFlow) {
self.control_flow.set(value)
}
pub(super) fn is_handling_event(&self) -> bool {
self.event_handler.in_use()
}
pub fn control_flow(&self) -> ControlFlow {
self.control_flow.get()
}
pub fn handle_redraw(self: &Rc<Self>, window_id: WindowId) {
if !self.event_handler.in_use() {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
if self.stop_on_redraw.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
}
}
pub fn queue_redraw(&self, window_id: WindowId) {
let mut pending_redraw = self.pending_redraw.borrow_mut();
if !pending_redraw.contains(&window_id) {
pending_redraw.push(window_id);
}
self.run_loop.wake_up();
}
#[track_caller]
pub fn maybe_queue_with_handler(
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop) + 'static,
) {
if !self.event_handler.in_use() {
self.with_handler(callback);
} else {
tracing::debug!("had to queue event since another is currently being handled");
let this = Rc::clone(self);
self.run_loop.queue_closure(move || {
this.with_handler(callback);
});
}
}
#[track_caller]
fn with_handler(
self: &Rc<Self>,
callback: impl FnOnce(&mut dyn ApplicationHandler, &ActiveEventLoop),
) {
let event_loop = ActiveEventLoop { app_state: Rc::clone(self), mtm: self.mtm };
self.event_handler.handle(|app| callback(app, &event_loop));
}
pub fn dispatch_init_events(self: &Rc<Self>) {
self.with_handler(|app, event_loop| app.new_events(event_loop, StartCause::Init));
self.with_handler(|app, event_loop| app.can_create_surfaces(event_loop));
}
pub fn wakeup(self: &Rc<Self>) {
if !self.event_handler.ready() || !self.is_running() {
return;
}
if self.stop_after_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
let start = self.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.with_handler(|app, event_loop| app.new_events(event_loop, cause));
}
pub fn cleared(self: &Rc<Self>) {
if !self.event_handler.ready() || !self.is_running() {
return;
}
let redraw = mem::take(&mut *self.pending_redraw.borrow_mut());
for window_id in redraw {
self.with_handler(|app, event_loop| {
app.window_event(event_loop, window_id, WindowEvent::RedrawRequested);
});
}
self.with_handler(|app, event_loop| {
app.about_to_wait(event_loop);
});
if self.exiting() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
notify_windows_of_exit(&app);
}
if self.stop_before_wait.get() {
let app = NSApplication::sharedApplication(self.mtm);
stop_app_immediately(&app);
}
self.start_time.set(Some(Instant::now()));
let wait_timeout = self.wait_timeout.get(); let app_timeout = match self.control_flow() {
ControlFlow::Wait => None,
ControlFlow::Poll => Some(Instant::now()),
ControlFlow::WaitUntil(instant) => Some(instant),
};
self.waker.borrow_mut().start_at(min_timeout(wait_timeout, app_timeout));
}
pub fn pasteboards(&self) -> &Pasteboards {
&self.pasteboards
}
pub fn drag_state(&self) -> &RefCell<Option<DragState>> {
&self.drag_state
}
pub(crate) fn proposed_drag_action(
&self,
source_operations: NSDragOperation,
) -> Option<DndAction> {
self.drag_state().borrow().as_ref().and_then(|drag_state| {
crate::dnd::preferred_drag_operation(source_operations, &drag_state.valid_actions)
})
}
}
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))))
}