cranpose_core/
callbacks.rs1use 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
9pub trait SharedParam {
12 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 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
72pub 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 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 pub fn new() -> Self {
151 Self::default()
152 }
153
154 pub fn update<F>(&self, f: F)
156 where
157 F: FnMut() + 'static,
158 {
159 self.update_boxed(Box::new(f));
160 }
161
162 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 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#[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 pub fn new() -> Self {
209 Self::default()
210 }
211
212 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 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;