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