dioxus_core/
events.rs

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