ratatui_toolkit/primitives/termtui/
grid.rs1use crate::primitives::termtui::row::Row;
4use crate::primitives::termtui::size::Size;
5use std::collections::VecDeque;
6
7#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
9pub struct Pos {
10 pub col: u16,
11 pub row: u16,
12}
13
14impl Pos {
15 pub fn new(col: u16, row: u16) -> Self {
16 Self { col, row }
17 }
18}
19
20#[derive(Clone, Debug)]
26pub struct Grid {
27 rows: VecDeque<Row>,
29 size: Size,
31 pos: Pos,
33 scrollback_len: usize,
35 scrollback_offset: usize,
37 used_rows: usize,
39 scroll_top: u16,
41 scroll_bottom: u16,
43 saved_pos: Option<Pos>,
45}
46
47impl Grid {
48 pub fn new(size: Size, scrollback_len: usize) -> Self {
50 let rows = (0..size.rows)
51 .map(|_| Row::new(size.cols))
52 .collect::<VecDeque<_>>();
53
54 Self {
55 rows,
56 size,
57 pos: Pos::default(),
58 scrollback_len,
59 scrollback_offset: 0,
60 used_rows: 0,
61 scroll_top: 0,
62 scroll_bottom: size.rows,
63 saved_pos: None,
64 }
65 }
66
67 pub fn size(&self) -> Size {
69 self.size
70 }
71
72 pub fn pos(&self) -> Pos {
74 self.pos
75 }
76
77 pub fn set_pos(&mut self, pos: Pos) {
79 self.pos = Pos {
80 col: pos.col.min(self.size.cols.saturating_sub(1)),
81 row: pos.row.min(self.size.rows.saturating_sub(1)),
82 };
83 }
84
85 pub fn set_col(&mut self, col: u16) {
87 self.pos.col = col.min(self.size.cols.saturating_sub(1));
88 }
89
90 pub fn set_row(&mut self, row: u16) {
92 self.pos.row = row.min(self.size.rows.saturating_sub(1));
93 }
94
95 pub fn save_pos(&mut self) {
97 self.saved_pos = Some(self.pos);
98 }
99
100 pub fn restore_pos(&mut self) {
102 if let Some(pos) = self.saved_pos {
103 self.pos = pos;
104 }
105 }
106
107 fn row0(&self) -> usize {
109 self.rows.len().saturating_sub(self.size.rows as usize)
110 }
111
112 pub fn scrollback(&self) -> usize {
114 self.scrollback_offset
115 }
116
117 pub fn set_scrollback(&mut self, offset: usize) {
119 let max_offset = self.row0();
120 self.scrollback_offset = offset.min(max_offset);
121 }
122
123 pub fn scrollback_available(&self) -> usize {
125 self.row0()
126 }
127
128 pub fn set_scroll_region(&mut self, top: u16, bottom: u16) {
130 self.scroll_top = top.min(self.size.rows.saturating_sub(1));
131 self.scroll_bottom = bottom.min(self.size.rows).max(self.scroll_top + 1);
132 }
133
134 pub fn reset_scroll_region(&mut self) {
136 self.scroll_top = 0;
137 self.scroll_bottom = self.size.rows;
138 }
139
140 pub fn visible_row(&self, row: u16) -> Option<&Row> {
142 let idx = self.row0() + row as usize;
143 let idx = idx.saturating_sub(self.scrollback_offset);
144 self.rows.get(idx)
145 }
146
147 pub fn drawing_row(&self, row: u16) -> Option<&Row> {
149 let idx = self.row0() + row as usize;
150 self.rows.get(idx)
151 }
152
153 pub fn drawing_row_mut(&mut self, row: u16) -> Option<&mut Row> {
155 let idx = self.row0() + row as usize;
156 if row as usize >= self.used_rows {
157 self.used_rows = row as usize + 1;
158 }
159 self.rows.get_mut(idx)
160 }
161
162 pub fn current_row(&self) -> Option<&Row> {
164 self.drawing_row(self.pos.row)
165 }
166
167 pub fn current_row_mut(&mut self) -> Option<&mut Row> {
169 let row = self.pos.row;
170 self.drawing_row_mut(row)
171 }
172
173 pub fn scroll_up(&mut self, count: usize) {
175 for _ in 0..count {
176 if self.scroll_top == 0 && self.scroll_bottom == self.size.rows {
178 self.rows.push_back(Row::new(self.size.cols));
180
181 while self.rows.len() > self.size.rows as usize + self.scrollback_len {
183 self.rows.pop_front();
184 }
185 } else {
186 let top_idx = self.row0() + self.scroll_top as usize;
188 let bottom_idx = self.row0() + self.scroll_bottom as usize - 1;
189
190 if top_idx < self.rows.len() && bottom_idx < self.rows.len() {
191 self.rows.remove(top_idx);
193 self.rows.insert(bottom_idx, Row::new(self.size.cols));
195 }
196 }
197 }
198 }
199
200 pub fn scroll_down(&mut self, count: usize) {
202 for _ in 0..count {
203 let top_idx = self.row0() + self.scroll_top as usize;
204 let bottom_idx = self.row0() + self.scroll_bottom as usize - 1;
205
206 if top_idx < self.rows.len() && bottom_idx < self.rows.len() {
207 self.rows.remove(bottom_idx);
209 self.rows.insert(top_idx, Row::new(self.size.cols));
211 }
212 }
213 }
214
215 pub fn clear(&mut self) {
217 for row in self.rows.iter_mut() {
218 row.clear();
219 }
220 self.used_rows = 0;
221 }
222
223 pub fn clear_below(&mut self) {
225 let pos_row = self.pos.row;
226 let pos_col = self.pos.col;
227 let cols = self.size.cols;
228 let rows = self.size.rows;
229
230 if let Some(row) = self.drawing_row_mut(pos_row) {
232 row.erase(pos_col, cols);
233 }
234
235 for r in (pos_row + 1)..rows {
237 if let Some(row) = self.drawing_row_mut(r) {
238 row.clear();
239 }
240 }
241 }
242
243 pub fn clear_above(&mut self) {
245 let pos_row = self.pos.row;
246 let pos_col = self.pos.col;
247
248 for r in 0..pos_row {
250 if let Some(row) = self.drawing_row_mut(r) {
251 row.clear();
252 }
253 }
254
255 if let Some(row) = self.drawing_row_mut(pos_row) {
257 row.erase(0, pos_col + 1);
258 }
259 }
260
261 pub fn resize(&mut self, new_size: Size) {
263 for row in self.rows.iter_mut() {
265 row.resize(new_size.cols);
266 }
267
268 while self.rows.len() < new_size.rows as usize {
270 self.rows.push_back(Row::new(new_size.cols));
271 }
272
273 self.size = new_size;
275
276 self.pos.col = self.pos.col.min(new_size.cols.saturating_sub(1));
278 self.pos.row = self.pos.row.min(new_size.rows.saturating_sub(1));
279 self.scroll_bottom = new_size.rows;
280 }
281
282 pub fn get_selected_text(&self, low_x: i32, low_y: i32, high_x: i32, high_y: i32) -> String {
286 let mut contents = String::new();
287
288 let row0 = self.row0() as i32;
289 let start_row = (row0 + low_y).max(0) as usize;
290 let end_row = (row0 + high_y).max(0) as usize;
291
292 for (i, row) in self.rows.iter().enumerate() {
293 if i < start_row || i > end_row {
294 continue;
295 }
296
297 let width = row.width();
298
299 let start_col = if i == start_row {
301 (low_x.max(0) as u16).min(width)
302 } else {
303 0
304 };
305
306 let end_col = if i == end_row {
307 (high_x.max(0) as u16).min(width)
308 } else {
309 width
310 };
311
312 row.write_contents(&mut contents, start_col, end_col);
314
315 if i != end_row && !row.wrapped() {
317 contents.push('\n');
318 }
319 }
320
321 contents
323 .lines()
324 .map(|line| line.trim_end())
325 .collect::<Vec<_>>()
326 .join("\n")
327 }
328
329 pub fn visible_rows(&self) -> impl Iterator<Item = &Row> {
331 let start = self.row0().saturating_sub(self.scrollback_offset);
332 let end = start + self.size.rows as usize;
333 self.rows.iter().skip(start).take(end - start)
334 }
335
336 pub fn drawing_rows(&self) -> impl Iterator<Item = &Row> {
338 let start = self.row0();
339 self.rows.iter().skip(start).take(self.size.rows as usize)
340 }
341}
342
343#[cfg(test)]
344mod tests {
345 use super::*;
346
347 #[test]
348 fn test_grid_new() {
349 let grid = Grid::new(Size::new(80, 24), 1000);
350 assert_eq!(grid.size().cols, 80);
351 assert_eq!(grid.size().rows, 24);
352 assert_eq!(grid.pos().col, 0);
353 assert_eq!(grid.pos().row, 0);
354 }
355
356 #[test]
357 fn test_grid_cursor() {
358 let mut grid = Grid::new(Size::new(80, 24), 1000);
359
360 grid.set_pos(Pos::new(10, 5));
361 assert_eq!(grid.pos(), Pos::new(10, 5));
362
363 grid.set_pos(Pos::new(100, 50));
365 assert_eq!(grid.pos(), Pos::new(79, 23));
366 }
367
368 #[test]
369 fn test_grid_scroll_up() {
370 let mut grid = Grid::new(Size::new(80, 24), 100);
371
372 if let Some(row) = grid.drawing_row_mut(0) {
374 if let Some(cell) = row.get_mut(0) {
375 cell.set_text("A");
376 }
377 }
378
379 grid.scroll_up(1);
381
382 assert_eq!(grid.scrollback_available(), 1);
384 }
385
386 #[test]
387 fn test_grid_scrollback() {
388 let mut grid = Grid::new(Size::new(80, 24), 100);
389
390 for _ in 0..10 {
392 grid.scroll_up(1);
393 }
394
395 assert_eq!(grid.scrollback_available(), 10);
396
397 grid.set_scrollback(5);
399 assert_eq!(grid.scrollback(), 5);
400
401 grid.set_scrollback(1000);
403 assert_eq!(grid.scrollback(), 10);
404 }
405
406 #[test]
407 fn test_grid_get_selected_text() {
408 let mut grid = Grid::new(Size::new(80, 24), 100);
409
410 if let Some(row) = grid.drawing_row_mut(0) {
412 for (i, c) in "Hello World".chars().enumerate() {
413 if let Some(cell) = row.get_mut(i as u16) {
414 cell.set_text(c.to_string());
415 }
416 }
417 }
418
419 let text = grid.get_selected_text(0, 0, 5, 0);
420 assert_eq!(text, "Hello");
421 }
422
423 #[test]
424 fn test_grid_resize() {
425 let mut grid = Grid::new(Size::new(80, 24), 100);
426
427 grid.resize(Size::new(120, 40));
428 assert_eq!(grid.size().cols, 120);
429 assert_eq!(grid.size().rows, 40);
430 }
431}