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 let mut pos_x = x;
99 for ch in s.chars() {
100 if pos_x >= self.width {
101 break;
102 }
103 self.set(pos_x, y, ch);
104 pos_x += 1;
105 }
106 }
107
108 pub fn to_lines(&self) -> Vec<String> {
111 let mut lines = Vec::with_capacity(self.height as usize);
112 for y in 0..self.height {
113 let start = (y as usize) * (self.width as usize);
114 let end = start + (self.width as usize);
115 let line: String = self.cells[start..end].iter().collect();
116 lines.push(line.trim_end().to_string());
117 }
118 lines
119 }
120
121 pub fn cells(&self) -> &[char] {
123 &self.cells
124 }
125
126 pub fn cells_mut(&mut self) -> &mut [char] {
128 &mut self.cells
129 }
130
131 pub fn fill_rect(&mut self, x: u16, y: u16, width: u16, height: u16, ch: char) {
133 for row in y..y.saturating_add(height).min(self.height) {
134 for col in x..x.saturating_add(width).min(self.width) {
135 self.set(col, row, ch);
136 }
137 }
138 }
139}
140
141impl Default for TextGrid {
142 fn default() -> Self {
143 Self::new(80, 24)
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150
151 #[test]
152 fn test_new() {
153 let grid = TextGrid::new(10, 5);
154 assert_eq!(grid.width(), 10);
155 assert_eq!(grid.height(), 5);
156 assert_eq!(grid.len(), 50);
157 assert!(!grid.is_empty());
158 }
159
160 #[test]
161 fn test_default() {
162 let grid = TextGrid::default();
163 assert_eq!(grid.width(), 80);
164 assert_eq!(grid.height(), 24);
165 }
166
167 #[test]
168 fn test_get_set() {
169 let mut grid = TextGrid::new(10, 5);
170 assert_eq!(grid.get(0, 0), Some(' '));
171
172 grid.set(3, 2, 'X');
173 assert_eq!(grid.get(3, 2), Some('X'));
174
175 assert_eq!(grid.get(100, 100), None);
177 }
178
179 #[test]
180 fn test_set_out_of_bounds() {
181 let mut grid = TextGrid::new(10, 5);
182 grid.set(100, 100, 'X'); assert_eq!(grid.get(0, 0), Some(' ')); }
185
186 #[test]
187 fn test_clear() {
188 let mut grid = TextGrid::new(10, 5);
189 grid.set(0, 0, 'X');
190 grid.set(5, 3, 'Y');
191 grid.clear();
192 assert_eq!(grid.get(0, 0), Some(' '));
193 assert_eq!(grid.get(5, 3), Some(' '));
194 }
195
196 #[test]
197 fn test_reset() {
198 let mut grid = TextGrid::new(10, 5);
199 grid.set(0, 0, 'X');
200 grid.reset();
201 assert_eq!(grid.get(0, 0), Some(' '));
202 }
203
204 #[test]
205 fn test_resize() {
206 let mut grid = TextGrid::new(10, 5);
207 grid.set(0, 0, 'X');
208 grid.resize(20, 10);
209 assert_eq!(grid.width(), 20);
210 assert_eq!(grid.height(), 10);
211 assert_eq!(grid.len(), 200);
212 assert_eq!(grid.get(0, 0), Some(' ')); }
214
215 #[test]
216 fn test_write_str() {
217 let mut grid = TextGrid::new(10, 5);
218 grid.write_str(2, 1, "Hello");
219 assert_eq!(grid.get(2, 1), Some('H'));
220 assert_eq!(grid.get(3, 1), Some('e'));
221 assert_eq!(grid.get(4, 1), Some('l'));
222 assert_eq!(grid.get(5, 1), Some('l'));
223 assert_eq!(grid.get(6, 1), Some('o'));
224 }
225
226 #[test]
227 fn test_write_str_truncation() {
228 let mut grid = TextGrid::new(5, 1);
229 grid.write_str(2, 0, "Hello World");
230 assert_eq!(grid.get(2, 0), Some('H'));
232 assert_eq!(grid.get(3, 0), Some('e'));
233 assert_eq!(grid.get(4, 0), Some('l'));
234 }
235
236 #[test]
237 fn test_to_lines() {
238 let mut grid = TextGrid::new(10, 3);
239 grid.write_str(0, 0, "Line 1");
240 grid.write_str(0, 1, "Line 2");
241 grid.write_str(0, 2, "Line 3");
242
243 let lines = grid.to_lines();
244 assert_eq!(lines.len(), 3);
245 assert_eq!(lines[0], "Line 1");
246 assert_eq!(lines[1], "Line 2");
247 assert_eq!(lines[2], "Line 3");
248 }
249
250 #[test]
251 fn test_to_lines_trims_trailing_spaces() {
252 let mut grid = TextGrid::new(20, 2);
253 grid.write_str(0, 0, "Hello");
254 grid.write_str(0, 1, "World");
255
256 let lines = grid.to_lines();
257 assert_eq!(lines[0], "Hello"); assert_eq!(lines[1], "World");
259 }
260
261 #[test]
262 fn test_fill_rect() {
263 let mut grid = TextGrid::new(10, 5);
264 grid.fill_rect(2, 1, 3, 2, '#');
265
266 assert_eq!(grid.get(2, 1), Some('#'));
268 assert_eq!(grid.get(3, 1), Some('#'));
269 assert_eq!(grid.get(4, 1), Some('#'));
270 assert_eq!(grid.get(2, 2), Some('#'));
271 assert_eq!(grid.get(3, 2), Some('#'));
272 assert_eq!(grid.get(4, 2), Some('#'));
273
274 assert_eq!(grid.get(1, 1), Some(' '));
276 assert_eq!(grid.get(5, 1), Some(' '));
277 assert_eq!(grid.get(2, 0), Some(' '));
278 assert_eq!(grid.get(2, 3), Some(' '));
279 }
280
281 #[test]
282 fn test_fill_rect_clipped() {
283 let mut grid = TextGrid::new(5, 5);
284 grid.fill_rect(3, 3, 10, 10, 'X'); assert_eq!(grid.get(3, 3), Some('X'));
288 assert_eq!(grid.get(4, 3), Some('X'));
289 assert_eq!(grid.get(3, 4), Some('X'));
290 assert_eq!(grid.get(4, 4), Some('X'));
291 }
292
293 #[test]
294 fn test_cells_access() {
295 let mut grid = TextGrid::new(3, 2);
296 grid.set(0, 0, 'A');
297 grid.set(1, 0, 'B');
298 grid.set(2, 0, 'C');
299
300 let cells = grid.cells();
301 assert_eq!(cells[0], 'A');
302 assert_eq!(cells[1], 'B');
303 assert_eq!(cells[2], 'C');
304
305 let cells_mut = grid.cells_mut();
306 cells_mut[0] = 'X';
307 assert_eq!(grid.get(0, 0), Some('X'));
308 }
309
310 #[test]
311 fn test_empty_grid() {
312 let grid = TextGrid::new(0, 0);
313 assert!(grid.is_empty());
314 assert_eq!(grid.len(), 0);
315 assert_eq!(grid.get(0, 0), None);
316 }
317}