cranpose_core/
callbacks.rs1use crate::composer_context;
2use crate::{Composer, ComposerCore, RecomposeScope};
3use std::cell::RefCell;
4use std::rc::Rc;
5
6pub struct ParamState<T> {
7 pub(crate) value: Option<T>,
8}
9
10impl<T> ParamState<T> {
11 pub fn update(&mut self, new_value: &T) -> bool
12 where
13 T: PartialEq + Clone,
14 {
15 match self.value.as_mut() {
16 Some(old) if old == new_value => false,
17 Some(old) => {
18 old.clone_from(new_value);
19 true
20 }
21 None => {
22 self.value = Some(new_value.clone());
23 true
24 }
25 }
26 }
27
28 pub fn value(&self) -> Option<T>
29 where
30 T: Clone,
31 {
32 self.value.clone()
33 }
34}
35
36pub struct ParamSlot<T> {
39 val: RefCell<Option<T>>,
40}
41
42impl<T> Default for ParamSlot<T> {
43 fn default() -> Self {
44 Self {
45 val: RefCell::new(None),
46 }
47 }
48}
49
50impl<T> ParamSlot<T> {
51 pub fn set(&self, v: T) {
52 *self.val.borrow_mut() = Some(v);
53 }
54
55 pub fn take(&self) -> Option<T> {
57 self.val.borrow_mut().take()
58 }
59}
60
61type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
62type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
63
64struct CallbackScopeGuard {
65 core: Rc<ComposerCore>,
66}
67
68impl CallbackScopeGuard {
69 fn push(composer: &Composer, scope: RecomposeScope) -> Self {
70 composer.core.scope_stack.borrow_mut().push(scope);
71 Self {
72 core: composer.clone_core(),
73 }
74 }
75}
76
77impl Drop for CallbackScopeGuard {
78 fn drop(&mut self) {
79 self.core.scope_stack.borrow_mut().pop();
80 }
81}
82
83fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
84 let captured_scope = scope.borrow().clone();
85 if let Some(saved_scope) = captured_scope {
86 if let Some(composer) = composer_context::current_composer() {
87 let _scope_guard = CallbackScopeGuard::push(&composer, saved_scope);
88 return f();
89 }
90 }
91
92 f()
93}
94
95fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
96 composer.core.scope_stack.borrow().last().cloned()
97}
98
99#[derive(Clone)]
100pub struct CallbackHolder {
101 rc: CallbackCell,
102 creator_scope: CallbackScopeCell,
103}
104
105impl CallbackHolder {
106 pub fn new() -> Self {
108 Self::default()
109 }
110
111 pub fn update<F>(&self, f: F)
113 where
114 F: FnMut() + 'static,
115 {
116 self.update_boxed(Box::new(f));
117 }
118
119 pub fn update_boxed(&self, f: Box<dyn FnMut() + 'static>) {
123 *self.rc.borrow_mut() = Some(f);
124 *self.creator_scope.borrow_mut() =
125 composer_context::try_with_composer(callback_owner_scope).flatten();
126 }
127
128 pub fn clone_rc(&self) -> impl Fn() + 'static {
130 let rc = self.rc.clone();
131 let creator_scope = self.creator_scope.clone();
132 move || {
133 with_callback_scope(&creator_scope, || {
134 if let Some(callback) = rc.borrow_mut().as_mut() {
135 callback();
136 }
137 });
138 }
139 }
140}
141
142impl Default for CallbackHolder {
143 fn default() -> Self {
144 Self {
145 rc: Rc::new(RefCell::new(None)),
146 creator_scope: Rc::new(RefCell::new(None)),
147 }
148 }
149}
150
151#[derive(Clone)]
154pub struct CallbackHolder1<A: 'static> {
155 #[allow(clippy::type_complexity)]
156 rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
157 creator_scope: CallbackScopeCell,
158}
159
160impl<A: 'static> CallbackHolder1<A> {
161 pub fn new() -> Self {
163 Self::default()
164 }
165
166 pub fn update<F>(&self, f: F)
168 where
169 F: FnMut(A) + 'static,
170 {
171 *self.rc.borrow_mut() = Some(Box::new(f));
172 *self.creator_scope.borrow_mut() =
173 composer_context::try_with_composer(callback_owner_scope).flatten();
174 }
175
176 pub fn clone_rc(&self) -> impl Fn(A) + 'static {
178 let rc = self.rc.clone();
179 let creator_scope = self.creator_scope.clone();
180 move |arg| {
181 with_callback_scope(&creator_scope, || {
182 if let Some(callback) = rc.borrow_mut().as_mut() {
183 callback(arg);
184 }
185 });
186 }
187 }
188}
189
190impl<A: 'static> Default for CallbackHolder1<A> {
191 fn default() -> Self {
192 Self {
193 rc: Rc::new(RefCell::new(None)),
194 creator_scope: Rc::new(RefCell::new(None)),
195 }
196 }
197}
198
199pub struct ReturnSlot<T> {
200 value: Option<T>,
201}
202
203impl<T: Clone> ReturnSlot<T> {
204 pub fn store(&mut self, value: T) {
205 self.value = Some(value);
206 }
207
208 pub fn get(&self) -> Option<T> {
209 self.value.clone()
210 }
211}
212
213impl<T> Default for ParamState<T> {
214 fn default() -> Self {
215 Self { value: None }
216 }
217}
218
219impl<T> Default for ReturnSlot<T> {
220 fn default() -> Self {
221 Self { value: None }
222 }
223}
224
225#[cfg(test)]
226mod callback_holder_tests {
227 use super::{CallbackHolder, CallbackHolder1, ParamSlot};
228 use std::cell::Cell;
229 use std::rc::Rc;
230
231 #[test]
232 fn param_slot_take_reports_absence_instead_of_panicking() {
233 let slot = ParamSlot::default();
234
235 assert_eq!(slot.take(), None);
236 slot.set(11);
237 assert_eq!(slot.take(), Some(11));
238 assert_eq!(slot.take(), None);
239 }
240
241 #[test]
242 fn callback_holder_default_forwarder_is_noop() {
243 let forwarder = CallbackHolder::new().clone_rc();
244 forwarder();
245 }
246
247 #[test]
248 fn callback_holder_forwarder_uses_latest_callback() {
249 let holder = CallbackHolder::new();
250 let total = Rc::new(Cell::new(0));
251 let forwarder = holder.clone_rc();
252
253 let first_total = Rc::clone(&total);
254 holder.update(move || first_total.set(first_total.get() + 1));
255 forwarder();
256
257 let second_total = Rc::clone(&total);
258 holder.update(move || second_total.set(second_total.get() + 10));
259 forwarder();
260
261 assert_eq!(total.get(), 11);
262 }
263
264 #[test]
265 fn callback_holder1_default_forwarder_is_noop() {
266 let forwarder = CallbackHolder1::<i32>::new().clone_rc();
267 forwarder(7);
268 }
269
270 #[test]
271 fn callback_holder1_forwarder_uses_latest_callback() {
272 let holder = CallbackHolder1::<i32>::new();
273 let total = Rc::new(Cell::new(0));
274 let forwarder = holder.clone_rc();
275
276 let first_total = Rc::clone(&total);
277 holder.update(move |value| first_total.set(first_total.get() + value));
278 forwarder(2);
279
280 let second_total = Rc::clone(&total);
281 holder.update(move |value| second_total.set(second_total.get() + value * 5));
282 forwarder(3);
283
284 assert_eq!(total.get(), 17);
285 }
286}