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}