#![allow(trivial_casts)]
use std::{fmt, sync::Once};
use crate::{
foundation::NSString,
objective_c_runtime::{
class,
declare::ClassDecl,
id, msg_send,
runtime::{Class, Object, Sel},
sel, sel_impl,
traits::FromId,
ShareId,
},
utils::get_variable,
};
pub static ACTION_CALLBACK_PTR: &str = "RUST_Action_Callback_Ptr";
pub struct Action(Box<dyn Fn() + Send + Sync + 'static>);
pub struct ActionHandler {
pub invoker: ShareId<Object>,
pub action: Box<Action>,
}
impl ActionHandler {
pub fn new<F: Fn() + Send + Sync + 'static>(control: &Object, action: F) -> Self {
let block = Box::new(Action(Box::new(action)));
let ptr = Box::into_raw(block);
let invoker = unsafe {
ShareId::from_ptr({
let invoker: id = msg_send![Self::register_handler_class::<F>(), alloc];
let invoker: id = msg_send![invoker, init];
(*invoker).set_ivar(ACTION_CALLBACK_PTR, ptr as usize);
let _: () = msg_send![control, setAction: sel!(perform:)];
let _: () = msg_send![control, setTarget: invoker];
invoker
})
};
ActionHandler {
invoker,
action: unsafe { Box::from_raw(ptr) },
}
}
fn register_handler_class<F>() -> *const Class
where
F: Fn() + 'static,
{
static mut CLASS: *const Class = 0 as *const Class;
static INIT: Once = Once::new();
INIT.call_once(|| unsafe {
let mut decl = ClassDecl::new("RUST_ActionHandler", class!(NSObject)).unwrap();
decl.add_ivar::<usize>(ACTION_CALLBACK_PTR);
decl.add_method(
sel!(perform:),
perform::<F> as extern "C" fn(&mut Object, _, id),
);
CLASS = decl.register();
});
unsafe { CLASS }
}
}
impl fmt::Debug for Action {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "Action")
}
}
impl fmt::Debug for ActionHandler {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", unsafe {
NSString::from_id(msg_send![self.invoker, debugDescription])
})
}
}
extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id) {
let action = get_variable::<Action>(this, ACTION_CALLBACK_PTR);
(action.0)();
}