Skip to main content

mockforge_tui/widgets/
help.rs

1//! Help overlay popup showing all keybindings.
2
3use ratatui::{
4    layout::{Constraint, Flex, Layout, Rect},
5    text::{Line, Span},
6    widgets::{Block, Borders, Clear, Paragraph},
7    Frame,
8};
9
10use crate::theme::Theme;
11
12const HELP_TEXT: &[(&str, &str)] = &[
13    ("Global", ""),
14    ("  q / Ctrl+C", "Quit"),
15    ("  Tab / Shift+Tab", "Next / Previous tab"),
16    ("  1-0", "Jump to tab 1-10"),
17    ("  r", "Refresh current screen"),
18    ("  /", "Open filter input"),
19    ("  ?", "Toggle this help"),
20    ("  :", "Command palette"),
21    ("", ""),
22    ("Navigation", ""),
23    ("  j / ↓", "Scroll down"),
24    ("  k / ↑", "Scroll up"),
25    ("  g / G", "Jump to top / bottom"),
26    ("  PgUp / PgDn", "Page up / down"),
27    ("  Enter", "Select / expand"),
28    ("  Esc", "Close popup / cancel"),
29    ("", ""),
30    ("Screen-specific", ""),
31    ("  f", "Toggle follow mode (Logs)"),
32    ("  e", "Edit selected item (Config)"),
33    ("  t", "Toggle (Chaos, Time Travel)"),
34    ("  s", "Sort column (Routes, Fixtures)"),
35    ("  d", "Delete selected item"),
36];
37
38/// Render the help overlay centred on screen.
39pub fn render(frame: &mut Frame) {
40    let area = centered_rect(60, 70, frame.area());
41
42    // Clear the background behind the popup.
43    frame.render_widget(Clear, area);
44
45    let lines: Vec<Line> = HELP_TEXT
46        .iter()
47        .map(|(key, desc)| {
48            if desc.is_empty() {
49                // Section header or blank line
50                Line::from(Span::styled(*key, Theme::title()))
51            } else {
52                Line::from(vec![
53                    Span::styled(format!("{key:<22}"), Theme::key_hint()),
54                    Span::styled(*desc, Theme::base()),
55                ])
56            }
57        })
58        .collect();
59
60    let block = Block::default()
61        .title(" Help — press ? or Esc to close ")
62        .title_style(Theme::title())
63        .borders(Borders::ALL)
64        .border_style(Theme::dim())
65        .style(Theme::surface());
66
67    let paragraph = Paragraph::new(lines).block(block);
68    frame.render_widget(paragraph, area);
69}
70
71/// Return a centred `Rect` that takes `percent_x`% width and `percent_y`% height.
72fn centered_rect(percent_x: u16, percent_y: u16, area: Rect) -> Rect {
73    let vertical = Layout::vertical([Constraint::Percentage(percent_y)])
74        .flex(Flex::Center)
75        .split(area);
76    Layout::horizontal([Constraint::Percentage(percent_x)])
77        .flex(Flex::Center)
78        .split(vertical[0])[0]
79}