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) -> T {
57 self.val
58 .borrow_mut()
59 .take()
60 .expect("ParamSlot take() called before set")
61 }
62}
63
64type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
65type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
66
67struct CallbackScopeGuard {
68 core: Rc<ComposerCore>,
69}
70
71impl CallbackScopeGuard {
72 fn push(composer: &Composer, scope: RecomposeScope) -> Self {
73 composer.core.scope_stack.borrow_mut().push(scope);
74 Self {
75 core: composer.clone_core(),
76 }
77 }
78}
79
80impl Drop for CallbackScopeGuard {
81 fn drop(&mut self) {
82 self.core.scope_stack.borrow_mut().pop();
83 }
84}
85
86fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
87 let captured_scope = scope.borrow().clone();
88 let mut f = Some(f);
89 if let Some(saved_scope) = captured_scope {
90 if let Some(result) = composer_context::try_with_composer(|composer| {
91 let _scope_guard = CallbackScopeGuard::push(composer, saved_scope);
92 f.take().expect("callback closure already taken")()
93 }) {
94 return result;
95 }
96 }
97
98 f.take().expect("callback closure already taken")()
99}
100
101fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
102 composer.core.scope_stack.borrow().last().cloned()
103}
104
105#[derive(Clone)]
106pub struct CallbackHolder {
107 rc: CallbackCell,
108 creator_scope: CallbackScopeCell,
109}
110
111impl CallbackHolder {
112 pub fn new() -> Self {
114 Self::default()
115 }
116
117 pub fn update<F>(&self, f: F)
119 where
120 F: FnMut() + 'static,
121 {
122 *self.rc.borrow_mut() = Some(Box::new(f));
123 *self.creator_scope.borrow_mut() =
124 composer_context::try_with_composer(callback_owner_scope).flatten();
125 }
126
127 pub fn clone_rc(&self) -> impl Fn() + 'static {
129 let rc = self.rc.clone();
130 let creator_scope = self.creator_scope.clone();
131 move || {
132 with_callback_scope(&creator_scope, || {
133 if let Some(callback) = rc.borrow_mut().as_mut() {
134 callback();
135 }
136 });
137 }
138 }
139}
140
141impl Default for CallbackHolder {
142 fn default() -> Self {
143 Self {
144 rc: Rc::new(RefCell::new(None)),
145 creator_scope: Rc::new(RefCell::new(None)),
146 }
147 }
148}
149
150#[derive(Clone)]
153pub struct CallbackHolder1<A: 'static> {
154 #[allow(clippy::type_complexity)]
155 rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
156 creator_scope: CallbackScopeCell,
157}
158
159impl<A: 'static> CallbackHolder1<A> {
160 pub fn new() -> Self {
162 Self::default()
163 }
164
165 pub fn update<F>(&self, f: F)
167 where
168 F: FnMut(A) + 'static,
169 {
170 *self.rc.borrow_mut() = Some(Box::new(f));
171 *self.creator_scope.borrow_mut() =
172 composer_context::try_with_composer(callback_owner_scope).flatten();
173 }
174
175 pub fn clone_rc(&self) -> impl Fn(A) + 'static {
177 let rc = self.rc.clone();
178 let creator_scope = self.creator_scope.clone();
179 move |arg| {
180 with_callback_scope(&creator_scope, || {
181 if let Some(callback) = rc.borrow_mut().as_mut() {
182 callback(arg);
183 }
184 });
185 }
186 }
187}
188
189impl<A: 'static> Default for CallbackHolder1<A> {
190 fn default() -> Self {
191 Self {
192 rc: Rc::new(RefCell::new(None)),
193 creator_scope: Rc::new(RefCell::new(None)),
194 }
195 }
196}
197
198pub struct ReturnSlot<T> {
199 value: Option<T>,
200}
201
202impl<T: Clone> ReturnSlot<T> {
203 pub fn store(&mut self, value: T) {
204 self.value = Some(value);
205 }
206
207 pub fn get(&self) -> Option<T> {
208 self.value.clone()
209 }
210}
211
212impl<T> Default for ParamState<T> {
213 fn default() -> Self {
214 Self { value: None }
215 }
216}
217
218impl<T> Default for ReturnSlot<T> {
219 fn default() -> Self {
220 Self { value: None }
221 }
222}
223
224#[cfg(test)]
225mod callback_holder_tests {
226 use super::{CallbackHolder, CallbackHolder1};
227 use std::cell::Cell;
228 use std::rc::Rc;
229
230 #[test]
231 fn callback_holder_default_forwarder_is_noop() {
232 let forwarder = CallbackHolder::new().clone_rc();
233 forwarder();
234 }
235
236 #[test]
237 fn callback_holder_forwarder_uses_latest_callback() {
238 let holder = CallbackHolder::new();
239 let total = Rc::new(Cell::new(0));
240 let forwarder = holder.clone_rc();
241
242 let first_total = Rc::clone(&total);
243 holder.update(move || first_total.set(first_total.get() + 1));
244 forwarder();
245
246 let second_total = Rc::clone(&total);
247 holder.update(move || second_total.set(second_total.get() + 10));
248 forwarder();
249
250 assert_eq!(total.get(), 11);
251 }
252
253 #[test]
254 fn callback_holder1_default_forwarder_is_noop() {
255 let forwarder = CallbackHolder1::<i32>::new().clone_rc();
256 forwarder(7);
257 }
258
259 #[test]
260 fn callback_holder1_forwarder_uses_latest_callback() {
261 let holder = CallbackHolder1::<i32>::new();
262 let total = Rc::new(Cell::new(0));
263 let forwarder = holder.clone_rc();
264
265 let first_total = Rc::clone(&total);
266 holder.update(move |value| first_total.set(first_total.get() + value));
267 forwarder(2);
268
269 let second_total = Rc::clone(&total);
270 holder.update(move |value| second_total.set(second_total.get() + value * 5));
271 forwarder(3);
272
273 assert_eq!(total.get(), 17);
274 }
275}