use std::fmt;
use std::sync::{Arc, Mutex};
use lazy_static::lazy_static;
use objc::runtime::Object;
use objc::{class, msg_send, sel, sel_impl};
use objc_id::Id;
use crate::appkit::menu::Menu;
use crate::foundation::{id, nil, AutoReleasePool, NSUInteger, NO, YES};
use crate::invoker::TargetActionHandler;
use crate::notification_center::Dispatcher;
use crate::utils::activate_cocoa_multithreading;
mod class;
use class::register_app_class;
mod delegate;
use delegate::register_app_delegate_class;
mod enums;
pub use enums::*;
mod traits;
pub use traits::AppDelegate;
pub(crate) static APP_PTR: &str = "rstAppPtr";
#[inline]
fn shared_application<F: Fn(id)>(handler: F) {
let app: id = unsafe { msg_send![register_app_class(), sharedApplication] };
handler(app);
}
pub struct App<T = (), M = ()> {
pub objc: Id<Object>,
pub objc_delegate: Id<Object>,
pub delegate: Box<T>,
pub pool: AutoReleasePool,
_message: std::marker::PhantomData<M>
}
impl<T, M> fmt::Debug for App<T, M> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let delegate = format!("{:p}", self.delegate);
f.debug_struct("App")
.field("objc", &self.objc)
.field("objc_delegate", &self.objc_delegate)
.field("delegate", &delegate)
.field("pool", &self.pool)
.finish()
}
}
impl<T> App<T> {
pub fn run(&self) {
unsafe {
let shared_app: id = msg_send![class!(RSTApplication), sharedApplication];
let _: () = msg_send![shared_app, run];
self.pool.drain();
}
}
}
impl<T> App<T>
where
T: AppDelegate + 'static
{
pub fn new(_bundle_id: &str, delegate: T) -> Self {
activate_cocoa_multithreading();
let pool = AutoReleasePool::new();
let objc = unsafe {
let app: id = msg_send![register_app_class(), sharedApplication];
Id::from_ptr(app)
};
let app_delegate = Box::new(delegate);
let objc_delegate = unsafe {
let delegate_class = register_app_delegate_class::<T>();
let delegate: id = msg_send![delegate_class, new];
let delegate_ptr: *const T = &*app_delegate;
(&mut *delegate).set_ivar(APP_PTR, delegate_ptr as usize);
let _: () = msg_send![&*objc, setDelegate: delegate];
Id::from_ptr(delegate)
};
App {
objc,
objc_delegate,
delegate: app_delegate,
pool,
_message: std::marker::PhantomData
}
}
}
impl<T, M> App<T, M>
where
M: Send + Sync + 'static,
T: AppDelegate + Dispatcher<Message = M>
{
pub fn dispatch_main(message: M) {
let queue = dispatch::Queue::main();
queue.exec_async(move || unsafe {
let app: id = msg_send![register_app_class(), sharedApplication];
let app_delegate: id = msg_send![app, delegate];
let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR);
let delegate = delegate_ptr as *const T;
(&*delegate).on_ui_message(message);
});
}
pub fn dispatch_background(message: M) {
let queue = dispatch::Queue::main();
queue.exec_async(move || unsafe {
let app: id = msg_send![register_app_class(), sharedApplication];
let app_delegate: id = msg_send![app, delegate];
let delegate_ptr: usize = *(*app_delegate).get_ivar(APP_PTR);
let delegate = delegate_ptr as *const T;
(&*delegate).on_background_message(message);
});
}
}
impl App {
pub fn register_for_remote_notifications() {
shared_application(|app| unsafe {
let _: () = msg_send![app, registerForRemoteNotifications];
});
}
pub fn unregister_for_remote_notifications() {
shared_application(|app| unsafe {
let _: () = msg_send![app, unregisterForRemoteNotifications];
});
}
pub fn set_relaunch_on_login(relaunch: bool) {
shared_application(|app| unsafe {
if relaunch {
let _: () = msg_send![app, enableRelaunchOnLogin];
} else {
let _: () = msg_send![app, disableRelaunchOnLogin];
}
});
}
pub fn reply_to_termination_request(should_terminate: bool) {
shared_application(|app| unsafe {
let _: () = msg_send![app, replyToApplicationShouldTerminate:match should_terminate {
true => YES,
false => NO
}];
});
}
pub fn reply_to_open_or_print(response: AppDelegateResponse) {
shared_application(|app| unsafe {
let r: NSUInteger = response.into();
let _: () = msg_send![app, replyToOpenOrPrint: r];
});
}
pub fn set_menu(mut menus: Vec<Menu>) {
let main_menu = unsafe {
let menu_cls = class!(NSMenu);
let item_cls = class!(NSMenuItem);
let main_menu: id = msg_send![menu_cls, new];
for menu in menus.iter_mut() {
let item: id = msg_send![item_cls, new];
let _: () = msg_send![item, setSubmenu:&*menu.0];
let _: () = msg_send![main_menu, addItem: item];
}
main_menu
};
shared_application(move |app| unsafe {
let _: () = msg_send![app, setMainMenu: main_menu];
});
}
pub fn activate() {
shared_application(|app| unsafe {
let _: () = msg_send![app, setActivationPolicy:0];
let current_app: id = msg_send![class!(NSRunningApplication), currentApplication];
let _: () = msg_send![current_app, activateWithOptions:1<<1];
});
}
pub fn terminate() {
shared_application(|app| unsafe {
let _: () = msg_send![app, terminate: nil];
});
}
}