cranpose_core/
callbacks.rs1use std::{
2 cell::{Cell, RefCell},
3 rc::Rc,
4};
5
6use crate::{Composer, ComposerCore, Key, RecomposeScope, ValueSlotHandle, composer_context};
7
8pub struct ParamState<T> {
11 pub(crate) value: Option<T>,
12}
13
14pub trait SharedParam {
17 fn same_allocation(&self, other: &Self) -> bool;
19}
20
21impl<T: ?Sized> SharedParam for Rc<T> {
22 fn same_allocation(&self, other: &Self) -> bool {
23 Self::ptr_eq(self, other)
24 }
25}
26
27impl<T: ?Sized> SharedParam for std::sync::Arc<T> {
28 fn same_allocation(&self, other: &Self) -> bool {
29 Self::ptr_eq(self, other)
30 }
31}
32
33impl<T> ParamState<T> {
34 pub fn update_fields(
39 &mut self,
40 fresh: impl FnOnce() -> T,
41 refresh: impl FnOnce(&mut T) -> bool,
42 ) -> bool {
43 match self.value.as_mut() {
44 Some(stored) => refresh(stored),
45 None => {
46 self.value = Some(fresh());
47 true
48 }
49 }
50 }
51
52 pub fn value(&self) -> Option<T>
54 where
55 T: Clone,
56 {
57 self.value.clone()
58 }
59}
60
61pub struct ParamReturnState<T, R> {
64 params: ParamState<T>,
65 returned: Option<R>,
66}
67
68impl<T, R> ParamReturnState<T, R> {
69 pub fn update_fields(
71 &mut self,
72 fresh: impl FnOnce() -> T,
73 refresh: impl FnOnce(&mut T) -> bool,
74 ) -> bool {
75 self.params.update_fields(fresh, refresh)
76 }
77
78 pub fn value(&self) -> Option<T>
80 where
81 T: Clone,
82 {
83 self.params.value()
84 }
85
86 pub fn returned(&self) -> Option<R>
88 where
89 R: Clone,
90 {
91 self.returned.clone()
92 }
93
94 pub fn store_return(&mut self, value: R) {
96 self.returned = Some(value);
97 }
98}
99
100impl<T, R> Default for ParamReturnState<T, R> {
101 fn default() -> Self {
102 Self {
103 params: ParamState::default(),
104 returned: None,
105 }
106 }
107}
108
109pub fn refresh_param<T: PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
112 if stored == new {
113 return false;
114 }
115 stored.clone_from(new);
116 true
117}
118
119pub fn store_param<T: Clone>(stored: &mut T, new: &T) -> bool {
124 stored.clone_from(new);
125 true
126}
127
128pub fn refresh_shared_param<T: SharedParam + PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
132 if stored.same_allocation(new) || stored == new {
133 return false;
134 }
135 stored.clone_from(new);
136 true
137}
138
139pub struct ParamSlot<T> {
142 val: RefCell<Option<T>>,
143}
144
145impl<T> Default for ParamSlot<T> {
146 fn default() -> Self {
147 Self {
148 val: RefCell::new(None),
149 }
150 }
151}
152
153impl<T> ParamSlot<T> {
154 pub fn set(&self, v: T) {
156 *self.val.borrow_mut() = Some(v);
157 }
158
159 pub fn take(&self) -> Option<T> {
161 self.val.borrow_mut().take()
162 }
163}
164
165type CallbackScopeCell = RefCell<Option<RecomposeScope>>;
166
167struct CallbackShared<F: ?Sized> {
170 owner: CallbackOwner,
171 callback: RefCell<Option<Box<F>>>,
172}
173
174#[derive(Default)]
177struct CallbackOwner {
178 creator_scope: CallbackScopeCell,
180 renders: Cell<bool>,
182}
183
184struct CallbackEntry {
187 _scope: Option<CallbackScopeGuard>,
188}
189
190impl CallbackOwner {
191 #[inline(never)]
196 fn enter(&self) -> Option<CallbackEntry> {
197 if !callback_owner_is_active(&self.creator_scope) {
198 return None;
199 }
200 if !self.renders.get()
201 && (composer_context::has_composer() || !crate::in_applied_snapshot())
202 {
203 self.renders.set(true);
204 }
205 let scope = self.creator_scope.borrow().clone().and_then(|scope| {
206 composer_context::current_composer()
207 .map(|composer| CallbackScopeGuard::push(&composer, scope))
208 });
209 Some(CallbackEntry { _scope: scope })
210 }
211}
212
213struct CallbackScopeGuard {
214 core: Rc<ComposerCore>,
215}
216
217impl CallbackScopeGuard {
218 fn push(composer: &Composer, scope: RecomposeScope) -> Self {
219 composer.core.scope_stack.borrow_mut().push(scope);
220 Self {
221 core: composer.clone_core(),
222 }
223 }
224}
225
226impl Drop for CallbackScopeGuard {
227 fn drop(&mut self) {
228 self.core.scope_stack.borrow_mut().pop();
229 }
230}
231
232fn callback_owner_is_active(scope: &CallbackScopeCell) -> bool {
233 scope
234 .borrow()
235 .as_ref()
236 .is_none_or(RecomposeScope::is_effectively_active)
237}
238
239fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
240 composer.core.scope_stack.borrow().last().cloned()
241}
242
243pub struct CallbackHolder<F: ?Sized + 'static = dyn FnMut()> {
246 shared: Rc<CallbackShared<F>>,
247}
248
249impl<F: ?Sized + 'static> CallbackHolder<F> {
250 pub fn new() -> Self {
252 Self::default()
253 }
254
255 pub fn update_boxed(&self, callback: Box<F>) {
259 *self.shared.callback.borrow_mut() = Some(callback);
260 *self.shared.owner.creator_scope.borrow_mut() =
261 composer_context::try_with_composer(callback_owner_scope).flatten();
262 }
263
264 pub fn renders(&self) -> bool {
268 self.shared.owner.renders.get()
269 }
270
271 pub fn forwarder(&self) -> CallbackForwarder<F> {
273 CallbackForwarder {
274 shared: Rc::clone(&self.shared),
275 }
276 }
277
278 #[doc(hidden)]
282 pub fn __hold(
283 composer: &Composer,
284 source: Key,
285 callback: Box<F>,
286 ) -> (ValueSlotHandle<'_, Self>, bool) {
287 composer.__update_param_slot(source, Self::new, |holder: &mut Self| {
288 holder.update_boxed(callback);
289 holder.renders()
290 })
291 }
292
293 #[doc(hidden)]
296 pub fn __slot(composer: &Composer, source: Key) -> ValueSlotHandle<'_, Self> {
297 composer.__use_param_slot(source, Self::new)
298 }
299
300 #[doc(hidden)]
302 pub fn __forwarder<'pass>(
303 composer: &'pass Composer,
304 slot: ValueSlotHandle<'pass, Self>,
305 ) -> CallbackForwarder<F> {
306 composer.with_slot_value(slot, Self::forwarder)
307 }
308}
309
310impl<F: ?Sized + 'static> Clone for CallbackHolder<F> {
311 fn clone(&self) -> Self {
312 Self {
313 shared: Rc::clone(&self.shared),
314 }
315 }
316}
317
318impl CallbackHolder {
319 pub fn update(&self, callback: impl FnMut() + 'static) {
321 self.update_boxed(Box::new(callback));
322 }
323
324 pub fn clone_rc(&self) -> impl Fn() + 'static + use<> {
326 let forwarder = self.forwarder();
327 move || forwarder.call(|callback| callback())
328 }
329}
330
331impl<F: ?Sized + 'static> Default for CallbackHolder<F> {
332 fn default() -> Self {
333 Self {
334 shared: Rc::new(CallbackShared {
335 owner: CallbackOwner::default(),
336 callback: RefCell::new(None),
337 }),
338 }
339 }
340}
341
342pub struct CallbackForwarder<F: ?Sized + 'static> {
345 shared: Rc<CallbackShared<F>>,
346}
347
348impl<F: ?Sized + 'static> CallbackForwarder<F> {
349 pub fn call(&self, call: impl FnOnce(&mut F)) {
352 let Some(_entry) = self.shared.owner.enter() else {
353 return;
354 };
355 if let Some(callback) = self.shared.callback.borrow_mut().as_mut() {
356 call(callback);
357 }
358 }
359}
360
361impl<F: ?Sized + 'static> Clone for CallbackForwarder<F> {
362 fn clone(&self) -> Self {
363 Self {
364 shared: Rc::clone(&self.shared),
365 }
366 }
367}
368
369pub struct ReturnSlot<T> {
372 value: Option<T>,
373}
374
375impl<T: Clone> ReturnSlot<T> {
376 pub fn store(&mut self, value: T) {
378 self.value = Some(value);
379 }
380
381 pub fn get(&self) -> Option<T> {
383 self.value.clone()
384 }
385}
386
387impl<T> Default for ParamState<T> {
388 fn default() -> Self {
389 Self { value: None }
390 }
391}
392
393impl<T> Default for ReturnSlot<T> {
394 fn default() -> Self {
395 Self { value: None }
396 }
397}
398
399#[cfg(test)]
400#[path = "tests/callbacks_callback_holder_tests.rs"]
401mod callback_holder_tests;
402
403#[cfg(test)]
404#[path = "tests/callbacks_param_state_tests.rs"]
405mod param_state_tests;