use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{CompositionLocal, CompositionLocalProvider, compositionLocalOf, remember};
use cranpose_macros::composable;
pub(crate) struct FocusWithinObserver {
focused: Cell<u32>,
on_change: RefCell<Rc<dyn Fn(bool)>>,
}
fn with_left_in_move<R>(f: impl FnOnce(&mut Option<Vec<Rc<FocusWithinObserver>>>) -> R) -> R {
crate::thread_state::with(|state| f(&mut state.left_in_focus_move.borrow_mut()))
}
impl FocusWithinObserver {
fn tell(&self, focused: bool) {
let on_change = Rc::clone(&self.on_change.borrow());
on_change(focused);
}
fn enter(observer: &Rc<Self>) {
let focused = observer.focused.get() + 1;
observer.focused.set(focused);
if focused != 1 {
return;
}
let came_back = with_left_in_move(|left| {
left.as_mut()
.and_then(|left| {
let index = left.iter().position(|other| Rc::ptr_eq(other, observer))?;
Some(left.swap_remove(index))
})
.is_some()
});
if !came_back {
observer.tell(true);
}
}
fn leave(observer: &Rc<Self>) {
let Some(focused) = observer.focused.get().checked_sub(1) else {
return;
};
observer.focused.set(focused);
if focused != 0 {
return;
}
let deferred = with_left_in_move(|left| {
left.as_mut()
.map(|left| left.push(Rc::clone(observer)))
.is_some()
});
if !deferred {
observer.tell(false);
}
}
}
struct FocusMove;
impl Drop for FocusMove {
fn drop(&mut self) {
let left = with_left_in_move(Option::take).unwrap_or_default();
for observer in left {
if observer.focused.get() == 0 {
observer.tell(false);
}
}
}
}
pub(crate) fn moving_focus(block: impl FnOnce()) {
let starts_move = with_left_in_move(|left| {
if left.is_some() {
return false;
}
*left = Some(Vec::new());
true
});
let _move = starts_move.then_some(FocusMove);
block();
}
#[derive(Clone)]
pub(crate) struct FocusWithinChain {
observer: Rc<FocusWithinObserver>,
outer: Option<Rc<FocusWithinChain>>,
}
impl PartialEq for FocusWithinChain {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.observer, &other.observer) && self.outer == other.outer
}
}
impl FocusWithinChain {
pub(crate) fn enter(&self) {
FocusWithinObserver::enter(&self.observer);
if let Some(outer) = &self.outer {
outer.enter();
}
}
pub(crate) fn leave(&self) {
FocusWithinObserver::leave(&self.observer);
if let Some(outer) = &self.outer {
outer.leave();
}
}
}
fn local_focus_within() -> CompositionLocal<Option<FocusWithinChain>> {
crate::thread_state::with_environment(|locals| {
locals
.focus_within
.get_or_init(|| compositionLocalOf(|| None))
.clone()
})
}
pub(crate) fn current_focus_within() -> Option<FocusWithinChain> {
cranpose_core::composer_context::current_composer()?;
local_focus_within().current()
}
#[composable]
pub fn FocusWithin(on_change: impl Fn(bool) + 'static, content: impl FnOnce()) {
let on_change: Rc<dyn Fn(bool)> = Rc::new(on_change);
let observer = remember(|| {
Rc::new(FocusWithinObserver {
focused: Cell::new(0),
on_change: RefCell::new(Rc::new(|_| {})),
})
})
.with(Rc::clone);
*observer.on_change.borrow_mut() = on_change;
let local = local_focus_within();
let chain = FocusWithinChain {
observer,
outer: local.current().map(Rc::new),
};
CompositionLocalProvider(vec![local.provides(Some(chain))], content);
}