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
28fn 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 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
96fn 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 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 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
139fn 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}