|
@@ -1,7 +1,8 @@
|
|
|
-use crate::{global_context::current_scope_id, Runtime, ScopeId};
|
|
|
+use crate::{global_context::current_scope_id, properties::SuperFrom, Runtime, ScopeId};
|
|
|
use generational_box::GenerationalBox;
|
|
|
use std::{
|
|
|
cell::{Cell, RefCell},
|
|
|
+ marker::PhantomData,
|
|
|
rc::Rc,
|
|
|
};
|
|
|
|
|
@@ -147,7 +148,6 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
|
|
///
|
|
|
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
|
|
|
///
|
|
|
-///
|
|
|
/// # Example
|
|
|
///
|
|
|
/// ```rust, no_run
|
|
@@ -169,7 +169,37 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
|
|
/// }
|
|
|
/// }
|
|
|
/// ```
|
|
|
-pub struct EventHandler<T = ()> {
|
|
|
+pub type EventHandler<T = ()> = Callback<T>;
|
|
|
+
|
|
|
+/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
|
|
|
+///
|
|
|
+/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
|
|
|
+///
|
|
|
+///
|
|
|
+/// # Example
|
|
|
+///
|
|
|
+/// ```rust, ignore
|
|
|
+/// rsx!{
|
|
|
+/// MyComponent { onclick: move |evt| {
|
|
|
+/// tracing::debug!("clicked");
|
|
|
+/// 42
|
|
|
+/// } }
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// #[derive(Props)]
|
|
|
+/// struct MyProps {
|
|
|
+/// onclick: Callback<MouseEvent, i32>,
|
|
|
+/// }
|
|
|
+///
|
|
|
+/// fn MyComponent(cx: MyProps) -> Element {
|
|
|
+/// rsx!{
|
|
|
+/// button {
|
|
|
+/// onclick: move |evt| println!("number: {}", cx.onclick.call(evt)),
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+/// }
|
|
|
+/// ```
|
|
|
+pub struct Callback<Args = (), Ret = ()> {
|
|
|
pub(crate) origin: ScopeId,
|
|
|
/// During diffing components with EventHandler, we move the EventHandler over in place instead of rerunning the child component.
|
|
|
///
|
|
@@ -190,87 +220,172 @@ pub struct EventHandler<T = ()> {
|
|
|
///
|
|
|
/// We double box here because we want the data to be copy (GenerationalBox) and still update in place (ExternalListenerCallback)
|
|
|
/// 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>
|
|
|
- pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<T>>>,
|
|
|
+ pub(super) callback: GenerationalBox<Option<ExternalListenerCallback<Args, Ret>>>,
|
|
|
}
|
|
|
|
|
|
-impl<T> std::fmt::Debug for EventHandler<T> {
|
|
|
+impl<Args, Ret> std::fmt::Debug for Callback<Args, Ret> {
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
- f.debug_struct("EventHandler")
|
|
|
+ f.debug_struct("Callback")
|
|
|
.field("origin", &self.origin)
|
|
|
.field("callback", &self.callback)
|
|
|
.finish()
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T: 'static> Default for EventHandler<T> {
|
|
|
+impl<T: 'static, Ret: Default + 'static> Default for Callback<T, Ret> {
|
|
|
fn default() -> Self {
|
|
|
- EventHandler::new(|_| {})
|
|
|
+ Callback::new(|_| Ret::default())
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<F: FnMut(T) + 'static, T: 'static> From<F> for EventHandler<T> {
|
|
|
- fn from(f: F) -> Self {
|
|
|
- EventHandler::new(f)
|
|
|
+/// A helper trait for [`Callback`]s that allows functions to accept a [`Callback`] that may return an async block which will automatically be spawned.
|
|
|
+///
|
|
|
+/// ```rust, no_run
|
|
|
+/// use dioxus::prelude::*;
|
|
|
+/// fn accepts_fn<Ret: dioxus_core::SpawnIfAsync<Marker>, Marker>(callback: impl FnMut(u32) -> Ret + 'static) {
|
|
|
+/// let callback = Callback::new(callback);
|
|
|
+/// }
|
|
|
+/// // You can accept both async and non-async functions
|
|
|
+/// accepts_fn(|x| async move { println!("{}", x) });
|
|
|
+/// accepts_fn(|x| println!("{}", x));
|
|
|
+/// ```
|
|
|
+#[rustversion::attr(
|
|
|
+ since(1.78.0),
|
|
|
+ diagnostic::on_unimplemented(
|
|
|
+ message = "`SpawnIfAsync` is not implemented for `{Self}`",
|
|
|
+ label = "Return Value",
|
|
|
+ note = "Closures (or event handlers) in dioxus need to return either: nothing (the unit type `()`), or an async block that dioxus will automatically spawn",
|
|
|
+ note = "You likely need to add a semicolon to the end of the event handler to make it return nothing",
|
|
|
+ )
|
|
|
+)]
|
|
|
+pub trait SpawnIfAsync<Marker, Ret = ()>: Sized {
|
|
|
+ /// Spawn the value into the dioxus runtime if it is an async block
|
|
|
+ fn spawn(self) -> Ret;
|
|
|
+}
|
|
|
+
|
|
|
+// Support for FnMut -> Ret for any return type
|
|
|
+impl<Ret> SpawnIfAsync<(), Ret> for Ret {
|
|
|
+ fn spawn(self) -> Ret {
|
|
|
+ self
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// Support for FnMut -> async { anything } for the unit return type
|
|
|
+#[doc(hidden)]
|
|
|
+pub struct AsyncMarker<O>(PhantomData<O>);
|
|
|
+impl<F: std::future::Future<Output = O> + 'static, O> SpawnIfAsync<AsyncMarker<O>, ()> for F {
|
|
|
+ fn spawn(self) {
|
|
|
+ crate::prelude::spawn(async move {
|
|
|
+ self.await;
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+// 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
|
|
|
+#[doc(hidden)]
|
|
|
+pub struct MarkerWrapper<T>(PhantomData<T>);
|
|
|
+
|
|
|
+// Closure can be created from FnMut -> async { anything } or FnMut -> Ret
|
|
|
+impl<
|
|
|
+ Function: FnMut(Args) -> Spawn + 'static,
|
|
|
+ Args: 'static,
|
|
|
+ Spawn: SpawnIfAsync<Marker, Ret> + 'static,
|
|
|
+ Ret: 'static,
|
|
|
+ Marker,
|
|
|
+ > SuperFrom<Function, MarkerWrapper<Marker>> for Callback<Args, Ret>
|
|
|
+{
|
|
|
+ fn super_from(input: Function) -> Self {
|
|
|
+ Callback::new(input)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+#[test]
|
|
|
+fn closure_types_infer() {
|
|
|
+ #[allow(unused)]
|
|
|
+ fn compile_checks() {
|
|
|
+ // You should be able to use a closure as a callback
|
|
|
+ let callback: Callback<(), ()> = Callback::new(|_| {});
|
|
|
+ // Or an async closure
|
|
|
+ let callback: Callback<(), ()> = Callback::new(|_| async {});
|
|
|
+
|
|
|
+ // You can also pass in a closure that returns a value
|
|
|
+ let callback: Callback<(), u32> = Callback::new(|_| 123);
|
|
|
+
|
|
|
+ // Or pass in a value
|
|
|
+ let callback: Callback<u32, ()> = Callback::new(|value: u32| async move {
|
|
|
+ println!("{}", value);
|
|
|
+ });
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T> Copy for EventHandler<T> {}
|
|
|
+impl<Args, Ret> Copy for Callback<Args, Ret> {}
|
|
|
|
|
|
-impl<T> Clone for EventHandler<T> {
|
|
|
+impl<Args, Ret> Clone for Callback<Args, Ret> {
|
|
|
fn clone(&self) -> Self {
|
|
|
*self
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T: 'static> PartialEq for EventHandler<T> {
|
|
|
+impl<Args: 'static, Ret: 'static> PartialEq for Callback<Args, Ret> {
|
|
|
fn eq(&self, _: &Self) -> bool {
|
|
|
true
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-type ExternalListenerCallback<T> = Rc<RefCell<dyn FnMut(T)>>;
|
|
|
+type ExternalListenerCallback<Args, Ret> = Rc<RefCell<dyn FnMut(Args) -> Ret>>;
|
|
|
|
|
|
-impl<T: 'static> EventHandler<T> {
|
|
|
- /// Create a new [`EventHandler`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.
|
|
|
+impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
|
|
+ /// Create a new [`Callback`] from an [`FnMut`]. The callback is owned by the current scope and will be dropped when the scope is dropped.
|
|
|
/// This should not be called directly in the body of a component because it will not be dropped until the component is dropped.
|
|
|
#[track_caller]
|
|
|
- pub fn new(mut f: impl FnMut(T) + 'static) -> EventHandler<T> {
|
|
|
+ pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(
|
|
|
+ mut f: impl FnMut(Args) -> MaybeAsync + 'static,
|
|
|
+ ) -> Self {
|
|
|
let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
|
|
|
- let callback = owner.insert(Some(Rc::new(RefCell::new(move |event: T| {
|
|
|
- f(event);
|
|
|
- })) as Rc<RefCell<dyn FnMut(T)>>));
|
|
|
- EventHandler {
|
|
|
+ let callback = owner.insert(Some(
|
|
|
+ Rc::new(RefCell::new(move |event: Args| f(event).spawn()))
|
|
|
+ as Rc<RefCell<dyn FnMut(Args) -> Ret>>,
|
|
|
+ ));
|
|
|
+ Self {
|
|
|
callback,
|
|
|
origin: current_scope_id().expect("to be in a dioxus runtime"),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// Leak a new [`EventHandler`] that will not be dropped unless it is manually dropped.
|
|
|
+ /// Leak a new [`Callback`] that will not be dropped unless it is manually dropped.
|
|
|
#[track_caller]
|
|
|
- pub fn leak(mut f: impl FnMut(T) + 'static) -> EventHandler<T> {
|
|
|
- let callback = GenerationalBox::leak(Some(Rc::new(RefCell::new(move |event: T| {
|
|
|
- f(event);
|
|
|
- })) as Rc<RefCell<dyn FnMut(T)>>));
|
|
|
- EventHandler {
|
|
|
+ pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {
|
|
|
+ let callback =
|
|
|
+ GenerationalBox::leak(Some(Rc::new(RefCell::new(move |event: Args| f(event)))
|
|
|
+ as Rc<RefCell<dyn FnMut(Args) -> Ret>>));
|
|
|
+ Self {
|
|
|
callback,
|
|
|
origin: current_scope_id().expect("to be in a dioxus runtime"),
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- /// Call this event handler with the appropriate event type
|
|
|
+ /// Call this callback with the appropriate argument type
|
|
|
///
|
|
|
- /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
|
|
|
- pub fn call(&self, event: T) {
|
|
|
+ /// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
|
|
|
+ pub fn call(&self, arguments: Args) -> Ret {
|
|
|
if let Some(callback) = self.callback.read().as_ref() {
|
|
|
Runtime::with(|rt| rt.scope_stack.borrow_mut().push(self.origin));
|
|
|
- {
|
|
|
+ let value = {
|
|
|
let mut callback = callback.borrow_mut();
|
|
|
- callback(event);
|
|
|
- }
|
|
|
+ callback(arguments)
|
|
|
+ };
|
|
|
Runtime::with(|rt| rt.scope_stack.borrow_mut().pop());
|
|
|
+ value
|
|
|
+ } else {
|
|
|
+ panic!("Callback was manually dropped")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /// Create a `impl FnMut + Copy` closure from the Closure type
|
|
|
+ pub fn into_closure(self) -> impl FnMut(Args) -> Ret + Copy + 'static {
|
|
|
+ move |args| self.call(args)
|
|
|
+ }
|
|
|
+
|
|
|
/// Forcibly drop the internal handler callback, releasing memory
|
|
|
///
|
|
|
/// This will force any future calls to "call" to not doing anything
|
|
@@ -280,22 +395,22 @@ impl<T: 'static> EventHandler<T> {
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
/// This should only be used by the `rsx!` macro.
|
|
|
- pub fn __set(&mut self, value: ExternalListenerCallback<T>) {
|
|
|
+ pub fn __set(&mut self, value: ExternalListenerCallback<Args, Ret>) {
|
|
|
self.callback.set(Some(value));
|
|
|
}
|
|
|
|
|
|
#[doc(hidden)]
|
|
|
/// This should only be used by the `rsx!` macro.
|
|
|
- pub fn __take(&self) -> ExternalListenerCallback<T> {
|
|
|
+ pub fn __take(&self) -> ExternalListenerCallback<Args, Ret> {
|
|
|
self.callback
|
|
|
.read()
|
|
|
.clone()
|
|
|
- .expect("EventHandler was manually dropped")
|
|
|
+ .expect("Callback was manually dropped")
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl<T: 'static> std::ops::Deref for EventHandler<T> {
|
|
|
- type Target = dyn Fn(T) + 'static;
|
|
|
+impl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {
|
|
|
+ type Target = dyn Fn(Args) -> Ret + 'static;
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
// https://github.com/dtolnay/case-studies/tree/master/callable-types
|