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}