rsnaker/graphics/menus/
selectable_item.rs1use ratatui::{
2 buffer::Buffer,
3 layout::Rect,
4 style::{Color, Style},
5 text::{Line, Span},
6 widgets::{Block, Borders, List, ListItem, Widget},
7};
8pub 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}