diff --git a/packages/yew/src/callback.rs b/packages/yew/src/callback.rs index 68edd25a304..1ba61d9de74 100644 --- a/packages/yew/src/callback.rs +++ b/packages/yew/src/callback.rs @@ -9,92 +9,194 @@ use std::rc::Rc; use crate::html::ImplicitClone; -/// Universal callback wrapper. -/// -/// An `Rc` wrapper is used to make it cloneable. -pub struct Callback { - /// A callback which can be called multiple times - pub(crate) cb: Rc OUT>, -} +macro_rules! generate_callback_impls { + ($callback:ident, $in_ty:ty, $out_var:ident => $out_val:expr) => { + impl OUT + 'static> From for $callback { + fn from(func: F) -> Self { + $callback { cb: Rc::new(func) } + } + } -impl OUT + 'static> From for Callback { - fn from(func: F) -> Self { - Callback { cb: Rc::new(func) } - } -} + impl Clone for $callback { + fn clone(&self) -> Self { + Self { + cb: self.cb.clone(), + } + } + } -impl Clone for Callback { - fn clone(&self) -> Self { - Self { - cb: self.cb.clone(), + #[allow(clippy::vtable_address_comparisons)] + impl PartialEq for $callback { + fn eq(&self, other: &$callback) -> bool { + let ($callback { cb }, $callback { cb: rhs_cb }) = (self, other); + Rc::ptr_eq(cb, rhs_cb) + } } - } -} -#[allow(clippy::vtable_address_comparisons)] -impl PartialEq for Callback { - fn eq(&self, other: &Callback) -> bool { - let (Callback { cb }, Callback { cb: rhs_cb }) = (self, other); - Rc::ptr_eq(cb, rhs_cb) - } -} + impl fmt::Debug for $callback { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "$callback<_>") + } + } -impl fmt::Debug for Callback { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "Callback<_>") - } -} + impl $callback { + /// This method calls the callback's function. + pub fn emit(&self, value: $in_ty) -> OUT { + (*self.cb)(value) + } + } -impl Callback { - /// This method calls the callback's function. - pub fn emit(&self, value: IN) -> OUT { - (*self.cb)(value) - } + impl $callback { + /// Creates a "no-op" callback which can be used when it is not suitable to use an + /// `Option<$callback>`. + pub fn noop() -> Self { + Self::from(|_: $in_ty| ()) + } + } + + impl Default for $callback { + fn default() -> Self { + Self::noop() + } + } + + impl $callback { + /// Creates a new [`Callback`] from another callback and a function. + /// + /// That when emitted will call that function and will emit the original callback + pub fn reform(&self, func: F) -> Callback + where + F: Fn(T) -> IN + 'static, + { + let this = self.clone(); + let func = move |input: T| { + #[allow(unused_mut)] + let mut $out_var = func(input); + this.emit($out_val) + }; + func.into() + } + + /// Creates a new [`CallbackRef`] from another callback and a function. + /// + /// That when emitted will call that function and will emit the original callback + pub fn reform_ref(&self, func: F) -> CallbackRef + where + F: Fn(&T) -> $in_ty + 'static, + { + let this = self.clone(); + let func = move |input: &T| { + #[allow(unused_mut)] + let mut $out_var = func(input); + this.emit($out_val) + }; + func.into() + } + + /// Creates a new [`CallbackRefMut`] from another callback and a function. + /// + /// That when emitted will call that function and will emit the original callback + pub fn reform_ref_mut(&self, func: F) -> CallbackRefMut + where + F: Fn(&mut T) -> $in_ty + 'static, + { + let this = self.clone(); + let func = move |input: &mut T| { + #[allow(unused_mut)] + let mut $out_var = func(input); + this.emit($out_val) + }; + func.into() + } + + /// Creates a new [`Callback`] from another callback and a function. + /// + /// When emitted will call the function and, only if it returns `Some(value)`, will emit + /// `value` to the original callback. + pub fn filter_reform(&self, func: F) -> Callback> + where + F: Fn(T) -> Option + 'static, + { + let this = self.clone(); + let func = move |input: T| { + func(input).map( + #[allow(unused_mut)] + |mut $out_var| this.emit($out_val), + ) + }; + func.into() + } + + /// Creates a new [`CallbackRef`] from another callback and a function. + /// + /// When emitted will call the function and, only if it returns `Some(value)`, will emit + /// `value` to the original callback. + pub fn filter_reform_ref(&self, func: F) -> CallbackRef> + where + F: Fn(&T) -> Option<$in_ty> + 'static, + { + let this = self.clone(); + let func = move |input: &T| { + func(input).map( + #[allow(unused_mut)] + |mut $out_var| this.emit($out_val), + ) + }; + func.into() + } + + /// Creates a new [`CallbackRefMut`] from another callback and a function. + /// + /// When emitted will call the function and, only if it returns `Some(value)`, will emit + /// `value` to the original callback. + pub fn filter_reform_ref_mut(&self, func: F) -> CallbackRefMut> + where + F: Fn(&mut T) -> Option<$in_ty> + 'static, + { + let this = self.clone(); + let func = move |input: &mut T| { + func(input).map( + #[allow(unused_mut)] + |mut $out_var| this.emit($out_val), + ) + }; + func.into() + } + } + + impl ImplicitClone for $callback {} + }; } -impl Callback { - /// Creates a "no-op" callback which can be used when it is not suitable to use an - /// `Option`. - pub fn noop() -> Self { - Self::from(|_| ()) - } +/// Universal callback wrapper. +/// +/// An `Rc` wrapper is used to make it cloneable. +pub struct Callback { + /// A callback which can be called multiple times + pub(crate) cb: Rc OUT>, } -impl Default for Callback { - fn default() -> Self { - Self::noop() - } +generate_callback_impls!(Callback, IN, output => output); + +/// Universal callback wrapper with reference in argument. +/// +/// An `Rc` wrapper is used to make it cloneable. +pub struct CallbackRef { + /// A callback which can be called multiple times + pub(crate) cb: Rc OUT>, } -impl Callback { - /// Creates a new callback from another callback and a function - /// That when emitted will call that function and will emit the original callback - pub fn reform(&self, func: F) -> Callback - where - F: Fn(T) -> IN + 'static, - { - let this = self.clone(); - let func = move |input| { - let output = func(input); - this.emit(output) - }; - Callback::from(func) - } +generate_callback_impls!(CallbackRef, &IN, output => #[allow(clippy::needless_borrow)] &output); - /// Creates a new callback from another callback and a function. - /// When emitted will call the function and, only if it returns `Some(value)`, will emit - /// `value` to the original callback. - pub fn filter_reform(&self, func: F) -> Callback> - where - F: Fn(T) -> Option + 'static, - { - let this = self.clone(); - let func = move |input| func(input).map(|output| this.emit(output)); - Callback::from(func) - } +/// Universal callback wrapper with mutable reference in argument. +/// +/// An `Rc` wrapper is used to make it cloneable. +pub struct CallbackRefMut { + /// A callback which can be called multiple times + pub(crate) cb: Rc OUT>, } -impl ImplicitClone for Callback {} +generate_callback_impls!(CallbackRefMut, &mut IN, output => &mut output); #[cfg(test)] mod test { @@ -144,4 +246,58 @@ mod test { vec![true, false] ); } + + #[test] + fn test_ref() { + let callback: CallbackRef = CallbackRef::from(|x: &usize| *x); + assert_eq!(callback.emit(&42), 42); + } + + #[test] + fn test_ref_mut() { + let callback: CallbackRefMut = CallbackRefMut::from(|x: &mut usize| *x = 42); + let mut value: usize = 0; + callback.emit(&mut value); + assert_eq!(value, 42); + } + + #[test] + fn test_reform_ref() { + let callback: Callback = Callback::from(|x: usize| x + 1); + let reformed: CallbackRef = callback.reform_ref(|x: &usize| *x + 2); + assert_eq!(reformed.emit(&42), 45); + } + + #[test] + fn test_reform_ref_mut() { + let callback: CallbackRefMut = CallbackRefMut::from(|x: &mut usize| *x += 1); + let reformed: CallbackRefMut = callback.reform_ref_mut(|x: &mut usize| { + *x += 2; + x + }); + let mut value: usize = 42; + reformed.emit(&mut value); + assert_eq!(value, 45); + } + + #[test] + fn test_filter_reform_ref() { + let callback: Callback = Callback::from(|x: usize| x + 1); + let reformed: CallbackRef> = + callback.filter_reform_ref(|x: &usize| Some(*x + 2)); + assert_eq!(reformed.emit(&42), Some(45)); + } + + #[test] + fn test_filter_reform_ref_mut() { + let callback: CallbackRefMut = CallbackRefMut::from(|x: &mut usize| *x += 1); + let reformed: CallbackRefMut> = + callback.filter_reform_ref_mut(|x: &mut usize| { + *x += 2; + Some(x) + }); + let mut value: usize = 42; + reformed.emit(&mut value).expect("is some"); + assert_eq!(value, 45); + } } diff --git a/packages/yew/src/lib.rs b/packages/yew/src/lib.rs index 65a5cdd4566..3e47e9a5c30 100644 --- a/packages/yew/src/lib.rs +++ b/packages/yew/src/lib.rs @@ -330,7 +330,7 @@ pub mod prelude { #[cfg(feature = "csr")] pub use crate::app_handle::AppHandle; - pub use crate::callback::Callback; + pub use crate::callback::{Callback, CallbackRef, CallbackRefMut}; pub use crate::context::{ContextHandle, ContextProvider}; pub use crate::events::*; pub use crate::functional::*;