thd75_tui/ui/
help.rs

1use ratatui::Frame;
2use ratatui::layout::{Constraint, Direction, Layout, Rect};
3use ratatui::style::{Color, Modifier, Style};
4use ratatui::text::{Line, Span};
5use ratatui::widgets::{Block, Borders, Clear, Paragraph};
6
7pub(crate) fn render(frame: &mut Frame<'_>) {
8    let area = centered_rect(60, 70, frame.area());
9
10    // Clear the area behind the popup
11    frame.render_widget(Clear, area);
12
13    let block = Block::default()
14        .title(" Keybindings ")
15        .borders(Borders::ALL)
16        .border_style(Style::default().fg(Color::Cyan));
17
18    let lines = vec![
19        header("Navigation"),
20        binding("Tab / Shift-Tab", "Cycle pane focus"),
21        binding("1-4", "Jump to pane (BandA/B/Main/Detail)"),
22        binding("c/s/S/a/d/g/m/F", "Switch view"),
23        Line::from(""),
24        header("Channel List"),
25        binding("j/k", "Navigate"),
26        binding("g / G", "First / last"),
27        binding("Enter", "Tune target band"),
28        binding("e", "Edit channel (freq/mode/name)"),
29        binding("/", "Search by name"),
30        binding("Esc", "Clear filter"),
31        Line::from(""),
32        header("Band Pane"),
33        binding("k/j", "Step freq up/down"),
34        binding("f", "Enter frequency (MHz)"),
35        binding("p", "Cycle power (H/M/L/EL)"),
36        binding("t", "Toggle attenuator"),
37        binding("[ / ]", "Squelch down/up"),
38        Line::from(""),
39        header("Settings"),
40        binding("Enter", "Toggle setting"),
41        binding("+ / -", "Adjust value"),
42        Line::from(""),
43        header("APRS"),
44        binding("a", "Toggle APRS mode (on APRS panel)"),
45        binding("j/k", "Navigate station list"),
46        binding("M", "Compose message to station"),
47        binding("b", "Manual beacon"),
48        Line::from(""),
49        header("GPS"),
50        binding("g", "Toggle GPS on/off (on GPS panel)"),
51        binding("p", "Toggle PC output (on GPS panel)"),
52        Line::from(""),
53        header("D-STAR"),
54        binding("d", "Toggle D-STAR gateway mode (on D-STAR panel)"),
55        binding("C", "Set CQ (URCALL = CQCQCQ)"),
56        binding("u", "Set URCALL (prompt)"),
57        binding("r", "Connect reflector (prompt)"),
58        binding("U", "Unlink reflector"),
59        Line::from(""),
60        header("FM Radio"),
61        binding("F", "Switch to FM Radio panel"),
62        binding("f", "Toggle FM on/off (on FM panel)"),
63        Line::from(""),
64        header("MCP"),
65        binding("r", "Read memory from radio"),
66        binding("w", "Write memory to radio"),
67        Line::from(""),
68        header("Global"),
69        binding("?", "This help"),
70        binding("q / Ctrl-C", "Quit"),
71    ];
72
73    frame.render_widget(Paragraph::new(lines).block(block), area);
74}
75
76fn header(text: &str) -> Line<'static> {
77    Line::from(Span::styled(
78        format!("  {text}"),
79        Style::default()
80            .fg(Color::Cyan)
81            .add_modifier(Modifier::BOLD),
82    ))
83}
84
85fn binding(key: &str, desc: &str) -> Line<'static> {
86    Line::from(vec![
87        Span::styled(format!("    {key:<20}"), Style::default().fg(Color::Yellow)),
88        Span::styled(desc.to_string(), Style::default().fg(Color::White)),
89    ])
90}
91
92fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
93    let vertical = Layout::default()
94        .direction(Direction::Vertical)
95        .constraints([
96            Constraint::Percentage((100 - percent_y) / 2),
97            Constraint::Percentage(percent_y),
98            Constraint::Percentage((100 - percent_y) / 2),
99        ])
100        .split(area);
101
102    Layout::default()
103        .direction(Direction::Horizontal)
104        .constraints([
105            Constraint::Percentage((100 - percent_x) / 2),
106            Constraint::Percentage(percent_x),
107            Constraint::Percentage((100 - percent_x) / 2),
108        ])
109        .split(vertical[1])[1]
110}