//! Handler type aliases for action callbacks.
//!
//! This module provides type aliases for boxed closures that take an environment
//! reference. These are used for event handlers and callbacks throughout the framework.
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;
/// A boxed action handler that can be called multiple times.
///
/// This is essentially `Box<dyn FnMut(&Environment) -> T>`.
pub type BoxedAction<T = ()> = Box<dyn FnMut(&Environment) -> T>;
/// A boxed action handler that can only be called once.
///
/// This is essentially `Box<dyn FnOnce(&Environment) -> T>`.
pub type BoxedActionOnce<T = ()> = Box<dyn FnOnce(&Environment) -> T>;
/// Type alias for a boxed action handler (backwards compatibility).
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>()
)
})
}
/// A repeatable handler that can extract arguments from the environment.
#[diagnostic::on_unimplemented(
message = "this handler's parameters cannot be extracted from the environment",
label = "expected a handler whose parameters implement `Extractor`",
note = "A WaterUI handler takes no parameters or parameters implementing `Extractor`: `#[state]` makes an owned `Clone` type extractable from `.state(&value)` injections, `State<T>` wraps an injected value of a foreign type, `Use<T>` reads an environment value, `Option<E>` tolerates a missing one, and `impl_extractor!` marks a `Clone` type installed as an environment value. Repeated parameters of the same state type bind positionally — the first `.state()` call feeds the first parameter of that type."
)]
pub trait Handler<Args, T = ()>: 'static {
/// Invokes the handler using values extracted from `env`.
fn call(&mut self, env: &Environment) -> T;
}
/// A one-shot handler that can extract arguments from the environment.
pub trait HandlerOnce<Args, T = ()>: 'static {
/// Invokes the handler once using values extracted from `env`.
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);
/// Creates a boxed action from a handler.
#[inline]
pub fn boxed_action<Args, T: 'static>(mut f: impl Handler<Args, T>) -> BoxedAction<T> {
Box::new(move |env: &Environment| f.call(env))
}
/// Creates a boxed one-shot action from a handler.
#[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))
}
// ============================================================================
// Event handlers
// ============================================================================
/// A boxed event handler that consumes one event payload alongside the
/// environment-driven extractors.
///
/// This is essentially `Box<dyn FnMut(E, &Environment) -> T>` and is the
/// counterpart to [`BoxedAction`] for views that report events upward —
/// for example media playback events, video errors, or any photo-style
/// completion notification. The first argument is the event payload (typed
/// to the producing component); the remaining arguments are extracted from
/// `env` exactly like [`Handler`] arguments.
pub type BoxedEventAction<E, T = ()> = Box<dyn FnMut(E, &Environment) -> T>;
/// A repeatable event handler that consumes a typed event payload plus
/// environment-extracted arguments.
///
/// Implemented for any closure of the form `FnMut(E, A1, ..., An) -> T`
/// where each `Ai: Extractor`. The shape mirrors [`Handler`] but inserts
/// an "event" position in the leading argument slot. This lets event
/// callbacks on views like `Photo::on_event`, `Video::on_event`, and
/// `WebView::on_event` reuse the same `State<T>` / `Environment` extractor
/// machinery as `Button::action`.
///
/// The `Args` tuple only counts extractor positions — the event payload is
/// not part of the tuple — so a closure like `|event: E| { ... }` matches
/// `EventHandler<E, (), ()>` and reads as "no extractors, returns unit".
pub trait EventHandler<E, Args, T = ()>: 'static {
/// Invokes the handler with the given event payload and the extractor
/// arguments resolved from `env`.
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);
/// Erases an [`EventHandler`] into a [`BoxedEventAction`] so a component
/// config can store the callback as a typed field without leaking the
/// extractor-tuple generics.
#[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))
}
// ============================================================================
// Shared (Clone-able) Actions
// ============================================================================
/// A shared action that can be cloned and called multiple times.
///
/// This uses `Rc<RefCell<...>>` to allow the action to be shared across
/// multiple owners while still supporting mutation.
type SharedActionFn<T> = Rc<RefCell<Box<dyn FnMut(&Environment) -> T>>>;
/// Cloneable action handle backed by shared mutable state.
#[derive(Clone)]
pub struct SharedAction<T = ()>(SharedActionFn<T>);
impl<T: 'static> SharedAction<T> {
/// Creates a new shared action from a closure.
pub fn new<Args>(f: impl Handler<Args, T>) -> Self {
Self(Rc::new(RefCell::new(boxed_action(f))))
}
/// Calls the action with the given environment.
#[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")
}
}
/// Creates a shared action from a closure that ignores the environment.
#[inline]
pub fn shared_action<Args, T: 'static>(f: impl Handler<Args, T>) -> SharedAction<T> {
SharedAction::new(f)
}
// ============================================================================
// ViewBuilder
// ============================================================================
/// A trait for types that can repeatedly construct views.
///
/// This is a convenience trait that provides similar functionality to `Fn() -> impl View`,
/// allowing types to be used as view factories.
pub trait ViewBuilder: 'static {
/// The type of view produced by this builder.
type Output: View;
/// Builds a 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)()
}
}
/// A builder for creating views from handler functions.
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> {
/// Creates a new `ViewBuilder` from a handler function.
#[must_use]
pub fn new(handler: impl ViewBuilder<Output = V>) -> Self {
Self(Rc::new(handler))
}
/// Builds a view by invoking the underlying handler.
#[must_use]
pub fn build(&self) -> V {
ViewBuilder::build(&*self.0)
}
/// Erases the specific view type, returning a builder that produces `AnyView`.
#[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);
}
}