Skip to main content

gitkraft_tui/features/theme/
view.rs

1use ratatui::layout::Rect;
2use ratatui::style::{Color, Modifier, Style};
3use ratatui::text::{Line, Span};
4use ratatui::widgets::{Block, Borders, List, ListItem};
5use ratatui::Frame;
6
7use gitkraft_core::{Rgb, THEME_NAMES};
8
9use crate::app::App;
10
11/// Convert a core [`Rgb`] to a ratatui [`Color`].
12fn rgb_to_color(rgb: Rgb) -> Color {
13    Color::Rgb(rgb.r, rgb.g, rgb.b)
14}
15
16/// Build the accent-colour swatch for each theme by pulling the `accent` field
17/// from the canonical core definition.  This replaces the old hand-maintained
18/// `THEME_COLORS` constant.
19fn accent_color_for_index(index: usize) -> Color {
20    rgb_to_color(gitkraft_core::theme_by_index(index).accent)
21}
22
23/// Render the theme picker panel on the right side of the main content area.
24///
25/// Shows a bordered block titled "Themes" with navigation hints at the top.
26/// Lists all theme names numbered 1–27. The current theme is highlighted with
27/// a reversed style and a `← ` arrow prefix.
28pub fn render(app: &mut App, frame: &mut Frame, area: Rect) {
29    let theme = app.theme();
30    let current = app.current_theme_index;
31
32    let items: Vec<ListItem> = THEME_NAMES
33        .iter()
34        .enumerate()
35        .map(|(i, name)| {
36            let number = format!("{:>2}. ", i + 1);
37            let color = accent_color_for_index(i);
38
39            if i == current {
40                // Highlighted row: arrow prefix, bold, custom background
41                let line = Line::from(vec![
42                    Span::styled(
43                        "← ",
44                        Style::default()
45                            .fg(theme.warning)
46                            .bg(theme.sel_bg)
47                            .add_modifier(Modifier::BOLD),
48                    ),
49                    Span::styled(
50                        number,
51                        Style::default()
52                            .fg(theme.text_primary)
53                            .bg(theme.sel_bg)
54                            .add_modifier(Modifier::BOLD),
55                    ),
56                    Span::styled(
57                        (*name).to_string(),
58                        Style::default()
59                            .fg(color)
60                            .bg(theme.sel_bg)
61                            .add_modifier(Modifier::BOLD),
62                    ),
63                ]);
64                ListItem::new(line)
65            } else {
66                let line = Line::from(vec![
67                    Span::styled("  ", Style::default().fg(theme.text_muted)),
68                    Span::styled(number, Style::default().fg(theme.text_muted)),
69                    Span::styled((*name).to_string(), Style::default().fg(color)),
70                ]);
71                ListItem::new(line)
72            }
73        })
74        .collect();
75
76    let block = Block::default()
77        .title(" Themes  ↑ prev  ↓/t next ")
78        .borders(Borders::ALL)
79        .border_style(Style::default().fg(theme.border_active))
80        .style(Style::default().bg(theme.bg));
81
82    let highlight_style = Style::default()
83        .bg(theme.sel_bg)
84        .add_modifier(Modifier::BOLD);
85
86    let list = List::new(items)
87        .block(block)
88        .highlight_style(highlight_style);
89
90    let mut state = app.theme_list_state;
91    frame.render_stateful_widget(list, area, &mut state);
92    app.theme_list_state = state;
93}