rust_macios/appkit/
action_handler.rs

1#![allow(trivial_casts)]
2
3use std::{fmt, sync::Once};
4
5use crate::{
6    foundation::NSString,
7    objective_c_runtime::{
8        class,
9        declare::ClassDecl,
10        id, msg_send,
11        runtime::{Class, Object, Sel},
12        sel, sel_impl,
13        traits::FromId,
14        ShareId,
15    },
16    utils::get_variable,
17};
18
19/// The pointer for `ActionHandler`
20pub static ACTION_CALLBACK_PTR: &str = "RUST_Action_Callback_Ptr";
21
22/// The action handler
23pub struct Action(Box<dyn Fn() + Send + Sync + 'static>);
24
25/// A handler that contains the class for callback storage and invocation on
26pub struct ActionHandler {
27    /// The class for the callback.
28    pub invoker: ShareId<Object>,
29    /// The callback.
30    pub action: Box<Action>,
31}
32
33impl ActionHandler {
34    /// Returns a new TargetEventHandler.
35    pub fn new<F: Fn() + Send + Sync + 'static>(control: &Object, action: F) -> Self {
36        let block = Box::new(Action(Box::new(action)));
37        let ptr = Box::into_raw(block);
38
39        let invoker = unsafe {
40            ShareId::from_ptr({
41                let invoker: id = msg_send![Self::register_handler_class::<F>(), alloc];
42                let invoker: id = msg_send![invoker, init];
43                (*invoker).set_ivar(ACTION_CALLBACK_PTR, ptr as usize);
44                let _: () = msg_send![control, setAction: sel!(perform:)];
45                let _: () = msg_send![control, setTarget: invoker];
46                invoker
47            })
48        };
49
50        ActionHandler {
51            invoker,
52            action: unsafe { Box::from_raw(ptr) },
53        }
54    }
55
56    fn register_handler_class<F>() -> *const Class
57    where
58        F: Fn() + 'static,
59    {
60        static mut CLASS: *const Class = 0 as *const Class;
61        static INIT: Once = Once::new();
62
63        INIT.call_once(|| unsafe {
64            let mut decl = ClassDecl::new("RUST_ActionHandler", class!(NSObject)).unwrap();
65
66            decl.add_ivar::<usize>(ACTION_CALLBACK_PTR);
67            decl.add_method(
68                sel!(perform:),
69                perform::<F> as extern "C" fn(&mut Object, _, id),
70            );
71
72            CLASS = decl.register();
73        });
74
75        unsafe { CLASS }
76    }
77}
78
79impl fmt::Debug for Action {
80    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81        write!(f, "Action")
82    }
83}
84
85impl fmt::Debug for ActionHandler {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(f, "{}", unsafe {
88            NSString::from_id(msg_send![self.invoker, debugDescription])
89        })
90    }
91}
92
93/// This will fire for an NSButton callback.
94extern "C" fn perform<F: Fn() + 'static>(this: &mut Object, _: Sel, _sender: id) {
95    let action = get_variable::<Action>(this, ACTION_CALLBACK_PTR);
96    (action.0)();
97}