rustic_rs/commands/tui/widgets/
select_table.rs1use super::{
2 Color, Constraint, Draw, Event, Frame, KeyCode, KeyEventKind, Layout, Modifier, ProcessEvent,
3 Rect, Row, Scrollbar, ScrollbarOrientation, ScrollbarState, SizedWidget, Style, Stylize, Table,
4 TableState, Text, layout, style,
5};
6use std::iter::once;
7use style::palette::tailwind;
8
9struct TableColors {
10 buffer_bg: Color,
11 header_bg: Color,
12 header_fg: Color,
13 row_fg: Color,
14 selected_style_fg: Color,
15 normal_row_color: Color,
16 alt_row_color: Color,
17}
18
19impl TableColors {
20 fn new(color: &tailwind::Palette) -> Self {
21 Self {
22 buffer_bg: tailwind::SLATE.c950,
23 header_bg: color.c900,
24 header_fg: tailwind::SLATE.c200,
25 row_fg: tailwind::SLATE.c200,
26 selected_style_fg: color.c400,
27 normal_row_color: tailwind::SLATE.c950,
28 alt_row_color: tailwind::SLATE.c900,
29 }
30 }
31}
32
33pub struct SelectTable {
34 header: Vec<Text<'static>>,
35 table: Table<'static>,
36 state: TableState,
37 scroll_state: ScrollbarState,
38 rows: usize,
39 rows_display: usize,
40 row_height: usize,
41}
42
43impl SelectTable {
44 pub fn new(header: Vec<Text<'static>>) -> Self {
45 let table = Table::default();
46
47 Self {
48 header,
49 table,
50 state: TableState::default(),
51 scroll_state: ScrollbarState::new(0),
52 rows: 0,
53 rows_display: 0,
54 row_height: 0,
55 }
56 }
57
58 pub fn set_content(&mut self, content: Vec<Vec<Text<'static>>>, row_height: usize) {
59 let colors = TableColors::new(&tailwind::BLUE);
60 let selected_style = Style::default()
61 .add_modifier(Modifier::REVERSED)
62 .fg(colors.selected_style_fg);
63
64 let header_style = Style::default().fg(colors.header_fg).bg(colors.header_bg);
65
66 self.row_height = row_height;
67 let widths = once(&self.header)
68 .chain(content.iter())
69 .map(|row| row.iter().map(Text::width).collect())
70 .reduce(|widths: Vec<usize>, row| {
71 row.iter()
72 .zip(widths.iter())
73 .map(|(r, w)| r.max(w))
74 .copied()
75 .collect()
76 })
77 .unwrap_or_default();
78
79 self.rows = content.len();
80 self.scroll_state = ScrollbarState::new(self.rows * self.row_height);
81
82 let content = content.into_iter().enumerate().map(|(i, row)| {
83 let color = match i % 2 {
84 0 => colors.normal_row_color,
85 _ => colors.alt_row_color,
86 };
87 Row::new(row)
88 .style(Style::new().fg(colors.row_fg).bg(color))
89 .height(self.row_height.try_into().unwrap())
90 });
91
92 self.table = Table::default()
93 .header(Row::new(self.header.clone()).style(header_style))
94 .row_highlight_style(selected_style)
95 .bg(colors.buffer_bg)
96 .widths(widths.iter().map(|w| {
97 (*w).try_into()
98 .ok()
99 .map_or(Constraint::Min(0), Constraint::Length)
100 }))
101 .flex(layout::Flex::SpaceBetween)
102 .rows(content);
103 }
104
105 pub fn selected(&self) -> Option<usize> {
106 self.state.selected()
107 }
108
109 pub fn select(&mut self, index: Option<usize>) {
110 self.state.select(index);
111 }
112
113 pub fn set_to(&mut self, i: usize) {
114 self.state.select(Some(i));
115 self.scroll_state = self.scroll_state.position(i * self.row_height);
116 }
117
118 pub fn go_forward(&mut self, step: usize) {
119 if let Some(selected_old) = self.state.selected() {
120 let selected = (selected_old + step).min(self.rows - 1);
121 self.set_to(selected);
122 }
123 }
124
125 pub fn go_back(&mut self, step: usize) {
126 if let Some(selected_old) = self.state.selected() {
127 let selected = selected_old.saturating_sub(step);
128 self.set_to(selected);
129 }
130 }
131
132 pub fn next(&mut self) {
133 self.go_forward(1);
134 }
135
136 pub fn page_down(&mut self) {
137 self.go_forward(self.rows_display);
138 }
139
140 pub fn previous(&mut self) {
141 self.go_back(1);
142 }
143
144 pub fn page_up(&mut self) {
145 self.go_back(self.rows_display);
146 }
147
148 pub fn home(&mut self) {
149 if self.state.selected().is_some() {
150 self.set_to(0);
151 }
152 }
153
154 pub fn end(&mut self) {
155 if self.state.selected().is_some() {
156 self.set_to(self.rows - 1);
157 }
158 }
159
160 pub fn set_rows(&mut self, rows: usize) {
161 self.rows_display = rows / self.row_height;
162 }
163}
164
165impl ProcessEvent for SelectTable {
166 type Result = ();
167 fn input(&mut self, event: Event) {
168 use KeyCode::{Down, End, Home, PageDown, PageUp, Up};
169 match event {
170 Event::Key(key) if key.kind == KeyEventKind::Press => match key.code {
171 Down => self.next(),
172 Up => self.previous(),
173 PageDown => self.page_down(),
174 PageUp => self.page_up(),
175 Home => self.home(),
176 End => self.end(),
177 _ => {}
178 },
179 _ => {}
180 }
181 }
182}
183
184impl SizedWidget for SelectTable {}
185
186impl Draw for SelectTable {
187 fn draw(&mut self, area: Rect, f: &mut Frame<'_>) {
188 self.set_rows(area.height.into());
189 let chunks = Layout::horizontal([Constraint::Min(0), Constraint::Length(1)]).split(area);
190 f.render_stateful_widget(&self.table, chunks[0], &mut self.state);
191 f.render_stateful_widget(
192 Scrollbar::default()
193 .orientation(ScrollbarOrientation::VerticalRight)
194 .begin_symbol(None)
195 .end_symbol(None),
196 chunks[1],
197 &mut self.scroll_state,
198 );
199 }
200}