1#[derive(Debug, Clone)]
13pub struct TextGrid {
14 cells: Vec<char>,
15 width: u16,
16 height: u16,
17}
18
19impl TextGrid {
20 pub fn new(width: u16, height: u16) -> Self {
22 let size = (width as usize) * (height as usize);
23 Self {
24 cells: vec![' '; size],
25 width,
26 height,
27 }
28 }
29
30 #[inline]
32 pub fn width(&self) -> u16 {
33 self.width
34 }
35
36 #[inline]
38 pub fn height(&self) -> u16 {
39 self.height
40 }
41
42 #[inline]
44 pub fn len(&self) -> usize {
45 self.cells.len()
46 }
47
48 #[inline]
50 pub fn is_empty(&self) -> bool {
51 self.cells.is_empty()
52 }
53
54 #[inline]
56 fn index(&self, x: u16, y: u16) -> Option<usize> {
57 if x < self.width && y < self.height {
58 Some((y as usize) * (self.width as usize) + (x as usize))
59 } else {
60 None
61 }
62 }
63
64 pub fn get(&self, x: u16, y: u16) -> Option<char> {
66 self.index(x, y).map(|idx| self.cells[idx])
67 }
68
69 pub fn set(&mut self, x: u16, y: u16, ch: char) {
71 if let Some(idx) = self.index(x, y) {
72 self.cells[idx] = ch;
73 }
74 }
75
76 pub fn clear(&mut self) {
78 self.cells.fill(' ');
79 }
80
81 pub fn reset(&mut self) {
83 self.clear();
84 }
85
86 pub fn resize(&mut self, width: u16, height: u16) {
88 self.width = width;
89 self.height = height;
90 let size = (width as usize) * (height as usize);
91 self.cells.clear();
92 self.cells.resize(size, ' ');
93 }
94
95 pub fn write_str(&mut self, x: u16, y: u16, s: &str) {
98 for (i, ch) in s.chars().enumerate() {
99 let Ok(off) = u16::try_from(i) else { break };
103 let Some(pos_x) = x.checked_add(off) else {
104 break;
105 };
106 if pos_x >= self.width {
107 break;
108 }
109 self.set(pos_x, y, ch);
110 }
111 }
112
113 pub fn to_lines(&self) -> Vec<String> {
116 let mut lines = Vec::with_capacity(self.height as usize);
117 for y in 0..self.height {
118 let start = (y as usize) * (self.width as usize);
119 let end = start + (self.width as usize);
120 let line: String = self.cells[start..end].iter().collect();
121 lines.push(line.trim_end().to_string());
122 }
123 lines
124 }
125
126 pub fn cells(&self) -> &[char] {
128 &self.cells
129 }
130
131 pub fn cells_mut(&mut self) -> &mut [char] {
133 &mut self.cells
134 }
135
136 pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, ch: char) {
138 for row in y..y.saturating_add(height).min(self.height) {
139 for col in x..x.saturating_add(width).min(self.width) {
140 self.set(col, row, ch);
141 }
142 }
143 }
144}
145
146impl Default for TextGrid {
147 fn default() -> Self {
148 Self::new(80, 24)
149 }
150}
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_new() {
158 let grid = TextGrid::new(10, 5);
159 assert_eq!(grid.width(), 10);
160 assert_eq!(grid.height(), 5);
161 assert_eq!(grid.len(), 50);
162 assert!(!grid.is_empty());
163 }
164
165 #[test]
166 fn test_default() {
167 let grid = TextGrid::default();
168 assert_eq!(grid.width(), 80);
169 assert_eq!(grid.height(), 24);
170 }
171
172 #[test]
173 fn test_get_set() {
174 let mut grid = TextGrid::new(10, 5);
175 assert_eq!(grid.get(0, 0), Some(' '));
176
177 grid.set(3, 2, 'X');
178 assert_eq!(grid.get(3, 2), Some('X'));
179
180 assert_eq!(grid.get(100, 100), None);
182 }
183
184 #[test]
185 fn test_set_out_of_bounds() {
186 let mut grid = TextGrid::new(10, 5);
187 grid.set(100, 100, 'X'); assert_eq!(grid.get(0, 0), Some(' ')); }
190
191 #[test]
192 fn test_clear() {
193 let mut grid = TextGrid::new(10, 5);
194 grid.set(0, 0, 'X');
195 grid.set(5, 3, 'Y');
196 grid.clear();
197 assert_eq!(grid.get(0, 0), Some(' '));
198 assert_eq!(grid.get(5, 3), Some(' '));
199 }
200
201 #[test]
202 fn test_reset() {
203 let mut grid = TextGrid::new(10, 5);
204 grid.set(0, 0, 'X');
205 grid.reset();
206 assert_eq!(grid.get(0, 0), Some(' '));
207 }
208
209 #[test]
210 fn test_resize() {
211 let mut grid = TextGrid::new(10, 5);
212 grid.set(0, 0, 'X');
213 grid.resize(20, 10);
214 assert_eq!(grid.width(), 20);
215 assert_eq!(grid.height(), 10);
216 assert_eq!(grid.len(), 200);
217 assert_eq!(grid.get(0, 0), Some(' ')); }
219
220 #[test]
221 fn test_write_str() {
222 let mut grid = TextGrid::new(10, 5);
223 grid.write_str(2, 1, "Hello");
224 assert_eq!(grid.get(2, 1), Some('H'));
225 assert_eq!(grid.get(3, 1), Some('e'));
226 assert_eq!(grid.get(4, 1), Some('l'));
227 assert_eq!(grid.get(5, 1), Some('l'));
228 assert_eq!(grid.get(6, 1), Some('o'));
229 }
230
231 #[test]
232 fn test_write_str_truncation() {
233 let mut grid = TextGrid::new(5, 1);
234 grid.write_str(2, 0, "Hello World");
235 assert_eq!(grid.get(2, 0), Some('H'));
237 assert_eq!(grid.get(3, 0), Some('e'));
238 assert_eq!(grid.get(4, 0), Some('l'));
239 }
240
241 #[test]
242 fn test_to_lines() {
243 let mut grid = TextGrid::new(10, 3);
244 grid.write_str(0, 0, "Line 1");
245 grid.write_str(0, 1, "Line 2");
246 grid.write_str(0, 2, "Line 3");
247
248 let lines = grid.to_lines();
249 assert_eq!(lines.len(), 3);
250 assert_eq!(lines[0], "Line 1");
251 assert_eq!(lines[1], "Line 2");
252 assert_eq!(lines[2], "Line 3");
253 }
254
255 #[test]
256 fn test_to_lines_trims_trailing_spaces() {
257 let mut grid = TextGrid::new(20, 2);
258 grid.write_str(0, 0, "Hello");
259 grid.write_str(0, 1, "World");
260
261 let lines = grid.to_lines();
262 assert_eq!(lines[0], "Hello"); assert_eq!(lines[1], "World");
264 }
265
266 #[test]
267 fn test_fill_rect() {
268 let mut grid = TextGrid::new(10, 5);
269 grid.fill_rect(2, 1, 3, 2, '#');
270
271 assert_eq!(grid.get(2, 1), Some('#'));
273 assert_eq!(grid.get(3, 1), Some('#'));
274 assert_eq!(grid.get(4, 1), Some('#'));
275 assert_eq!(grid.get(2, 2), Some('#'));
276 assert_eq!(grid.get(3, 2), Some('#'));
277 assert_eq!(grid.get(4, 2), Some('#'));
278
279 assert_eq!(grid.get(1, 1), Some(' '));
281 assert_eq!(grid.get(5, 1), Some(' '));
282 assert_eq!(grid.get(2, 0), Some(' '));
283 assert_eq!(grid.get(2, 3), Some(' '));
284 }
285
286 #[test]
287 fn test_fill_rect_clipped() {
288 let mut grid = TextGrid::new(5, 5);
289 grid.fill_rect(3, 3, 10, 10, 'X'); assert_eq!(grid.get(3, 3), Some('X'));
293 assert_eq!(grid.get(4, 3), Some('X'));
294 assert_eq!(grid.get(3, 4), Some('X'));
295 assert_eq!(grid.get(4, 4), Some('X'));
296 }
297
298 #[test]
299 fn test_cells_access() {
300 let mut grid = TextGrid::new(3, 2);
301 grid.set(0, 0, 'A');
302 grid.set(1, 0, 'B');
303 grid.set(2, 0, 'C');
304
305 let cells = grid.cells();
306 assert_eq!(cells[0], 'A');
307 assert_eq!(cells[1], 'B');
308 assert_eq!(cells[2], 'C');
309
310 let cells_mut = grid.cells_mut();
311 cells_mut[0] = 'X';
312 assert_eq!(grid.get(0, 0), Some('X'));
313 }
314
315 #[test]
316 fn test_empty_grid() {
317 let grid = TextGrid::new(0, 0);
318 assert!(grid.is_empty());
319 assert_eq!(grid.len(), 0);
320 assert_eq!(grid.get(0, 0), None);
321 }
322}