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 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}