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