src/ui/widgets/help_modal.rs (2890B)
1 use ratatui::layout::{Constraint, Direction, Layout, Rect};
2 use ratatui::style::{Color, Modifier, Style};
3 use ratatui::text::{Line, Span};
4 use ratatui::widgets::{Block, Borders, Clear, Paragraph};
5 use ratatui::Frame;
6
7 use crate::ui::text::centered_rect;
8 use crate::ui::theme::Theme;
9
10 /// Left column: playback and global commands.
11 const PLAYBACK: &[(&str, &str)] = &[
12 ("Space", "Play / Pause"),
13 ("n / N", "Next / Previous track"),
14 ("+ / -", "Volume up / down"),
15 ("\u{2192} / \u{2190}", "Seek forward / back"),
16 ("s", "Toggle shuffle"),
17 ("r", "Cycle repeat mode"),
18 ("b", "Add to playlist"),
19 ("p", "Info panel: clock / art"),
20 ("", ""),
21 ("Ctrl+S", "Search"),
22 ("Ctrl+H", "Help (this modal)"),
23 ("Ctrl+A", "About OMMP"),
24 ("Ctrl+L", "Sync library"),
25 ];
26
27 /// Right column: navigation and the queue.
28 const NAVIGATION: &[(&str, &str)] = &[
29 ("1-7", "Switch tab"),
30 ("Tab / Shift+Tab", "Cycle pane focus"),
31 ("h / l", "Focus prev / next pane"),
32 ("j / k", "Navigate list"),
33 ("g / G", "Jump to first / last"),
34 ("Enter", "Select / Activate"),
35 ("", ""),
36 ("d", "Remove from queue"),
37 ("c", "Clear queue"),
38 ("", ""),
39 ("Ctrl+drag", "Resize panels"),
40 ("Double-click", "Play queue track"),
41 ("", ""),
42 ("Esc", "Close modal"),
43 ("q / Ctrl+C", "Quit"),
44 ];
45
46 pub fn render_help_modal(frame: &mut Frame, area: Rect, theme: &Theme) {
47 // Two columns: a single list of 27 entries does not fit an 80x24 terminal,
48 // and there is no scrolling — the tail, including `q: Quit`, was simply cut.
49 let modal = centered_rect(72, 70, area);
50
51 frame.render_widget(Clear, modal);
52
53 let block = Block::default()
54 .borders(Borders::ALL)
55 .border_style(Style::default().fg(Color::Cyan))
56 .title(" Keybindings ")
57 .title_style(Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD));
58
59 let inner = block.inner(modal);
60 frame.render_widget(block, modal);
61
62 let columns = Layout::default()
63 .direction(Direction::Horizontal)
64 .constraints([Constraint::Percentage(50), Constraint::Percentage(50)])
65 .split(inner);
66
67 for (bindings, area) in [(PLAYBACK, columns[0]), (NAVIGATION, columns[1])] {
68 frame.render_widget(Paragraph::new(render_column(bindings, theme)), area);
69 }
70 }
71
72 fn render_column(bindings: &[(&str, &str)], theme: &Theme) -> Vec<Line<'static>> {
73 bindings
74 .iter()
75 .map(|(key, desc)| {
76 if key.is_empty() {
77 return Line::from("");
78 }
79 Line::from(vec![
80 Span::styled(
81 format!(" {:16}", key),
82 Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD),
83 ),
84 Span::styled(desc.to_string(), Style::default().fg(theme.fg)),
85 ])
86 })
87 .collect()
88 }