Skip to main content

waterui_core/foundation/
handler.rs

1//! Handler type aliases for action callbacks.
2//!
3//! This module provides type aliases for boxed closures that take an environment
4//! reference. These are used for event handlers and callbacks throughout the framework.
5
6use crate::extract::{ExtractionState, Extractor};
7use crate::{AnyView, View};
8use alloc::boxed::Box;
9use alloc::rc::Rc;
10use core::any::type_name;
11use core::cell::RefCell;
12use core::fmt;
13
14use crate::Environment;
15
16/// A boxed action handler that can be called multiple times.
17///
18/// This is essentially `Box<dyn FnMut(&Environment) -> T>`.
19pub type BoxedAction<T = ()> = Box<dyn FnMut(&Environment) -> T>;
20
21/// A boxed action handler that can only be called once.
22///
23/// This is essentially `Box<dyn FnOnce(&Environment) -> T>`.
24pub type BoxedActionOnce<T = ()> = Box<dyn FnOnce(&Environment) -> T>;
25
26/// Type alias for a boxed action handler (backwards compatibility).
27pub type ActionObject = BoxedAction<()>;
28
29fn extract_or_panic<T: Extractor>(env: &Environment, state: &mut ExtractionState) -> T {
30    T::extract_from_action(env, state).unwrap_or_else(|error| {
31        panic!(
32            "failed to extract `{}` from environment for action: {error}",
33            type_name::<T>()
34        )
35    })
36}
37
38/// A repeatable handler that can extract arguments from the environment.
39pub trait Handler<Args, T = ()>: 'static {
40    /// Invokes the handler using values extracted from `env`.
41    fn call(&mut self, env: &Environment) -> T;
42}
43
44/// A one-shot handler that can extract arguments from the environment.
45pub trait HandlerOnce<Args, T = ()>: 'static {
46    /// Invokes the handler once using values extracted from `env`.
47    fn call_once(self, env: &Environment) -> T;
48}
49
50macro_rules! impl_handler {
51    () => {
52        impl<F, Output> Handler<(), Output> for F
53        where
54            F: FnMut() -> Output + 'static,
55        {
56            fn call(&mut self, _env: &Environment) -> Output {
57                self()
58            }
59        }
60
61        impl<F, Output> HandlerOnce<(), Output> for F
62        where
63            F: FnOnce() -> Output + 'static,
64        {
65            fn call_once(self, _env: &Environment) -> Output {
66                self()
67            }
68        }
69    };
70    ($($T:ident),+) => {
71        impl<Func, Output, $($T),+> Handler<($($T,)+), Output> for Func
72        where
73            Func: FnMut($($T),+) -> Output + 'static,
74            $($T: Extractor),+
75        {
76            #[allow(non_snake_case)]
77            fn call(&mut self, env: &Environment) -> Output {
78                let mut state = ExtractionState::default();
79                $(let $T = extract_or_panic::<$T>(env, &mut state);)+
80                self($($T),+)
81            }
82        }
83
84        impl<Func, Output, $($T),+> HandlerOnce<($($T,)+), Output> for Func
85        where
86            Func: FnOnce($($T),+) -> Output + 'static,
87            $($T: Extractor),+
88        {
89            #[allow(non_snake_case)]
90            fn call_once(self, env: &Environment) -> Output {
91                let mut state = ExtractionState::default();
92                $(let $T = extract_or_panic::<$T>(env, &mut state);)+
93                self($($T),+)
94            }
95        }
96    };
97}
98
99impl_handler!();
100impl_handler!(A);
101impl_handler!(A, B);
102impl_handler!(A, B, C);
103impl_handler!(A, B, C, D);
104impl_handler!(A, B, C, D, E);
105impl_handler!(A, B, C, D, E, F);
106impl_handler!(A, B, C, D, E, F, G);
107impl_handler!(A, B, C, D, E, F, G, H);
108
109/// Creates a boxed action from a handler.
110#[inline]
111pub fn boxed_action<Args, T: 'static>(mut f: impl Handler<Args, T>) -> BoxedAction<T> {
112    Box::new(move |env: &Environment| f.call(env))
113}
114
115/// Creates a boxed one-shot action from a handler.
116#[inline]
117pub fn boxed_action_once<Args, T: 'static>(f: impl HandlerOnce<Args, T>) -> BoxedActionOnce<T> {
118    Box::new(move |env: &Environment| f.call_once(env))
119}
120
121// ============================================================================
122// Event handlers
123// ============================================================================
124
125/// A boxed event handler that consumes one event payload alongside the
126/// environment-driven extractors.
127///
128/// This is essentially `Box<dyn FnMut(E, &Environment) -> T>` and is the
129/// counterpart to [`BoxedAction`] for views that report events upward —
130/// for example media playback events, video errors, or any photo-style
131/// completion notification. The first argument is the event payload (typed
132/// to the producing component); the remaining arguments are extracted from
133/// `env` exactly like [`Handler`] arguments.
134pub type BoxedEventAction<E, T = ()> = Box<dyn FnMut(E, &Environment) -> T>;
135
136/// A repeatable event handler that consumes a typed event payload plus
137/// environment-extracted arguments.
138///
139/// Implemented for any closure of the form `FnMut(E, A1, ..., An) -> T`
140/// where each `Ai: Extractor`. The shape mirrors [`Handler`] but inserts
141/// an "event" position in the leading argument slot. This lets event
142/// callbacks on views like `Photo::on_event`, `Video::on_event`, and
143/// `WebView::on_event` reuse the same `State<T>` / `Environment` extractor
144/// machinery as `Button::action`.
145///
146/// The `Args` tuple only counts extractor positions — the event payload is
147/// not part of the tuple — so a closure like `|event: E| { ... }` matches
148/// `EventHandler<E, (), ()>` and reads as "no extractors, returns unit".
149pub trait EventHandler<E, Args, T = ()>: 'static {
150    /// Invokes the handler with the given event payload and the extractor
151    /// arguments resolved from `env`.
152    fn call(&mut self, event: E, env: &Environment) -> T;
153}
154
155macro_rules! impl_event_handler {
156    () => {
157        impl<F, E, Output> EventHandler<E, (), Output> for F
158        where
159            F: FnMut(E) -> Output + 'static,
160        {
161            fn call(&mut self, event: E, _env: &Environment) -> Output {
162                self(event)
163            }
164        }
165    };
166    ($($T:ident),+) => {
167        impl<Func, E, Output, $($T),+> EventHandler<E, ($($T,)+), Output> for Func
168        where
169            Func: FnMut(E, $($T),+) -> Output + 'static,
170            $($T: Extractor),+
171        {
172            #[allow(non_snake_case)]
173            fn call(&mut self, event: E, env: &Environment) -> Output {
174                let mut state = ExtractionState::default();
175                $(let $T = extract_or_panic::<$T>(env, &mut state);)+
176                self(event, $($T),+)
177            }
178        }
179    };
180}
181
182impl_event_handler!();
183impl_event_handler!(A);
184impl_event_handler!(A, B);
185impl_event_handler!(A, B, C);
186impl_event_handler!(A, B, C, D);
187impl_event_handler!(A, B, C, D, E1);
188impl_event_handler!(A, B, C, D, E1, F);
189impl_event_handler!(A, B, C, D, E1, F, G);
190impl_event_handler!(A, B, C, D, E1, F, G, H);
191
192/// Erases an [`EventHandler`] into a [`BoxedEventAction`] so a component
193/// config can store the callback as a typed field without leaking the
194/// extractor-tuple generics.
195#[inline]
196pub fn boxed_event_handler<E, Args, T: 'static>(
197    mut f: impl EventHandler<E, Args, T>,
198) -> BoxedEventAction<E, T>
199where
200    E: 'static,
201{
202    Box::new(move |event: E, env: &Environment| f.call(event, env))
203}
204
205// ============================================================================
206// Shared (Clone-able) Actions
207// ============================================================================
208
209/// A shared action that can be cloned and called multiple times.
210///
211/// This uses `Rc<RefCell<...>>` to allow the action to be shared across
212/// multiple owners while still supporting mutation.
213type SharedActionFn<T> = Rc<RefCell<Box<dyn FnMut(&Environment) -> T>>>;
214
215/// Cloneable action handle backed by shared mutable state.
216#[derive(Clone)]
217pub struct SharedAction<T = ()>(SharedActionFn<T>);
218
219impl<T: 'static> SharedAction<T> {
220    /// Creates a new shared action from a closure.
221    pub fn new<Args>(f: impl Handler<Args, T>) -> Self {
222        Self(Rc::new(RefCell::new(boxed_action(f))))
223    }
224
225    /// Calls the action with the given environment.
226    #[expect(
227        clippy::must_use_candidate,
228        reason = "actions are side-effectful and may intentionally return unit"
229    )]
230    pub fn call(&self, env: &Environment) -> T {
231        (self.0.borrow_mut())(env)
232    }
233}
234
235impl<T> fmt::Debug for SharedAction<T> {
236    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237        f.write_str("SharedAction")
238    }
239}
240
241/// Creates a shared action from a closure that ignores the environment.
242#[inline]
243pub fn shared_action<Args, T: 'static>(f: impl Handler<Args, T>) -> SharedAction<T> {
244    SharedAction::new(f)
245}
246
247// ============================================================================
248// ViewBuilder
249// ============================================================================
250
251/// A trait for types that can repeatedly construct views.
252///
253/// This is a convenience trait that provides similar functionality to `Fn() -> impl View`,
254/// allowing types to be used as view factories.
255pub trait ViewBuilder: 'static {
256    /// The type of view produced by this builder.
257    type Output: View;
258    /// Builds a view
259    fn build(&self) -> Self::Output;
260}
261
262impl<V: View, F> ViewBuilder for F
263where
264    F: 'static + Fn() -> V,
265{
266    type Output = V;
267    fn build(&self) -> Self::Output {
268        (self)()
269    }
270}
271
272/// A builder for creating views from handler functions.
273pub struct AnyViewBuilder<V = AnyView>(Rc<dyn ViewBuilder<Output = V>>);
274
275impl<V> Clone for AnyViewBuilder<V> {
276    fn clone(&self) -> Self {
277        Self(Rc::clone(&self.0))
278    }
279}
280
281impl<V: View> AnyViewBuilder<V> {
282    /// Creates a new `ViewBuilder` from a handler function.
283    #[must_use]
284    pub fn new(handler: impl ViewBuilder<Output = V>) -> Self {
285        Self(Rc::new(handler))
286    }
287
288    /// Builds a view by invoking the underlying handler.
289    #[must_use]
290    pub fn build(&self) -> V {
291        ViewBuilder::build(&*self.0)
292    }
293
294    /// Erases the specific view type, returning a builder that produces `AnyView`.
295    #[must_use]
296    pub fn erase(self) -> AnyViewBuilder<AnyView> {
297        AnyViewBuilder::new(move || {
298            let v = self.build();
299            AnyView::new(v)
300        })
301    }
302}
303
304impl<V> fmt::Debug for AnyViewBuilder<V> {
305    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
306        f.write_str("AnyViewBuilder")
307    }
308}
309
310#[cfg(test)]
311mod tests {
312    use super::*;
313    use alloc::rc::Rc;
314    use core::cell::Cell;
315
316    #[test]
317    fn shared_action_invokes_unit_handler_repeatedly() {
318        let count = Rc::new(Cell::new(0));
319        let captured_count = Rc::clone(&count);
320        let action = shared_action(move || captured_count.set(captured_count.get() + 1));
321
322        action.call(&Environment::default());
323        action.call(&Environment::default());
324
325        assert_eq!(count.get(), 2);
326    }
327
328    #[test]
329    fn shared_action_preserves_return_values() {
330        let action = shared_action(|| 7);
331
332        assert_eq!(action.call(&Environment::default()), 7);
333    }
334}