gpui_nav/
navigator.rs

1use crate::screen::Screen;
2use gpui::{AnyView, AppContext, Context, Entity};
3
4/// A navigation stack that manages screen transitions.
5///
6/// The navigator is generic over the app state type and can be integrated
7/// into any GPUI application.
8///
9/// # Example
10///
11/// ```rust
12/// use gpui_nav::Navigator;
13///
14/// pub struct AppState {
15///     navigator: Navigator,
16///     // ... other state
17/// }
18/// ```
19pub struct Navigator {
20    stack: Vec<AnyView>,
21    history: Vec<&'static str>,
22}
23
24impl Navigator {
25    /// Creates a new empty navigator.
26    ///
27    /// # Example
28    ///
29    /// ```rust
30    /// use gpui_nav::Navigator;
31    ///
32    /// let navigator = Navigator::new();
33    /// assert!(navigator.is_empty());
34    /// ```
35    #[must_use]
36    pub fn new() -> Self {
37        Self {
38            stack: Vec::new(),
39            history: Vec::new(),
40        }
41    }
42
43    /// Pushes a new screen onto the navigation stack.
44    ///
45    /// The screen's `on_enter` method will be called if implemented.
46    /// This method is generic over the context type, allowing it to work
47    /// with any app state.
48    ///
49    /// # Example
50    ///
51    /// ```rust,ignore
52    /// // In your app state method:
53    /// pub fn navigator_push<S: Screen>(&mut self, screen: S, cx: &mut Context<Self>) {
54    ///     self.navigator.push(screen, cx);
55    /// }
56    /// ```
57    pub fn push<S: Screen, T: 'static>(&mut self, screen: S, cx: &mut Context<T>) {
58        let screen_id = screen.id();
59        let entity: Entity<S> = cx.new(|_| screen);
60        self.stack.push(entity.into());
61        self.history.push(screen_id);
62        cx.notify();
63    }
64
65    /// Pops the current screen from the stack.
66    ///
67    /// Returns `true` if a screen was popped, `false` if the stack has only one screen.
68    ///
69    /// # Example
70    ///
71    /// ```rust,ignore
72    /// if navigator.pop(cx) {
73    ///     println!("Navigated back");
74    /// } else {
75    ///     println!("Already at root screen");
76    /// }
77    /// ```
78    pub fn pop<T: 'static>(&mut self, cx: &mut Context<T>) -> bool {
79        if self.stack.len() > 1 {
80            self.stack.pop();
81            self.history.pop();
82            cx.notify();
83            true
84        } else {
85            false
86        }
87    }
88
89    /// Replaces the current screen with a new one.
90    ///
91    /// Returns `true` if a screen was replaced, `false` if the stack is empty.
92    ///
93    /// # Example
94    ///
95    /// ```rust,ignore
96    /// if navigator.replace(LoginScreen::new(ctx), cx) {
97    ///     println!("Screen replaced");
98    /// } else {
99    ///     println!("No screen to replace, stack is empty");
100    /// }
101    /// ```
102    pub fn replace<S: Screen, T: 'static>(&mut self, screen: S, cx: &mut Context<T>) -> bool {
103        if self.stack.is_empty() {
104            false
105        } else {
106            self.stack.pop();
107            self.history.pop();
108            self.push(screen, cx);
109            true
110        }
111    }
112
113    /// Returns a reference to the current screen, if any.
114    #[must_use]
115    pub fn current(&self) -> Option<&AnyView> {
116        self.stack.last()
117    }
118
119    /// Returns the navigation history as screen IDs.
120    #[must_use]
121    pub fn history(&self) -> &[&'static str] {
122        &self.history
123    }
124
125    /// Clears the entire stack and pushes a new root screen.
126    ///
127    /// Useful for logout flows or resetting the app state.
128    pub fn clear_and_push<S: Screen, T: 'static>(&mut self, screen: S, cx: &mut Context<T>) {
129        self.stack.clear();
130        self.history.clear();
131        self.push(screen, cx);
132    }
133
134    /// Returns whether the navigator can go back.
135    #[must_use]
136    pub fn can_go_back(&self) -> bool {
137        self.stack.len() > 1
138    }
139
140    /// Returns the number of screens in the stack.
141    #[must_use]
142    pub fn len(&self) -> usize {
143        self.stack.len()
144    }
145
146    /// Returns whether the navigation stack is empty.
147    #[must_use]
148    pub fn is_empty(&self) -> bool {
149        self.stack.is_empty()
150    }
151}
152
153impl Default for Navigator {
154    fn default() -> Self {
155        Self::new()
156    }
157}