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 pub(crate) fn id(&self) -> u64 {
34 self.id
35 }
36
37 fn encloses(&self, scope: &SaveableScope) -> bool {
40 let (Some(own), Some(inner)) = (self.saveables.namespace(), scope.namespace()) else {
41 return false;
42 };
43 inner
44 .strip_prefix(own)
45 .is_some_and(|rest| rest.is_empty() || rest.starts_with('/'))
46 }
47}
48
49impl<R> PartialEq for Entry<R> {
50 fn eq(&self, other: &Self) -> bool {
51 self.id == other.id
52 }
53}
54
55#[derive(Clone)]
56pub(crate) struct BackStack<R> {
57 entries: Vec<Entry<R>>,
58 saved: Vec<Vec<Entry<R>>>,
61 next_id: u64,
62 saveables: SaveableScope,
65}
66
67impl<R: Clone + PartialEq> BackStack<R> {
68 fn new(start: R, saveables: SaveableScope) -> Self {
69 let mut stack = Self {
70 entries: Vec::new(),
71 saved: Vec::new(),
72 next_id: 0,
73 saveables,
74 };
75 stack.push(start);
76 stack
77 }
78
79 fn push(&mut self, route: R) {
80 self.entries
81 .push(Entry::new(self.next_id, route, &self.saveables));
82 self.next_id += 1;
83 }
84
85 fn split_above(&mut self, route: &R, inclusive: bool) -> Vec<Entry<R>> {
90 let Some(index) = self.entries.iter().rposition(|entry| entry.route == *route) else {
91 return Vec::new();
92 };
93 self.entries
94 .split_off(if inclusive { index } else { index + 1 })
95 }
96
97 fn keep(&mut self, popped: Vec<Entry<R>>) -> Vec<Entry<R>> {
102 let (replaced, kept) = std::mem::take(&mut self.saved)
103 .into_iter()
104 .partition::<Vec<_>, _>(|segment| {
105 segment
106 .iter()
107 .any(|entry| popped.iter().any(|new| new.route == entry.route))
108 });
109 self.saved = kept;
110 self.saved.push(popped);
111 let replaced: Vec<Entry<R>> = replaced.into_iter().flatten().collect();
112 for entry in &replaced {
113 entry.store.clear();
114 }
115 replaced
116 }
117
118 fn take_saved(&mut self, route: &R) -> Option<Vec<Entry<R>>> {
120 let index = self
121 .saved
122 .iter()
123 .position(|segment| segment.iter().any(|entry| entry.route == *route))?;
124 Some(self.saved.swap_remove(index))
125 }
126
127 fn top_route(&self) -> Option<&R> {
128 self.entries.last().map(|entry| &entry.route)
129 }
130
131 fn find(&self, id: u64) -> Option<&Entry<R>> {
134 self.entries
135 .iter()
136 .chain(self.saved.iter().flatten())
137 .find(|entry| entry.id == id)
138 }
139
140 pub(crate) fn can_pop(&self) -> bool {
141 self.entries.len() > 1
142 }
143
144 pub(crate) fn screens_and_dialogs<S>(
149 &self,
150 dialog: impl Fn(&R) -> Option<S>,
151 ) -> HostView<R, S> {
152 let is_screen = |entry: &Entry<R>| dialog(&entry.route).is_none();
153 let at = self.entries.iter().rposition(is_screen);
154 let toward = at
155 .and_then(|at| self.entries[..at].iter().rposition(is_screen))
156 .map(|index| self.entries[index].clone());
157 let from = at.map_or(0, |at| at + 1);
158 let dialogs = self.entries[from..]
159 .iter()
160 .enumerate()
161 .filter_map(|(offset, entry)| {
162 dialog(&entry.route).map(|spec| (from + offset, entry.clone(), spec))
163 })
164 .collect();
165 HostView {
166 screen: at.map(|at| self.entries[at].clone()),
167 toward,
168 dialogs,
169 }
170 }
171}
172
173pub(crate) struct HostView<R, S> {
175 pub(crate) screen: Option<Entry<R>>,
177 pub(crate) toward: Option<Entry<R>>,
179 pub(crate) dialogs: Vec<(usize, Entry<R>, S)>,
182}
183
184#[derive(Clone, Default)]
187pub(crate) struct EntryResults(Vec<(u64, Rc<dyn Any>)>);
188
189impl EntryResults {
190 fn position<T: 'static>(&self, entry: u64) -> Option<usize> {
192 self.0
193 .iter()
194 .position(|(id, value)| *id == entry && value.is::<T>())
195 }
196
197 fn holds_any(&self, entry: u64) -> bool {
198 self.0.iter().any(|(id, _)| *id == entry)
199 }
200}
201
202pub struct NavOptions<R> {
205 pop_up_to: Option<(R, bool)>,
206 launch_single_top: bool,
207 save_state: bool,
208 restore_state: bool,
209}
210
211impl<R> Default for NavOptions<R> {
212 fn default() -> Self {
213 Self {
214 pop_up_to: None,
215 launch_single_top: false,
216 save_state: false,
217 restore_state: false,
218 }
219 }
220}
221
222impl<R> NavOptions<R> {
223 pub fn new() -> Self {
225 Self::default()
226 }
227
228 #[must_use]
232 pub fn pop_up_to(mut self, route: R, inclusive: bool) -> Self {
233 self.pop_up_to = Some((route, inclusive));
234 self
235 }
236
237 #[must_use]
240 pub fn launch_single_top(mut self) -> Self {
241 self.launch_single_top = true;
242 self
243 }
244
245 #[must_use]
251 pub fn save_state(mut self) -> Self {
252 self.save_state = true;
253 self
254 }
255
256 #[must_use]
261 pub fn restore_state(mut self) -> Self {
262 self.restore_state = true;
263 self
264 }
265}
266
267pub struct NavController<R: Clone + 'static> {
274 pub(crate) stack: MutableState<BackStack<R>>,
275 results: MutableState<EntryResults>,
278}
279
280impl<R: Clone + 'static> Clone for NavController<R> {
281 fn clone(&self) -> Self {
282 *self
283 }
284}
285
286impl<R: Clone + 'static> Copy for NavController<R> {}
287
288impl<R: Clone + 'static> PartialEq for NavController<R> {
289 fn eq(&self, other: &Self) -> bool {
290 self.stack == other.stack
291 }
292}
293
294impl<R: Clone + PartialEq + 'static> NavController<R> {
295 pub fn navigate(&self, route: R) {
297 self.navigate_with(route, NavOptions::new());
298 }
299
300 pub fn navigate_with(&self, route: R, options: NavOptions<R>) {
303 let forgotten = self.stack.update(|stack| {
304 let popped = options
305 .pop_up_to
306 .map(|(target, inclusive)| stack.split_above(&target, inclusive))
307 .unwrap_or_default();
308 let forgotten = if options.save_state && !popped.is_empty() {
309 stack.keep(popped)
310 } else {
311 popped
312 };
313 let restored = options
314 .restore_state
315 .then(|| stack.take_saved(&route))
316 .flatten();
317 if let Some(restored) = restored {
318 stack.entries.extend(restored);
319 } else if !(options.launch_single_top && stack.top_route() == Some(&route)) {
320 stack.push(route);
321 }
322 forgotten
323 });
324 self.forget(forgotten);
325 }
326
327 pub fn pop_back_stack(&self) -> bool {
330 self.forget(self.stack.update(|stack| stack.entries.pop()))
331 }
332
333 pub fn pop_back_stack_to(&self, route: &R, inclusive: bool) -> bool {
337 self.forget(
338 self.stack
339 .update(|stack| stack.split_above(route, inclusive)),
340 )
341 }
342
343 pub(crate) fn pop_to_depth(&self, depth: usize) {
345 self.forget(self.stack.update(|stack| {
346 let keep = depth.min(stack.entries.len());
347 stack.entries.split_off(keep)
348 }));
349 }
350
351 pub(crate) fn open_deep_link(&self, route: R) {
355 self.forget(self.stack.update(|stack| {
356 let popped = stack.entries.split_off(stack.entries.len().min(1));
357 if stack.top_route() != Some(&route) {
358 stack.push(route);
359 }
360 popped
361 }));
362 }
363
364 pub fn navigate_up(&self) -> bool {
367 self.forget(
368 self.stack
369 .update(|stack| stack.can_pop().then(|| stack.entries.pop()).flatten()),
370 )
371 }
372
373 pub fn current_route(&self) -> Option<R> {
377 self.stack.read(|stack| stack.top_route().cloned())
378 }
379
380 pub fn back_stack(&self) -> Vec<R> {
384 self.stack.read(|stack| {
385 stack
386 .entries
387 .iter()
388 .map(|entry| entry.route.clone())
389 .collect()
390 })
391 }
392
393 pub fn composing_entry(&self) -> Option<NavBackStackEntry<R>> {
398 let scope = SaveableScope::current();
399 self.entry_where(|entries| entries.iter().find(|entry| entry.encloses(&scope)))
400 }
401
402 pub fn previous_back_stack_entry(&self) -> Option<NavBackStackEntry<R>> {
405 self.entry_where(|entries| entries.iter().nth_back(1))
406 }
407
408 pub fn back_stack_entry(&self, route: &R) -> Option<NavBackStackEntry<R>> {
411 self.entry_where(|entries| entries.iter().rfind(|entry| entry.route == *route))
412 }
413
414 fn entry_where(
417 &self,
418 find: impl FnOnce(&[Entry<R>]) -> Option<&Entry<R>>,
419 ) -> Option<NavBackStackEntry<R>> {
420 let id = self
421 .stack
422 .try_with(|stack| find(&stack.entries).map(Entry::id))
423 .flatten()?;
424 Some(NavBackStackEntry {
425 controller: *self,
426 id,
427 })
428 }
429
430 fn forget(&self, popped: impl IntoIterator<Item = Entry<R>>) -> bool {
434 let mut any = false;
435 for entry in popped {
436 entry.saveables.forget();
437 if self
438 .results
439 .try_with(|results| results.holds_any(entry.id))
440 .unwrap_or(false)
441 {
442 self.results
443 .update(|results| results.0.retain(|(id, _)| *id != entry.id));
444 }
445 any = true;
446 }
447 any
448 }
449}
450
451pub struct NavBackStackEntry<R: Clone + 'static> {
491 controller: NavController<R>,
492 id: u64,
493}
494
495impl<R: Clone + 'static> Clone for NavBackStackEntry<R> {
496 fn clone(&self) -> Self {
497 *self
498 }
499}
500
501impl<R: Clone + 'static> Copy for NavBackStackEntry<R> {}
502
503impl<R: Clone + 'static> PartialEq for NavBackStackEntry<R> {
504 fn eq(&self, other: &Self) -> bool {
505 self.controller == other.controller && self.id == other.id
506 }
507}
508
509impl<R: Clone + PartialEq + 'static> NavBackStackEntry<R> {
510 pub fn route(&self) -> Option<R> {
513 self.controller
514 .stack
515 .try_with(|stack| stack.find(self.id).map(|entry| entry.route.clone()))
516 .flatten()
517 }
518
519 pub fn result<T: Clone + 'static>(&self) -> Option<T> {
523 self.controller.results.read(|results| {
524 let index = results.position::<T>(self.id)?;
525 results.0[index].1.downcast_ref::<T>().cloned()
526 })
527 }
528
529 pub fn set_result<T: 'static>(&self, value: T) {
533 let live = self
534 .controller
535 .stack
536 .try_with(|stack| stack.find(self.id).is_some())
537 .unwrap_or(false);
538 if !live {
539 return;
540 }
541 let (id, value) = (self.id, Rc::new(value));
542 self.controller
543 .results
544 .update(|results| match results.position::<T>(id) {
545 Some(index) => results.0[index].1 = value,
546 None => results.0.push((id, value)),
547 });
548 }
549
550 pub fn take_result<T: Clone + 'static>(&self) -> Option<T> {
554 let id = self.id;
555 self.controller
556 .results
557 .try_with(|results| results.position::<T>(id))
558 .flatten()?;
559 let value = self.controller.results.update(|results| {
560 let index = results.position::<T>(id)?;
561 Some(results.0.swap_remove(index).1)
562 })?;
563 Rc::downcast::<T>(value).ok().map(Rc::unwrap_or_clone)
564 }
565}
566
567struct NavControllerState<R: Clone + 'static> {
568 _saved: Option<SaveableRegistration>,
571 stack: OwnedMutableState<BackStack<R>>,
572 results: OwnedMutableState<EntryResults>,
573}
574
575impl<R: Clone + 'static> NavControllerState<R> {
576 fn new(stack: OwnedMutableState<BackStack<R>>, saved: Option<SaveableRegistration>) -> Self {
577 Self {
578 _saved: saved,
579 stack,
580 results: ownedMutableStateOfNeverEqual(EntryResults::default()),
581 }
582 }
583
584 fn controller(&self) -> NavController<R> {
585 NavController {
586 stack: self.stack.handle(),
587 results: self.results.handle(),
588 }
589 }
590}
591
592#[track_caller]
602pub fn rememberNavController<R: Clone + PartialEq + 'static>(start: R) -> NavController<R> {
603 let location = Location::caller();
604 let state = viewModel(location, |_| {
605 let saveables = SaveableScope::current().child(location).in_memory();
606 saveables.forget();
607 NavControllerState::new(
608 ownedMutableStateOfNeverEqual(BackStack::new(start, saveables)),
609 None,
610 )
611 });
612 state.get().controller()
613}
614
615#[track_caller]
626pub fn rememberSaveableNavController<R: Clone + PartialEq + 'static>(
627 key: &'static str,
628 start: R,
629 saver: Saver<R>,
630) -> NavController<R> {
631 let state = viewModel(Location::caller(), |_| {
632 let scope = SaveableScope::current();
633 let saveables = scope.child(key);
634 let restored = scope
635 .restore(key)
636 .and_then(|saved| saved_stack::decode(&saved, &saver, &saveables));
637 let stack = match restored {
638 Some(stack) => {
639 saveables.forget_children_except(
640 stack
641 .entries
642 .iter()
643 .chain(stack.saved.iter().flatten())
644 .map(|entry| entry.id),
645 );
646 stack
647 }
648 None => {
649 saveables.forget();
650 BackStack::new(start, saveables)
651 }
652 };
653 let stack = ownedMutableStateOfNeverEqual(stack);
654 let handle = stack.handle();
655 let saved = scope.register(key, move || {
656 handle.try_with(|stack| saved_stack::encode(stack, &saver))
657 });
658 NavControllerState::new(stack, Some(saved))
659 });
660 state.get().controller()
661}