terminal_ui/data/
lazy_stateful_table.rs1use tui::widgets::TableState;
2
3use super::Stateful;
4
5pub const CAPACITY: usize = 1000;
6const ROOM: usize = 100;
7
8pub trait LazySource<T> {
9 fn source(&self, from: usize, to: usize) -> Vec<T>;
10 fn source_elements_containing(&self, element: usize, quantity: usize) -> (Vec<T>, usize, usize);
11}
12
13enum Area {
14 Below,
15 Inside,
16 Above,
17}
18
19impl Area {
20 fn current_area(i: usize, elements: usize) -> Area {
21 match i {
22 i if i < ((elements / 2).overflowing_sub(ROOM).0) => Area::Below,
23 i if (((elements / 2).overflowing_sub(ROOM).0)..=(elements / 2 + ROOM)).contains(&i) => Area::Inside,
24 i if i > (elements / 2 + ROOM) => Area::Above,
25 _ => Area::Below,
26 }
27 }
28}
29
30pub struct LazyStatefulTable<T> {
31 pub state: TableState,
32 pub items: Vec<T>,
33 offset: usize,
34 source: Box<dyn LazySource<T>>,
35}
36
37impl<T: Clone> LazyStatefulTable<T> {
38 pub fn new(source: Box<dyn LazySource<T>>) -> LazyStatefulTable<T> {
39 let items = source.source(0, CAPACITY);
40 LazyStatefulTable {
41 state: TableState::default(),
42 items,
43 offset: 0,
44 source,
45 }
46 }
47
48 pub fn reload(&mut self) {
49 self.items = self.source.source(self.offset, CAPACITY);
50
51 self.state.select(match self.state.selected() {
52 Some(i) => Some(i.min(if !self.items.is_empty() {self.items.len() - 1} else {0})),
53 _ => None,
54 });
55 }
56
57
58 pub fn navigate_to(&mut self, element: usize) {
59 let source = self.source.source_elements_containing(element, CAPACITY);
60
61 self.items = source.0;
62 self.offset = source.1;
63 self.state.select(Some(source.2));
64
65 }
66
67
68 pub fn navigate_to_bottom(&mut self) {
69 let mut current = self.next();
70 let mut next = self.next();
71 while current != next {
72 current = next;
73 next = self.next();
74 }
75 }
76
77 pub fn get_selected_item(&self) -> Option<T>{
78 match self.state.selected() {
79 Some(i) => self.items.get(i).cloned(),
80 None => None
81 }
82 }
83
84 pub fn clear(&mut self) {
85 self.state.select(None);
86 self.items.clear();
87 }
88
89 fn select_and_set_scroll_on_top(&mut self, index: usize) {
90 unsafe {
94 self.state = std::mem::transmute::<(usize, Option<usize>), TableState>((index, None))
95 }
96 }
97
98}
99
100impl<T: Clone> Stateful<T> for LazyStatefulTable<T> {
101 fn next(&mut self) -> usize {
102 if self.items.is_empty() {
103 self.items = self.source.source(0, CAPACITY)
104 }
105 if !self.items.is_empty() {
106 let i = match self.state.selected() {
107 Some(i) => match Area::current_area(i, self.items.len()) {
108 Area::Below | Area::Inside => {
109 if (i + 1) < self.items.len() {
110 i + 1
111 } else {
112 i
113 }
114 }
115 Area::Above => {
116 let len = self.items.len();
117 let last_element = len + self.offset;
118
119 let new_data = self.source.source(last_element, last_element + ROOM);
120
121 let received_elements = new_data.len();
122 self.items.rotate_left(received_elements);
123
124 self.items[(len - received_elements)..len]
125 .iter_mut()
126 .zip(new_data)
127 .for_each(|(current, new_data)| *current = new_data);
128 self.offset += received_elements;
129
130 self.state.select(None);
131 i - received_elements + if (i + 1) < len { 1 } else { 0 }
132 }
133 },
134
135 None => 0,
136 };
137 self.state.select(Some(i));
138 }
139
140 self.state.selected().unwrap_or_default()
141 }
142
143 fn previous(&mut self) -> usize {
144 if self.items.is_empty() {
145 self.items = self.source.source(0, CAPACITY)
146 }
147 if !self.items.is_empty() {
148 let i = match self.state.selected() {
149 Some(i) => match Area::current_area(i, self.items.len()) {
150 Area::Above | Area::Inside => {
151 if i > 0 {
152 i - 1
153 } else {
154 i
155 }
156 }
157 Area::Below => {
158 let initial_element = if self.offset > ROOM {
159 self.offset - ROOM
160 } else {
161 0
162 };
163
164 let new_data = self.source.source(initial_element, self.offset);
165
166 let received_elements = new_data.len();
167
168 let selected = i + received_elements - if i > 0 { 1 } else { 0 };
169
170 if received_elements > 0 {
171 self.items.rotate_right(received_elements);
172
173 self.items[0..received_elements]
174 .iter_mut()
175 .zip(new_data)
176 .for_each(|(current, new_data)| *current = new_data);
177 self.offset -= received_elements;
178
179
180 self.select_and_set_scroll_on_top(selected);
181
182
183 }
184 selected
185 }
186 },
187
188 None => 0,
189 };
190 self.state.select(Some(i));
191 }
192
193 self.state.selected().unwrap_or_default()
194 }
195
196 fn unselect(&mut self) {
197 self.state.select(None);
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use super::*;
204
205 struct TestSourcer<T> {
206 items: Vec<T>,
207 }
208
209 impl<T: Clone> LazySource<T> for TestSourcer<T> {
210 fn source(&self, from: usize, to: usize) -> Vec<T> {
211 self.items[from.min(self.items.len())..to.min(self.items.len())].to_vec()
212 }
213
214 fn source_elements_containing(
215 &self,
216 _element: usize,
217 _quantity: usize,
218 ) -> (Vec<T>, usize, usize) {
219 todo!()
220 }
221 }
222
223 #[test]
224 fn source_init() {
225 let test_source = TestSourcer {
226 items: (0..2000_usize).collect(),
227 };
228 let lazy_table = LazyStatefulTable::new(Box::new(test_source));
229
230 assert!(lazy_table.items.len() == CAPACITY)
231 }
232
233 #[test]
234 fn single_next_doesnt_source() {
235 let test_source = TestSourcer {
236 items: (0..2000_usize).collect(),
237 };
238 let mut lazy_table = LazyStatefulTable::new(Box::new(test_source));
239 lazy_table.next();
240 assert!(lazy_table.items[0] == 0 && *lazy_table.items.last().unwrap() == 999);
241 }
242
243 #[test]
244 fn double_next_doesnt_source() {
245 let test_source = TestSourcer {
246 items: (0..2000_usize).collect(),
247 };
248 let mut lazy_table = LazyStatefulTable::new(Box::new(test_source));
249 lazy_table.next();
250 lazy_table.next();
251 assert!(lazy_table.items[0] == 0 && *lazy_table.items.last().unwrap() == 999);
252 }
253
254 #[test]
255 fn next_inside_doesnt_source() {
256 let test_source = TestSourcer {
257 items: (0..2000_usize).collect(),
258 };
259 let mut lazy_table = LazyStatefulTable::new(Box::new(test_source));
260 for _ in 0..(CAPACITY / 2) {
261 lazy_table.next();
262 }
263 assert!(lazy_table.items[0] == 0 && *lazy_table.items.last().unwrap() == 999);
264 }
265
266 #[test]
267 fn next_outside_sources() {
268 let test_source = TestSourcer {
269 items: (0..2000_usize).collect(),
270 };
271 let mut lazy_table = LazyStatefulTable::new(Box::new(test_source));
272 lazy_table.state.select(Some(CAPACITY / 2 + ROOM + 1));
273 lazy_table.next();
274 assert!(lazy_table.items[0] == 100 && *lazy_table.items.last().unwrap() == 1099);
275 }
276
277 #[test]
278 fn previous_outside_sources() {
279 let test_source = TestSourcer {
280 items: (0..2000_usize).collect(),
281 };
282 let mut lazy_table = LazyStatefulTable::new(Box::new(test_source));
283 lazy_table.state.select(Some(CAPACITY / 2 + ROOM + 1));
284 lazy_table.next();
285 lazy_table.state.select(Some(CAPACITY / 2 - ROOM - 1));
286 lazy_table.previous();
287 assert!(lazy_table.items[0] == 0 && *lazy_table.items.last().unwrap() == 999);
288 }
289}