use std::{
cell::{Cell, RefCell},
rc::Rc,
};
use cranpose_core::{
CompositionLocal, MutableState, OwnedMutableState, compositionLocalOf, try_mutableStateOf,
};
struct ModalEntry {
id: u64,
on_back: Rc<dyn Fn()>,
}
pub(crate) struct ModalState {
entries: RefCell<Vec<ModalEntry>>,
next_id: Cell<u64>,
depth: RefCell<Option<OwnedMutableState<usize>>>,
}
impl ModalState {
pub(crate) fn new() -> Self {
Self {
entries: RefCell::new(Vec::new()),
next_id: Cell::new(1),
depth: RefCell::new(None),
}
}
fn depth_state(&self) -> Option<MutableState<usize>> {
let mut state = self.depth.borrow_mut();
if state.is_none() {
*state = try_mutableStateOf(self.entries.borrow().len())
.map(|depth| MutableState::retain(&depth));
}
state.as_ref().map(OwnedMutableState::handle)
}
fn publish_depth(&self) {
let depth = self.entries.borrow().len();
let state = self.depth.borrow().as_ref().map(OwnedMutableState::handle);
if let Some(state) = state
&& state.get() != depth
{
state.set(depth);
}
}
}
pub struct ModalRegistration {
id: u64,
depth: usize,
app_context: crate::render_state::AppContextId,
}
impl Drop for ModalRegistration {
fn drop(&mut self) {
crate::render_state::enter_app_context_by_id(self.app_context, || {
crate::render_state::with_modal_state(|state| {
state
.entries
.borrow_mut()
.retain(|entry| entry.id != self.id);
state.publish_depth();
});
crate::text_field_focus::clear_focus_for_closed_modal(self.depth);
});
}
}
pub fn register_modal(on_back: Rc<dyn Fn()>) -> ModalRegistration {
let app_context = crate::render_state::current_app_context_id();
crate::render_state::with_modal_state(|state| {
let id = state.next_id.get();
state.next_id.set(id + 1);
state.entries.borrow_mut().push(ModalEntry { id, on_back });
let depth = state.entries.borrow().len();
state.publish_depth();
ModalRegistration {
id,
depth,
app_context,
}
})
}
pub fn modal_depth() -> usize {
crate::render_state::with_modal_state(|state| {
state
.depth_state()
.map_or_else(|| state.entries.borrow().len(), |depth| depth.get())
})
}
pub(crate) fn current_modal_depth() -> usize {
crate::render_state::with_modal_state(|state| state.entries.borrow().len())
}
pub fn local_modal_depth() -> CompositionLocal<usize> {
thread_local! {
static LOCAL: RefCell<Option<CompositionLocal<usize>>> = const { RefCell::new(None) };
}
LOCAL.with(|cell| {
cell.borrow_mut()
.get_or_insert_with(|| compositionLocalOf(|| 0usize))
.clone()
})
}
pub fn dispatch_modal_back() -> bool {
let innermost = crate::render_state::with_modal_state(|state| {
state
.entries
.borrow()
.last()
.map(|entry| Rc::clone(&entry.on_back))
});
match innermost {
Some(on_back) => {
on_back();
true
}
None => false,
}
}
pub fn clear_modals() {
crate::render_state::with_modal_state(|state| {
state.entries.borrow_mut().clear();
state.publish_depth();
});
}
#[cfg(test)]
#[path = "tests/modal_tests.rs"]
mod tests;