cranpose_core/
callbacks.rs1use std::{cell::RefCell, rc::Rc};
2
3use crate::{Composer, ComposerCore, RecomposeScope, composer_context};
4
5pub struct ParamState<T> {
8 pub(crate) value: Option<T>,
9}
10
11pub trait SharedParam {
14 fn same_allocation(&self, other: &Self) -> bool;
16}
17
18impl<T: ?Sized> SharedParam for Rc<T> {
19 fn same_allocation(&self, other: &Self) -> bool {
20 Rc::ptr_eq(self, other)
21 }
22}
23
24impl<T: ?Sized> SharedParam for std::sync::Arc<T> {
25 fn same_allocation(&self, other: &Self) -> bool {
26 std::sync::Arc::ptr_eq(self, other)
27 }
28}
29
30impl<T> ParamState<T> {
31 pub fn update_fields(
36 &mut self,
37 fresh: impl FnOnce() -> T,
38 refresh: impl FnOnce(&mut T) -> bool,
39 ) -> bool {
40 match self.value.as_mut() {
41 Some(stored) => refresh(stored),
42 None => {
43 self.value = Some(fresh());
44 true
45 }
46 }
47 }
48
49 pub fn value(&self) -> Option<T>
51 where
52 T: Clone,
53 {
54 self.value.clone()
55 }
56}
57
58pub struct ParamReturnState<T, R> {
61 params: ParamState<T>,
62 returned: Option<R>,
63}
64
65impl<T, R> ParamReturnState<T, R> {
66 pub fn update_fields(
68 &mut self,
69 fresh: impl FnOnce() -> T,
70 refresh: impl FnOnce(&mut T) -> bool,
71 ) -> bool {
72 self.params.update_fields(fresh, refresh)
73 }
74
75 pub fn value(&self) -> Option<T>
77 where
78 T: Clone,
79 {
80 self.params.value()
81 }
82
83 pub fn returned(&self) -> Option<R>
85 where
86 R: Clone,
87 {
88 self.returned.clone()
89 }
90
91 pub fn store_return(&mut self, value: R) {
93 self.returned = Some(value);
94 }
95}
96
97impl<T, R> Default for ParamReturnState<T, R> {
98 fn default() -> Self {
99 Self {
100 params: ParamState::default(),
101 returned: None,
102 }
103 }
104}
105
106pub fn refresh_param<T: PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
109 if stored == new {
110 return false;
111 }
112 stored.clone_from(new);
113 true
114}
115
116pub fn store_param<T: Clone>(stored: &mut T, new: &T) -> bool {
121 stored.clone_from(new);
122 true
123}
124
125pub fn refresh_shared_param<T: SharedParam + PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
129 if stored.same_allocation(new) || stored == new {
130 return false;
131 }
132 stored.clone_from(new);
133 true
134}
135
136pub struct ParamSlot<T> {
139 val: RefCell<Option<T>>,
140}
141
142impl<T> Default for ParamSlot<T> {
143 fn default() -> Self {
144 Self {
145 val: RefCell::new(None),
146 }
147 }
148}
149
150impl<T> ParamSlot<T> {
151 pub fn set(&self, v: T) {
153 *self.val.borrow_mut() = Some(v);
154 }
155
156 pub fn take(&self) -> Option<T> {
158 self.val.borrow_mut().take()
159 }
160}
161
162type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
163type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
164
165struct CallbackScopeGuard {
166 core: Rc<ComposerCore>,
167}
168
169impl CallbackScopeGuard {
170 fn push(composer: &Composer, scope: RecomposeScope) -> Self {
171 composer.core.scope_stack.borrow_mut().push(scope);
172 Self {
173 core: composer.clone_core(),
174 }
175 }
176}
177
178impl Drop for CallbackScopeGuard {
179 fn drop(&mut self) {
180 self.core.scope_stack.borrow_mut().pop();
181 }
182}
183
184fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
185 let captured_scope = scope.borrow().clone();
186 if let Some(saved_scope) = captured_scope
187 && let Some(composer) = composer_context::current_composer()
188 {
189 let _scope_guard = CallbackScopeGuard::push(&composer, saved_scope);
190 return f();
191 }
192
193 f()
194}
195
196fn callback_owner_is_active(scope: &CallbackScopeCell) -> bool {
197 scope
198 .borrow()
199 .as_ref()
200 .is_none_or(RecomposeScope::is_effectively_active)
201}
202
203fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
204 composer.core.scope_stack.borrow().last().cloned()
205}
206
207#[derive(Clone)]
211pub struct CallbackHolder {
212 rc: CallbackCell,
213 creator_scope: CallbackScopeCell,
214}
215
216impl CallbackHolder {
217 pub fn new() -> Self {
219 Self::default()
220 }
221
222 pub fn update<F>(&self, f: F)
224 where
225 F: FnMut() + 'static,
226 {
227 self.update_boxed(Box::new(f));
228 }
229
230 pub fn update_boxed(&self, f: Box<dyn FnMut() + 'static>) {
234 *self.rc.borrow_mut() = Some(f);
235 *self.creator_scope.borrow_mut() =
236 composer_context::try_with_composer(callback_owner_scope).flatten();
237 }
238
239 pub fn clone_rc(&self) -> impl Fn() + 'static + use<> {
241 let rc = self.rc.clone();
242 let creator_scope = self.creator_scope.clone();
243 move || {
244 if !callback_owner_is_active(&creator_scope) {
245 return;
246 }
247 with_callback_scope(&creator_scope, || {
248 if let Some(callback) = rc.borrow_mut().as_mut() {
249 callback();
250 }
251 });
252 }
253 }
254}
255
256impl Default for CallbackHolder {
257 fn default() -> Self {
258 Self {
259 rc: Rc::new(RefCell::new(None)),
260 creator_scope: Rc::new(RefCell::new(None)),
261 }
262 }
263}
264
265#[derive(Clone)]
268pub struct CallbackHolder1<A: 'static> {
269 #[expect(clippy::type_complexity)]
270 rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
271 creator_scope: CallbackScopeCell,
272}
273
274impl<A: 'static> CallbackHolder1<A> {
275 pub fn new() -> Self {
277 Self::default()
278 }
279
280 pub fn update<F>(&self, f: F)
282 where
283 F: FnMut(A) + 'static,
284 {
285 *self.rc.borrow_mut() = Some(Box::new(f));
286 *self.creator_scope.borrow_mut() =
287 composer_context::try_with_composer(callback_owner_scope).flatten();
288 }
289
290 pub fn clone_rc(&self) -> impl Fn(A) + 'static {
292 let rc = self.rc.clone();
293 let creator_scope = self.creator_scope.clone();
294 move |arg| {
295 if !callback_owner_is_active(&creator_scope) {
296 return;
297 }
298 with_callback_scope(&creator_scope, || {
299 if let Some(callback) = rc.borrow_mut().as_mut() {
300 callback(arg);
301 }
302 });
303 }
304 }
305}
306
307impl<A: 'static> Default for CallbackHolder1<A> {
308 fn default() -> Self {
309 Self {
310 rc: Rc::new(RefCell::new(None)),
311 creator_scope: Rc::new(RefCell::new(None)),
312 }
313 }
314}
315
316pub struct ReturnSlot<T> {
319 value: Option<T>,
320}
321
322impl<T: Clone> ReturnSlot<T> {
323 pub fn store(&mut self, value: T) {
325 self.value = Some(value);
326 }
327
328 pub fn get(&self) -> Option<T> {
330 self.value.clone()
331 }
332}
333
334impl<T> Default for ParamState<T> {
335 fn default() -> Self {
336 Self { value: None }
337 }
338}
339
340impl<T> Default for ReturnSlot<T> {
341 fn default() -> Self {
342 Self { value: None }
343 }
344}
345
346#[cfg(test)]
347#[path = "tests/callbacks_callback_holder_tests.rs"]
348mod callback_holder_tests;
349
350#[cfg(test)]
351#[path = "tests/callbacks_param_state_tests.rs"]
352mod param_state_tests;