Skip to main content

dioxus_core/
events.rs

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