basalt_tui/
theme_selector.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Style, Stylize},
5 widgets::{
6 Block, BorderType, List, ListItem, ListState, Scrollbar, ScrollbarOrientation,
7 ScrollbarState, StatefulWidget,
8 },
9};
10
11use crate::config::Theme;
12
13#[derive(Debug, Default, Clone, PartialEq)]
14pub struct ThemeSelectorState {
15 pub(crate) items: Vec<(String, Theme)>,
16 list_state: ListState,
17}
18
19impl ThemeSelectorState {
20 pub fn new(items: Vec<(String, Theme)>) -> Self {
21 Self {
22 items,
23 list_state: ListState::default().with_selected(Some(0)),
24 }
25 }
26
27 pub fn select_theme(&mut self, theme: &Theme) {
30 if let Some(index) = self.items.iter().position(|(_, item)| item == theme) {
31 self.list_state.select(Some(index));
32 }
33 }
34
35 pub fn selected_theme(&self) -> Option<Theme> {
36 self.list_state
37 .selected()
38 .and_then(|index| self.items.get(index))
39 .map(|(_, theme)| *theme)
40 }
41
42 pub fn selected_name(&self) -> Option<&str> {
43 self.list_state
44 .selected()
45 .and_then(|index| self.items.get(index))
46 .map(|(name, _)| name.as_str())
47 }
48
49 pub fn next(&mut self) {
50 let index = self
51 .list_state
52 .selected()
53 .map(|i| (i + 1).min(self.items.len().saturating_sub(1)));
54 self.list_state.select(index);
55 }
56
57 pub fn previous(&mut self) {
58 self.list_state.select_previous();
59 }
60}
61
62pub struct ThemeSelector {
63 pub border_type: BorderType,
64 pub theme: Theme,
65}
66
67impl ThemeSelector {
68 pub fn new(border_type: BorderType, theme: Theme) -> Self {
69 Self { border_type, theme }
70 }
71}
72
73impl StatefulWidget for ThemeSelector {
74 type State = ThemeSelectorState;
75
76 fn render(self, area: Rect, buf: &mut Buffer, state: &mut Self::State) {
77 let items: Vec<ListItem> = state
78 .items
79 .iter()
80 .map(|(name, _)| ListItem::new(format!(" {name}")))
81 .collect();
82
83 let items_count = items.len();
84
85 List::new(items)
86 .block(
87 Block::bordered()
88 .fg(self.theme.muted)
89 .bg(self.theme.background)
90 .title(" Themes ")
91 .title_style(Style::default().italic().bold())
92 .border_type(self.border_type),
93 )
94 .fg(self.theme.text)
95 .highlight_style(Style::new().reversed().fg(self.theme.muted))
96 .highlight_symbol(" ")
97 .render(area, buf, &mut state.list_state);
98
99 let min_item_amount = 4;
101
102 if !area.is_empty() && items_count > min_item_amount {
103 let mut scroll_state =
104 ScrollbarState::new(items_count).position(state.list_state.selected().unwrap_or(0));
105
106 Scrollbar::new(ScrollbarOrientation::VerticalRight).render(
107 area,
108 buf,
109 &mut scroll_state,
110 );
111 }
112 }
113}