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}