thd75_tui/ui/
gps.rs

1use ratatui::Frame;
2use ratatui::layout::Rect;
3use ratatui::style::{Color, Style};
4use ratatui::text::{Line, Span};
5use ratatui::widgets::{Block, Borders, Paragraph};
6
7use crate::app::{App, Pane};
8
9fn kv_line(label: &str, value: String, value_color: Color) -> Line<'_> {
10    Line::from(vec![
11        Span::styled(
12            format!("  {label:<18}"),
13            Style::default().fg(Color::DarkGray),
14        ),
15        Span::styled(value, Style::default().fg(value_color)),
16    ])
17}
18
19const fn on_off(b: bool) -> &'static str {
20    if b { "On" } else { "Off" }
21}
22
23pub(crate) fn render(app: &App, frame: &mut Frame<'_>, left_area: Rect, right_area: Rect) {
24    render_status(app, frame, left_area);
25    render_config(app, frame, right_area);
26}
27
28// ---------------------------------------------------------------------------
29// Left pane: GPS Status
30// ---------------------------------------------------------------------------
31
32fn render_status(app: &App, frame: &mut Frame<'_>, area: Rect) {
33    let block = Block::default()
34        .title(" GPS Status ")
35        .borders(Borders::ALL)
36        .border_style(super::border_style(app, Pane::Main));
37
38    let s = &app.state;
39
40    let mut lines: Vec<Line<'_>> = Vec::new();
41
42    lines.push(Line::from(""));
43    lines.push(kv_line(
44        "GPS",
45        on_off(s.gps_enabled).into(),
46        if s.gps_enabled {
47            Color::Green
48        } else {
49            Color::DarkGray
50        },
51    ));
52    lines.push(kv_line(
53        "PC Output",
54        on_off(s.gps_pc_output).into(),
55        if s.gps_pc_output {
56            Color::Green
57        } else {
58            Color::DarkGray
59        },
60    ));
61
62    lines.push(Line::from(""));
63    if s.gps_enabled {
64        lines.push(Line::from(Span::styled(
65            "  Position data requires NMEA PC output.",
66            Style::default().fg(Color::DarkGray),
67        )));
68        lines.push(Line::from(Span::styled(
69            "  GPS fix info is not available via CAT.",
70            Style::default().fg(Color::DarkGray),
71        )));
72    } else {
73        lines.push(Line::from(Span::styled(
74            "  GPS is disabled.",
75            Style::default().fg(Color::DarkGray),
76        )));
77        lines.push(Line::from(Span::styled(
78            "  Press [g] to enable.",
79            Style::default().fg(Color::DarkGray),
80        )));
81    }
82
83    // Key hints
84    lines.push(Line::from(""));
85    lines.push(Line::from(""));
86    lines.push(Line::from(vec![
87        Span::styled(" [g]", Style::default().fg(Color::Yellow)),
88        Span::styled(" Toggle GPS  ", Style::default().fg(Color::White)),
89        Span::styled("[p]", Style::default().fg(Color::Yellow)),
90        Span::styled(" Toggle PC Output", Style::default().fg(Color::White)),
91    ]));
92
93    frame.render_widget(Paragraph::new(lines).block(block), area);
94}
95
96// ---------------------------------------------------------------------------
97// Right pane: GPS Configuration (NMEA sentences)
98// ---------------------------------------------------------------------------
99
100fn render_config(app: &App, frame: &mut Frame<'_>, area: Rect) {
101    let block = Block::default()
102        .title(" GPS Configuration ")
103        .borders(Borders::ALL)
104        .border_style(super::border_style(app, Pane::Detail));
105
106    let s = &app.state;
107
108    let mut lines: Vec<Line<'_>> = Vec::new();
109
110    lines.push(Line::from(""));
111    lines.push(Line::from(Span::styled(
112        "  NMEA Sentences",
113        Style::default().fg(Color::Cyan),
114    )));
115    lines.push(Line::from(""));
116
117    if let Some((gga, gll, gsa, gsv, rmc, vtg)) = s.gps_sentences {
118        // Two-column layout for sentence flags
119        lines.push(sentence_row("GGA", gga, "GLL", gll));
120        lines.push(sentence_row("GSA", gsa, "GSV", gsv));
121        lines.push(sentence_row("RMC", rmc, "VTG", vtg));
122    } else {
123        lines.push(Line::from(Span::styled(
124            "  (not available)",
125            Style::default().fg(Color::DarkGray),
126        )));
127    }
128
129    lines.push(Line::from(""));
130
131    // GPS mode info
132    if let Some(mode) = s.gps_mode {
133        lines.push(kv_line("Mode", format!("{mode}"), Color::Yellow));
134    }
135
136    frame.render_widget(Paragraph::new(lines).block(block), area);
137}
138
139/// Render a two-column row of sentence enable flags.
140fn sentence_row<'a>(label_a: &'a str, val_a: bool, label_b: &'a str, val_b: bool) -> Line<'a> {
141    let color = |v: bool| {
142        if v { Color::Green } else { Color::DarkGray }
143    };
144    Line::from(vec![
145        Span::styled(
146            format!("  {label_a}:  "),
147            Style::default().fg(Color::DarkGray),
148        ),
149        Span::styled(
150            format!("{:<6}", on_off(val_a)),
151            Style::default().fg(color(val_a)),
152        ),
153        Span::styled(
154            format!("{label_b}:  "),
155            Style::default().fg(Color::DarkGray),
156        ),
157        Span::styled(on_off(val_b).to_string(), Style::default().fg(color(val_b))),
158    ])
159}