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
23thread_local! {
24 static MODALS: RefCell<Vec<ModalEntry>> = const { RefCell::new(Vec::new()) };
25 static NEXT_ID: Cell<u64> = const { Cell::new(1) };
26 static DEPTH_COUNT: Cell<usize> = const { Cell::new(0) };
27 static DEPTH: RefCell<Option<OwnedMutableState<usize>>> = const { RefCell::new(None) };
28}
29
30fn depth_state() -> Option<MutableState<usize>> {
31 DEPTH.with(|cell| {
32 let mut cell = cell.borrow_mut();
33 if cell.is_none() {
34 *cell = try_mutableStateOf(DEPTH_COUNT.with(Cell::get))
35 .map(|depth| MutableState::retain(&depth));
36 }
37 cell.as_ref().map(OwnedMutableState::handle)
38 })
39}
40
41fn publish_depth() {
42 let depth = MODALS.with(|modals| modals.borrow().len());
43 DEPTH_COUNT.with(|count| count.set(depth));
44 if let Some(state) = DEPTH.with(|cell| cell.borrow().as_ref().map(OwnedMutableState::handle))
45 && state.get() != depth
46 {
47 state.set(depth);
48 }
49}
50
51pub struct ModalRegistration {
53 id: u64,
54 depth: usize,
55}
56
57impl Drop for ModalRegistration {
58 fn drop(&mut self) {
59 MODALS.with(|modals| modals.borrow_mut().retain(|entry| entry.id != self.id));
60 publish_depth();
61 if crate::render_state::has_current_app_context() {
62 crate::text_field_focus::clear_focus_for_closed_modal(self.depth);
63 }
64 }
65}
66
67pub fn register_modal(on_back: Rc<dyn Fn()>) -> ModalRegistration {
70 let id = NEXT_ID.with(|next| {
71 let id = next.get();
72 next.set(id + 1);
73 id
74 });
75 let depth = MODALS.with(|modals| {
76 let mut modals = modals.borrow_mut();
77 modals.push(ModalEntry { id, on_back });
78 modals.len()
79 });
80 publish_depth();
81 ModalRegistration { id, depth }
82}
83
84pub fn modal_depth() -> usize {
87 match depth_state() {
88 Some(state) => state.get(),
89 None => DEPTH_COUNT.with(Cell::get),
90 }
91}
92
93pub(crate) fn current_modal_depth() -> usize {
94 DEPTH_COUNT.with(Cell::get)
95}
96
97pub fn local_modal_depth() -> CompositionLocal<usize> {
111 thread_local! {
112 static LOCAL: RefCell<Option<CompositionLocal<usize>>> = const { RefCell::new(None) };
113 }
114 LOCAL.with(|cell| {
115 cell.borrow_mut()
116 .get_or_insert_with(|| compositionLocalOf(|| 0usize))
117 .clone()
118 })
119}
120
121pub fn dispatch_modal_back() -> bool {
127 let innermost = MODALS.with(|modals| {
128 modals
129 .borrow()
130 .last()
131 .map(|entry| Rc::clone(&entry.on_back))
132 });
133 match innermost {
134 Some(on_back) => {
135 on_back();
136 true
137 }
138 None => false,
139 }
140}
141
142pub fn clear_modals() {
145 MODALS.with(|modals| modals.borrow_mut().clear());
146 publish_depth();
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 #[test]
154 fn the_innermost_modal_takes_the_back_request() {
155 clear_modals();
156 let outer = Rc::new(Cell::new(0u32));
157 let inner = Rc::new(Cell::new(0u32));
158 let outer_counter = Rc::clone(&outer);
159 let inner_counter = Rc::clone(&inner);
160
161 let _outer = register_modal(Rc::new(move || outer_counter.set(outer_counter.get() + 1)));
162 let inner_registration =
163 register_modal(Rc::new(move || inner_counter.set(inner_counter.get() + 1)));
164
165 assert!(dispatch_modal_back());
166 assert_eq!(inner.get(), 1);
167 assert_eq!(outer.get(), 0);
168
169 drop(inner_registration);
170 assert!(dispatch_modal_back());
171 assert_eq!(outer.get(), 1);
172 clear_modals();
173 }
174
175 #[test]
176 fn a_back_request_with_no_modal_open_is_not_taken() {
177 clear_modals();
178 assert!(!dispatch_modal_back());
179 }
180
181 #[test]
182 fn the_depth_counts_what_is_open_and_falls_back_to_zero() {
183 clear_modals();
184 assert_eq!(modal_depth(), 0);
185
186 let outer = register_modal(Rc::new(|| {}));
187 assert_eq!(modal_depth(), 1);
188 let inner = register_modal(Rc::new(|| {}));
189 assert_eq!(modal_depth(), 2);
190
191 drop(inner);
192 assert_eq!(modal_depth(), 1);
193 drop(outer);
194 assert_eq!(modal_depth(), 0);
195 }
196
197 #[test]
198 fn registrations_leave_the_stack_when_dropped() {
199 clear_modals();
200 {
201 let _first = register_modal(Rc::new(|| {}));
202 let _second = register_modal(Rc::new(|| {}));
203 assert_eq!(MODALS.with(|modals| modals.borrow().len()), 2);
204 }
205 assert_eq!(MODALS.with(|modals| modals.borrow().len()), 0);
206 assert!(!dispatch_modal_back());
207 }
208}