1use ratatui::{
10 layout::{Alignment, Constraint, Direction, Layout, Rect},
11 style::Modifier,
12 text::{Line, Span},
13 widgets::{Block, Borders, Paragraph},
14 Frame,
15};
16
17use crate::theme::AxonmlTheme;
18
19#[derive(Debug, Clone)]
25pub struct KeyBinding {
26 pub key: &'static str,
27 pub description: &'static str,
28}
29
30#[derive(Debug, Clone)]
32pub struct HelpCategory {
33 pub name: &'static str,
34 pub bindings: Vec<KeyBinding>,
35}
36
37pub struct HelpView {
43 pub categories: Vec<HelpCategory>,
45
46 pub scroll_offset: usize,
48
49 pub compact: bool,
51}
52
53impl HelpView {
54 pub fn new() -> Self {
56 Self {
57 categories: Self::default_categories(),
58 scroll_offset: 0,
59 compact: false,
60 }
61 }
62
63 fn default_categories() -> Vec<HelpCategory> {
65 vec![
66 HelpCategory {
67 name: "Navigation",
68 bindings: vec![
69 KeyBinding { key: "Tab / Shift+Tab", description: "Switch between views" },
70 KeyBinding { key: "1-6", description: "Jump to specific view (Model, Data, Training, Graphs, Files, Help)" },
71 KeyBinding { key: "j / Down", description: "Move selection down" },
72 KeyBinding { key: "k / Up", description: "Move selection up" },
73 KeyBinding { key: "h / Left", description: "Collapse / Previous" },
74 KeyBinding { key: "l / Right", description: "Expand / Next" },
75 KeyBinding { key: "g / Home", description: "Go to first item" },
76 KeyBinding { key: "G / End", description: "Go to last item" },
77 KeyBinding { key: "Ctrl+u / PageUp", description: "Page up" },
78 KeyBinding { key: "Ctrl+d / PageDown", description: "Page down" },
79 ],
80 },
81 HelpCategory {
82 name: "Model View",
83 bindings: vec![
84 KeyBinding { key: "o", description: "Open model file" },
85 KeyBinding { key: "Enter", description: "View layer details" },
86 KeyBinding { key: "d", description: "Toggle detailed view" },
87 KeyBinding { key: "e", description: "Export model info" },
88 KeyBinding { key: "v", description: "Visualize model graph" },
89 ],
90 },
91 HelpCategory {
92 name: "Data View",
93 bindings: vec![
94 KeyBinding { key: "o", description: "Open dataset file" },
95 KeyBinding { key: "Tab", description: "Switch panel (classes/features)" },
96 KeyBinding { key: "s", description: "View data statistics" },
97 KeyBinding { key: "p", description: "Preview samples" },
98 ],
99 },
100 HelpCategory {
101 name: "Training View",
102 bindings: vec![
103 KeyBinding { key: "t", description: "Start training" },
104 KeyBinding { key: "Space", description: "Pause/Resume training" },
105 KeyBinding { key: "s", description: "Stop training" },
106 KeyBinding { key: "c", description: "Open training config" },
107 KeyBinding { key: "Ctrl+s", description: "Save checkpoint" },
108 KeyBinding { key: "d", description: "Toggle detailed metrics" },
109 ],
110 },
111 HelpCategory {
112 name: "Graphs View",
113 bindings: vec![
114 KeyBinding { key: "< / >", description: "Switch chart type" },
115 KeyBinding { key: "+/-", description: "Zoom in/out" },
116 KeyBinding { key: "r", description: "Reset zoom" },
117 KeyBinding { key: "e", description: "Export chart as image" },
118 KeyBinding { key: "l", description: "Toggle legend" },
119 ],
120 },
121 HelpCategory {
122 name: "Files View",
123 bindings: vec![
124 KeyBinding { key: "Enter", description: "Open file/Toggle directory" },
125 KeyBinding { key: "Backspace", description: "Go to parent directory" },
126 KeyBinding { key: ".", description: "Toggle hidden files" },
127 KeyBinding { key: "/", description: "Search files" },
128 KeyBinding { key: "n", description: "New file/directory" },
129 KeyBinding { key: "Delete", description: "Delete file" },
130 KeyBinding { key: "r", description: "Rename file" },
131 ],
132 },
133 HelpCategory {
134 name: "Global",
135 bindings: vec![
136 KeyBinding { key: "?", description: "Show/Hide help" },
137 KeyBinding { key: ":", description: "Command mode" },
138 KeyBinding { key: "Ctrl+c / q", description: "Quit application" },
139 KeyBinding { key: "Ctrl+l", description: "Redraw screen" },
140 KeyBinding { key: "Esc", description: "Cancel / Close popup" },
141 ],
142 },
143 ]
144 }
145
146 pub fn scroll_up(&mut self) {
148 if self.scroll_offset > 0 {
149 self.scroll_offset -= 1;
150 }
151 }
152
153 pub fn scroll_down(&mut self) {
155 self.scroll_offset += 1;
156 }
157
158 pub fn toggle_compact(&mut self) {
160 self.compact = !self.compact;
161 }
162
163 pub fn render(&mut self, frame: &mut Frame, area: Rect) {
165 let chunks = Layout::default()
166 .direction(Direction::Vertical)
167 .constraints([
168 Constraint::Length(5), Constraint::Min(10), Constraint::Length(3), ])
172 .split(area);
173
174 self.render_header(frame, chunks[0]);
175 self.render_keybindings(frame, chunks[1]);
176 self.render_footer(frame, chunks[2]);
177 }
178
179 fn render_header(&self, frame: &mut Frame, area: Rect) {
180 let header_text = vec![
181 Line::from(Span::styled(
182 "Axonml TUI - Keyboard Shortcuts",
183 AxonmlTheme::title(),
184 )),
185 Line::from(""),
186 Line::from(Span::styled(
187 "Press '?' to toggle help, 'q' to close",
188 AxonmlTheme::muted(),
189 )),
190 ];
191
192 let header = Paragraph::new(header_text)
193 .block(
194 Block::default()
195 .borders(Borders::ALL)
196 .border_style(AxonmlTheme::border())
197 .title(Span::styled(" Help ", AxonmlTheme::header())),
198 )
199 .alignment(Alignment::Center);
200
201 frame.render_widget(header, area);
202 }
203
204 fn render_keybindings(&self, frame: &mut Frame, area: Rect) {
205 let num_columns = if area.width > 120 { 3 } else if area.width > 80 { 2 } else { 1 };
207
208 let column_constraints: Vec<Constraint> = (0..num_columns)
209 .map(|_| Constraint::Percentage(100 / num_columns as u16))
210 .collect();
211
212 let columns = Layout::default()
213 .direction(Direction::Horizontal)
214 .constraints(column_constraints)
215 .split(area);
216
217 let categories_per_column = (self.categories.len() + num_columns - 1) / num_columns;
219
220 for (col_idx, column_area) in columns.iter().enumerate() {
221 let start_idx = col_idx * categories_per_column;
222 let end_idx = (start_idx + categories_per_column).min(self.categories.len());
223
224 if start_idx >= self.categories.len() {
225 continue;
226 }
227
228 let column_categories = &self.categories[start_idx..end_idx];
229 self.render_column(frame, *column_area, column_categories);
230 }
231 }
232
233 fn render_column(&self, frame: &mut Frame, area: Rect, categories: &[HelpCategory]) {
234 let mut lines: Vec<Line> = Vec::new();
235
236 for category in categories {
237 lines.push(Line::from(Span::styled(
239 format!(" {} ", category.name),
240 AxonmlTheme::header().add_modifier(Modifier::UNDERLINED),
241 )));
242 lines.push(Line::from(""));
243
244 for binding in &category.bindings {
246 lines.push(Line::from(vec![
247 Span::styled(format!(" {:20}", binding.key), AxonmlTheme::key()),
248 Span::styled(binding.description, AxonmlTheme::key_desc()),
249 ]));
250 }
251
252 lines.push(Line::from(""));
253 }
254
255 let content = Paragraph::new(lines)
256 .block(
257 Block::default()
258 .borders(Borders::ALL)
259 .border_style(AxonmlTheme::border()),
260 );
261
262 frame.render_widget(content, area);
263 }
264
265 fn render_footer(&self, frame: &mut Frame, area: Rect) {
266 let footer_text = Line::from(vec![
267 Span::styled("Tip: ", AxonmlTheme::muted()),
268 Span::styled(
269 "Use Tab to switch views, number keys (1-6) for quick access",
270 AxonmlTheme::info(),
271 ),
272 ]);
273
274 let footer = Paragraph::new(footer_text)
275 .block(
276 Block::default()
277 .borders(Borders::ALL)
278 .border_style(AxonmlTheme::border())
279 .title(Span::styled(" Quick Tips ", AxonmlTheme::header())),
280 )
281 .alignment(Alignment::Center);
282
283 frame.render_widget(footer, area);
284 }
285}
286
287impl Default for HelpView {
288 fn default() -> Self {
289 Self::new()
290 }
291}