dioxus_core/
events.rs

1use crate::{current_scope_id, properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
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#[doc(hidden)]
336pub struct AsyncMarker;
337impl<F: std::future::Future<Output = ()> + 'static> SpawnIfAsync<AsyncMarker> for F {
338    fn spawn(self) {
339        // Quick poll once to deal with things like prevent_default in the same tick
340        let mut fut = Box::pin(self);
341        let res = fut.as_mut().now_or_never();
342
343        if res.is_none() {
344            crate::spawn(async move {
345                fut.await;
346            });
347        }
348    }
349}
350
351// Support for FnMut -> async { Result(()) } for the unit return type
352#[doc(hidden)]
353pub struct AsyncResultMarker;
354
355impl<T> SpawnIfAsync<AsyncResultMarker> for T
356where
357    T: std::future::Future<Output = crate::Result<()>> + 'static,
358{
359    #[inline]
360    fn spawn(self) {
361        // Quick poll once to deal with things like prevent_default in the same tick
362        let mut fut = Box::pin(self);
363        let res = fut.as_mut().now_or_never();
364
365        if res.is_none() {
366            crate::spawn(async move {
367                if let Err(err) = fut.await {
368                    crate::throw_error(err)
369                }
370            });
371        }
372    }
373}
374
375// Support for FnMut -> Result(()) for the unit return type
376impl SpawnIfAsync<()> for crate::Result<()> {
377    #[inline]
378    fn spawn(self) {
379        if let Err(err) = self {
380            crate::throw_error(err)
381        }
382    }
383}
384
385// 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
386#[doc(hidden)]
387pub struct MarkerWrapper<T>(PhantomData<T>);
388
389// Closure can be created from FnMut -> async { anything } or FnMut -> Ret
390impl<
391        Function: FnMut(Args) -> Spawn + 'static,
392        Args: 'static,
393        Spawn: SpawnIfAsync<Marker, Ret> + 'static,
394        Ret: 'static,
395        Marker,
396    > SuperFrom<Function, MarkerWrapper<Marker>> for Callback<Args, Ret>
397{
398    fn super_from(input: Function) -> Self {
399        Callback::new(input)
400    }
401}
402
403impl<
404        Function: FnMut(Event<T>) -> Spawn + 'static,
405        T: 'static,
406        Spawn: SpawnIfAsync<Marker> + 'static,
407        Marker,
408    > SuperFrom<Function, MarkerWrapper<Marker>> for ListenerCallback<T>
409{
410    fn super_from(input: Function) -> Self {
411        ListenerCallback::new(input)
412    }
413}
414
415// ListenerCallback<T> can be created from Callback<Event<T>>
416impl<T: 'static> SuperFrom<Callback<Event<T>>> for ListenerCallback<T> {
417    fn super_from(input: Callback<Event<T>>) -> Self {
418        // https://github.com/rust-lang/rust-clippy/issues/15072
419        #[allow(clippy::redundant_closure)]
420        ListenerCallback::new(move |event| input(event))
421    }
422}
423
424#[doc(hidden)]
425pub struct UnitClosure<Marker>(PhantomData<Marker>);
426
427// Closure can be created from FnMut -> async { () } or FnMut -> Ret
428impl<
429        Function: FnMut() -> Spawn + 'static,
430        Spawn: SpawnIfAsync<Marker, Ret> + 'static,
431        Ret: 'static,
432        Marker,
433    > SuperFrom<Function, UnitClosure<Marker>> for Callback<(), Ret>
434{
435    fn super_from(mut input: Function) -> Self {
436        Callback::new(move |()| input())
437    }
438}
439
440#[test]
441fn closure_types_infer() {
442    #[allow(unused)]
443    fn compile_checks() {
444        // You should be able to use a closure as a callback
445        let callback: Callback<(), ()> = Callback::new(|_| {});
446        // Or an async closure
447        let callback: Callback<(), ()> = Callback::new(|_| async {});
448
449        // You can also pass in a closure that returns a value
450        let callback: Callback<(), u32> = Callback::new(|_| 123);
451
452        // Or pass in a value
453        let callback: Callback<u32, ()> = Callback::new(|value: u32| async move {
454            println!("{}", value);
455        });
456
457        // Unit closures shouldn't require an argument
458        let callback: Callback<(), ()> = Callback::super_from(|| async move {
459            println!("hello world");
460        });
461    }
462}
463
464impl<Args, Ret> Copy for Callback<Args, Ret> {}
465
466impl<Args, Ret> Clone for Callback<Args, Ret> {
467    fn clone(&self) -> Self {
468        *self
469    }
470}
471
472impl<Args: 'static, Ret: 'static> PartialEq for Callback<Args, Ret> {
473    fn eq(&self, other: &Self) -> bool {
474        self.callback.ptr_eq(&other.callback) && self.origin == other.origin
475    }
476}
477
478pub(super) struct ExternalListenerCallback<Args, Ret> {
479    callback: Box<dyn FnMut(Args) -> Ret>,
480    runtime: std::rc::Weak<Runtime>,
481}
482
483impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
484    /// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.
485    /// This should not be called directly in the body of a component because it will not be dropped until the component is dropped.
486    #[track_caller]
487    pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(
488        mut f: impl FnMut(Args) -> MaybeAsync + 'static,
489    ) -> Self {
490        let runtime = Runtime::current();
491        let origin = runtime.current_scope_id();
492        let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
493        let callback = owner.insert_rc(Some(ExternalListenerCallback {
494            callback: Box::new(move |event: Args| f(event).spawn()),
495            runtime: Rc::downgrade(&runtime),
496        }));
497        Self { callback, origin }
498    }
499
500    /// Leak a new [`Callback`] that will not be dropped unless it is manually dropped.
501    #[track_caller]
502    pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {
503        let runtime = Runtime::current();
504        let origin = runtime.current_scope_id();
505        let callback = GenerationalBox::leak_rc(
506            Some(ExternalListenerCallback {
507                callback: Box::new(move |event: Args| f(event).spawn()),
508                runtime: Rc::downgrade(&runtime),
509            }),
510            Location::caller(),
511        );
512        Self { callback, origin }
513    }
514
515    /// Call this callback with the appropriate argument type
516    ///
517    /// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
518    #[track_caller]
519    pub fn call(&self, arguments: Args) -> Ret {
520        if let Some(callback) = self.callback.write().as_mut() {
521            let runtime = callback
522                .runtime
523                .upgrade()
524                .expect("Callback was called after the runtime was dropped");
525            let _guard = RuntimeGuard::new(runtime.clone());
526            runtime.with_scope_on_stack(self.origin, || (callback.callback)(arguments))
527        } else {
528            panic!("Callback was manually dropped")
529        }
530    }
531
532    /// Create a `impl FnMut + Copy` closure from the Closure type
533    pub fn into_closure(self) -> impl FnMut(Args) -> Ret + Copy + 'static {
534        move |args| self.call(args)
535    }
536
537    /// Forcibly drop the internal handler callback, releasing memory
538    ///
539    /// This will force any future calls to "call" to not doing anything
540    pub fn release(&self) {
541        self.callback.set(None);
542    }
543
544    /// Replace the function in the callback with a new one
545    pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) {
546        let runtime = Runtime::current();
547        self.callback.set(Some(ExternalListenerCallback {
548            callback,
549            runtime: Rc::downgrade(&runtime),
550        }));
551    }
552
553    #[doc(hidden)]
554    /// This should only be used by the `rsx!` macro.
555    pub fn __point_to(&mut self, other: &Self) {
556        self.callback.point_to(other.callback).unwrap();
557    }
558}
559
560impl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {
561    type Target = dyn Fn(Args) -> Ret + 'static;
562
563    fn deref(&self) -> &Self::Target {
564        // https://github.com/dtolnay/case-studies/tree/master/callable-types
565
566        // First we create a closure that captures something with the Same in memory layout as Self (MaybeUninit<Self>).
567        let uninit_callable = std::mem::MaybeUninit::<Self>::uninit();
568        // Then move that value into the closure. We assume that the closure now has a in memory layout of Self.
569        let uninit_closure = move |t| Self::call(unsafe { &*uninit_callable.as_ptr() }, t);
570
571        // 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.
572        let size_of_closure = std::mem::size_of_val(&uninit_closure);
573        assert_eq!(size_of_closure, std::mem::size_of::<Self>());
574
575        // Then cast the lifetime of the closure to the lifetime of &self.
576        fn cast_lifetime<'a, T>(_a: &T, b: &'a T) -> &'a T {
577            b
578        }
579        let reference_to_closure = cast_lifetime(
580            {
581                // The real closure that we will never use.
582                &uninit_closure
583            },
584            #[allow(clippy::missing_transmute_annotations)]
585            // 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.
586            unsafe {
587                std::mem::transmute(self)
588            },
589        );
590
591        // Cast the closure to a trait object.
592        reference_to_closure as &_
593    }
594}
595
596type AnyEventHandler = Rc<RefCell<dyn FnMut(Event<dyn Any>)>>;
597
598/// An owned callback type used in [`AttributeValue::Listener`](crate::AttributeValue::Listener).
599///
600/// This is the type that powers the `on` attributes in the `rsx!` macro, allowing you to pass event
601/// handlers to elements.
602///
603/// ```rust, ignore
604/// rsx! {
605///     button {
606///         onclick: AttributeValue::Listener(ListenerCallback::new(move |evt: Event<MouseData>| {
607///             // ...
608///         }))
609///     }
610/// }
611/// ```
612pub struct ListenerCallback<T = ()> {
613    pub(crate) origin: ScopeId,
614    callback: AnyEventHandler,
615    _marker: PhantomData<T>,
616}
617
618impl<T> Clone for ListenerCallback<T> {
619    fn clone(&self) -> Self {
620        Self {
621            origin: self.origin,
622            callback: self.callback.clone(),
623            _marker: PhantomData,
624        }
625    }
626}
627
628impl<T> PartialEq for ListenerCallback<T> {
629    fn eq(&self, other: &Self) -> bool {
630        // We compare the pointers of the callbacks, since they are unique
631        Rc::ptr_eq(&self.callback, &other.callback) && self.origin == other.origin
632    }
633}
634
635impl<T> ListenerCallback<T> {
636    /// Create a new [`ListenerCallback`] from a callback
637    ///
638    /// This is expected to be called within a runtime scope. Make sure a runtime is current before
639    /// calling this method.
640    pub fn new<MaybeAsync, Marker>(mut f: impl FnMut(Event<T>) -> MaybeAsync + 'static) -> Self
641    where
642        T: 'static,
643        MaybeAsync: SpawnIfAsync<Marker>,
644    {
645        Self {
646            origin: current_scope_id(),
647            callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| {
648                let data = event.data.downcast::<T>().unwrap();
649                f(Event {
650                    metadata: event.metadata.clone(),
651                    data,
652                })
653                .spawn();
654            })),
655            _marker: PhantomData,
656        }
657    }
658
659    /// Call the callback with an event
660    ///
661    /// This is expected to be called within a runtime scope. Make sure a runtime is current before
662    /// calling this method.
663    pub fn call(&self, event: Event<dyn Any>) {
664        Runtime::current().with_scope_on_stack(self.origin, || {
665            (self.callback.borrow_mut())(event);
666        });
667    }
668
669    /// Erase the type of the callback, allowing it to be used with any type of event
670    pub fn erase(self) -> ListenerCallback {
671        ListenerCallback {
672            origin: self.origin,
673            callback: self.callback,
674            _marker: PhantomData,
675        }
676    }
677}