v2rmp 0.3.6

A powerful Terminal User Interface (TUI) for route optimization using the Chinese Postman Problem algorithm
Documentation
use anyhow::Result;
use crossterm::{
    event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyModifiers},
    execute,
    terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
use ratatui::{backend::CrosstermBackend, Terminal};
use std::io;
use v2rmp::app::{App, InputField, LogLevel, View};

fn main() -> Result<()> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;
    let mut app = App::new();
    app.log(LogLevel::Info, "rmpca v0.1.0 - Route Optimization TUI");
    app.log(LogLevel::Info, "Press 'q' to quit, '?' for help");
    app.log(
        LogLevel::Info,
        "Clipboard: Ctrl+C (copy), Ctrl+V (paste), Ctrl+X (cut)",
    );
    let mut clipboard = arboard::Clipboard::new().ok();
    if clipboard.is_none() {
        app.log(
            LogLevel::Warn,
            "Clipboard unavailable - copy/paste disabled",
        );
    }
    let res = run_app(&mut terminal, &mut app, &mut clipboard);
    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;
    if let Err(err) = res {
        eprintln!("Error: {:?}", err);
    }
    Ok(())
}

fn run_app(
    terminal: &mut Terminal<CrosstermBackend<io::Stdout>>,
    app: &mut App,
    clipboard: &mut Option<arboard::Clipboard>,
) -> Result<()> {
    loop {
        terminal.draw(|f| match app.current_view {
            View::Home => v2rmp::ui::home::render(f, app),
            View::Extract => v2rmp::ui::extract::render(f, app),
            View::Compile => v2rmp::ui::compile::render(f, app),
            View::Optimize => v2rmp::ui::optimize::render(f, app),
            View::BrowseMaps => v2rmp::ui::browse_maps::render(f, app),
            View::BrowseRoutes => v2rmp::ui::browse_routes::render(f, app),
            View::Help => v2rmp::ui::home::render(f, app),
        })?;
        if !app.running {
            break;
        }
        if event::poll(std::time::Duration::from_millis(100))? {
            if let Event::Key(key) = event::read()? {
                if app.input_mode.active {
                    match key.code {
                        KeyCode::Enter => app.confirm_input(),
                        KeyCode::Esc => app.cancel_input(),
                        KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                            if let Some(ref mut clip) = clipboard {
                                if clip.set_text(&app.input_mode.buffer).is_ok() {
                                    app.log(LogLevel::Success, "Copied to clipboard");
                                }
                            }
                        }
                        KeyCode::Char('v') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                            if let Some(ref mut clip) = clipboard {
                                if let Ok(text) = clip.get_text() {
                                    app.input_mode.buffer.push_str(&text);
                                    app.log(LogLevel::Success, "Pasted from clipboard");
                                }
                            }
                        }
                        KeyCode::Char('x') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                            if let Some(ref mut clip) = clipboard {
                                if clip.set_text(&app.input_mode.buffer).is_ok() {
                                    app.input_mode.buffer.clear();
                                    app.log(LogLevel::Success, "Cut to clipboard");
                                }
                            }
                        }
                        KeyCode::Char(c) => app.input_mode.buffer.push(c),
                        KeyCode::Backspace => {
                            app.input_mode.buffer.pop();
                        }
                        _ => {}
                    }
                } else {
                    match key.code {
                        KeyCode::Char('q') => app.running = false,
                        KeyCode::Char('?') => app.current_view = View::Help,
                        KeyCode::Esc | KeyCode::Char('h') => app.current_view = View::Home,
                        KeyCode::Up | KeyCode::Char('k') => app.navigate_up(),
                        KeyCode::Down | KeyCode::Char('j') => app.navigate_down(),
                        KeyCode::Enter => handle_enter(app),
                        KeyCode::Char('1') => app.current_view = View::Extract,
                        KeyCode::Char('2') => app.current_view = View::Compile,
                        KeyCode::Char('3') => app.current_view = View::Optimize,
                        KeyCode::Char('4') => app.current_view = View::BrowseMaps,
                        KeyCode::Char('5') => app.current_view = View::BrowseRoutes,
                        _ => {}
                    }
                }
            }
        }
    }
    Ok(())
}

fn handle_enter(app: &mut App) {
    match app.current_view {
        View::Home => match app.workflow_selection {
            0 => app.current_view = View::Extract,
            1 => app.current_view = View::Compile,
            2 => app.current_view = View::Optimize,
            3 => app.current_view = View::BrowseMaps,
            4 => app.current_view = View::BrowseRoutes,
            _ => {}
        },
        View::Extract => app.start_input(InputField::BoundingBox),
        View::Compile => app.start_input(InputField::InputFile),
        View::Optimize => app.start_input(InputField::CacheFile),
        _ => {}
    }
}