use std::{
rc::Rc,
sync::atomic::{AtomicUsize, Ordering},
};
use crate::{Composer, ComposerCore, StateId, thread_state};
static INSTALLED_COMPOSERS: AtomicUsize = AtomicUsize::new(0);
#[inline]
fn any_composer_installed() -> bool {
INSTALLED_COMPOSERS.load(Ordering::Relaxed) != 0
}
#[must_use = "ComposerScopeGuard restores the previous composer on drop"]
pub struct ComposerScopeGuard {
previous: Option<Rc<ComposerCore>>,
}
impl Drop for ComposerScopeGuard {
fn drop(&mut self) {
replace_current(self.previous.take());
}
}
pub fn enter(composer: &Composer) -> ComposerScopeGuard {
ComposerScopeGuard {
previous: replace_current(Some(composer.clone_core())),
}
}
fn replace_current(composer: Option<Rc<ComposerCore>>) -> Option<Rc<ComposerCore>> {
let installs = composer.is_some();
let previous = thread_state::runtimes(|state| state.current_composer.replace(composer));
match (previous.is_some(), installs) {
(false, true) => {
INSTALLED_COMPOSERS.fetch_add(1, Ordering::Relaxed);
}
(true, false) => {
INSTALLED_COMPOSERS.fetch_sub(1, Ordering::Relaxed);
}
_ => {}
}
previous
}
pub(crate) fn without_composer<R>(f: impl FnOnce() -> R) -> R {
let _suspended = ComposerScopeGuard {
previous: replace_current(None),
};
f()
}
#[inline]
pub fn with_composer<R>(f: impl FnOnce(&Composer) -> R) -> R {
f(&__current_composer())
}
#[doc(hidden)]
pub fn __current_composer() -> Composer {
thread_state::runtimes(|state| {
Composer::from_core(Rc::clone(
state
.current_composer
.borrow()
.as_ref()
.expect("with_composer: no active composer"),
))
})
}
#[inline]
pub(crate) fn with_current_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
if !any_composer_installed() {
return None;
}
with_installed_core(f)
}
#[inline]
fn with_installed_core<R>(f: impl FnOnce(&ComposerCore) -> R) -> Option<R> {
installed_core().map(|core| f(&core))
}
#[inline(never)]
fn installed_core() -> Option<Rc<ComposerCore>> {
thread_state::try_runtimes(|state| state.current_composer.borrow().clone()).flatten()
}
#[inline]
pub(crate) fn has_composer() -> bool {
any_composer_installed() && installed_core().is_some()
}
pub fn current_composer() -> Option<Composer> {
if !any_composer_installed() {
return None;
}
thread_state::try_runtimes(|state| {
let core = Rc::clone(state.current_composer.borrow().as_ref()?);
Some(Composer::from_core(core))
})
.flatten()
}
pub fn note_nested_slots_host(host: &Rc<crate::SlotsHost>) {
let Some(composer) = current_composer() else {
return;
};
let holder = composer.active_slots_host();
if Rc::ptr_eq(&holder, host) {
return;
}
holder.note_nested_host(host);
}
pub fn try_with_composer<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
current_composer().map(|composer| f(&composer))
}
#[expect(non_snake_case, reason = "names follow Compose's casing")]
pub fn withCurrentComposer<R>(f: impl FnOnce(&Composer) -> R) -> R {
with_composer(f)
}
pub(crate) fn with_current_composer_opt<R>(f: impl FnOnce(&Composer) -> R) -> Option<R> {
try_with_composer(f)
}
#[doc(hidden)]
pub fn recompose_current_scope_again() {
if let Some(scope) = with_current_composer_opt(Composer::current_recompose_scope).flatten() {
scope.request_pending_recompose();
}
}
#[doc(hidden)]
pub fn current_recompose_scope_invalidated_only_by(
allowed_sources: impl IntoIterator<Item = StateId>,
) -> Option<bool> {
with_current_composer_opt(|composer| {
composer
.restarted_scope()?
.invalidated_only_by(&allowed_sources.into_iter().collect())
})
.flatten()
}