rsnaker/graphics/menus/
selectable_item.rs

1use ratatui::{
2    buffer::Buffer,
3    layout::Rect,
4    style::{Color, Style},
5    text::{Line, Span},
6    widgets::{Block, Borders, List, ListItem, Widget},
7};
8///TODO finish it to have a graphical option setup alongside CLI !
9/// Generic selectable list for Ratatui
10pub struct SelectableList<T> {
11    pub title: String,
12    pub items: Vec<T>,
13    pub selected: usize,
14    pub fg: Color,
15    pub bg: Color,
16    formatter: fn(&T) -> String,
17}
18
19impl<T> SelectableList<T> {
20    pub fn new(
21        title: String,
22        items: Vec<T>,
23        formatter: fn(&T) -> String,
24        fg: Color,
25        bg: Color,
26    ) -> Self {
27        Self {
28            title,
29            items,
30            selected: 0,
31            fg,
32            bg,
33            formatter,
34        }
35    }
36
37    pub fn next(&mut self) {
38        self.selected = (self.selected + 1) % self.items.len();
39    }
40
41    pub fn prev(&mut self) {
42        if self.selected == 0 {
43            self.selected = self.items.len() - 1;
44        } else {
45            self.selected -= 1;
46        }
47    }
48
49    #[must_use]
50    pub fn selected_item(&self) -> &T {
51        &self.items[self.selected]
52    }
53
54    pub fn update_selected(&mut self, value: T) {
55        self.items[self.selected] = value;
56    }
57}
58
59impl<T> Widget for &SelectableList<T> {
60    fn render(self, area: Rect, buf: &mut Buffer) {
61        let list_items: Vec<ListItem> = self
62            .items
63            .iter()
64            .enumerate()
65            .map(|(i, item)| {
66                let content = (self.formatter)(item);
67                let style = if i == self.selected {
68                    Style::default().fg(self.bg).bg(self.fg)
69                } else {
70                    Style::default()
71                };
72                ListItem::new(Line::from(Span::styled(content, style)))
73            })
74            .collect();
75
76        let list = List::new(list_items).block(
77            Block::default()
78                .borders(Borders::ALL)
79                .title(self.title.clone()),
80        );
81
82        list.render(area, buf);
83    }
84}