v2rmp 0.4.5

rmpca — Route Optimization TUI & Agent Engine
Documentation
use ratatui::Frame;

use crate::app::{App, Status};

pub fn draw(f: &mut Frame, app: &App, area: ratatui::layout::Rect) {
    let inner = super::draw_panel(f, "Clean Map", area);


    let cyan = ratatui::style::Color::Cyan;
    let yellow = ratatui::style::Color::Yellow;
    let green = ratatui::style::Color::Green;
    let gray = ratatui::style::Color::DarkGray;
    let white = ratatui::style::Color::White;

    let opts = &app.clean_options;
    let sel = app.clean_selection;

    let toggles: Vec<(usize, &str, bool)> = vec![
        (0, "Make valid geometries", opts.make_valid),
        (1, "Drop invalid features", opts.drop_invalid),
        (2, "Remove self-loops", opts.remove_selfloops),
        (3, "Dedupe edges", opts.dedupe_edges),
        (4, "Remove isolates", opts.remove_isolates),
        (5, "Merge node positions", opts.merge_node_positions),
        (6, "Include polygons", opts.include_polygons),
        (7, "Include points", opts.include_points),
        (8, "Merge parallel edges", opts.merge_parallel_edges),
        (
            9,
            "Merge parallel edge properties",
            opts.merge_parallel_edge_properties,
        ),
    ];

    let mut lines: Vec<ratatui::text::Line> = vec![
        ratatui::text::Line::from(ratatui::text::Span::styled(
            "Clean GeoJSON",
            ratatui::style::Style::default()
                .fg(cyan)
                .add_modifier(ratatui::style::Modifier::BOLD),
        )),
        ratatui::text::Line::from(""),
        ratatui::text::Line::from("Repair geometry, remove duplicates, keep largest component."),
        ratatui::text::Line::from(""),
    ];

    let input_display = match &app.clean_input_file {
        Some(p) => {
            ratatui::text::Span::styled(p.clone(), ratatui::style::Style::default().fg(green))
        }
        None => ratatui::text::Span::styled(
            "(not set - press 'i')".to_string(),
            ratatui::style::Style::default().fg(yellow),
        ),
    };
    let output_display = match &app.clean_output_file {
        Some(p) => {
            ratatui::text::Span::styled(p.clone(), ratatui::style::Style::default().fg(green))
        }
        None => ratatui::text::Span::styled(
            "(auto-derived from input)".to_string(),
            ratatui::style::Style::default().fg(gray),
        ),
    };

    lines.push(ratui_line(vec![span_raw("Input:   "), input_display]));
    lines.push(ratui_line(vec![span_raw("Output:  "), output_display]));
    lines.push(ratatui::text::Line::from(""));

    lines.push(ratui_line(vec![
        span_raw("  Min length (m): "),
        span_val(format!("{:.1}", opts.min_length_m), sel == 10),
        span_raw("   Node snap (m): "),
        span_val(format!("{:.1}", opts.node_snap_m), sel == 11),
    ]));
    lines.push(ratui_line(vec![
        span_raw("  Max components: "),
        span_val(format!("{}", opts.max_components), sel == 12),
        span_raw("   Simplify tol (m): "),
        span_val(format!("{:.1}", opts.simplify_tolerance_m), sel == 13),
    ]));
    lines.push(ratui_line(vec![
        span_raw("  Precision decimals: "),
        span_val(format!("{}", opts.node_precision_decimals), sel == 14),
    ]));
    lines.push(ratatui::text::Line::from(""));

    lines.push(ratatui::text::Line::from("  Options:"));
    for (idx, label, val) in &toggles {
        let check = if *val { "[x]" } else { "[ ]" };
        let style = if *idx == sel {
            ratatui::style::Style::default()
                .fg(yellow)
                .add_modifier(ratatui::style::Modifier::BOLD)
        } else {
            ratatui::style::Style::default().fg(white)
        };
        lines.push(ratatui::text::Line::from(ratatui::text::Span::styled(
            format!("    {} {}  ", check, label),
            style,
        )));
    }
    lines.push(ratatui::text::Line::from(""));

    let status_text = format!("Status: {}", app.clean_status);
    let status_color = app.clean_status.color();
    lines.push(ratui_line(vec![ratatui::text::Span::styled(
        status_text,
        ratatui::style::Style::default().fg(status_color),
    )]));

    if let Status::Done(ref msg) = app.clean_status {
        if !msg.is_empty() {
            lines.push(ratatui::text::Line::from(""));
            for line in msg.lines() {
                lines.push(ratatui::text::Line::from(format!("  {}", line)));
            }
        }
    }

    lines.push(ratatui::text::Line::from(""));
    lines.push(ratui_line(vec![ratatui::text::Span::styled(
        "Controls: ",
        ratatui::style::Style::default().fg(cyan),
    )]));
    lines.push(ratatui::text::Line::from(
        "  [Space] Toggle option    [Enter] Run clean",
    ));
    lines.push(ratatui::text::Line::from(
        "  [I] Set input file       [O] Set output file",
    ));
    lines.push(ratatui::text::Line::from(
        "  [Esc] Return to home     [R] Reset to defaults",
    ));

    let paragraph = ratatui::widgets::Paragraph::new(lines);
    f.render_widget(paragraph, inner);

    super::draw_input_prompt(f, app, area);
}

fn span_raw(s: &str) -> ratatui::text::Span<'static> {
    ratatui::text::Span::raw(s.to_string())
}

fn span_val(s: String, highlighted: bool) -> ratatui::text::Span<'static> {
    if highlighted {
        ratatui::text::Span::styled(
            s,
            ratatui::style::Style::default()
                .fg(ratatui::style::Color::Yellow)
                .add_modifier(ratatui::style::Modifier::BOLD),
        )
    } else {
        ratatui::text::Span::styled(
            s,
            ratatui::style::Style::default().fg(ratatui::style::Color::Green),
        )
    }
}

fn ratui_line(spans: Vec<ratatui::text::Span<'static>>) -> ratatui::text::Line<'static> {
    ratatui::text::Line::from(spans)
}