Switch to owned listener type (#4289)
* Switch to owned listener type * remove html event workaround * fix event conversion * keep track of the origin scope * fix clippy * fix docs * Use "ListenerCallback" instead of "ListenerCb" * Add a small example --------- Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
This commit is contained in:
1 parent
3bf8f09c65
commit
af6fc3a542
5 files changed
+143
-270
No files matched your search
+120
-10
@@ -1,6 +1,8 @@
|
||||
use crate::{properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId};
|
||||
use crate::{
|
||||
prelude::current_scope_id, properties::SuperFrom, runtime::RuntimeGuard, Runtime, ScopeId,
|
||||
};
|
||||
use generational_box::GenerationalBox;
|
||||
use std::{cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
|
||||
use std::{any::Any, cell::RefCell, marker::PhantomData, panic::Location, rc::Rc};
|
||||
|
||||
/// A wrapper around some generic data that handles the event's state
|
||||
///
|
||||
@@ -67,6 +69,17 @@ impl<T: ?Sized> Event<T> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert this event into a boxed event with a dynamic type
|
||||
pub fn into_any(self) -> Event<dyn Any>
|
||||
where
|
||||
T: Sized,
|
||||
{
|
||||
Event {
|
||||
data: self.data as Rc<dyn Any>,
|
||||
metadata: self.metadata,
|
||||
}
|
||||
}
|
||||
|
||||
/// Prevent this event from continuing to bubble up the tree to parent elements.
|
||||
///
|
||||
/// # Example
|
||||
@@ -376,6 +389,27 @@ impl<
|
||||
}
|
||||
}
|
||||
|
||||
impl<
|
||||
Function: FnMut(Event<T>) -> Spawn + 'static,
|
||||
T: 'static,
|
||||
Spawn: SpawnIfAsync<Marker> + 'static,
|
||||
Marker,
|
||||
> SuperFrom<Function, MarkerWrapper<Marker>> for ListenerCallback<T>
|
||||
{
|
||||
fn super_from(input: Function) -> Self {
|
||||
ListenerCallback::new(input)
|
||||
}
|
||||
}
|
||||
|
||||
// ListenerCallback<T> can be created from Callback<Event<T>>
|
||||
impl<T: 'static> SuperFrom<Callback<Event<T>>> for ListenerCallback<T> {
|
||||
fn super_from(input: Callback<Event<T>>) -> Self {
|
||||
// https://github.com/rust-lang/rust-clippy/issues/15072
|
||||
#[allow(clippy::redundant_closure)]
|
||||
ListenerCallback::new(move |event| input(event))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct UnitClosure<Marker>(PhantomData<Marker>);
|
||||
|
||||
@@ -471,14 +505,6 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
|
||||
Self { callback, origin }
|
||||
}
|
||||
|
||||
/// Leak a new reference to the [`Callback`] that will not be dropped unless the callback is dropped manually
|
||||
pub(crate) fn leak_reference(&self) -> generational_box::BorrowResult<Callback<Args, Ret>> {
|
||||
Ok(Callback {
|
||||
callback: self.callback.leak_reference()?,
|
||||
origin: self.origin,
|
||||
})
|
||||
}
|
||||
|
||||
/// Call this callback with the appropriate argument type
|
||||
///
|
||||
/// This borrows the callback using a RefCell. Recursively calling a callback will cause a panic.
|
||||
@@ -559,3 +585,87 @@ impl<Args: 'static, Ret: 'static> std::ops::Deref for Callback<Args, Ret> {
|
||||
reference_to_closure as &_
|
||||
}
|
||||
}
|
||||
|
||||
type AnyEventHandler = Rc<RefCell<dyn FnMut(Event<dyn Any>)>>;
|
||||
|
||||
/// An owned callback type used in [`AttributeValue::Listener`](crate::AttributeValue::Listener).
|
||||
///
|
||||
/// This is the type that powers the `on` attributes in the `rsx!` macro, allowing you to pass event
|
||||
/// handlers to elements.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx! {
|
||||
/// button {
|
||||
/// onclick: AttributeValue::Listener(ListenerCallback::new(move |evt: Event<MouseData>| {
|
||||
/// // ...
|
||||
/// }
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub struct ListenerCallback<T = ()> {
|
||||
pub(crate) origin: ScopeId,
|
||||
callback: AnyEventHandler,
|
||||
_marker: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T> Clone for ListenerCallback<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
origin: self.origin,
|
||||
callback: self.callback.clone(),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> PartialEq for ListenerCallback<T> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
// We compare the pointers of the callbacks, since they are unique
|
||||
Rc::ptr_eq(&self.callback, &other.callback) && self.origin == other.origin
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ListenerCallback<T> {
|
||||
/// Create a new [`ListenerCallback`] from a callback
|
||||
///
|
||||
/// This is expected to be called within a runtime scope. Make sure a runtime is current before
|
||||
/// calling this method.
|
||||
pub fn new<MaybeAsync, Marker>(mut f: impl FnMut(Event<T>) -> MaybeAsync + 'static) -> Self
|
||||
where
|
||||
T: 'static,
|
||||
MaybeAsync: SpawnIfAsync<Marker>,
|
||||
{
|
||||
Self {
|
||||
origin: current_scope_id().expect("ListenerCallback must be created within a scope"),
|
||||
callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| {
|
||||
let data = event.data.downcast::<T>().unwrap();
|
||||
f(Event {
|
||||
metadata: event.metadata.clone(),
|
||||
data,
|
||||
})
|
||||
.spawn();
|
||||
})),
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
/// Call the callback with an event
|
||||
///
|
||||
/// This is expected to be called within a runtime scope. Make sure a runtime is current before
|
||||
/// calling this method.
|
||||
pub fn call(&self, event: Event<dyn Any>) {
|
||||
let runtime = Runtime::current().expect("ListenerCallback must be called within a runtime");
|
||||
runtime.with_scope_on_stack(self.origin, || {
|
||||
(self.callback.borrow_mut())(event);
|
||||
});
|
||||
}
|
||||
|
||||
/// Erase the type of the callback, allowing it to be used with any type of event
|
||||
pub fn erase(self) -> ListenerCallback {
|
||||
ListenerCallback {
|
||||
origin: self.origin,
|
||||
callback: self.callback,
|
||||
_marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user