Skip to main content

afia_component/
callback.rs

1//! Types and functions used for handling events and adding event listeners to dom nodes.
2
3use crate::dom::event::DomEvent;
4use crate::ComponentImports;
5
6/// Generates an `__afia__$handle_event` function that expects that a pointer to a [`Callback`]
7/// was used as the context when calling [`crate::DomElement::add_event_listener`]'s context.
8///
9/// The generated functions behaves similar to the following:
10/// ```ignore
11/// use afia_component::callback::Callback;
12/// use afia_component::dom::event::DomEvent;
13///
14/// #[export_name = "__afia__$handle_event"]
15/// pub extern "C" fn handle_event(event: DomEvent, callback: &'static Callback) {
16///     callback.call(event);
17/// }
18/// ```
19#[macro_export]
20macro_rules! handle_events_using_callbacks {
21    () => {
22        #[export_name = "__afia__$handle_event"]
23        #[allow(missing_docs)]
24        pub extern "C" fn handle_event(event_handle: i64, callback: *mut Callback) {
25            let callback: &'static Callback = unsafe { &*callback };
26
27            let event_handle =
28                $crate::dom::event::DomEvent::_private_new(event_handle, callback.imports());
29
30            (callback.callback())(event_handle, callback.context());
31        }
32    };
33}
34
35/// A callback that can be used for event listeners in the function
36/// [`add_event_listener`], it gets passed down to the exported
37/// `__afia__$handle_event` function.
38pub struct Callback {
39    context: isize,
40    imports: &'static ComponentImports,
41    callback: fn(DomEvent, isize),
42}
43
44impl Callback {
45    /// TODO: Delete this. Just a way for us to let some of our older code make use of this newer
46    ///  [`Callback`] abstraction.
47    ///  This is currently used in one place. We should not use it anywhere else. Once we delete
48    ///  that place we can delete this method.
49    pub fn temporary_way_to_call_using_integer_event_handle(&self, event_handle: i64) {
50        (self.callback)(
51            DomEvent::_private_new(event_handle, self.imports),
52            self.context,
53        )
54    }
55
56    /// Create a [`Callback`] by providing a function that takes an event handle
57    /// and a pointer to the associated context.
58    pub fn new_with_context_pointer<T>(
59        context: *mut T,
60        imports: &'static ComponentImports,
61        // TODO: Change think about whether to change this to.
62        //  Just a quick idea that I'm jotting down... this commit is already large to trying to land
63        //  it without any new stuff ...
64        //  `callback: fn(DomEvent, ContextPtr<T>)`
65        //  Using `ContextPtr<T>` allows the user to access the `T` without needing to write unsafe
66        //  pointer dereferencing code.
67        callback: fn(DomEvent, *mut T),
68    ) -> Self {
69        // SAFETY: safe because we are casting between two pointer types of the same size
70        let callback: fn(DomEvent, isize) = unsafe { std::mem::transmute(callback) };
71
72        Self {
73            context: context as isize,
74            callback,
75            imports,
76        }
77    }
78
79    /// Create a [`Callback`] by providing a function that takes an event
80    /// handle as an argument.
81    pub fn new_without_context(imports: &'static ComponentImports, callback: fn(DomEvent)) -> Self {
82        Self {
83            context: callback as isize,
84            imports,
85            callback: |event: DomEvent, context: isize| {
86                // SAFETY: Safe because we stored the callback function pointer as the context.
87                let callback: fn(DomEvent) = unsafe { std::mem::transmute(context) };
88
89                callback(event)
90            },
91        }
92    }
93
94    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
95    #[doc(hidden)]
96    pub fn callback(&self) -> fn(DomEvent, isize) {
97        self.callback
98    }
99
100    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
101    #[doc(hidden)]
102    pub fn context(&self) -> isize {
103        self.context
104    }
105
106    // PRIVATE: Used by the `handle_events_using_callbacks` macro.
107    #[doc(hidden)]
108    pub fn imports(&self) -> &'static ComponentImports {
109        self.imports
110    }
111}