Skip to main content

nms_copilot/map/
mod.rs

1//! Interactive ASCII galaxy map using ratatui.
2//!
3//! Provides a full-screen TUI map with cursor navigation and zoom drill-down.
4//! Three zoom levels: Galaxy → Region → Local.
5
6pub mod input;
7pub mod render;
8pub mod state;
9
10use std::io;
11
12use crossterm::cursor::{Hide, Show};
13use crossterm::execute;
14use crossterm::terminal::{EnterAlternateScreen, LeaveAlternateScreen};
15use ratatui::Terminal;
16use ratatui::backend::CrosstermBackend;
17
18use nms_graph::GalaxyModel;
19
20use crate::session::SessionState;
21use state::MapState;
22
23/// Enter the interactive map, returning when the user exits.
24///
25/// Sets up an alternate screen with raw mode, runs the event loop,
26/// and restores the terminal on exit (even if the loop errors).
27pub fn run_map(model: &GalaxyModel, session: &SessionState) -> io::Result<()> {
28    // Setup terminal
29    crossterm::terminal::enable_raw_mode()?;
30    let mut stdout = io::stdout();
31    execute!(stdout, EnterAlternateScreen, Hide)?;
32    let backend = CrosstermBackend::new(stdout);
33    let mut terminal = Terminal::new(backend)?;
34
35    let result = run_map_loop(&mut terminal, model, session);
36
37    // Teardown (always runs)
38    crossterm::terminal::disable_raw_mode()?;
39    execute!(terminal.backend_mut(), Show, LeaveAlternateScreen)?;
40
41    result
42}
43
44/// Main event loop for the interactive map.
45fn run_map_loop(
46    terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
47    model: &GalaxyModel,
48    session: &SessionState,
49) -> io::Result<()> {
50    let size = terminal.size()?;
51    let mut state = MapState::new(model, session);
52    state.resize(size.width, size.height);
53
54    loop {
55        terminal.draw(|frame| {
56            render::render(frame, &state, model);
57        })?;
58
59        input::handle_input(&mut state)?;
60
61        if state.should_quit {
62            break;
63        }
64    }
65
66    Ok(())
67}