use std::sync::{Arc, Mutex};
use crate::ui::Screen;
pub type ScreenFn = fn() -> Screen;
#[derive(Clone)]
pub struct ScreenEntry {
pub name: &'static str,
pub build: ScreenFn,
}
pub struct NavigationStack {
stack: Mutex<Vec<ScreenEntry>>,
}
impl NavigationStack {
pub fn new() -> Self {
Self {
stack: Mutex::new(Vec::new()),
}
}
pub fn push(&self, entry: ScreenEntry) {
log::info!("[Bubba Nav] → {}", entry.name);
self.stack.lock().unwrap().push(entry);
}
pub fn pop(&self) -> bool {
let mut stack = self.stack.lock().unwrap();
if stack.len() <= 1 {
log::warn!("[Bubba Nav] Cannot pop root screen.");
return false;
}
let exiting = stack.pop().unwrap();
log::info!("[Bubba Nav] ← {}", exiting.name);
true
}
pub fn current(&self) -> Option<Screen> {
let stack = self.stack.lock().unwrap();
stack.last().map(|e| (e.build)())
}
pub fn depth(&self) -> usize {
self.stack.lock().unwrap().len()
}
pub fn breadcrumbs(&self) -> Vec<&'static str> {
self.stack.lock().unwrap().iter().map(|e| e.name).collect()
}
}
impl Default for NavigationStack {
fn default() -> Self {
Self::new()
}
}
use std::sync::OnceLock;
static NAV_STACK: OnceLock<Arc<NavigationStack>> = OnceLock::new();
pub fn global_stack() -> Arc<NavigationStack> {
Arc::clone(NAV_STACK.get_or_init(|| Arc::new(NavigationStack::new())))
}
pub fn navigate_to(name: &'static str, build: ScreenFn) {
global_stack().push(ScreenEntry { name, build });
crate::runtime::on_navigate();
}
pub fn navigate_back() -> bool {
let result = global_stack().pop();
if result { crate::runtime::on_navigate(); }
result
}
#[macro_export]
macro_rules! navigate {
($screen:ident) => {
$crate::navigation::navigate_to(stringify!($screen), $screen)
};
}
pub use navigate_to as navigate;
#[cfg(test)]
mod tests {
use super::*;
use crate::ui::{Element, Screen};
fn dummy_screen() -> Screen {
Screen::new(Element::div().text("dummy"))
}
#[test]
fn push_and_pop() {
let stack = NavigationStack::new();
stack.push(ScreenEntry { name: "Home", build: dummy_screen });
assert_eq!(stack.depth(), 1);
stack.push(ScreenEntry { name: "Profile", build: dummy_screen });
assert_eq!(stack.depth(), 2);
assert!(stack.pop());
assert_eq!(stack.depth(), 1);
assert!(!stack.pop());
}
}