1use std::{
10 cell::{Cell, RefCell},
11 rc::Rc,
12};
13
14use cranpose_core::{
15 CompositionLocal, MutableState, OwnedMutableState, compositionLocalOf, try_mutableStateOf,
16};
17
18struct ModalEntry {
19 id: u64,
20 on_back: Rc<dyn Fn()>,
21}
22
23pub(crate) struct ModalState {
24 entries: RefCell<Vec<ModalEntry>>,
25 next_id: Cell<u64>,
26 depth: RefCell<Option<OwnedMutableState<usize>>>,
27}
28
29impl ModalState {
30 pub(crate) fn new() -> Self {
31 Self {
32 entries: RefCell::new(Vec::new()),
33 next_id: Cell::new(1),
34 depth: RefCell::new(None),
35 }
36 }
37
38 fn depth_state(&self) -> Option<MutableState<usize>> {
39 let mut state = self.depth.borrow_mut();
40 if state.is_none() {
41 *state = try_mutableStateOf(self.entries.borrow().len())
42 .map(|depth| MutableState::retain(&depth));
43 }
44 state.as_ref().map(OwnedMutableState::handle)
45 }
46
47 fn publish_depth(&self) {
48 let depth = self.entries.borrow().len();
49 let state = self.depth.borrow().as_ref().map(OwnedMutableState::handle);
50 if let Some(state) = state
51 && state.get() != depth
52 {
53 state.set(depth);
54 }
55 }
56}
57
58pub struct ModalRegistration {
60 id: u64,
61 depth: usize,
62 app_context: crate::render_state::AppContextId,
63}
64
65impl Drop for ModalRegistration {
66 fn drop(&mut self) {
67 crate::render_state::enter_app_context_by_id(self.app_context, || {
68 crate::render_state::with_modal_state(|state| {
69 state
70 .entries
71 .borrow_mut()
72 .retain(|entry| entry.id != self.id);
73 state.publish_depth();
74 });
75 crate::text_field_focus::clear_focus_for_closed_modal(self.depth);
76 });
77 }
78}
79
80pub fn register_modal(on_back: Rc<dyn Fn()>) -> ModalRegistration {
84 let app_context = crate::render_state::current_app_context_id();
85 crate::render_state::with_modal_state(|state| {
86 let id = state.next_id.get();
87 state.next_id.set(id + 1);
88 state.entries.borrow_mut().push(ModalEntry { id, on_back });
89 let depth = state.entries.borrow().len();
90 state.publish_depth();
91 ModalRegistration {
92 id,
93 depth,
94 app_context,
95 }
96 })
97}
98
99pub fn modal_depth() -> usize {
102 crate::render_state::with_modal_state(|state| {
103 state
104 .depth_state()
105 .map_or_else(|| state.entries.borrow().len(), |depth| depth.get())
106 })
107}
108
109pub(crate) fn current_modal_depth() -> usize {
110 crate::render_state::with_modal_state(|state| state.entries.borrow().len())
111}
112
113pub fn local_modal_depth() -> CompositionLocal<usize> {
127 thread_local! {
128 static LOCAL: RefCell<Option<CompositionLocal<usize>>> = const { RefCell::new(None) };
129 }
130 LOCAL.with(|cell| {
131 cell.borrow_mut()
132 .get_or_insert_with(|| compositionLocalOf(|| 0usize))
133 .clone()
134 })
135}
136
137pub fn dispatch_modal_back() -> bool {
143 let innermost = crate::render_state::with_modal_state(|state| {
144 state
145 .entries
146 .borrow()
147 .last()
148 .map(|entry| Rc::clone(&entry.on_back))
149 });
150 match innermost {
151 Some(on_back) => {
152 on_back();
153 true
154 }
155 None => false,
156 }
157}
158
159pub fn clear_modals() {
161 crate::render_state::with_modal_state(|state| {
162 state.entries.borrow_mut().clear();
163 state.publish_depth();
164 });
165}
166
167#[cfg(test)]
168#[path = "tests/modal_tests.rs"]
169mod tests;