dioxus_core/
events.rs

1use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
2use generational_box::GenerationalBox;
3use std::{cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
4
5/// A wrapper around some generic data that handles the event's state
6///
7///
8/// Prevent this event from continuing to bubble up the tree to parent elements.
9///
10/// # Example
11///
12/// ```rust, no_run
13/// # use dioxus::prelude::*;
14/// rsx! {
15///     button {
16///         onclick: move |evt: Event<MouseData>| {
17///             evt.stop_propagation();
18///         }
19///     }
20/// };
21/// ```
22pub struct Event<T: 'static + ?Sized> {
23    /// The data associated with this event
24    pub data: Rc<T>,
25    pub(crate) metadata: Rc<RefCell<EventMetadata>>,
26}
27
28#[derive(Clone, Copy)]
29pub(crate) struct EventMetadata {
30    pub(crate) propagates: bool,
31    pub(crate) prevent_default: bool,
32}
33
34impl<T: ?Sized + 'static> Event<T> {
35    /// Create a new event from the inner data
36    pub fn new(data: Rc<T>, propagates: bool) -> Self {
37        Self {
38            data,
39            metadata: Rc::new(RefCell::new(EventMetadata {
40                propagates,
41                prevent_default: false,
42            })),
43        }
44    }
45}
46
47impl<T: ?Sized> Event<T> {
48    /// Map the event data to a new type
49    ///
50    /// # Example
51    ///
52    /// ```rust, no_run
53    /// # use dioxus::prelude::*;
54    /// rsx! {
55    ///    button {
56    ///       onclick: move |evt: MouseEvent| {
57    ///          let data = evt.map(|data| data.client_coordinates());
58    ///          println!("{:?}", data.data());
59    ///       }
60    ///    }
61    /// };
62    /// ```
63    pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {
64        Event {
65            data: Rc::new(f(&self.data)),
66            metadata: self.metadata.clone(),
67        }
68    }
69
70    /// Prevent this event from continuing to bubble up the tree to parent elements.
71    ///
72    /// # Example
73    ///
74    /// ```rust, no_run
75    /// # use dioxus::prelude::*;
76    /// rsx! {
77    ///     button {
78    ///         onclick: move |evt: Event<MouseData>| {
79    ///             # #[allow(deprecated)]
80    ///             evt.cancel_bubble();
81    ///         }
82    ///     }
83    /// };
84    /// ```
85    #[deprecated = "use stop_propagation instead"]
86    pub fn cancel_bubble(&self) {
87        self.metadata.borrow_mut().propagates = false;
88    }
89
90    /// Check if the event propagates up the tree to parent elements
91    pub fn propagates(&self) -> bool {
92        self.metadata.borrow().propagates
93    }
94
95    /// Prevent this event from continuing to bubble up the tree to parent elements.
96    ///
97    /// # Example
98    ///
99    /// ```rust, no_run
100    /// # use dioxus::prelude::*;
101    /// rsx! {
102    ///     button {
103    ///         onclick: move |evt: Event<MouseData>| {
104    ///             evt.stop_propagation();
105    ///         }
106    ///     }
107    /// };
108    /// ```
109    pub fn stop_propagation(&self) {
110        self.metadata.borrow_mut().propagates = false;
111    }
112
113    /// Get a reference to the inner data from this event
114    ///
115    /// ```rust, no_run
116    /// # use dioxus::prelude::*;
117    /// rsx! {
118    ///     button {
119    ///         onclick: move |evt: Event<MouseData>| {
120    ///             let data = evt.data();
121    ///             async move {
122    ///                 println!("{:?}", data);
123    ///             }
124    ///         }
125    ///     }
126    /// };
127    /// ```
128    pub fn data(&self) -> Rc<T> {
129        self.data.clone()
130    }
131
132    /// Prevent the default action of the event.
133    ///
134    /// # Example
135    ///
136    /// ```rust
137    /// # use dioxus::prelude::*;
138    /// fn App() -> Element {
139    ///     rsx! {
140    ///         a {
141    ///             // You can prevent the default action of the event with `prevent_default`
142    ///             onclick: move |event| {
143    ///                 event.prevent_default();
144    ///             },
145    ///             href: "https://dioxuslabs.com",
146    ///             "don't go to the link"
147    ///         }
148    ///     }
149    /// }
150    /// ```
151    ///
152    /// Note: This must be called synchronously when handling the event. Calling it after the event has been handled will have no effect.
153    ///
154    /// <div class="warning">
155    ///
156    /// This method is not available on the LiveView renderer because LiveView handles all events over a websocket which cannot block.
157    ///
158    /// </div>
159    #[track_caller]
160    pub fn prevent_default(&self) {
161        self.metadata.borrow_mut().prevent_default = true;
162    }
163
164    /// Check if the default action of the event is enabled.
165    pub fn default_action_enabled(&self) -> bool {
166        !self.metadata.borrow().prevent_default
167    }
168}
169
170impl<T: ?Sized> Clone for Event<T> {
171    fn clone(&self) -> Self {
172        Self {
173            metadata: self.metadata.clone(),
174            data: self.data.clone(),
175        }
176    }
177}
178
179impl<T> std::ops::Deref for Event<T> {
180    type Target = Rc<T>;
181    fn deref(&self) -> &Self::Target {
182        &self.data
183    }
184}
185
186impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        f.debug_struct("UiEvent")
189            .field("bubble_state", &self.propagates())
190            .field("prevent_default", &!self.default_action_enabled())
191            .field("data", &self.data)
192            .finish()
193    }
194}
195
196/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
197///
198/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
199///
200/// # Example
201///
202/// ```rust, no_run
203/// # use dioxus::prelude::*;
204/// rsx! {
205///     MyComponent { onclick: move |evt| tracing::debug!("clicked") }
206/// };
207///
208/// #[derive(Props, Clone, PartialEq)]
209/// struct MyProps {
210///     onclick: EventHandler<MouseEvent>,
211/// }
212///
213/// fn MyComponent(cx: MyProps) -> Element {
214///     rsx! {
215///         button {
216///             onclick: move |evt| cx.onclick.call(evt),
217///         }
218///     }
219/// }
220/// ```
221pub type EventHandler<T = ()> = Callback<T>;
222
223/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
224///
225/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
226///
227///
228/// # Example
229///
230/// ```rust, ignore
231/// rsx! {
232///     MyComponent { onclick: move |evt| {
233///         tracing::debug!("clicked");
234///         42
235///     } }
236/// }
237///
238/// #[derive(Props)]
239/// struct MyProps {
240///     onclick: Callback<MouseEvent, i32>,
241/// }
242///
243/// fn MyComponent(cx: MyProps) -> Element {
244///     rsx! {
245///         button {
246///             onclick: move |evt| println!("number: {}", cx.onclick.call(evt)),
247///         }
248///     }
249/// }
250/// ```
251pub struct Callback<Args = (), Ret = ()> {
252    pub(crate) origin: ScopeId,
253    /// During diffing components with EventHandler, we move the EventHandler over in place instead of rerunning the child component.
254    ///
255    /// ```rust
256    /// # use dioxus::prelude::*;
257    /// #[component]
258    /// fn Child(onclick: EventHandler<MouseEvent>) -> Element {
259    ///     rsx! {
260    ///         button {
261    ///             // Diffing Child will not rerun this component, it will just update the callback in place so that if this callback is called, it will run the latest version of the callback
262    ///             onclick: move |evt| onclick(evt),
263    ///         }
264    ///     }
265    /// }
266    /// ```
267    ///
268    /// This is both more efficient and allows us to avoid out of date EventHandlers.
269    ///
270    /// We double box here because we want the data to be copy (GenerationalBox) and still update in place (ExternalListenerCallback)
271    /// This isn't an ideal solution for performance, but it is non-breaking and fixes the issues described in <https://github.com/DioxusLabs/dioxus/pull/2298>
272    pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<Args, Ret>>>,
273}
274
275impl<Args, Ret> std::fmt::Debug for Callback<Args, Ret> {
276    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
277        f.debug_struct("Callback")
278            .field("origin", &self.origin)
279            .field("callback", &self.callback)
280            .finish()
281    }
282}
283
284impl<T: 'static, Ret: Default + 'static> Default for Callback<T, Ret> {
285    fn default() -> Self {
286        Callback::new(|_| Ret::default())
287    }
288}
289
290/// A helper trait for [`Callback`]s that allows functions to accept a [`Callback`] that may return an async block which will automatically be spawned.
291///
292/// ```rust, no_run
293/// use dioxus::prelude::*;
294/// fn accepts_fn<Ret: dioxus_core::SpawnIfAsync<Marker>, Marker>(callback: impl FnMut(u32) -> Ret + 'static) {
295///     let callback = Callback::new(callback);
296/// }
297/// // You can accept both async and non-async functions
298/// accepts_fn(|x| async move { println!("{}", x) });
299/// accepts_fn(|x| println!("{}", x));
300/// ```
301#[rustversion::attr(
302    since(1.78.0),
303    diagnostic::on_unimplemented(
304        message = "`SpawnIfAsync` is not implemented for `{Self}`",
305        label = "Return Value",
306        note = "Closures (or event handlers) in dioxus need to return either: nothing (the unit type `()`), or an async block that dioxus will automatically spawn",
307        note = "You likely need to add a semicolon to the end of the event handler to make it return nothing",
308    )
309)]
310pub trait SpawnIfAsync<Marker, Ret = ()>: Sized {
311    /// Spawn the value into the dioxus runtime if it is an async block
312    fn spawn(self) -> Ret;
313}
314
315// Support for FnMut -> Ret for any return type
316impl<Ret> SpawnIfAsync<(), Ret> for Ret {
317    fn spawn(self) -> Ret {
318        self
319    }
320}
321
322// Support for FnMut -> async { unit } for the unit return type
323#[doc(hidden)]
324pub struct AsyncMarker;
325impl<F: std::future::Future<Output = ()> + 'static> SpawnIfAsync<AsyncMarker> for F {
326    fn spawn(self) {
327        crate::prelude::spawn(async move {
328            self.await;
329        });
330    }
331}
332
333// Support for FnMut -> async { Result(()) } for the unit return type
334#[doc(hidden)]
335pub struct AsyncResultMarker;
336
337impl<T> SpawnIfAsync<AsyncResultMarker> for T
338where
339    T: std::future::Future<Output = crate::Result<()>> + 'static,
340{
341    #[inline]
342    fn spawn(self) {
343        crate::prelude::spawn(async move {
344            if let Err(err) = self.await {
345                crate::prelude::throw_error(err)
346            }
347        });
348    }
349}
350
351// Support for FnMut -> Result(()) for the unit return type
352impl SpawnIfAsync<()> for crate::Result<()> {
353    #[inline]
354    fn spawn(self) {
355        if let Err(err) = self {
356            crate::prelude::throw_error(err)
357        }
358    }
359}
360
361// We can't directly forward the marker because it would overlap with a bunch of other impls, so we wrap it in another type instead
362#[doc(hidden)]
363pub struct MarkerWrapper<T>(PhantomData<T>);
364
365// Closure can be created from FnMut -> async { anything } or FnMut -> Ret
366impl<
367        Function: FnMut(Args) -> Spawn + 'static,
368        Args: 'static,
369        Spawn: SpawnIfAsync<Marker, Ret> + 'static,
370        Ret: 'static,
371        Marker,
372    > SuperFrom<Function, MarkerWrapper<Marker>> for Callback<Args, Ret>
373{
374    fn super_from(input: Function) -> Self {
375        Callback::new(input)
376    }
377}
378
379#[doc(hidden)]
380pub struct UnitClosure<Marker>(PhantomData<Marker>);
381
382// Closure can be created from FnMut -> async { () } or FnMut -> Ret
383impl<
384        Function: FnMut() -> Spawn + 'static,
385        Spawn: SpawnIfAsync<Marker, Ret> + 'static,
386        Ret: 'static,
387        Marker,
388    > SuperFrom<Function, UnitClosure<Marker>> for Callback<(), Ret>
389{
390    fn super_from(mut input: Function) -> Self {
391        Callback::new(move |()| input())
392    }
393}
394
395#[test]
396fn closure_types_infer() {
397    #[allow(unused)]
398    fn compile_checks() {
399        // You should be able to use a closure as a callback
400        let callback: Callback<(), ()> = Callback::new(|_| {});
401        // Or an async closure
402        let callback: Callback<(), ()> = Callback::new(|_| async {});
403
404        // You can also pass in a closure that returns a value
405        let callback: Callback<(), u32> = Callback::new(|_| 123);
406
407        // Or pass in a value
408        let callback: Callback<u32, ()> = Callback::new(|value: u32| async move {
409            println!("{}", value);
410        });
411
412        // Unit closures shouldn't require an argument
413        let callback: Callback<(), ()> = Callback::super_from(|| async move {
414            println!("hello world");
415        });
416    }
417}
418
419impl<Args, Ret> Copy for Callback<Args, Ret> {}
420
421impl<Args, Ret> Clone for Callback<Args, Ret> {
422    fn clone(&self) -> Self {
423        *self
424    }
425}
426
427impl<Args: 'static, Ret: 'static> PartialEq for Callback<Args, Ret> {
428    fn eq(&self, other: &Self) -> bool {
429        self.callback.ptr_eq(&other.callback) && self.origin == other.origin
430    }
431}
432
433pub(super) struct ExternalListenerCallback<Args, Ret> {
434    callback: Box<dyn FnMut(Args) -> Ret>,
435    runtime: std::rc::Weak<Runtime>,
436}
437
438impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
439    /// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.
440    /// This should not be called directly in the body of a component because it will not be dropped until the component is dropped.
441    #[track_caller]
442    pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(
443        mut f: impl FnMut(Args) -> MaybeAsync + 'static,
444    ) -> Self {
445        let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
446        let origin = runtime
447            .current_scope_id()
448            .unwrap_or_else(|e| panic!("{}", e));
449        let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
450        let callback = owner.insert_rc(Some(ExternalListenerCallback {
451            callback: Box::new(move |event: Args| f(event).spawn()),
452            runtime: Rc::downgrade(&runtime),
453        }));
454        Self { callback, origin }
455    }
456
457    /// Leak a new [`Callback`] that will not be dropped unless it is manually dropped.
458    #[track_caller]
459    pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {
460        let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
461        let origin = runtime
462            .current_scope_id()
463            .unwrap_or_else(|e| panic!("{}", e));
464        let callback = GenerationalBox::leak_rc(
465            Some(ExternalListenerCallback {
466                callback: Box::new(move |event: Args| f(event).spawn()),
467                runtime: Rc::downgrade(&runtime),
468            }),
469            Location::caller(),
470        );
471        Self { callback, origin }
472    }
473
474    /// Leak a new reference to the [`Callback`] that will not be dropped unless the callback is dropped manually
475    pub(crate) fn leak_reference(&self) -> generational_box::BorrowResult<Callback<Args, Ret>> {
476        Ok(Callback {
477            callback: self.callback.leak_reference()?,
478            origin: self.origin,
479        })
480    }
481
482    /// Call this callback with the appropriate argument type
483    ///
484    /// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
485    #[track_caller]
486    pub fn call(&self, arguments: Args) -> Ret {
487        if let Some(callback) = self.callback.write().as_mut() {
488            let runtime = callback
489                .runtime
490                .upgrade()
491                .expect("Callback was called after the runtime was dropped");
492            let _guard = RuntimeGuard::new(runtime.clone());
493            runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments))
494        } else {
495            panic!("Callback was manually dropped")
496        }
497    }
498
499    /// Create a `impl FnMut + Copy` closure from the Closure type
500    pub fn into_closure(self) -> impl FnMut(Args) -> Ret + Copy + 'static {
501        move |args| self.call(args)
502    }
503
504    /// Forcibly drop the internal handler callback, releasing memory
505    ///
506    /// This will force any future calls to "call" to not doing anything
507    pub fn release(&self) {
508        self.callback.set(None);
509    }
510
511    /// Replace the function in the callback with a new one
512    pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) {
513        let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
514        self.callback.set(Some(ExternalListenerCallback {
515            callback,
516            runtime: Rc::downgrade(&runtime),
517        }));
518    }
519
520    #[doc(hidden)]
521    /// This should only be used by the `rsx!` macro.
522    pub fn __point_to(&mut self, other: &Self) {
523        self.callback.point_to(other.callback).unwrap();
524    }
525}
526
527impl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {
528    type Target = dyn Fn(Args) -> Ret + 'static;
529
530    fn deref(&self) -> &Self::Target {
531        // https://github.com/dtolnay/case-studies/tree/master/callable-types
532
533        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
534        let uninit_callable = std::mem::MaybeUninit::<Self>::uninit();
535        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
536        let uninit_closure = move |t| Self::call(unsafe { &*uninit_callable.as_ptr() }, t);
537
538        // Check that the size of the closure is the same as the size of Self in case the compiler changed the layout of the closure.
539        let size_of_closure = std::mem::size_of_val(&uninit_closure);
540        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
541
542        // Then cast the lifetime of the closure to the lifetime of &self.
543        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
544            b
545        }
546        let reference_to_closure = cast_lifetime(
547            {
548                // The real closure that we will never use.
549                &uninit_closure
550            },
551            #[allow(clippy::missing_transmute_annotations)]
552            // We transmute self into a reference to the closure. This is safe because we know that the closure has the same memory layout as Self so &Closure == &Self.
553            unsafe {
554                std::mem::transmute(self)
555            },
556        );
557
558        // Cast the closure to a trait object.
559        reference_to_closure as &_
560    }
561}