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}