ommp.git

ommp

~ Oh My Music Player — a standalone terminal music player built with Rust and ratatui
git clone https://github.com/debuman/ommp.git

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 }