ratatui_toolkit/vt100_term/
grid.rs1use super::cell::Cell;
7use std::collections::VecDeque;
8
9#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub struct Size {
12 pub rows: usize,
13 pub cols: usize,
14}
15
16#[derive(Debug, Clone)]
18pub struct Row {
19 cells: Vec<Cell>,
20 wrapped: bool, }
22
23impl Row {
24 fn new(cols: usize) -> Self {
26 Self {
27 cells: vec![Cell::default(); cols],
28 wrapped: false,
29 }
30 }
31
32 pub fn cols(&self) -> usize {
34 self.cells.len()
35 }
36
37 pub fn wrapped(&self) -> bool {
39 self.wrapped
40 }
41
42 pub fn set_wrapped(&mut self, wrapped: bool) {
44 self.wrapped = wrapped;
45 }
46
47 pub fn get(&self, col: usize) -> Option<&Cell> {
49 self.cells.get(col)
50 }
51
52 pub fn get_mut(&mut self, col: usize) -> Option<&mut Cell> {
54 self.cells.get_mut(col)
55 }
56
57 pub fn set(&mut self, col: usize, cell: Cell) {
59 if col < self.cells.len() {
60 self.cells[col] = cell;
61 }
62 }
63
64 pub fn text_content(&self, start: usize, width: usize) -> String {
66 let end = (start + width).min(self.cells.len());
67 self.cells[start..end]
68 .iter()
69 .map(|cell| cell.text.as_str())
70 .collect::<String>()
71 .trim_end()
72 .to_string()
73 }
74}
75
76#[derive(Debug, Clone)]
81pub struct Grid {
82 size: Size,
84
85 rows: VecDeque<Row>,
87
88 scrollback_len: usize,
90
91 scrollback_offset: usize,
93}
94
95impl Grid {
96 pub fn new(rows: usize, cols: usize, scrollback_len: usize) -> Self {
98 let size = Size { rows, cols };
99 let mut grid_rows = VecDeque::with_capacity(rows + scrollback_len);
100
101 for _ in 0..rows {
102 grid_rows.push_back(Row::new(cols));
103 }
104
105 Self {
106 size,
107 rows: grid_rows,
108 scrollback_len,
109 scrollback_offset: 0,
110 }
111 }
112
113 pub fn size(&self) -> Size {
115 self.size
116 }
117
118 pub fn scrollback(&self) -> usize {
120 self.scrollback_offset
121 }
122
123 pub fn scrollback_len(&self) -> usize {
125 self.scrollback_len
126 }
127
128 pub fn set_scrollback(&mut self, offset: usize) {
130 self.scrollback_offset = offset.min(self.row0());
131 }
132
133 pub fn scroll_screen_up(&mut self, n: usize) {
135 self.scrollback_offset = (self.scrollback_offset + n).min(self.row0());
136 }
137
138 pub fn scroll_screen_down(&mut self, n: usize) {
140 self.scrollback_offset = self.scrollback_offset.saturating_sub(n);
141 }
142
143 fn row0(&self) -> usize {
145 self.rows.len().saturating_sub(self.size.rows)
146 }
147
148 pub fn visible_rows(&self) -> impl Iterator<Item = &Row> {
150 let start = self.row0().saturating_sub(self.scrollback_offset);
151 self.rows.iter().skip(start).take(self.size.rows)
152 }
153
154 pub fn cell(&self, row: usize, col: usize) -> Option<&Cell> {
156 let start = self.row0().saturating_sub(self.scrollback_offset);
157 let actual_row = start + row;
158 self.rows.get(actual_row)?.get(col)
159 }
160
161 pub fn cell_mut(&mut self, row: usize, col: usize) -> Option<&mut Cell> {
163 let row_index = self.row0() + row;
164 self.rows.get_mut(row_index)?.get_mut(col)
165 }
166
167 pub fn row(&self, row: usize) -> Option<&Row> {
169 let start = self.row0();
170 self.rows.get(start + row)
171 }
172
173 pub fn scroll_up(&mut self, count: usize) {
175 for _ in 0..count.min(self.size.rows) {
176 let row0 = self.row0();
177
178 self.rows
180 .insert(row0 + self.size.rows, Row::new(self.size.cols));
181
182 if self.scrollback_len > 0 {
184 if let Some(removed) = self.rows.remove(row0) {
185 self.rows.insert(row0, removed);
187 }
188
189 while self.rows.len() > self.size.rows + self.scrollback_len {
191 self.rows.pop_front();
192 }
193
194 if self.scrollback_offset > 0 {
196 self.scrollback_offset = self.row0().min(self.scrollback_offset + 1);
197 }
198 } else {
199 self.rows.remove(row0);
200 }
201 }
202 }
203
204 pub fn resize(&mut self, rows: usize, cols: usize) {
206 self.size = Size { rows, cols };
207
208 while self.rows.len() < rows {
210 self.rows.push_back(Row::new(cols));
211 }
212
213 for row in &mut self.rows {
215 row.cells.resize(cols, Cell::default());
216 }
217 }
218
219 pub fn get_selected_text(&self, low_x: i32, low_y: i32, high_x: i32, high_y: i32) -> String {
221 let mut contents = String::new();
222 let lines_len = high_y - low_y + 1;
223
224 for i in 0..lines_len {
225 let row_idx = (self.row0() as i32 + low_y + i) as usize;
226
227 if let Some(row) = self.rows.get(row_idx) {
228 let start = if i == 0 { low_x.max(0) as usize } else { 0 };
229 let width = if i == lines_len - 1 {
230 (high_x + 1 - start as i32).max(0) as usize
231 } else {
232 row.cols().saturating_sub(start)
233 };
234
235 let text = row.text_content(start, width);
236 contents.push_str(&text);
237
238 if i != lines_len - 1 && !row.wrapped() {
240 contents.push('\n');
241 }
242 }
243 }
244
245 contents
246 }
247
248 pub fn clear(&mut self) {
250 for row in &mut self.rows {
251 for cell in &mut row.cells {
252 *cell = Cell::default();
253 }
254 }
255 }
256}