1#![allow(non_snake_case)]
2use std::{any::Any, cell::RefCell, fmt::Debug, rc::Rc};
3
4use repose_core::*;
5use repose_ui::{Box as VBox, Stack, ViewExt, anim::animate_f32};
6use serde::{Deserialize, Serialize};
7
8pub trait NavKey: Clone + Debug + 'static + Serialize + for<'de> Deserialize<'de> {}
9impl<T> NavKey for T where T: Clone + Debug + 'static + Serialize + for<'de> Deserialize<'de> {}
10
11#[derive(Clone, Copy, PartialEq, Eq, Debug)]
12pub enum TransitionDir {
13 None,
14 Push,
15 Pop,
16}
17
18#[derive(Default)]
19pub struct SavedState {
20 map: RefCell<std::collections::HashMap<&'static str, Box<dyn Any>>>,
21 results: RefCell<std::collections::HashMap<&'static str, Box<dyn Any>>>,
22}
23impl SavedState {
24 pub fn remember<T: 'static + Clone>(
25 &self,
26 key: &'static str,
27 init: impl FnOnce() -> T,
28 ) -> Rc<RefCell<T>> {
29 if let Some(b) = self.map.borrow().get(key) {
30 if let Some(rc) = b.downcast_ref::<Rc<RefCell<T>>>() {
31 return rc.clone();
32 }
33 }
34 let rc = Rc::new(RefCell::new(init()));
35 self.map.borrow_mut().insert(key, Box::new(rc.clone()));
36 rc
37 }
38 pub fn set_result<T: 'static>(&self, key: &'static str, val: T) {
39 self.results.borrow_mut().insert(key, Box::new(val));
40 }
41 pub fn take_result<T: 'static>(&self, key: &'static str) -> Option<T> {
42 self.results
43 .borrow_mut()
44 .remove(key)?
45 .downcast::<T>()
46 .ok()
47 .map(|b| *b)
48 }
49}
50
51struct Entry<K: NavKey> {
52 id: u64,
53 key: K,
54 saved: Rc<SavedState>,
55}
56
57struct BackState<K: NavKey> {
58 entries: Vec<Entry<K>>,
59 next_id: u64,
60 last_dir: TransitionDir,
61}
62
63#[derive(Clone)]
64pub struct NavBackStack<K: NavKey> {
65 inner: Rc<RefCell<BackState<K>>>,
66 version: Rc<Signal<u64>>,
67}
68impl<K: NavKey> NavBackStack<K> {
69 pub fn top(&self) -> Option<(u64, K, Rc<SavedState>)> {
70 let s = self.inner.borrow();
71 s.entries
72 .last()
73 .map(|e| (e.id, e.key.clone(), e.saved.clone()))
74 }
75 pub fn size(&self) -> usize {
76 self.inner.borrow().entries.len()
77 }
78 pub fn last_dir(&self) -> TransitionDir {
79 self.inner.borrow().last_dir
80 }
81 fn bump(&self) {
82 let v = self.version.get();
83 self.version.set(v.wrapping_add(1));
84 }
85
86 fn push_inner(&self, key: K) {
87 let mut s = self.inner.borrow_mut();
88 let id = s.next_id;
89 s.next_id += 1;
90 s.entries.push(Entry {
91 id,
92 key,
93 saved: Rc::new(SavedState::default()),
94 });
95 s.last_dir = TransitionDir::Push;
96 }
97 fn pop_inner(&self) -> Option<Entry<K>> {
98 let mut s = self.inner.borrow_mut();
99 s.last_dir = TransitionDir::Pop;
100 s.entries.pop()
101 }
102 fn replace_inner(&self, key: K) {
103 let mut s = self.inner.borrow_mut();
104 if let Some(last) = s.entries.last_mut() {
105 last.key = key;
106 } else {
107 let id = s.next_id;
108 s.next_id += 1;
109 s.entries.push(Entry {
110 id,
111 key,
112 saved: Rc::new(SavedState::default()),
113 });
114 }
115 s.last_dir = TransitionDir::Push;
116 }
117
118 pub fn to_json(&self) -> String
119 where
120 K: Serialize,
121 {
122 let s = self.inner.borrow();
123 let keys: Vec<&K> = s.entries.iter().map(|e| &e.key).collect();
124 serde_json::to_string(&keys).unwrap_or("[]".into())
125 }
126 pub fn from_json(&self, json: &str)
127 where
128 K: for<'de> Deserialize<'de>,
129 {
130 if let Ok(keys) = serde_json::from_str::<Vec<K>>(json) {
131 let mut s = self.inner.borrow_mut();
132 s.entries.clear();
133 for k in keys {
134 let id = s.next_id;
135 s.next_id += 1;
136 s.entries.push(Entry {
137 id,
138 key: k,
139 saved: Rc::new(SavedState::default()),
140 });
141 }
142 s.last_dir = TransitionDir::None;
143 drop(s);
144 self.bump();
145 }
146 }
147}
148
149#[derive(Clone)]
150pub struct Navigator<K: NavKey> {
151 pub stack: NavBackStack<K>,
152}
153impl<K: NavKey> Navigator<K> {
154 pub fn push(&self, k: K) {
155 self.stack.push_inner(k);
156 self.stack.bump();
157 }
158 pub fn replace(&self, k: K) {
159 self.stack.replace_inner(k);
160 self.stack.bump();
161 }
162 pub fn pop(&self) -> bool {
163 if self.stack.size() <= 1 {
165 return false;
166 }
167 let ok = self.stack.pop_inner().is_some();
168 if ok {
169 self.stack.bump();
170 }
171 ok
172 }
173 pub fn clear_and_push(&self, k: K) {
174 while self.stack.pop_inner().is_some() {}
175 self.stack.push_inner(k);
176 self.stack.bump();
177 }
178 pub fn pop_to<F: Fn(&K) -> bool>(&self, pred: F, inclusive: bool) {
179 let count = {
180 let s = self.stack.inner.borrow();
181 if let Some(idx) = s.entries.iter().rposition(|e| pred(&e.key)) {
182 s.entries.len() - idx - (if inclusive { 0 } else { 1 })
183 } else {
184 0
185 }
186 };
187 for _ in 0..count {
188 self.stack.pop_inner();
189 }
190 if count > 0 {
191 self.stack.bump();
192 }
193 }
194}
195
196pub fn remember_back_stack<K: NavKey>(start: K) -> std::rc::Rc<NavBackStack<K>> {
197 remember_with_key("nav3:stack", || NavBackStack {
198 inner: std::rc::Rc::new(std::cell::RefCell::new(BackState {
199 entries: vec![Entry {
200 id: 1,
201 key: start,
202 saved: std::rc::Rc::new(SavedState::default()),
203 }],
204 next_id: 2,
205 last_dir: TransitionDir::None,
206 })),
207 version: std::rc::Rc::new(signal(0)),
208 })
209}
210
211pub struct EntryScope<K: NavKey> {
212 id: u64,
213 key: K,
214 saved: Rc<SavedState>,
215 nav: Navigator<K>,
216}
217impl<K: NavKey> EntryScope<K> {
218 pub fn id(&self) -> u64 {
219 self.id
220 }
221 pub fn key(&self) -> &K {
222 &self.key
223 }
224 pub fn navigator(&self) -> Navigator<K> {
225 self.nav.clone()
226 }
227 pub fn remember_saveable<T: 'static + Clone>(
228 &self,
229 slot: &'static str,
230 init: impl FnOnce() -> T,
231 ) -> Rc<RefCell<T>> {
232 self.saved.remember(slot, init)
233 }
234 pub fn set_result<T: 'static>(&self, slot: &'static str, v: T) {
235 self.saved.set_result(slot, v)
236 }
237 pub fn take_result<T: 'static>(&self, slot: &'static str) -> Option<T> {
238 self.saved.take_result(slot)
239 }
240}
241
242pub type EntryRenderer<K> = Rc<dyn Fn(&EntryScope<K>) -> View>;
243pub fn renderer<K: NavKey>(f: impl Fn(&EntryScope<K>) -> View + 'static) -> EntryRenderer<K> {
244 Rc::new(f)
245}
246
247#[derive(Clone, Copy)]
248pub struct NavTransition {
249 pub slide_px: f32,
250 pub fade: bool,
251 pub spec: AnimationSpec,
252}
253impl Default for NavTransition {
254 fn default() -> Self {
255 Self {
256 slide_px: 60.0,
257 fade: true,
258 spec: AnimationSpec::fast(),
259 }
260 }
261}
262
263pub fn NavDisplay<K: NavKey>(
264 stack: Rc<NavBackStack<K>>,
265 make_view: EntryRenderer<K>,
266 on_back: Option<Rc<dyn Fn()>>,
267 transition: NavTransition,
268) -> View {
269 let _v = stack.version.get(); let (id, key, saved) = match stack.top() {
271 Some(t) => t,
272 None => return VBox(Modifier::new()),
273 };
274 let scope = EntryScope {
275 id,
276 key,
277 saved,
278 nav: Navigator {
279 stack: (*stack).clone(),
280 },
281 };
282
283 let dir = stack.last_dir();
284 if dir == TransitionDir::None {
285 let v = (make_view)(&scope);
286 return maybe_intercept_back(v, on_back);
287 }
288
289 let t = animate_f32(
290 format!("nav3:{id}"),
291 if dir == TransitionDir::Push { 1.0 } else { 0.0 },
292 transition.spec,
293 );
294 let slide = if dir == TransitionDir::Push {
295 1.0 - t
296 } else {
297 t
298 };
299 let dx = slide
300 * transition.slide_px
301 * if dir == TransitionDir::Push {
302 1.0
303 } else {
304 -1.0
305 };
306 let alpha = if transition.fade {
307 0.75 + 0.25 * (1.0 - slide)
308 } else {
309 1.0
310 };
311
312 let v = (make_view)(&scope);
313 let framed = Stack(Modifier::new().fill_max_size())
314 .child(VBox(Modifier::new().translate(dx, 0.0).alpha(alpha)).child(v));
315 maybe_intercept_back(framed, on_back)
316}
317
318fn maybe_intercept_back(v: View, _on_back: Option<Rc<dyn Fn()>>) -> View {
319 v
321}
322
323pub mod back {
327 use std::{cell::RefCell, rc::Rc};
328
329 type Handler = Rc<dyn Fn() -> bool>;
330
331 thread_local! {
332 static H: RefCell<Option<Handler>> = RefCell::new(None);
333 }
334
335 pub fn set(handler: Option<Handler>) {
336 H.with(|h| *h.borrow_mut() = handler);
337 }
338
339 pub fn handle() -> bool {
340 H.with(|h| {
341 if let Some(handler) = h.borrow().as_ref() {
342 handler()
343 } else {
344 false
345 }
346 })
347 }
348}
349
350pub fn InstallBackHandler<K: NavKey>(stack: NavBackStack<K>) -> Dispose {
352 let nav = Navigator {
353 stack: stack.clone(),
354 };
355 back::set(Some(Rc::new(move || nav.pop())));
356 on_unmount(|| back::set(None))
357}