ailloli_ui_runtime/input/
click_action.rs

1//! Type-erased click callbacks dispatched through an event context.
2
3use std::rc::Rc;
4
5use super::EventCtx;
6
7/// Shared single-threaded click callback.
8type ClickHandler<A> = Rc<dyn Fn(&mut EventCtx<A>)>;
9
10/// Lazily constructs a dispatched action every time it is resolved.
11///
12/// Clones share the same `Rc` factory. Neither the factory nor this wrapper is
13/// `Send` or `Sync`; it belongs to the UI thread. Factory panics propagate.
14///
15/// # Examples
16///
17/// ```
18/// use std::cell::Cell;
19/// use std::rc::Rc;
20/// use ailloli_ui_runtime::input::DeferredAction;
21/// let calls = Rc::new(Cell::new(0));
22/// let seen = calls.clone();
23/// let action = DeferredAction::new(move || { seen.set(seen.get() + 1); seen.get() });
24/// assert_eq!((action.resolve(), action.resolve()), (1, 2));
25/// ```
26pub struct DeferredAction<A> {
27    /// UI-local factory invoked afresh for each resolution.
28    factory: Rc<dyn Fn() -> A>,
29}
30
31/// Implements the `Clone` contract for `DeferredAction<A>`.
32impl<A> Clone for DeferredAction<A> {
33    /// Produces the clone required by the standard cloning contract.
34    fn clone(&self) -> Self {
35        Self {
36            factory: self.factory.clone(),
37        }
38    }
39}
40
41/// Provides the operations defined for `DeferredAction<A>`.
42impl<A> DeferredAction<A> {
43    /// Wraps a UI-thread factory without invoking it.
44    ///
45    /// # Examples
46    ///
47    /// ```
48    /// use ailloli_ui_runtime::input::DeferredAction;
49    /// let action = DeferredAction::new(|| String::from("save"));
50    /// assert_eq!(action.resolve(), "save");
51    /// ```
52    pub fn new(factory: impl Fn() -> A + 'static) -> Self {
53        Self {
54            factory: Rc::new(factory),
55        }
56    }
57
58    /// Invokes the factory and returns its newly produced action.
59    ///
60    /// No result is cached; repeated calls can return different values.
61    ///
62    /// # Examples
63    ///
64    /// ```
65    /// use ailloli_ui_runtime::input::DeferredAction;
66    /// let action = DeferredAction::new(|| 42);
67    /// assert_eq!(action.resolve(), 42);
68    /// ```
69    pub fn resolve(&self) -> A {
70        (self.factory)()
71    }
72}
73
74/// Executable UI-thread callback for one click-like activation.
75///
76/// # Examples
77///
78/// ```
79/// use ailloli_ui_core::ElementId;
80/// use ailloli_ui_runtime::app::RuntimeHandle;
81/// use ailloli_ui_runtime::input::{ClickAction, EventCtx};
82/// let runtime = RuntimeHandle::<u8>::new();
83/// let mut ctx = EventCtx::new(runtime.clone(), ElementId(1));
84/// ClickAction::handler(|ctx| ctx.dispatch(7)).run(&mut ctx);
85/// assert_eq!(runtime.take_actions(), vec![7]);
86/// ```
87pub struct ClickAction<A> {
88    /// UI-local callback executed synchronously for the click event.
89    handler: ClickHandler<A>,
90}
91
92/// Provides the operations defined for `ClickAction<A>`.
93impl<A> ClickAction<A> {
94    /// Wraps a UI-thread event-context callback.
95    ///
96    /// The callback is retained by `Rc`; captured resources live until every
97    /// action clone/internal reference is dropped. Callback panics propagate.
98    ///
99    /// # Examples
100    ///
101    /// ```
102    /// use ailloli_ui_core::ElementId;
103    /// use ailloli_ui_runtime::app::RuntimeHandle;
104    /// use ailloli_ui_runtime::input::{ClickAction, EventCtx};
105    /// let runtime = RuntimeHandle::<&'static str>::new();
106    /// let mut ctx = EventCtx::new(runtime.clone(), ElementId(1));
107    /// let action = ClickAction::handler(|ctx| ctx.dispatch("open"));
108    /// action.run(&mut ctx);
109    /// assert_eq!(runtime.take_actions(), vec!["open"]);
110    /// ```
111    pub fn handler(handler: impl Fn(&mut EventCtx<A>) + 'static) -> Self {
112        Self {
113            handler: Rc::new(handler),
114        }
115    }
116
117    /// Runs the stored callback synchronously with `ctx`.
118    ///
119    /// # Examples
120    ///
121    /// ```
122    /// use ailloli_ui_core::ElementId;
123    /// use ailloli_ui_runtime::app::RuntimeHandle;
124    /// use ailloli_ui_runtime::input::{ClickAction, EventCtx};
125    /// let runtime = RuntimeHandle::<i32>::new();
126    /// let mut ctx = EventCtx::new(runtime.clone(), ElementId(9));
127    /// ClickAction::handler(|ctx| ctx.dispatch(3)).run(&mut ctx);
128    /// assert_eq!(runtime.take_actions(), vec![3]);
129    /// ```
130    pub fn run(&self, ctx: &mut EventCtx<A>) {
131        (self.handler)(ctx);
132    }
133}
134
135/// Converts static, deferred, or callback values into a [`ClickAction`].
136///
137/// A plain action requires `Clone` and dispatches a fresh clone on every run.
138/// [`DeferredAction`] instead invokes its factory each time.
139///
140/// # Examples
141///
142/// ```
143/// use ailloli_ui_core::ElementId;
144/// use ailloli_ui_runtime::app::RuntimeHandle;
145/// use ailloli_ui_runtime::input::{EventCtx, IntoClickAction};
146/// let runtime = RuntimeHandle::<String>::new();
147/// let mut ctx = EventCtx::new(runtime.clone(), ElementId(1));
148/// String::from("save").into_click_action().run(&mut ctx);
149/// assert_eq!(runtime.take_actions(), vec![String::from("save")]);
150/// ```
151pub trait IntoClickAction<A> {
152    /// Consumes `self` and returns its executable click callback.
153    ///
154    /// # Examples
155    ///
156    /// ```
157    /// use ailloli_ui_runtime::input::{ClickAction, IntoClickAction};
158    /// let action: ClickAction<()> = ClickAction::handler(|_| {}).into_click_action();
159    /// # let _ = action;
160    /// ```
161    fn into_click_action(self) -> ClickAction<A>;
162}
163
164/// Implements the `IntoClickAction<A>` contract for `ClickAction<A>`.
165impl<A> IntoClickAction<A> for ClickAction<A> {
166    /// Implements the into_click_action helper used by this module.
167    fn into_click_action(self) -> ClickAction<A> {
168        self
169    }
170}
171
172/// Implements the `IntoClickAction<A>` contract for `A`.
173impl<A: Clone + 'static> IntoClickAction<A> for A {
174    /// Implements the into_click_action helper used by this module.
175    fn into_click_action(self) -> ClickAction<A> {
176        ClickAction::handler(move |ctx| ctx.dispatch(self.clone()))
177    }
178}
179
180/// Implements the `IntoClickAction<A>` contract for `DeferredAction<A>`.
181impl<A: 'static> IntoClickAction<A> for DeferredAction<A> {
182    /// Implements the into_click_action helper used by this module.
183    fn into_click_action(self) -> ClickAction<A> {
184        ClickAction::handler(move |ctx| ctx.dispatch(self.resolve()))
185    }
186}