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