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}