bubba_core/navigation/
mod.rs1use std::sync::{Arc, Mutex};
17use crate::ui::Screen;
18
19pub type ScreenFn = fn() -> Screen;
21
22#[derive(Clone)]
24pub struct ScreenEntry {
25 pub name: &'static str,
27 pub build: ScreenFn,
29}
30
31pub struct NavigationStack {
36 stack: Mutex<Vec<ScreenEntry>>,
37}
38
39impl NavigationStack {
40 pub fn new() -> Self {
42 Self {
43 stack: Mutex::new(Vec::new()),
44 }
45 }
46
47 pub fn push(&self, entry: ScreenEntry) {
49 log::info!("[Bubba Nav] → {}", entry.name);
50 self.stack.lock().unwrap().push(entry);
51 }
52
53 pub fn pop(&self) -> bool {
56 let mut stack = self.stack.lock().unwrap();
57 if stack.len() <= 1 {
58 log::warn!("[Bubba Nav] Cannot pop root screen.");
59 return false;
60 }
61 let exiting = stack.pop().unwrap();
62 log::info!("[Bubba Nav] ← {}", exiting.name);
63 true
64 }
65
66 pub fn current(&self) -> Option<Screen> {
68 let stack = self.stack.lock().unwrap();
69 stack.last().map(|e| (e.build)())
70 }
71
72 pub fn depth(&self) -> usize {
74 self.stack.lock().unwrap().len()
75 }
76
77 pub fn breadcrumbs(&self) -> Vec<&'static str> {
79 self.stack.lock().unwrap().iter().map(|e| e.name).collect()
80 }
81}
82
83impl Default for NavigationStack {
84 fn default() -> Self {
85 Self::new()
86 }
87}
88
89use std::sync::OnceLock;
92
93static NAV_STACK: OnceLock<Arc<NavigationStack>> = OnceLock::new();
94
95pub fn global_stack() -> Arc<NavigationStack> {
97 Arc::clone(NAV_STACK.get_or_init(|| Arc::new(NavigationStack::new())))
98}
99
100pub fn navigate_to(name: &'static str, build: ScreenFn) {
115 global_stack().push(ScreenEntry { name, build });
116 crate::runtime::on_navigate();
118}
119
120pub fn navigate_back() -> bool {
122 let result = global_stack().pop();
123 if result { crate::runtime::on_navigate(); }
124 result
125}
126
127#[macro_export]
130macro_rules! navigate {
131 ($screen:ident) => {
132 $crate::navigation::navigate_to(stringify!($screen), $screen)
133 };
134}
135
136pub use navigate_to as navigate;
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::ui::{Element, Screen};
143
144 fn dummy_screen() -> Screen {
145 Screen::new(Element::div().text("dummy"))
146 }
147
148 #[test]
149 fn push_and_pop() {
150 let stack = NavigationStack::new();
151 stack.push(ScreenEntry { name: "Home", build: dummy_screen });
152 assert_eq!(stack.depth(), 1);
153 stack.push(ScreenEntry { name: "Profile", build: dummy_screen });
154 assert_eq!(stack.depth(), 2);
155 assert!(stack.pop());
156 assert_eq!(stack.depth(), 1);
157 assert!(!stack.pop());
159 }
160}