1use std::{any::Any, panic::Location, rc::Rc};
2
3use cranpose_core::{MutableState, OwnedMutableState, ownedMutableStateOfNeverEqual};
4use cranpose_coroflow::{ViewModelStore, viewModel};
5use cranpose_services::{SaveableRegistration, SaveableScope, Saver};
6
7mod saved_stack;
8
9#[derive(Clone)]
15pub(crate) struct Entry<R> {
16 id: u64,
17 pub(crate) route: R,
18 pub(crate) store: ViewModelStore,
19 pub(crate) saveables: SaveableScope,
20}
21
22impl<R> Entry<R> {
23 fn new(id: u64, route: R, stack: &SaveableScope) -> Self {
24 Self {
25 id,
26 route,
27 store: ViewModelStore::default(),
28 saveables: stack.child(id),
29 }
30 }
31}
32
33impl<R> Entry<R> {
34 pub(crate) fn id(&self) -> u64 {
36 self.id
37 }
38
39 fn encloses(&self, scope: &SaveableScope) -> bool {
42 let (Some(own), Some(inner)) = (self.saveables.namespace(), scope.namespace()) else {
43 return false;
44 };
45 inner
46 .strip_prefix(own)
47 .is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
48 }
49}
50
51impl<R> PartialEq for Entry<R> {
52 fn eq(&self, other: &Self) -> bool {
53 self.id == other.id
54 }
55}
56
57#[derive(Clone)]
58pub(crate) struct BackStack<R> {
59 entries: Vec<Entry<R>>,
60 saved: Vec<Vec<Entry<R>>>,
63 next_id: u64,
64 saveables: SaveableScope,
67}
68
69impl<R: Clone + PartialEq> BackStack<R> {
70 fn new(start: R, saveables: SaveableScope) -> Self {
71 let mut stack = Self {
72 entries: Vec::new(),
73 saved: Vec::new(),
74 next_id: 0,
75 saveables,
76 };
77 stack.push(start);
78 stack
79 }
80
81 fn push(&mut self, route: R) {
82 self.entries
83 .push(Entry::new(self.next_id, route, &self.saveables));
84 self.next_id += 1;
85 }
86
87 fn split_above(&mut self, route: &R, inclusive: bool) -> Vec<Entry<R>> {
92 let Some(index) = self.entries.iter().rposition(|entry| entry.route == *route) else {
93 return Vec::new();
94 };
95 self.entries
96 .split_off(if inclusive { index } else { index + 1 })
97 }
98
99 fn keep(&mut self, popped: Vec<Entry<R>>) -> Vec<Entry<R>> {
104 let (replaced, kept) = std::mem::take(&mut self.saved)
105 .into_iter()
106 .partition::<Vec<_>, _>(|segment| {
107 segment
108 .iter()
109 .any(|entry| popped.iter().any(|new| new.route == entry.route))
110 });
111 self.saved = kept;
112 self.saved.push(popped);
113 let replaced: Vec<Entry<R>> = replaced.into_iter().flatten().collect();
114 for entry in &replaced {
115 entry.store.clear();
116 }
117 replaced
118 }
119
120 fn take_saved(&mut self, route: &R) -> Option<Vec<Entry<R>>> {
122 let index = self
123 .saved
124 .iter()
125 .position(|segment| segment.iter().any(|entry| entry.route == *route))?;
126 Some(self.saved.swap_remove(index))
127 }
128
129 fn top_route(&self) -> Option<&R> {
130 self.entries.last().map(|entry| &entry.route)
131 }
132
133 fn find(&self, id: u64) -> Option<&Entry<R>> {
136 self.entries
137 .iter()
138 .chain(self.saved.iter().flatten())
139 .find(|entry| entry.id == id)
140 }
141
142 pub(crate) fn can_pop(&self) -> bool {
143 self.entries.len() > 1
144 }
145
146 pub(crate) fn screens_and_dialogs<S>(
151 &self,
152 dialog: impl Fn(&R) -> Option<S>,
153 ) -> HostView<R, S> {
154 let is_screen = |entry: &Entry<R>| dialog(&entry.route).is_none();
155 let at = self.entries.iter().rposition(is_screen);
156 let toward = at
157 .and_then(|at| self.entries[..at].iter().rposition(is_screen))
158 .map(|index| self.entries[index].clone());
159 let from = at.map_or(0, |at| at + 1);
160 let dialogs = self.entries[from..]
161 .iter()
162 .enumerate()
163 .filter_map(|(offset, entry)| {
164 dialog(&entry.route).map(|spec| (from + offset, entry.clone(), spec))
165 })
166 .collect();
167 HostView {
168 screen: at.map(|at| self.entries[at].clone()),
169 toward,
170 dialogs,
171 }
172 }
173}
174
175pub(crate) struct HostView<R, S> {
177 pub(crate) screen: Option<Entry<R>>,
179 pub(crate) toward: Option<Entry<R>>,
181 pub(crate) dialogs: Vec<(usize, Entry<R>, S)>,
184}
185
186#[derive(Clone, Default)]
189pub(crate) struct EntryResults(Vec<(u64, Rc<dyn Any>)>);
190
191impl EntryResults {
192 fn position<T: 'static>(&self, entry: u64) -> Option<usize> {
194 self.0
195 .iter()
196 .position(|(id, value)| *id == entry && value.is::<T>())
197 }
198
199 fn holds_any(&self, entry: u64) -> bool {
200 self.0.iter().any(|(id, _)| *id == entry)
201 }
202}
203
204pub struct NavOptions<R> {
207 pop_up_to: Option<(R, bool)>,
208 launch_single_top: bool,
209 save_state: bool,
210 restore_state: bool,
211}
212
213impl<R> Default for NavOptions<R> {
214 fn default() -> Self {
215 Self {
216 pop_up_to: None,
217 launch_single_top: false,
218 save_state: false,
219 restore_state: false,
220 }
221 }
222}
223
224impl<R> NavOptions<R> {
225 pub fn new() -> Self {
227 Self::default()
228 }
229
230 #[must_use]
234 pub fn pop_up_to(mut self, route: R, inclusive: bool) -> Self {
235 self.pop_up_to = Some((route, inclusive));
236 self
237 }
238
239 #[must_use]
242 pub fn launch_single_top(mut self) -> Self {
243 self.launch_single_top = true;
244 self
245 }
246
247 #[must_use]
253 pub fn save_state(mut self) -> Self {
254 self.save_state = true;
255 self
256 }
257
258 #[must_use]
263 pub fn restore_state(mut self) -> Self {
264 self.restore_state = true;
265 self
266 }
267}
268
269pub struct NavController<R: Clone + 'static> {
276 pub(crate) stack: MutableState<BackStack<R>>,
277 results: MutableState<EntryResults>,
280}
281
282impl<R: Clone + 'static> Clone for NavController<R> {
283 fn clone(&self) -> Self {
284 *self
285 }
286}
287
288impl<R: Clone + 'static> Copy for NavController<R> {}
289
290impl<R: Clone + 'static> PartialEq for NavController<R> {
291 fn eq(&self, other: &Self) -> bool {
292 self.stack == other.stack
293 }
294}
295
296impl<R: Clone + PartialEq + 'static> NavController<R> {
297 pub fn navigate(&self, route: R) {
299 self.navigate_with(route, NavOptions::new());
300 }
301
302 pub fn navigate_with(&self, route: R, options: NavOptions<R>) {
305 let forgotten = self.stack.update(|stack| {
306 let popped = options
307 .pop_up_to
308 .map(|(target, inclusive)| stack.split_above(&target, inclusive))
309 .unwrap_or_default();
310 let forgotten = if options.save_state && !popped.is_empty() {
311 stack.keep(popped)
312 } else {
313 popped
314 };
315 let restored = options
316 .restore_state
317 .then(|| stack.take_saved(&route))
318 .flatten();
319 if let Some(restored) = restored {
320 stack.entries.extend(restored);
321 } else if !(options.launch_single_top && stack.top_route() == Some(&route)) {
322 stack.push(route);
323 }
324 forgotten
325 });
326 self.forget(forgotten);
327 }
328
329 pub fn pop_back_stack(&self) -> bool {
332 self.forget(self.stack.update(|stack| stack.entries.pop()))
333 }
334
335 pub fn pop_back_stack_to(&self, route: &R, inclusive: bool) -> bool {
339 self.forget(
340 self.stack
341 .update(|stack| stack.split_above(route, inclusive)),
342 )
343 }
344
345 pub(crate) fn pop_to_depth(&self, depth: usize) {
347 self.forget(self.stack.update(|stack| {
348 let keep = depth.min(stack.entries.len());
349 stack.entries.split_off(keep)
350 }));
351 }
352
353 pub(crate) fn open_deep_link(&self, route: R) {
357 self.forget(self.stack.update(|stack| {
358 let popped = stack.entries.split_off(stack.entries.len().min(1));
359 if stack.top_route() != Some(&route) {
360 stack.push(route);
361 }
362 popped
363 }));
364 }
365
366 pub fn navigate_up(&self) -> bool {
369 self.forget(
370 self.stack
371 .update(|stack| stack.can_pop().then(|| stack.entries.pop()).flatten()),
372 )
373 }
374
375 pub fn current_route(&self) -> Option<R> {
379 self.stack.read(|stack| stack.top_route().cloned())
380 }
381
382 pub fn back_stack(&self) -> Vec<R> {
386 self.stack.read(|stack| {
387 stack
388 .entries
389 .iter()
390 .map(|entry| entry.route.clone())
391 .collect()
392 })
393 }
394
395 pub fn composing_entry(&self) -> Option<NavBackStackEntry<R>> {
400 let scope = SaveableScope::current();
401 self.entry_where(|entries| entries.iter().find(|entry| entry.encloses(&scope)))
402 }
403
404 pub fn previous_back_stack_entry(&self) -> Option<NavBackStackEntry<R>> {
407 self.entry_where(|entries| entries.iter().nth_back(1))
408 }
409
410 pub fn back_stack_entry(&self, route: &R) -> Option<NavBackStackEntry<R>> {
413 self.entry_where(|entries| entries.iter().rfind(|entry| entry.route == *route))
414 }
415
416 fn entry_where(
419 &self,
420 find: impl FnOnce(&[Entry<R>]) -> Option<&Entry<R>>,
421 ) -> Option<NavBackStackEntry<R>> {
422 let id = self
423 .stack
424 .try_with(|stack| find(&stack.entries).map(Entry::id))
425 .flatten()?;
426 Some(NavBackStackEntry {
427 controller: *self,
428 id,
429 })
430 }
431
432 fn forget(&self, popped: impl IntoIterator<Item = Entry<R>>) -> bool {
436 let mut any = false;
437 for entry in popped {
438 entry.saveables.forget();
439 if self
440 .results
441 .try_with(|results| results.holds_any(entry.id))
442 .unwrap_or(false)
443 {
444 self.results
445 .update(|results| results.0.retain(|(id, _)| *id != entry.id));
446 }
447 any = true;
448 }
449 any
450 }
451}
452
453pub struct NavBackStackEntry<R: Clone + 'static> {
493 controller: NavController<R>,
494 id: u64,
495}
496
497impl<R: Clone + 'static> Clone for NavBackStackEntry<R> {
498 fn clone(&self) -> Self {
499 *self
500 }
501}
502
503impl<R: Clone + 'static> Copy for NavBackStackEntry<R> {}
504
505impl<R: Clone + 'static> PartialEq for NavBackStackEntry<R> {
506 fn eq(&self, other: &Self) -> bool {
507 self.controller == other.controller && self.id == other.id
508 }
509}
510
511impl<R: Clone + PartialEq + 'static> NavBackStackEntry<R> {
512 pub fn route(&self) -> Option<R> {
515 self.controller
516 .stack
517 .try_with(|stack| stack.find(self.id).map(|entry| entry.route.clone()))
518 .flatten()
519 }
520
521 pub fn result<T: Clone + 'static>(&self) -> Option<T> {
525 self.controller.results.read(|results| {
526 let index = results.position::<T>(self.id)?;
527 results.0[index].1.downcast_ref::<T>().cloned()
528 })
529 }
530
531 pub fn set_result<T: 'static>(&self, value: T) {
535 let live = self
536 .controller
537 .stack
538 .try_with(|stack| stack.find(self.id).is_some())
539 .unwrap_or(false);
540 if !live {
541 return;
542 }
543 let (id, value) = (self.id, Rc::new(value));
544 self.controller
545 .results
546 .update(|results| match results.position::<T>(id) {
547 Some(index) => results.0[index].1 = value,
548 None => results.0.push((id, value)),
549 });
550 }
551
552 pub fn take_result<T: Clone + 'static>(&self) -> Option<T> {
556 let id = self.id;
557 self.controller
558 .results
559 .try_with(|results| results.position::<T>(id))
560 .flatten()?;
561 let value = self.controller.results.update(|results| {
562 let index = results.position::<T>(id)?;
563 Some(results.0.swap_remove(index).1)
564 })?;
565 Rc::downcast::<T>(value).ok().map(Rc::unwrap_or_clone)
566 }
567}
568
569struct NavControllerState<R: Clone + 'static> {
570 _saved: Option<SaveableRegistration>,
573 stack: OwnedMutableState<BackStack<R>>,
574 results: OwnedMutableState<EntryResults>,
575}
576
577impl<R: Clone + 'static> NavControllerState<R> {
578 fn new(stack: OwnedMutableState<BackStack<R>>, saved: Option<SaveableRegistration>) -> Self {
579 Self {
580 _saved: saved,
581 stack,
582 results: ownedMutableStateOfNeverEqual(EntryResults::default()),
583 }
584 }
585
586 fn controller(&self) -> NavController<R> {
587 NavController {
588 stack: self.stack.handle(),
589 results: self.results.handle(),
590 }
591 }
592}
593
594#[track_caller]
604pub fn rememberNavController<R: Clone + PartialEq + 'static>(start: R) -> NavController<R> {
605 let location = Location::caller();
606 let state = viewModel(location, |_| {
607 let saveables = SaveableScope::current().child(location).in_memory();
608 saveables.forget();
609 NavControllerState::new(
610 ownedMutableStateOfNeverEqual(BackStack::new(start, saveables)),
611 None,
612 )
613 });
614 state.get().controller()
615}
616
617#[track_caller]
628pub fn rememberSaveableNavController<R: Clone + PartialEq + 'static>(
629 key: &'static str,
630 start: R,
631 saver: Saver<R>,
632) -> NavController<R> {
633 let state = viewModel(Location::caller(), |_| {
634 let scope = SaveableScope::current();
635 let saveables = scope.child(key);
636 let restored = scope
637 .restore(key)
638 .and_then(|saved| saved_stack::decode(&saved, &saver, &saveables));
639 let stack = match restored {
640 Some(stack) => {
641 saveables.forget_children_except(
642 stack
643 .entries
644 .iter()
645 .chain(stack.saved.iter().flatten())
646 .map(|entry| entry.id),
647 );
648 stack
649 }
650 None => {
651 saveables.forget();
652 BackStack::new(start, saveables)
653 }
654 };
655 let stack = ownedMutableStateOfNeverEqual(stack);
656 let handle = stack.handle();
657 let saved = scope.register(key, move || {
658 handle.try_with(|stack| saved_stack::encode(stack, &saver))
659 });
660 NavControllerState::new(stack, Some(saved))
661 });
662 state.get().controller()
663}