1use std::collections::VecDeque;
45
46#[derive(Debug, Clone)]
48pub struct Router<P> {
49 current: P,
50 history: VecDeque<P>,
51 forward_stack: Vec<P>,
52 max_history: usize,
53}
54
55#[derive(Debug, Clone, PartialEq)]
57pub enum RouterMsg<P> {
58 Navigate(P),
60 Back,
62 Forward,
64 Replace(P),
66 ClearHistory,
68}
69
70impl<P: Clone + PartialEq> Router<P> {
71 pub fn new(initial: P) -> Self {
73 Self {
74 current: initial,
75 history: VecDeque::new(),
76 forward_stack: Vec::new(),
77 max_history: 50,
78 }
79 }
80
81 pub fn with_max_history(mut self, max: usize) -> Self {
83 self.max_history = max;
84 self
85 }
86
87 pub fn current(&self) -> &P {
89 &self.current
90 }
91
92 pub fn is_at(&self, page: &P) -> bool {
94 &self.current == page
95 }
96
97 pub fn navigate(&mut self, page: P) {
99 if self.current == page {
100 return; }
102
103 self.history.push_back(self.current.clone());
105 if self.history.len() > self.max_history {
106 self.history.pop_front();
107 }
108
109 self.forward_stack.clear();
111
112 self.current = page;
113 }
114
115 pub fn replace(&mut self, page: P) {
117 self.current = page;
118 }
119
120 pub fn back(&mut self) -> bool {
122 if let Some(prev) = self.history.pop_back() {
123 self.forward_stack.push(self.current.clone());
124 self.current = prev;
125 true
126 } else {
127 false
128 }
129 }
130
131 pub fn forward(&mut self) -> bool {
133 if let Some(next) = self.forward_stack.pop() {
134 self.history.push_back(self.current.clone());
135 self.current = next;
136 true
137 } else {
138 false
139 }
140 }
141
142 pub fn can_back(&self) -> bool {
144 !self.history.is_empty()
145 }
146
147 pub fn can_forward(&self) -> bool {
149 !self.forward_stack.is_empty()
150 }
151
152 pub fn history_len(&self) -> usize {
154 self.history.len()
155 }
156
157 pub fn clear_history(&mut self) {
159 self.history.clear();
160 self.forward_stack.clear();
161 }
162
163 pub fn handle(&mut self, msg: RouterMsg<P>) {
165 match msg {
166 RouterMsg::Navigate(page) => self.navigate(page),
167 RouterMsg::Back => {
168 self.back();
169 }
170 RouterMsg::Forward => {
171 self.forward();
172 }
173 RouterMsg::Replace(page) => self.replace(page),
174 RouterMsg::ClearHistory => self.clear_history(),
175 }
176 }
177}
178
179impl<P: Default + Clone + PartialEq> Default for Router<P> {
180 fn default() -> Self {
181 Self::new(P::default())
182 }
183}
184
185use crate::ViewCtx;
190
191impl<'a, Msg> ViewCtx<'a, Msg> {
192 pub fn navigate<P>(&mut self, page: P, to_msg: impl FnOnce(RouterMsg<P>) -> Msg) {
194 self.emit(to_msg(RouterMsg::Navigate(page)));
195 }
196
197 pub fn router_back<P>(&mut self, to_msg: impl FnOnce(RouterMsg<P>) -> Msg) {
199 self.emit(to_msg(RouterMsg::Back));
200 }
201
202 pub fn router_forward<P>(&mut self, to_msg: impl FnOnce(RouterMsg<P>) -> Msg) {
204 self.emit(to_msg(RouterMsg::Forward));
205 }
206}
207
208pub struct NavLink<'a, P> {
214 label: &'a str,
215 page: P,
216 active_style: bool,
217}
218
219impl<'a, P: Clone + PartialEq> NavLink<'a, P> {
220 pub fn new(label: &'a str, page: P) -> Self {
221 Self {
222 label,
223 page,
224 active_style: true,
225 }
226 }
227
228 pub fn no_active_style(mut self) -> Self {
230 self.active_style = false;
231 self
232 }
233
234 pub fn show<Msg>(
236 self,
237 ctx: &mut ViewCtx<'_, Msg>,
238 router: &Router<P>,
239 to_msg: impl FnOnce(RouterMsg<P>) -> Msg,
240 ) -> bool {
241 let is_active = router.is_at(&self.page);
242
243 let response = if self.active_style && is_active {
244 ctx.ui
246 .add(egui::Button::new(self.label).fill(egui::Color32::from_rgb(59, 130, 246)))
247 } else {
248 ctx.ui.button(self.label)
249 };
250
251 if response.clicked() && !is_active {
252 ctx.emit(to_msg(RouterMsg::Navigate(self.page)));
253 true
254 } else {
255 false
256 }
257 }
258}
259
260pub struct BackButton<'a> {
262 label: &'a str,
263}
264
265impl<'a> BackButton<'a> {
266 pub fn new() -> Self {
267 Self { label: "Back" }
268 }
269
270 pub fn label(mut self, label: &'a str) -> Self {
271 self.label = label;
272 self
273 }
274
275 pub fn show<P, Msg>(
276 self,
277 ctx: &mut ViewCtx<'_, Msg>,
278 router: &Router<P>,
279 to_msg: impl FnOnce(RouterMsg<P>) -> Msg,
280 ) -> bool
281 where
282 P: Clone + PartialEq,
283 {
284 let enabled = router.can_back();
285 let response = ctx.ui.add_enabled(enabled, egui::Button::new(self.label));
286
287 if response.clicked() && enabled {
288 ctx.emit(to_msg(RouterMsg::Back));
289 true
290 } else {
291 false
292 }
293 }
294}
295
296impl<'a> Default for BackButton<'a> {
297 fn default() -> Self {
298 Self::new()
299 }
300}
301
302#[cfg(test)]
303mod tests {
304 use super::*;
305
306 #[derive(Clone, PartialEq, Debug, Default)]
307 enum TestPage {
308 #[default]
309 Home,
310 Settings,
311 Profile(u64),
312 }
313
314 #[test]
315 fn test_basic_navigation() {
316 let mut router = Router::new(TestPage::Home);
317
318 assert!(router.is_at(&TestPage::Home));
319
320 router.navigate(TestPage::Settings);
321 assert!(router.is_at(&TestPage::Settings));
322 assert!(router.can_back());
323 }
324
325 #[test]
326 fn test_back_forward() {
327 let mut router = Router::new(TestPage::Home);
328
329 router.navigate(TestPage::Settings);
330 router.navigate(TestPage::Profile(42));
331
332 assert!(router.back());
333 assert!(router.is_at(&TestPage::Settings));
334
335 assert!(router.forward());
336 assert!(router.is_at(&TestPage::Profile(42)));
337 }
338
339 #[test]
340 fn test_navigate_clears_forward() {
341 let mut router = Router::new(TestPage::Home);
342
343 router.navigate(TestPage::Settings);
344 router.back();
345
346 router.navigate(TestPage::Profile(1));
348 assert!(!router.can_forward());
349 }
350
351 #[test]
352 fn test_navigate_same_page() {
353 let mut router = Router::new(TestPage::Home);
354
355 router.navigate(TestPage::Home); assert!(!router.can_back()); }
358
359 #[test]
360 fn test_replace() {
361 let mut router = Router::new(TestPage::Home);
362
363 router.navigate(TestPage::Settings);
364 router.replace(TestPage::Profile(1));
365
366 assert!(router.is_at(&TestPage::Profile(1)));
367 assert_eq!(router.history_len(), 1); router.back();
370 assert!(router.is_at(&TestPage::Home));
371 }
372
373 #[test]
374 fn test_handle_msg() {
375 let mut router = Router::new(TestPage::Home);
376
377 router.handle(RouterMsg::Navigate(TestPage::Settings));
378 assert!(router.is_at(&TestPage::Settings));
379
380 router.handle(RouterMsg::Back);
381 assert!(router.is_at(&TestPage::Home));
382 }
383}