Skip to main content

cranpose_core/
composition_locals.rs

1use std::{any::Any, cell::RefCell, rc::Rc, sync::Arc};
2
3use crate::{
4    Composer, LocalKey, RuntimeHandle, composer_context,
5    state::{MutationPolicy, OwnedMutableState},
6};
7
8fn provider_entry_source(key: &LocalKey, caller: crate::Key) -> crate::Key {
9    (key.entry_source() ^ caller).wrapping_mul(0x0000_0100_0000_01b3)
10}
11
12/// A composition local paired with the value to provide, made by `provides` and passed to
13/// `CompositionLocalProvider`.
14pub struct ProvidedValue {
15    key: LocalKey,
16    #[expect(clippy::type_complexity)]
17    apply: Box<dyn Fn(&Composer, crate::Key) -> (Rc<dyn Any>, bool)>,
18}
19
20impl ProvidedValue {
21    pub(crate) fn key(&self) -> &LocalKey {
22        &self.key
23    }
24
25    /// The provided entry, and whether it replaced a different value of a
26    /// static local that this site provided before.
27    pub(crate) fn into_entry(
28        self,
29        composer: &Composer,
30        site: crate::Key,
31    ) -> (LocalKey, Rc<dyn Any>, bool) {
32        let ProvidedValue { key, apply } = self;
33        let (entry, static_changed) = apply(composer, site);
34        (key, entry, static_changed)
35    }
36}
37
38/// Makes `values` available to `content` and everything it composes. Where two values are for the
39/// same local, the last wins. Compose's `CompositionLocalProvider`.
40#[expect(non_snake_case)]
41#[track_caller]
42pub fn CompositionLocalProvider(
43    values: impl IntoIterator<Item = ProvidedValue>,
44    content: impl FnOnce(),
45) {
46    let site = crate::caller_location_key();
47    composer_context::with_composer(|composer| {
48        composer.with_composition_locals(values, site, |_composer| content());
49    });
50}
51
52pub(crate) struct LocalStateEntry<T: Clone + 'static> {
53    state: OwnedMutableState<T>,
54}
55
56type LocalEquivalentFn<T> = dyn Fn(&T, &T) -> bool + Send + Sync + 'static;
57
58struct LocalValuePolicy<T: Clone + 'static> {
59    equivalent: Arc<LocalEquivalentFn<T>>,
60}
61
62impl<T: Clone + 'static> MutationPolicy<T> for LocalValuePolicy<T> {
63    fn equivalent(&self, a: &T, b: &T) -> bool {
64        (self.equivalent)(a, b)
65    }
66}
67
68impl<T: Clone + 'static> LocalStateEntry<T> {
69    fn new(initial: T, runtime: RuntimeHandle, equivalent: Arc<LocalEquivalentFn<T>>) -> Self {
70        Self {
71            state: OwnedMutableState::with_runtime_and_policy(
72                initial,
73                runtime,
74                Rc::new(LocalValuePolicy { equivalent }),
75            ),
76        }
77    }
78
79    fn set(&self, value: T) {
80        self.state.replace(value);
81    }
82
83    pub(crate) fn value(&self) -> T {
84        self.state.value()
85    }
86}
87
88pub(crate) struct StaticLocalEntry<T: Clone + 'static> {
89    value: RefCell<T>,
90}
91
92impl<T: Clone + 'static> StaticLocalEntry<T> {
93    fn new(value: T) -> Self {
94        Self {
95            value: RefCell::new(value),
96        }
97    }
98
99    /// Stores `value` and says whether it differs from the stored one.
100    fn set(&self, value: T, equivalent: &LocalEquivalentFn<T>) -> bool {
101        let mut current = self.value.borrow_mut();
102        if equivalent(&current, &value) {
103            return false;
104        }
105        *current = value;
106        true
107    }
108
109    pub(crate) fn value(&self) -> T {
110        self.value.borrow().clone()
111    }
112}
113
114type ComputeLocalFn<T> = dyn Fn(&CompositionLocalAccessorScope) -> T;
115
116/// The value of a local whose provider computes it where it is read, from
117/// the other locals there. A reader recomposes when the provider gives a
118/// new computation, as with Compose's lambdas that are not remembered.
119pub(crate) struct ComputedLocalEntry<T: 'static> {
120    compute: OwnedMutableState<Rc<ComputeLocalFn<T>>>,
121}
122
123impl<T: 'static> ComputedLocalEntry<T> {
124    pub(crate) fn value(&self) -> T {
125        let compute = self.compute.value();
126        compute(&CompositionLocalAccessorScope { _private: () })
127    }
128}
129
130/// A static local's computed value: see [`ComputedLocalEntry`]. A new
131/// computation recomposes all the provider's content, as a new static value
132/// does.
133pub(crate) struct StaticComputedLocalEntry<T: 'static> {
134    compute: Rc<ComputeLocalFn<T>>,
135}
136
137impl<T: 'static> StaticComputedLocalEntry<T> {
138    pub(crate) fn value(&self) -> T {
139        (self.compute)(&CompositionLocalAccessorScope { _private: () })
140    }
141}
142
143/// What a computed composition local's value reads other locals through:
144/// their values where the computed local is read, not where it was
145/// provided. Compose's `CompositionLocalAccessorScope`.
146pub struct CompositionLocalAccessorScope {
147    _private: (),
148}
149
150impl CompositionLocalAccessorScope {
151    /// The value `local` has where the computed local is read. Compose's
152    /// `CompositionLocal.currentValue` in this scope.
153    pub fn current_value<T>(&self, local: &impl ReadableCompositionLocal<T>) -> T {
154        local.read_current()
155    }
156}
157
158/// A composition local whose current value a computed local can read; see
159/// [`CompositionLocalAccessorScope`].
160pub trait ReadableCompositionLocal<T> {
161    /// The local's value where it is read, as `current` gives it.
162    fn read_current(&self) -> T;
163}
164
165impl<T: Clone + 'static> ReadableCompositionLocal<T> for CompositionLocal<T> {
166    fn read_current(&self) -> T {
167        self.current()
168    }
169}
170
171impl<T: Clone + 'static> ReadableCompositionLocal<T> for StaticCompositionLocal<T> {
172    fn read_current(&self) -> T {
173        self.current()
174    }
175}
176
177/// A value that composables read from their surroundings without passing it as a parameter, as
178/// Jetpack Compose's `compositionLocalOf`. A scope that reads `current` recomposes when the
179/// provided value changes.
180#[derive(Clone)]
181pub struct CompositionLocal<T: Clone + 'static> {
182    pub(crate) key: LocalKey,
183    default: Rc<dyn Fn() -> T>,
184    equivalent: Arc<LocalEquivalentFn<T>>,
185}
186
187impl<T: Clone + 'static> PartialEq for CompositionLocal<T> {
188    fn eq(&self, other: &Self) -> bool {
189        self.key == other.key
190    }
191}
192
193impl<T: Clone + 'static> Eq for CompositionLocal<T> {}
194
195impl<T: Clone + 'static> CompositionLocal<T> {
196    /// Pairs this local with `value` for use in `CompositionLocalProvider`. When a later
197    /// composition provides a different value, the scopes that read the local recompose.
198    #[track_caller]
199    pub fn provides(&self, value: T) -> ProvidedValue {
200        let key = self.key.clone();
201        let entry_source = provider_entry_source(&key, crate::caller_location_key());
202        let equivalent = Arc::clone(&self.equivalent);
203        ProvidedValue {
204            key,
205            apply: Box::new(move |composer: &Composer, site: crate::Key| {
206                let runtime = composer.runtime_handle();
207                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
208                let entry_ref = composer.remember_internal(source, || {
209                    Rc::new(LocalStateEntry::new(
210                        value.clone(),
211                        runtime.clone(),
212                        Arc::clone(&equivalent),
213                    ))
214                });
215                entry_ref.update(|entry| entry.set(value.clone()));
216                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, false))
217            }),
218        }
219    }
220
221    /// Pairs this local with `compute`, which runs at each read of the local
222    /// under the provider and reads other locals there through its scope.
223    /// A reader recomposes when a local the computation read changes, and
224    /// when a later composition of the provider gives a new computation.
225    /// Compose's `providesComputed`.
226    #[track_caller]
227    pub fn provides_computed(
228        &self,
229        compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
230    ) -> ProvidedValue {
231        let key = self.key.clone();
232        let entry_source = provider_entry_source(&key, crate::caller_location_key());
233        let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
234        ProvidedValue {
235            key,
236            apply: Box::new(move |composer: &Composer, site: crate::Key| {
237                let runtime = composer.runtime_handle();
238                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
239                let entry_ref = composer.remember_internal(source, || {
240                    Rc::new(ComputedLocalEntry {
241                        compute: OwnedMutableState::with_runtime_and_policy(
242                            Rc::clone(&compute),
243                            runtime.clone(),
244                            Rc::new(LocalValuePolicy::<Rc<ComputeLocalFn<T>>> {
245                                equivalent: Arc::new(Rc::ptr_eq),
246                            }),
247                        ),
248                    })
249                });
250                entry_ref.update(|entry| entry.compute.replace(Rc::clone(&compute)));
251                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, false))
252            }),
253        }
254    }
255
256    /// Returns the value from the innermost enclosing provider, or the default value when none is
257    /// in scope. Must be called during composition. The reading scope recomposes when the provided
258    /// value changes.
259    pub fn current(&self) -> T {
260        composer_context::with_composer(|composer| composer.read_composition_local(self))
261    }
262
263    /// Returns the value that `current` gives where no provider is in scope.
264    pub fn default_value(&self) -> T {
265        (self.default)()
266    }
267}
268
269#[cfg(test)]
270fn malformed_provided_value_for_test(key: LocalKey, entry: Rc<dyn Any>) -> ProvidedValue {
271    ProvidedValue {
272        key,
273        apply: Box::new(move |_, _| (entry.clone(), false)),
274    }
275}
276
277#[cfg(test)]
278pub(crate) fn malformed_composition_local_for_test<T: Clone + 'static>(
279    local: &CompositionLocal<T>,
280    entry: Rc<dyn Any>,
281) -> ProvidedValue {
282    malformed_provided_value_for_test(local.key.clone(), entry)
283}
284
285/// Creates a [`CompositionLocal`] whose default is made by `default`. A provided value replaces the
286/// previous one when it differs by `==`. Compose's `compositionLocalOf`.
287#[expect(non_snake_case)]
288pub fn compositionLocalOf<T: Clone + PartialEq + 'static>(
289    default: impl Fn() -> T + 'static,
290) -> CompositionLocal<T> {
291    compositionLocalOfWithPolicy(default, |current, next| current == next)
292}
293
294/// Creates a [`CompositionLocal`] whose default is made by `default`. A provided value replaces the
295/// previous one only when `equivalent` returns `false` for the two.
296#[expect(non_snake_case)]
297pub fn compositionLocalOfWithPolicy<T: Clone + 'static>(
298    default: impl Fn() -> T + 'static,
299    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
300) -> CompositionLocal<T> {
301    CompositionLocal {
302        key: LocalKey::new(),
303        default: Rc::new(default),
304        equivalent: Arc::new(equivalent),
305    }
306}
307
308/// A composition local for values that rarely change, as Jetpack Compose's
309/// `staticCompositionLocalOf`. A read of `.current()` subscribes nothing, so
310/// it costs less than a read of a [`CompositionLocal`]. In return, a
311/// provider whose value changes recomposes everything it provides to, not
312/// only the readers: every composable call under it runs its body, and
313/// every subcomposition under it composes again the same way.
314#[derive(Clone)]
315pub struct StaticCompositionLocal<T: Clone + 'static> {
316    pub(crate) key: LocalKey,
317    default: Rc<dyn Fn() -> T>,
318    equivalent: Arc<LocalEquivalentFn<T>>,
319}
320
321impl<T: Clone + 'static> PartialEq for StaticCompositionLocal<T> {
322    fn eq(&self, other: &Self) -> bool {
323        self.key == other.key
324    }
325}
326
327impl<T: Clone + 'static> Eq for StaticCompositionLocal<T> {}
328
329impl<T: Clone + 'static> StaticCompositionLocal<T> {
330    /// Pairs this local with `value` for use in `CompositionLocalProvider`. When a later
331    /// composition provides a different value, everything under the provider recomposes.
332    #[track_caller]
333    pub fn provides(&self, value: T) -> ProvidedValue {
334        let key = self.key.clone();
335        let entry_source = provider_entry_source(&key, crate::caller_location_key());
336        let equivalent = Arc::clone(&self.equivalent);
337        ProvidedValue {
338            key,
339            apply: Box::new(move |composer: &Composer, site: crate::Key| {
340                let source = (entry_source ^ site).wrapping_mul(0x0000_0100_0000_01b3);
341                let entry_ref = composer
342                    .remember_internal(source, || Rc::new(StaticLocalEntry::new(value.clone())));
343                let changed = entry_ref.update(|entry| entry.set(value.clone(), &*equivalent));
344                entry_ref.with(|entry| (entry.clone() as Rc<dyn Any>, changed))
345            }),
346        }
347    }
348
349    /// Pairs this local with `compute`, which runs at each read of the local
350    /// under the provider and reads other locals there through its scope.
351    /// Each composition of the provider gives a new computation, which
352    /// recomposes everything under it. Compose's `providesComputed`.
353    #[track_caller]
354    pub fn provides_computed(
355        &self,
356        compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
357    ) -> ProvidedValue {
358        let compute: Rc<ComputeLocalFn<T>> = Rc::new(compute);
359        ProvidedValue {
360            key: self.key.clone(),
361            apply: Box::new(move |_: &Composer, _: crate::Key| {
362                let entry = Rc::new(StaticComputedLocalEntry {
363                    compute: Rc::clone(&compute),
364                });
365                (entry as Rc<dyn Any>, true)
366            }),
367        }
368    }
369
370    /// Returns the value from the innermost enclosing provider, or the default value when none is
371    /// in scope. Must be called during composition. A read subscribes nothing.
372    pub fn current(&self) -> T {
373        composer_context::with_composer(|composer| composer.read_static_composition_local(self))
374    }
375
376    /// Returns the value that `current` gives where no provider is in scope.
377    pub fn default_value(&self) -> T {
378        (self.default)()
379    }
380}
381
382#[cfg(test)]
383pub(crate) fn malformed_static_composition_local_for_test<T: Clone + 'static>(
384    local: &StaticCompositionLocal<T>,
385    entry: Rc<dyn Any>,
386) -> ProvidedValue {
387    malformed_provided_value_for_test(local.key.clone(), entry)
388}
389
390/// A [`StaticCompositionLocal`] whose provided values compare with `==`.
391#[expect(non_snake_case)]
392pub fn staticCompositionLocalOf<T: Clone + PartialEq + 'static>(
393    default: impl Fn() -> T + 'static,
394) -> StaticCompositionLocal<T> {
395    staticCompositionLocalOfWithPolicy(default, |current, next| current == next)
396}
397
398/// A [`StaticCompositionLocal`] whose provider recomposes its content when
399/// `equivalent` says a new value differs from the last one.
400#[expect(non_snake_case)]
401pub fn staticCompositionLocalOfWithPolicy<T: Clone + 'static>(
402    default: impl Fn() -> T + 'static,
403    equivalent: impl Fn(&T, &T) -> bool + Send + Sync + 'static,
404) -> StaticCompositionLocal<T> {
405    StaticCompositionLocal {
406        key: LocalKey::new(),
407        default: Rc::new(default),
408        equivalent: Arc::new(equivalent),
409    }
410}
411
412/// A [`CompositionLocal`] whose default is computed at each read without a
413/// provider, from the other locals there. A scope that reads it recomposes
414/// when a dynamic local the computation read changes. As the default reads
415/// locals, it is computed during composition only. Compose's
416/// `compositionLocalWithComputedDefaultOf`.
417#[expect(non_snake_case)]
418pub fn compositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
419    compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
420) -> CompositionLocal<T> {
421    compositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
422}
423
424/// A [`StaticCompositionLocal`] whose default is computed at each read
425/// without a provider, from the other locals there. Compose's
426/// `staticCompositionLocalWithComputedDefaultOf`.
427#[expect(non_snake_case)]
428pub fn staticCompositionLocalWithComputedDefaultOf<T: Clone + PartialEq + 'static>(
429    compute: impl Fn(&CompositionLocalAccessorScope) -> T + 'static,
430) -> StaticCompositionLocal<T> {
431    staticCompositionLocalOf(move || compute(&CompositionLocalAccessorScope { _private: () }))
432}