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