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_fields(
34 &mut self,
35 fresh: impl FnOnce() -> T,
36 refresh: impl FnOnce(&mut T) -> bool,
37 ) -> bool {
38 match self.value.as_mut() {
39 Some(stored) => refresh(stored),
40 None => {
41 self.value = Some(fresh());
42 true
43 }
44 }
45 }
46
47 pub fn value(&self) -> Option<T>
48 where
49 T: Clone,
50 {
51 self.value.clone()
52 }
53}
54
55pub fn refresh_param<T: PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
58 if stored == new {
59 return false;
60 }
61 stored.clone_from(new);
62 true
63}
64
65pub fn refresh_shared_param<T: SharedParam + PartialEq + Clone>(stored: &mut T, new: &T) -> bool {
69 if stored.same_allocation(new) || stored == new {
70 return false;
71 }
72 stored.clone_from(new);
73 true
74}
75
76pub struct ParamSlot<T> {
79 val: RefCell<Option<T>>,
80}
81
82impl<T> Default for ParamSlot<T> {
83 fn default() -> Self {
84 Self {
85 val: RefCell::new(None),
86 }
87 }
88}
89
90impl<T> ParamSlot<T> {
91 pub fn set(&self, v: T) {
92 *self.val.borrow_mut() = Some(v);
93 }
94
95 pub fn take(&self) -> Option<T> {
97 self.val.borrow_mut().take()
98 }
99}
100
101type CallbackCell = Rc<RefCell<Option<Box<dyn FnMut()>>>>;
102type CallbackScopeCell = Rc<RefCell<Option<RecomposeScope>>>;
103
104struct CallbackScopeGuard {
105 core: Rc<ComposerCore>,
106}
107
108impl CallbackScopeGuard {
109 fn push(composer: &Composer, scope: RecomposeScope) -> Self {
110 composer.core.scope_stack.borrow_mut().push(scope);
111 Self {
112 core: composer.clone_core(),
113 }
114 }
115}
116
117impl Drop for CallbackScopeGuard {
118 fn drop(&mut self) {
119 self.core.scope_stack.borrow_mut().pop();
120 }
121}
122
123fn with_callback_scope<R>(scope: &CallbackScopeCell, f: impl FnOnce() -> R) -> R {
124 let captured_scope = scope.borrow().clone();
125 if let Some(saved_scope) = captured_scope
126 && let Some(composer) = composer_context::current_composer()
127 {
128 let _scope_guard = CallbackScopeGuard::push(&composer, saved_scope);
129 return f();
130 }
131
132 f()
133}
134
135fn callback_owner_is_active(scope: &CallbackScopeCell) -> bool {
136 scope
137 .borrow()
138 .as_ref()
139 .is_none_or(RecomposeScope::is_effectively_active)
140}
141
142fn callback_owner_scope(composer: &Composer) -> Option<RecomposeScope> {
143 composer.core.scope_stack.borrow().last().cloned()
144}
145
146#[derive(Clone)]
147pub struct CallbackHolder {
148 rc: CallbackCell,
149 creator_scope: CallbackScopeCell,
150}
151
152impl CallbackHolder {
153 pub fn new() -> Self {
155 Self::default()
156 }
157
158 pub fn update<F>(&self, f: F)
160 where
161 F: FnMut() + 'static,
162 {
163 self.update_boxed(Box::new(f));
164 }
165
166 pub fn update_boxed(&self, f: Box<dyn FnMut() + 'static>) {
170 *self.rc.borrow_mut() = Some(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() + 'static + use<> {
177 let rc = self.rc.clone();
178 let creator_scope = self.creator_scope.clone();
179 move || {
180 if !callback_owner_is_active(&creator_scope) {
181 return;
182 }
183 with_callback_scope(&creator_scope, || {
184 if let Some(callback) = rc.borrow_mut().as_mut() {
185 callback();
186 }
187 });
188 }
189 }
190}
191
192impl Default for CallbackHolder {
193 fn default() -> Self {
194 Self {
195 rc: Rc::new(RefCell::new(None)),
196 creator_scope: Rc::new(RefCell::new(None)),
197 }
198 }
199}
200
201#[derive(Clone)]
204pub struct CallbackHolder1<A: 'static> {
205 #[expect(clippy::type_complexity)]
206 rc: Rc<RefCell<Option<Box<dyn FnMut(A)>>>>,
207 creator_scope: CallbackScopeCell,
208}
209
210impl<A: 'static> CallbackHolder1<A> {
211 pub fn new() -> Self {
213 Self::default()
214 }
215
216 pub fn update<F>(&self, f: F)
218 where
219 F: FnMut(A) + 'static,
220 {
221 *self.rc.borrow_mut() = Some(Box::new(f));
222 *self.creator_scope.borrow_mut() =
223 composer_context::try_with_composer(callback_owner_scope).flatten();
224 }
225
226 pub fn clone_rc(&self) -> impl Fn(A) + 'static {
228 let rc = self.rc.clone();
229 let creator_scope = self.creator_scope.clone();
230 move |arg| {
231 if !callback_owner_is_active(&creator_scope) {
232 return;
233 }
234 with_callback_scope(&creator_scope, || {
235 if let Some(callback) = rc.borrow_mut().as_mut() {
236 callback(arg);
237 }
238 });
239 }
240 }
241}
242
243impl<A: 'static> Default for CallbackHolder1<A> {
244 fn default() -> Self {
245 Self {
246 rc: Rc::new(RefCell::new(None)),
247 creator_scope: Rc::new(RefCell::new(None)),
248 }
249 }
250}
251
252pub struct ReturnSlot<T> {
253 value: Option<T>,
254}
255
256impl<T: Clone> ReturnSlot<T> {
257 pub fn store(&mut self, value: T) {
258 self.value = Some(value);
259 }
260
261 pub fn get(&self) -> Option<T> {
262 self.value.clone()
263 }
264}
265
266impl<T> Default for ParamState<T> {
267 fn default() -> Self {
268 Self { value: None }
269 }
270}
271
272impl<T> Default for ReturnSlot<T> {
273 fn default() -> Self {
274 Self { value: None }
275 }
276}
277
278#[cfg(test)]
279#[path = "tests/callbacks_callback_holder_tests.rs"]
280mod callback_holder_tests;
281
282#[cfg(test)]
283#[path = "tests/callbacks_param_state_tests.rs"]
284mod param_state_tests;