Skip to main content

cranpose_core/
callbacks.rs

1use std::{cell::RefCell, rc::Rc};
2
3use crate::{Composer, ComposerCore, RecomposeScope, composer_context};
4
5pub struct ParamState<T> {
6    pub(crate) value: Option<T>,
7}
8
9/// A parameter that points at a shared allocation: two that point at the
10/// same one are equal without comparing what it holds.
11pub trait SharedParam {
12    /// Whether `self` and `other` point at the same allocation.
13    fn same_allocation(&self, other: &Self) -> bool;
14}
15
16impl<T: ?Sized> SharedParam for Rc<T> {
17    fn same_allocation(&self, other: &Self) -> bool {
18        Rc::ptr_eq(self, other)
19    }
20}
21
22impl<T: ?Sized> SharedParam for std::sync::Arc<T> {
23    fn same_allocation(&self, other: &Self) -> bool {
24        std::sync::Arc::ptr_eq(self, other)
25    }
26}
27
28impl<T> ParamState<T> {
29    pub fn update(&mut self, new_value: &T) -> bool
30    where
31        T: PartialEq + Clone,
32    {
33        self.update_unless(new_value, |old, new| old == new)
34    }
35
36    /// [`update`](Self::update) for an `Rc` or `Arc` parameter: the same
37    /// allocation is unchanged without comparing its contents, which
38    /// `PartialEq` on a pointer to a type that is not `Eq` otherwise walks
39    /// in full on every recomposition.
40    pub fn update_shared(&mut self, new_value: &T) -> bool
41    where
42        T: SharedParam + PartialEq + Clone,
43    {
44        self.update_unless(new_value, |old, new| old.same_allocation(new) || old == new)
45    }
46
47    fn update_unless(&mut self, new_value: &T, unchanged: impl FnOnce(&T, &T) -> bool) -> bool
48    where
49        T: Clone,
50    {
51        match self.value.as_mut() {
52            Some(old) if unchanged(old, new_value) => false,
53            Some(old) => {
54                old.clone_from(new_value);
55                true
56            }
57            None => {
58                self.value = Some(new_value.clone());
59                true
60            }
61        }
62    }
63
64    pub fn value(&self) -> Option<T>
65    where
66        T: Clone,
67    {
68        self.value.clone()
69    }
70}
71
72/// ParamSlot holds function/closure parameters by ownership (no PartialEq/Clone required).
73/// Used by the `#[composable]` macro to store Fn-like parameters in the slot table.
74pub struct ParamSlot<T> {
75    val: RefCell<Option<T>>,
76}
77
78impl<T> Default for ParamSlot<T> {
79    fn default() -> Self {
80        Self {
81            val: RefCell::new(None),
82        }
83    }
84}
85
86impl<T> ParamSlot<T> {
87    pub fn set(&self, v: T) {
88        *self.val.borrow_mut() = Some(v);
89    }
90
91    /// Takes the value out temporarily for a recomposition callback.
92    pub fn take(&self) -> Option<T> {
93        self.val.borrow_mut().take()
94    }
95}
96
97type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
98type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
99
100struct CallbackScopeGuard {
101    core: Rc<ComposerCore>,
102}
103
104impl CallbackScopeGuard {
105    fn push(composer: &Composer, scope: RecomposeScope) -> Self {
106        composer.core.scope_stack.borrow_mut().push(scope);
107        Self {
108            core: composer.clone_core(),
109        }
110    }
111}
112
113impl Drop for CallbackScopeGuard {
114    fn drop(&mut self) {
115        self.core.scope_stack.borrow_mut().pop();
116    }
117}
118
119fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
120    let captured_scope = scope.borrow().clone();
121    if let Some(saved_scope) = captured_scope
122        && let Some(composer) = composer_context::current_composer()
123    {
124        let _scope_guard = CallbackScopeGuard::push(&composer, saved_scope);
125        return f();
126    }
127
128    f()
129}
130
131fn callback_owner_is_active(scope: &CallbackScopeCell) -> bool {
132    scope
133        .borrow()
134        .as_ref()
135        .is_none_or(RecomposeScope::is_effectively_active)
136}
137
138fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
139    composer.core.scope_stack.borrow().last().cloned()
140}
141
142#[derive(Clone)]
143pub struct CallbackHolder {
144    rc: CallbackCell,
145    creator_scope: CallbackScopeCell,
146}
147
148impl CallbackHolder {
149    /// Create a new holder with a no-op callback so that callers can immediately invoke it.
150    pub fn new() -> Self {
151        Self::default()
152    }
153
154    /// Replace the stored callback with a new closure provided by the caller.
155    pub fn update<F>(&self, f: F)
156    where
157        F: FnMut() + 'static,
158    {
159        self.update_boxed(Box::new(f));
160    }
161
162    /// Boxed form of [`Self::update`]: lets generated composable helpers take
163    /// callbacks type-erased at the public-fn boundary, so helper bodies are
164    /// compiled once instead of once per caller closure type.
165    pub fn update_boxed(&self, f: Box<dyn FnMut() + 'static>) {
166        *self.rc.borrow_mut() = Some(f);
167        *self.creator_scope.borrow_mut() =
168            composer_context::try_with_composer(callback_owner_scope).flatten();
169    }
170
171    /// Produce a forwarder closure that keeps the holder alive and forwards calls to it.
172    pub fn clone_rc(&self) -> impl Fn() + 'static + use<> {
173        let rc = self.rc.clone();
174        let creator_scope = self.creator_scope.clone();
175        move || {
176            if !callback_owner_is_active(&creator_scope) {
177                return;
178            }
179            with_callback_scope(&creator_scope, || {
180                if let Some(callback) = rc.borrow_mut().as_mut() {
181                    callback();
182                }
183            });
184        }
185    }
186}
187
188impl Default for CallbackHolder {
189    fn default() -> Self {
190        Self {
191            rc: Rc::new(RefCell::new(None)),
192            creator_scope: Rc::new(RefCell::new(None)),
193        }
194    }
195}
196
197/// CallbackHolder1 keeps the latest single-argument callback closure alive across recompositions.
198/// It mirrors [`CallbackHolder`] but supports callbacks that receive one argument.
199#[derive(Clone)]
200pub struct CallbackHolder1<A: 'static> {
201    #[expect(clippy::type_complexity)]
202    rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
203    creator_scope: CallbackScopeCell,
204}
205
206impl<A: 'static> CallbackHolder1<A> {
207    /// Create a new holder with a no-op callback so callers can invoke it immediately.
208    pub fn new() -> Self {
209        Self::default()
210    }
211
212    /// Replace the stored callback with a new closure provided by the caller.
213    pub fn update<F>(&self, f: F)
214    where
215        F: FnMut(A) + 'static,
216    {
217        *self.rc.borrow_mut() = Some(Box::new(f));
218        *self.creator_scope.borrow_mut() =
219            composer_context::try_with_composer(callback_owner_scope).flatten();
220    }
221
222    /// Produce a forwarder closure that keeps the holder alive and forwards calls to it.
223    pub fn clone_rc(&self) -> impl Fn(A) + 'static {
224        let rc = self.rc.clone();
225        let creator_scope = self.creator_scope.clone();
226        move |arg| {
227            if !callback_owner_is_active(&creator_scope) {
228                return;
229            }
230            with_callback_scope(&creator_scope, || {
231                if let Some(callback) = rc.borrow_mut().as_mut() {
232                    callback(arg);
233                }
234            });
235        }
236    }
237}
238
239impl<A: 'static> Default for CallbackHolder1<A> {
240    fn default() -> Self {
241        Self {
242            rc: Rc::new(RefCell::new(None)),
243            creator_scope: Rc::new(RefCell::new(None)),
244        }
245    }
246}
247
248pub struct ReturnSlot<T> {
249    value: Option<T>,
250}
251
252impl<T: Clone> ReturnSlot<T> {
253    pub fn store(&mut self, value: T) {
254        self.value = Some(value);
255    }
256
257    pub fn get(&self) -> Option<T> {
258        self.value.clone()
259    }
260}
261
262impl<T> Default for ParamState<T> {
263    fn default() -> Self {
264        Self { value: None }
265    }
266}
267
268impl<T> Default for ReturnSlot<T> {
269    fn default() -> Self {
270        Self { value: None }
271    }
272}
273
274#[cfg(test)]
275#[path = "tests/callbacks_callback_holder_tests.rs"]
276mod callback_holder_tests;
277
278#[cfg(test)]
279#[path = "tests/callbacks_param_state_tests.rs"]
280mod param_state_tests;