1use crate::geom::{Pos, Rect, Size};
2use crate::style::{Border, Color, Style};
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
6pub struct CellStyle {
7 pub fg: Option<Color>,
8 pub bg: Option<Color>,
9 pub bold: bool,
10 pub italic: bool,
11 pub underline: bool,
12}
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Cell {
17 pub symbol: String,
18 pub style: CellStyle,
19 pub continuation: bool,
21}
22
23impl Default for Cell {
24 fn default() -> Self {
25 Self {
26 symbol: " ".to_string(),
27 style: CellStyle::default(),
28 continuation: false,
29 }
30 }
31}
32
33#[derive(Debug, Clone)]
35pub struct DiffOp {
36 pub pos: Pos,
37 pub cell: Cell,
38}
39
40pub struct Buffer {
42 pub size: Size,
43 pub cells: Vec<Cell>,
44}
45
46impl Buffer {
47 pub fn new(size: Size) -> Self {
49 let len = size.width as usize * size.height as usize;
50 Self {
51 size,
52 cells: vec![Cell::default(); len],
53 }
54 }
55
56 pub fn resize(&mut self, size: Size) {
58 self.size = size;
59 let len = size.width as usize * size.height as usize;
60 self.cells = vec![Cell::default(); len];
61 }
62
63 pub fn clear(&mut self) {
65 for cell in &mut self.cells {
66 *cell = Cell::default();
67 }
68 }
69
70 pub fn get_mut(&mut self, x: u16, y: u16) -> Option<&mut Cell> {
72 if x >= self.size.width || y >= self.size.height {
73 return None;
74 }
75 let index = y as usize * self.size.width as usize + x as usize;
76 self.cells.get_mut(index)
77 }
78
79 pub fn write_text(&mut self, pos: Pos, clip: Rect, text: &str, style: &Style) {
83 let y = pos.y;
84
85 if y < clip.y || y >= clip.y.saturating_add(clip.height) {
86 return;
87 }
88
89 let cell_style = style.into_cell_style();
90 let clip_end = clip.x.saturating_add(clip.width);
91
92 let mut x = pos.x;
93 for ch in text.chars() {
94 let w = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0) as u16;
95 if w == 0 {
96 continue; }
98
99 if x.saturating_add(w) > clip_end {
101 break;
102 }
103
104 if x < clip.x {
106 x = x.saturating_add(w);
107 continue;
108 }
109
110 if let Some(cell) = self.get_mut(x, y) {
111 cell.symbol = ch.to_string();
112 cell.style = cell_style;
113 cell.continuation = false;
114 }
115
116 if w > 1 {
118 for i in 1..w {
119 if let Some(cont) = self.get_mut(x + i, y) {
120 cont.symbol = " ".to_string();
121 cont.style = cell_style;
122 cont.continuation = true;
123 }
124 }
125 }
126
127 x = x.saturating_add(w);
128 }
129 }
130
131 pub fn draw_border(&mut self, rect: Rect, border: Border, style: &Style) {
133 let (tl, t, tr, l, r, bl, b, br) = match border {
134 Border::None => return,
135 Border::Plain => ("┌", "─", "┐", "│", "│", "└", "─", "┘"),
136 Border::Rounded => ("╭", "─", "╮", "│", "│", "╰", "─", "╯"),
137 Border::Double => ("╔", "═", "╗", "║", "║", "╚", "═", "╝"),
138 };
139
140 let cell_style = style.into_cell_style();
141 let right = rect.x + rect.width.saturating_sub(1);
142 let bottom = rect.y + rect.height.saturating_sub(1);
143
144 self.set_cell(rect.x, rect.y, tl, cell_style);
146 self.set_cell(right, rect.y, tr, cell_style);
147 self.set_cell(rect.x, bottom, bl, cell_style);
148 self.set_cell(right, bottom, br, cell_style);
149
150 for x in (rect.x + 1)..right {
152 self.set_cell(x, rect.y, t, cell_style);
153 self.set_cell(x, bottom, b, cell_style);
154 }
155
156 for y in (rect.y + 1)..bottom {
158 self.set_cell(rect.x, y, l, cell_style);
159 self.set_cell(right, y, r, cell_style);
160 }
161 }
162
163 fn set_cell(&mut self, x: u16, y: u16, symbol: &str, style: CellStyle) {
164 if let Some(cell) = self.get_mut(x, y) {
165 cell.symbol = symbol.to_string();
166 cell.style = style;
167 }
168 }
169
170 pub fn all_ops(&self) -> Vec<DiffOp> {
172 let mut ops = Vec::with_capacity(self.cells.len());
173
174 for y in 0..self.size.height {
175 for x in 0..self.size.width {
176 let idx = y as usize * self.size.width as usize + x as usize;
177 if self.cells[idx].continuation {
178 continue;
179 }
180 ops.push(DiffOp {
181 pos: Pos { x, y },
182 cell: self.cells[idx].clone(),
183 });
184 }
185 }
186
187 ops
188 }
189
190 pub fn diff(&self, next: &Buffer) -> Vec<DiffOp> {
194 let mut ops = Vec::new();
195
196 for y in 0..next.size.height {
197 for x in 0..next.size.width {
198 let next_idx = y as usize * next.size.width as usize + x as usize;
199 let next_cell = &next.cells[next_idx];
200
201 if next_cell.continuation {
202 continue;
203 }
204
205 let changed = if x < self.size.width && y < self.size.height {
206 let self_idx = y as usize * self.size.width as usize + x as usize;
207 &self.cells[self_idx] != next_cell
208 } else {
209 true
210 };
211
212 if changed {
213 ops.push(DiffOp {
214 pos: Pos { x, y },
215 cell: next_cell.clone(),
216 });
217 }
218 }
219 }
220
221 ops
222 }
223}
224
225#[cfg(test)]
226mod tests {
227 use super::*;
228 use crate::geom::Size;
229 use crate::style::Style;
230
231 #[test]
232 fn test_new_buffer() {
233 let buf = Buffer::new(Size { width: 3, height: 2 });
234 assert_eq!(buf.size.width, 3);
235 assert_eq!(buf.size.height, 2);
236 assert_eq!(buf.cells.len(), 6);
237 }
238
239 #[test]
240 fn test_clear() {
241 let mut buf = Buffer::new(Size { width: 2, height: 1 });
242 buf.get_mut(0, 0).unwrap().symbol = "X".into();
243 buf.clear();
244 assert_eq!(buf.cells[0].symbol, " ");
245 }
246
247 #[test]
248 fn test_write_text_clipped() {
249 let mut buf = Buffer::new(Size { width: 5, height: 1 });
250 buf.write_text(Pos::default(), Rect { x: 0, y: 0, width: 3, height: 1 }, "hello", &Style::default());
251 assert_eq!(buf.cells[0].symbol, "h");
252 assert_eq!(buf.cells[2].symbol, "l");
253 assert_eq!(buf.cells[3].symbol, " "); }
255
256 #[test]
257 fn test_diff() {
258 let front = Buffer::new(Size { width: 2, height: 1 });
259 let mut back = Buffer::new(Size { width: 2, height: 1 });
260 back.get_mut(0, 0).unwrap().symbol = "X".into();
261 let ops = front.diff(&back);
262 assert_eq!(ops.len(), 1);
263 assert_eq!(ops[0].pos.x, 0);
264 }
265}