1use crate::widget::row::{ScrollAmount, ScrollDirection};
2use ratatui::widgets::TableState as TuiState;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum TableSize {
7 Normal,
9 Compact,
11 Minimized,
13}
14
15impl TableSize {
16 pub fn set_minimized(&mut self, minimized: bool) {
18 *self = if minimized {
19 Self::Minimized
20 } else {
21 Self::Normal
22 }
23 }
24
25 pub fn next(&self) -> Self {
27 match self {
28 Self::Normal => Self::Compact,
29 Self::Compact => Self::Minimized,
30 _ => Self::Normal,
31 }
32 }
33}
34
35#[derive(Clone, Debug)]
37pub struct TableState {
38 pub tui: TuiState,
40 pub scroll: ScrollAmount,
42 pub size: TableSize,
44 pub minimize_threshold: u16,
46}
47
48impl Default for TableState {
49 fn default() -> Self {
50 Self {
51 tui: TuiState::default(),
52 scroll: ScrollAmount::default(),
53 size: TableSize::Normal,
54 minimize_threshold: 90,
55 }
56 }
57}
58
59#[derive(Clone, Debug)]
61pub struct StatefulTable<T: Clone> {
62 pub default_items: Vec<T>,
64 pub items: Vec<T>,
66 pub state: TableState,
68}
69
70impl<T: Clone> StatefulTable<T> {
71 pub fn new(items: Vec<T>, mut state: TableState) -> StatefulTable<T> {
73 state.tui.select(Some(0));
74 Self {
75 default_items: items.clone(),
76 items,
77 state,
78 }
79 }
80
81 pub fn with_items(items: Vec<T>) -> StatefulTable<T> {
83 Self::new(items, TableState::default())
84 }
85
86 pub fn selected(&self) -> Option<&T> {
88 self.items.get(self.state.tui.selected()?)
89 }
90
91 pub fn next(&mut self) {
93 let i = match self.state.tui.selected() {
94 Some(i) => {
95 if i >= self.items.len().checked_sub(1).unwrap_or(i) {
96 0
97 } else {
98 i + 1
99 }
100 }
101 None => 0,
102 };
103 self.state.tui.select(Some(i));
104 self.reset_scroll();
105 }
106
107 pub fn previous(&mut self) {
109 let i = match self.state.tui.selected() {
110 Some(i) => {
111 if i == 0 {
112 self.items.len().checked_sub(1).unwrap_or(i)
113 } else {
114 i - 1
115 }
116 }
117 None => 0,
118 };
119 self.state.tui.select(Some(i));
120 self.reset_scroll();
121 }
122
123 pub fn scroll_row(&mut self, direction: ScrollDirection) {
126 match direction {
127 ScrollDirection::Up(value) => {
128 self.state.scroll.vertical =
129 self.state.scroll.vertical.saturating_sub(value);
130 }
131 ScrollDirection::Right(value) => {
132 self.state.scroll.horizontal = self
133 .state
134 .scroll
135 .horizontal
136 .checked_add(value)
137 .unwrap_or(self.state.scroll.horizontal)
138 }
139 ScrollDirection::Down(value) => {
140 self.state.scroll.vertical = self
141 .state
142 .scroll
143 .vertical
144 .checked_add(value)
145 .unwrap_or(self.state.scroll.vertical)
146 }
147 ScrollDirection::Left(value) => {
148 self.state.scroll.horizontal =
149 self.state.scroll.horizontal.saturating_sub(value);
150 }
151 _ => {}
152 }
153 }
154
155 pub fn reset_state(&mut self) {
157 self.items.clone_from(&self.default_items);
158 self.state.tui.select(Some(0));
159 }
160
161 pub fn reset_scroll(&mut self) {
163 self.state.scroll = ScrollAmount::default();
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170 use pretty_assertions::assert_eq;
171 #[test]
172 fn test_widget_table() {
173 let mut table =
174 StatefulTable::with_items(vec!["data1", "data2", "data3"]);
175 table.state.tui.select(Some(1));
176 assert_eq!(Some(&"data2"), table.selected());
177 table.next();
178 assert_eq!(Some(2), table.state.tui.selected());
179 table.previous();
180 assert_eq!(Some(1), table.state.tui.selected());
181 table.reset_scroll();
182 assert_eq!(
183 "ScrollAmount { vertical: 0, horizontal: 0 }",
184 &format!("{:?}", table.state.scroll)
185 );
186 table.scroll_row(ScrollDirection::Down(3));
187 table.scroll_row(ScrollDirection::Right(2));
188 assert_eq!(
189 "ScrollAmount { vertical: 3, horizontal: 2 }",
190 &format!("{:?}", table.state.scroll)
191 );
192 table.scroll_row(ScrollDirection::Up(1));
193 table.scroll_row(ScrollDirection::Left(1));
194 assert_eq!(
195 "ScrollAmount { vertical: 2, horizontal: 1 }",
196 &format!("{:?}", table.state.scroll)
197 );
198 table.reset_state();
199 assert_eq!(Some(0), table.state.tui.selected());
200 assert_eq!(table.default_items, table.items);
201 assert_eq!(TableSize::Normal, table.state.size);
202 table.state.size = TableSize::Minimized;
203 table.state.size.set_minimized(false);
204 assert_eq!(TableSize::Compact, table.state.size.next());
205 }
206}