1use crate::common::PageSize;
2
3#[derive(Debug, Clone)]
5pub struct TableState<T> {
6 pub items: Vec<T>,
7 pub selected: usize,
8 pub loading: bool,
9 pub filter: String,
10 pub page_size: PageSize,
11 pub expanded_item: Option<usize>,
12 pub scroll_offset: usize,
13}
14
15impl<T> Default for TableState<T> {
16 fn default() -> Self {
17 Self::new()
18 }
19}
20
21impl<T> TableState<T> {
22 pub fn new() -> Self {
23 Self {
24 items: Vec::new(),
25 selected: 0,
26 loading: false,
27 filter: String::new(),
28 page_size: PageSize::Fifty,
29 expanded_item: None,
30 scroll_offset: 0,
31 }
32 }
33
34 pub fn filtered<F>(&self, predicate: F) -> Vec<&T>
35 where
36 F: Fn(&T) -> bool,
37 {
38 self.items.iter().filter(|item| predicate(item)).collect()
39 }
40
41 pub fn paginate<'a>(&self, filtered: &'a [&'a T]) -> &'a [&'a T] {
42 let page_size = self.page_size.value();
43 let end_idx = (self.scroll_offset + page_size).min(filtered.len());
44 &filtered[self.scroll_offset..end_idx]
45 }
46
47 pub fn current_page(&self, _total_items: usize) -> usize {
48 self.scroll_offset / self.page_size.value()
49 }
50
51 pub fn total_pages(&self, total_items: usize) -> usize {
52 total_items.div_ceil(self.page_size.value())
53 }
54
55 pub fn next_item(&mut self, max: usize) {
56 if max > 0 {
57 let new_selected = (self.selected + 1).min(max - 1);
58 if new_selected != self.selected {
59 self.selected = new_selected;
60
61 let page_size = self.page_size.value();
63 if self.selected >= self.scroll_offset + page_size {
64 self.scroll_offset = self.selected - page_size + 1;
65 }
66 }
67 }
68 }
69
70 pub fn prev_item(&mut self) {
71 if self.selected > 0 {
72 self.selected -= 1;
73
74 if self.selected < self.scroll_offset {
76 self.scroll_offset = self.selected;
77 }
78 }
79 }
80
81 pub fn page_down(&mut self, max: usize) {
82 if max > 0 {
83 let page_size = self.page_size.value();
84 self.selected = (self.selected + 10).min(max - 1);
85
86 let current_page = self.selected / page_size;
88 self.scroll_offset = current_page * page_size;
89 }
90 }
91
92 pub fn page_up(&mut self) {
93 let page_size = self.page_size.value();
94 self.selected = self.selected.saturating_sub(10);
95
96 let current_page = self.selected / page_size;
98 self.scroll_offset = current_page * page_size;
99 }
100
101 pub fn snap_to_page(&mut self) {
102 let page_size = self.page_size.value();
103 let current_page = self.selected / page_size;
104 self.scroll_offset = current_page * page_size;
105 }
106
107 pub fn toggle_expand(&mut self) {
108 self.expanded_item = if self.expanded_item == Some(self.selected) {
109 None
110 } else {
111 Some(self.selected)
112 };
113 }
114
115 pub fn collapse(&mut self) {
116 self.expanded_item = None;
117 }
118
119 pub fn expand(&mut self) {
120 self.expanded_item = Some(self.selected);
121 }
122
123 pub fn is_expanded(&self) -> bool {
124 self.expanded_item == Some(self.selected)
125 }
126
127 pub fn has_expanded_item(&self) -> bool {
128 self.expanded_item.is_some()
129 }
130
131 pub fn goto_page(&mut self, page: usize, total_items: usize) {
132 let page_size = self.page_size.value();
133 let target = (page - 1) * page_size;
134 let max = total_items.saturating_sub(1);
135 self.selected = target.min(max);
136 self.scroll_offset = target.min(total_items.saturating_sub(page_size));
137 }
138
139 pub fn reset(&mut self) {
140 self.selected = 0;
141 self.scroll_offset = 0;
142 }
143
144 pub fn get_selected<'a>(&self, filtered: &'a [&'a T]) -> Option<&'a T> {
145 filtered.get(self.selected).copied()
146 }
147
148 pub fn filter_push(&mut self, c: char) {
150 self.filter.push(c);
151 self.reset();
152 }
153
154 pub fn filter_pop(&mut self) {
156 self.filter.pop();
157 self.reset();
158 }
159
160 pub fn filter_clear(&mut self) {
162 self.filter.clear();
163 self.reset();
164 }
165}
166
167#[cfg(test)]
168mod tests {
169 use super::*;
170
171 #[test]
172 fn test_table_state_default() {
173 let state: TableState<String> = TableState::new();
174 assert_eq!(state.selected, 0);
175 assert!(!state.loading);
176 assert_eq!(state.filter, "");
177 assert_eq!(state.page_size, PageSize::Fifty);
178 assert_eq!(state.expanded_item, None);
179 }
180
181 #[test]
182 fn test_filtered() {
183 let mut state = TableState::new();
184 state.items = vec![
185 "apple".to_string(),
186 "banana".to_string(),
187 "apricot".to_string(),
188 ];
189
190 let filtered = state.filtered(|item| item.starts_with('a'));
191 assert_eq!(filtered.len(), 2);
192 }
193
194 #[test]
195 fn test_paginate() {
196 let state = TableState::<String> {
197 page_size: PageSize::Ten,
198 selected: 0,
199 ..TableState::new()
200 };
201
202 let items: Vec<String> = (0..25).map(|i| i.to_string()).collect();
203 let refs: Vec<&String> = items.iter().collect();
204
205 let page = state.paginate(&refs);
206 assert_eq!(page.len(), 10);
207 }
208
209 #[test]
210 fn test_navigation() {
211 let mut state = TableState::<String>::new();
212
213 state.next_item(10);
214 assert_eq!(state.selected, 1);
215
216 state.prev_item();
217 assert_eq!(state.selected, 0);
218
219 state.page_down(100);
220 assert_eq!(state.selected, 10);
221
222 state.page_up();
223 assert_eq!(state.selected, 0);
224 }
225
226 #[test]
227 fn test_expand_toggle() {
228 let mut state = TableState::<String>::new();
229
230 assert!(!state.is_expanded());
231
232 state.toggle_expand();
233 assert!(state.is_expanded());
234
235 state.toggle_expand();
236 assert!(!state.is_expanded());
237
238 state.toggle_expand();
239 state.collapse();
240 assert!(!state.is_expanded());
241 }
242}