use crate::extract::{ExtractionState, Extractor};
use crate::{AnyView, View};
use alloc::boxed::Box;
use alloc::rc::Rc;
use core::any::type_name;
use core::cell::RefCell;
use core::fmt;
use crate::Environment;
pub type BoxedAction<T = ()> = Box<dyn FnMut(&Environment) -> T>;
pub type BoxedActionOnce<T = ()> = Box<dyn FnOnce(&Environment) -> T>;
pub type ActionObject = BoxedAction<()>;
fn extract_or_panic<T: Extractor>(env: &Environment, state: &mut ExtractionState) -> T {
T::extract_from_action(env, state).unwrap_or_else(|error| {
panic!(
"failed to extract `{}` from environment for action: {error}",
type_name::<T>()
)
})
}
pub trait Handler<Args, T = ()>: 'static {
fn call(&mut self, env: &Environment) -> T;
}
pub trait HandlerOnce<Args, T = ()>: 'static {
fn call_once(self, env: &Environment) -> T;
}
macro_rules! impl_handler {
() => {
impl<F, Output> Handler<(), Output> for F
where
F: FnMut() -> Output + 'static,
{
fn call(&mut self, _env: &Environment) -> Output {
self()
}
}
impl<F, Output> HandlerOnce<(), Output> for F
where
F: FnOnce() -> Output + 'static,
{
fn call_once(self, _env: &Environment) -> Output {
self()
}
}
};
($($T:ident),+) => {
impl<Func, Output, $($T),+> Handler<($($T,)+), Output> for Func
where
Func: FnMut($($T),+) -> Output + 'static,
$($T: Extractor),+
{
#[allow(non_snake_case)]
fn call(&mut self, env: &Environment) -> Output {
let mut state = ExtractionState::default();
$(let $T = extract_or_panic::<$T>(env, &mut state);)+
self($($T),+)
}
}
impl<Func, Output, $($T),+> HandlerOnce<($($T,)+), Output> for Func
where
Func: FnOnce($($T),+) -> Output + 'static,
$($T: Extractor),+
{
#[allow(non_snake_case)]
fn call_once(self, env: &Environment) -> Output {
let mut state = ExtractionState::default();
$(let $T = extract_or_panic::<$T>(env, &mut state);)+
self($($T),+)
}
}
};
}
impl_handler!();
impl_handler!(A);
impl_handler!(A, B);
impl_handler!(A, B, C);
impl_handler!(A, B, C, D);
impl_handler!(A, B, C, D, E);
impl_handler!(A, B, C, D, E, F);
impl_handler!(A, B, C, D, E, F, G);
impl_handler!(A, B, C, D, E, F, G, H);
#[inline]
pub fn boxed_action<Args, T: 'static>(mut f: impl Handler<Args, T>) -> BoxedAction<T> {
Box::new(move |env: &Environment| f.call(env))
}
#[inline]
pub fn boxed_action_once<Args, T: 'static>(f: impl HandlerOnce<Args, T>) -> BoxedActionOnce<T> {
Box::new(move |env: &Environment| f.call_once(env))
}
pub type BoxedEventAction<E, T = ()> = Box<dyn FnMut(E, &Environment) -> T>;
pub trait EventHandler<E, Args, T = ()>: 'static {
fn call(&mut self, event: E, env: &Environment) -> T;
}
macro_rules! impl_event_handler {
() => {
impl<F, E, Output> EventHandler<E, (), Output> for F
where
F: FnMut(E) -> Output + 'static,
{
fn call(&mut self, event: E, _env: &Environment) -> Output {
self(event)
}
}
};
($($T:ident),+) => {
impl<Func, E, Output, $($T),+> EventHandler<E, ($($T,)+), Output> for Func
where
Func: FnMut(E, $($T),+) -> Output + 'static,
$($T: Extractor),+
{
#[allow(non_snake_case)]
fn call(&mut self, event: E, env: &Environment) -> Output {
let mut state = ExtractionState::default();
$(let $T = extract_or_panic::<$T>(env, &mut state);)+
self(event, $($T),+)
}
}
};
}
impl_event_handler!();
impl_event_handler!(A);
impl_event_handler!(A, B);
impl_event_handler!(A, B, C);
impl_event_handler!(A, B, C, D);
impl_event_handler!(A, B, C, D, E1);
impl_event_handler!(A, B, C, D, E1, F);
impl_event_handler!(A, B, C, D, E1, F, G);
impl_event_handler!(A, B, C, D, E1, F, G, H);
#[inline]
pub fn boxed_event_handler<E, Args, T: 'static>(
mut f: impl EventHandler<E, Args, T>,
) -> BoxedEventAction<E, T>
where
E: 'static,
{
Box::new(move |event: E, env: &Environment| f.call(event, env))
}
type SharedActionFn<T> = Rc<RefCell<Box<dyn FnMut(&Environment) -> T>>>;
#[derive(Clone)]
pub struct SharedAction<T = ()>(SharedActionFn<T>);
impl<T: 'static> SharedAction<T> {
pub fn new<Args>(f: impl Handler<Args, T>) -> Self {
Self(Rc::new(RefCell::new(boxed_action(f))))
}
#[expect(
clippy::must_use_candidate,
reason = "actions are side-effectful and may intentionally return unit"
)]
pub fn call(&self, env: &Environment) -> T {
(self.0.borrow_mut())(env)
}
}
impl<T> fmt::Debug for SharedAction<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("SharedAction")
}
}
#[inline]
pub fn shared_action<Args, T: 'static>(f: impl Handler<Args, T>) -> SharedAction<T> {
SharedAction::new(f)
}
pub trait ViewBuilder: 'static {
type Output: View;
fn build(&self) -> Self::Output;
}
impl<V: View, F> ViewBuilder for F
where
F: 'static + Fn() -> V,
{
type Output = V;
fn build(&self) -> Self::Output {
(self)()
}
}
pub struct AnyViewBuilder<V = AnyView>(Rc<dyn ViewBuilder<Output = V>>);
impl<V> Clone for AnyViewBuilder<V> {
fn clone(&self) -> Self {
Self(Rc::clone(&self.0))
}
}
impl<V: View> AnyViewBuilder<V> {
#[must_use]
pub fn new(handler: impl ViewBuilder<Output = V>) -> Self {
Self(Rc::new(handler))
}
#[must_use]
pub fn build(&self) -> V {
ViewBuilder::build(&*self.0)
}
#[must_use]
pub fn erase(self) -> AnyViewBuilder<AnyView> {
AnyViewBuilder::new(move || {
let v = self.build();
AnyView::new(v)
})
}
}
impl<V> fmt::Debug for AnyViewBuilder<V> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("AnyViewBuilder")
}
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::rc::Rc;
use core::cell::Cell;
#[test]
fn shared_action_invokes_unit_handler_repeatedly() {
let count = Rc::new(Cell::new(0));
let captured_count = Rc::clone(&count);
let action = shared_action(move || captured_count.set(captured_count.get() + 1));
action.call(&Environment::default());
action.call(&Environment::default());
assert_eq!(count.get(), 2);
}
#[test]
fn shared_action_preserves_return_values() {
let action = shared_action(|| 7);
assert_eq!(action.call(&Environment::default()), 7);
}
}