Skip to main content

qframe/
router.rs

1//! Page navigation as a stack.
2
3/// Which way the last navigation went, for transitions that follow it.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
5pub enum Navigation {
6    /// Deeper: [`Router::push`] or [`Router::replace`].
7    #[default]
8    Forward,
9    /// Back: [`Router::back`].
10    Back,
11}
12
13/// The pages an application has visited, most recent last.
14///
15/// Draw [`Router::current`] inside [`View::page`](crate::widget::View::page) with a key per page
16/// so every page keeps its own focus and scroll position when the user comes back.
17#[derive(Debug, Clone, PartialEq, Eq)]
18pub struct Router<P> {
19    stack: Vec<P>,
20    direction: Navigation,
21}
22
23impl<P: Clone + PartialEq> Router<P> {
24    /// A router starting at `root`.
25    #[must_use]
26    pub fn new(root: P) -> Self {
27        Self { stack: vec![root], direction: Navigation::Forward }
28    }
29
30    /// The page on top.
31    #[must_use]
32    pub fn current(&self) -> &P {
33        self.stack.last().expect("the stack always holds the root page")
34    }
35
36    /// Opens `page` on top. Opening the current page again does nothing.
37    pub fn push(&mut self, page: P) {
38        if *self.current() != page {
39            self.stack.push(page);
40            self.direction = Navigation::Forward;
41        }
42    }
43
44    /// Goes back one page. Returns `false` at the root.
45    pub fn back(&mut self) -> bool {
46        if self.stack.len() > 1 {
47            self.stack.pop();
48            self.direction = Navigation::Back;
49            true
50        } else {
51            false
52        }
53    }
54
55    /// Replaces the current page without adding history.
56    pub fn replace(&mut self, page: P) {
57        if let Some(top) = self.stack.last_mut() {
58            *top = page;
59            self.direction = Navigation::Forward;
60        }
61    }
62
63    /// Whether [`Router::back`] would go anywhere.
64    #[must_use]
65    pub fn can_go_back(&self) -> bool {
66        self.stack.len() > 1
67    }
68
69    /// Which way the last successful navigation went; `Forward` before any.
70    #[must_use]
71    pub fn direction(&self) -> Navigation {
72        self.direction
73    }
74
75    /// Visited pages, root first.
76    #[must_use]
77    pub fn history(&self) -> &[P] {
78        &self.stack
79    }
80}
81
82#[cfg(test)]
83mod tests {
84    use super::*;
85
86    #[test]
87    fn push_back_and_replace() {
88        let mut router = Router::new("home");
89        router.push("button");
90        router.push("button");
91        router.push("list");
92        assert_eq!(router.history(), &["home", "button", "list"]);
93        assert_eq!(router.direction(), Navigation::Forward);
94        assert!(router.back());
95        assert_eq!(router.direction(), Navigation::Back);
96        assert_eq!(*router.current(), "button");
97        router.replace("tabs");
98        assert_eq!(router.history(), &["home", "tabs"]);
99        assert!(router.back());
100        assert!(!router.back());
101        assert!(!router.can_go_back());
102    }
103}