1use ratatui::prelude::*;
4use ratatui::widgets::{Block, Borders, Clear, Paragraph};
5
6pub struct HelpEntry {
8 pub key: &'static str,
9 pub description: &'static str,
10}
11
12pub struct HelpOverlay {
14 pub visible: bool,
15 pub entries: Vec<HelpEntry>,
16}
17
18impl Default for HelpOverlay {
19 fn default() -> Self {
20 Self {
21 visible: false,
22 entries: vec![
23 HelpEntry {
24 key: "Enter",
25 description: "Send message / toggle expand",
26 },
27 HelpEntry {
28 key: "Ctrl+P",
29 description: "Open fuzzy picker",
30 },
31 HelpEntry {
32 key: "Ctrl+H",
33 description: "Toggle this help",
34 },
35 HelpEntry {
36 key: "Ctrl+C/Q",
37 description: "Quit",
38 },
39 HelpEntry {
40 key: "PageUp/Down",
41 description: "Scroll chat",
42 },
43 HelpEntry {
44 key: "Up/Down",
45 description: "Input history",
46 },
47 HelpEntry {
48 key: "Esc",
49 description: "Close picker / help",
50 },
51 HelpEntry {
52 key: "Tab",
53 description: "Switch picker channel",
54 },
55 HelpEntry {
56 key: "Shift+Tab",
57 description: "Previous picker channel",
58 },
59 ],
60 }
61 }
62}
63
64impl HelpOverlay {
65 pub fn toggle(&mut self) {
66 self.visible = !self.visible;
67 }
68
69 pub fn close(&mut self) {
70 self.visible = false;
71 }
72
73 pub fn render(&self, area: Rect, buf: &mut Buffer) {
75 if !self.visible {
76 return;
77 }
78
79 let width = area.width.clamp(30, 50);
80 let height = (self.entries.len() as u16 + 4).min(area.height.saturating_sub(2));
81 let x = area.x + (area.width.saturating_sub(width)) / 2;
82 let y = area.y + (area.height.saturating_sub(height)) / 2;
83 let overlay = Rect::new(x, y, width, height);
84
85 Clear.render(overlay, buf);
86
87 let block = Block::default()
88 .borders(Borders::ALL)
89 .title(" Keybindings ")
90 .border_style(Style::default().fg(Color::Yellow))
91 .style(Style::default().bg(Color::Black));
92
93 let inner = block.inner(overlay);
94 block.render(overlay, buf);
95
96 let key_col_width = 14;
97 let lines: Vec<Line> = self
98 .entries
99 .iter()
100 .map(|e| {
101 let key_padded = format!("{:<width$}", e.key, width = key_col_width);
102 Line::from(vec![
103 Span::styled(
104 key_padded,
105 Style::default()
106 .fg(Color::Cyan)
107 .add_modifier(Modifier::BOLD),
108 ),
109 Span::styled(e.description, Style::default().fg(Color::White)),
110 ])
111 })
112 .collect();
113
114 Paragraph::new(Text::from(lines)).render(inner, buf);
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use super::*;
121
122 #[test]
123 fn help_toggle() {
124 let mut help = HelpOverlay::default();
125 assert!(!help.visible);
126 help.toggle();
127 assert!(help.visible);
128 help.toggle();
129 assert!(!help.visible);
130 }
131
132 #[test]
133 fn help_renders() {
134 let help = HelpOverlay::default();
135 let area = Rect::new(0, 0, 60, 20);
136 let mut buf = Buffer::empty(area);
137 help.render(area, &mut buf);
138 }
139}