cli_tutor/ui/
command_list.rs1use crate::app::App;
2use ratatui::{
3 layout::{Constraint, Direction, Layout, Rect},
4 style::{Color, Modifier, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, List, ListItem, ListState, Paragraph},
7 Frame,
8};
9
10pub fn render(app: &App, frame: &mut Frame, area: Rect) {
11 let nc = app.config.no_color;
12
13 let (search_area, list_area) = if app.search_active {
15 let chunks = Layout::default()
16 .direction(Direction::Vertical)
17 .constraints([Constraint::Length(1), Constraint::Min(0)])
18 .split(area);
19 (Some(chunks[0]), chunks[1])
20 } else {
21 (None, area)
22 };
23
24 if let Some(sa) = search_area {
26 let search_line = Line::from(vec![
27 Span::styled(
28 "/ ",
29 crate::ui::s(Style::default().fg(Color::Yellow).add_modifier(Modifier::BOLD), nc),
30 ),
31 Span::raw(app.search_query.clone()),
32 Span::styled(
33 " ",
34 crate::ui::s(Style::default().bg(Color::White).fg(Color::Black), nc),
35 ),
36 ]);
37 frame.render_widget(Paragraph::new(search_line), sa);
38 }
39
40 let visible = app.visible_module_indices();
41
42 let items: Vec<ListItem> = visible
43 .iter()
44 .map(|&i| {
45 let m = &app.modules[i];
46 let ex_count = m.exercises.len();
47 let completed = app
48 .progress
49 .modules
50 .get(&m.module.name)
51 .map(|p| p.completed.len())
52 .unwrap_or(0);
53
54 let badge = if completed == ex_count && ex_count > 0 {
55 " ✓"
56 } else {
57 ""
58 };
59
60 let is_selected = i == app.selected_module;
61
62 let line = Line::from(vec![
63 Span::styled(
64 format!("{:<10}", m.module.name),
65 if is_selected {
66 crate::ui::s(Style::default().add_modifier(Modifier::BOLD), nc)
67 } else {
68 Style::default()
69 },
70 ),
71 Span::styled(
72 format!("[{:>2}]", ex_count),
73 crate::ui::s(Style::default().fg(Color::DarkGray), nc),
74 ),
75 Span::styled(
76 badge,
77 crate::ui::s(Style::default().fg(Color::Green), nc),
78 ),
79 ]);
80 ListItem::new(line)
81 })
82 .collect();
83
84 let selected_pos = visible.iter().position(|&i| i == app.selected_module);
86 let mut state = ListState::default();
87 state.select(selected_pos);
88
89 let list = List::new(items)
90 .block(Block::default().borders(Borders::RIGHT).title("Modules"))
91 .highlight_style(crate::ui::s(
92 Style::default().bg(Color::White).fg(Color::Black),
93 nc,
94 ));
95
96 frame.render_stateful_widget(list, list_area, &mut state);
97}