cranpose_core/
composer_context.rs1use std::{
2 rc::Rc,
3 sync::atomic::{AtomicUsize, Ordering},
4};
5
6use crate::{Composer, ComposerCore, StateId, thread_state};
7
8static INSTALLED_COMPOSERS: AtomicUsize = AtomicUsize::new(0);
12
13#[inline]
15fn any_composer_installed() -> bool {
16 INSTALLED_COMPOSERS.load(Ordering::Relaxed) != 0
17}
18
19#[must_use = "ComposerScopeGuard restores the previous composer on drop"]
21pub struct ComposerScopeGuard {
22 previous: Option<Rc<ComposerCore>>,
23}
24
25impl Drop for ComposerScopeGuard {
26 fn drop(&mut self) {
27 replace_current(self.previous.take());
28 }
29}
30
31pub fn enter(composer: &Composer) -> ComposerScopeGuard {
34 ComposerScopeGuard {
35 previous: replace_current(Some(composer.clone_core())),
36 }
37}
38
39fn replace_current(composer: Option<Rc<ComposerCore>>) -> Option<Rc<ComposerCore>> {
40 let installs = composer.is_some();
41 let previous = thread_state::runtimes(|state| state.current_composer.replace(composer));
42 match (previous.is_some(), installs) {
43 (false, true) => {
44 INSTALLED_COMPOSERS.fetch_add(1, Ordering::Relaxed);
45 }
46 (true, false) => {
47 INSTALLED_COMPOSERS.fetch_sub(1, Ordering::Relaxed);
48 }
49 _ => {}
50 }
51 previous
52}
53
54pub(crate) fn without_composer<R>(f: impl FnOnce() -> R) -> R {
55 let _suspended = ComposerScopeGuard {
56 previous: replace_current(None),
57 };
58 f()
59}
60
61#[inline]
66pub fn with_composer<R>(f: impl FnOnce(&Composer) -> R) -> R {
67 f(&__current_composer())
68}
69
70#[doc(hidden)]
75pub fn __current_composer() -> Composer {
76 thread_state::runtimes(|state| {
77 Composer::from_core(Rc::clone(
78 state
79 .current_composer
80 .borrow()
81 .as_ref()
82 .expect("with_composer: no active composer"),
83 ))
84 })
85}
86
87#[inline]
92pub(crate) fn with_current_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
93 if !any_composer_installed() {
94 return None;
95 }
96 with_installed_core(f)
97}
98
99#[inline]
100fn with_installed_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
101 installed_core().map(|core| f(&core))
102}
103
104#[inline(never)]
109fn installed_core() -> Option<Rc<ComposerCore>> {
110 thread_state::try_runtimes(|state| state.current_composer.borrow().clone()).flatten()
111}
112
113#[inline]
115pub(crate) fn has_composer() -> bool {
116 any_composer_installed() && installed_core().is_some()
117}
118
119pub fn current_composer() -> Option<Composer> {
121 if !any_composer_installed() {
122 return None;
123 }
124 thread_state::try_runtimes(|state| {
125 let core = Rc::clone(state.current_composer.borrow().as_ref()?);
126 Some(Composer::from_core(core))
127 })
128 .flatten()
129}
130
131pub fn note_nested_slots_host(host: &Rc<crate::SlotsHost>) {
135 let Some(composer) = current_composer() else {
136 return;
137 };
138 let holder = composer.active_slots_host();
139 if Rc::ptr_eq(&holder, host) {
140 return;
141 }
142 holder.note_nested_host(host);
143}
144
145pub fn try_with_composer<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
148 current_composer().map(|composer| f(&composer))
149}
150
151#[expect(non_snake_case, reason = "names follow Compose's casing")]
155pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
156 with_composer(f)
157}
158
159pub(crate) fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
160 try_with_composer(f)
161}
162
163#[doc(hidden)]
167pub fn recompose_current_scope_again() {
168 if let Some(scope) = with_current_composer_opt(Composer::current_recompose_scope).flatten() {
169 scope.request_pending_recompose();
170 }
171}
172
173#[doc(hidden)]
174pub fn current_recompose_scope_invalidated_only_by(
175 allowed_sources: impl IntoIterator<Item = StateId>,
176) -> Option<bool> {
177 with_current_composer_opt(|composer| {
178 composer
182 .restarted_scope()?
183 .invalidated_only_by(&allowed_sources.into_iter().collect())
184 })
185 .flatten()
186}