dioxus_core/
events.rs

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