1use crate::cell::Cell;
2use crate::rect::Rect;
3use crate::style::Style;
4use unicode_width::UnicodeWidthChar;
5
6pub struct Buffer {
15 pub area: Rect,
17 pub content: Vec<Cell>,
19 clip_stack: Vec<Rect>,
20}
21
22impl Buffer {
23 pub fn empty(area: Rect) -> Self {
25 let size = area.area() as usize;
26 Self {
27 area,
28 content: vec![Cell::default(); size],
29 clip_stack: Vec::new(),
30 }
31 }
32
33 pub fn push_clip(&mut self, rect: Rect) {
39 let effective = if let Some(current) = self.clip_stack.last() {
40 intersect_rects(*current, rect)
41 } else {
42 rect
43 };
44 self.clip_stack.push(effective);
45 }
46
47 pub fn pop_clip(&mut self) {
52 self.clip_stack.pop();
53 }
54
55 fn effective_clip(&self) -> Option<&Rect> {
56 self.clip_stack.last()
57 }
58
59 #[inline]
60 fn index_of(&self, x: u32, y: u32) -> usize {
61 ((y - self.area.y) * self.area.width + (x - self.area.x)) as usize
62 }
63
64 #[inline]
66 pub fn in_bounds(&self, x: u32, y: u32) -> bool {
67 x >= self.area.x && x < self.area.right() && y >= self.area.y && y < self.area.bottom()
68 }
69
70 #[inline]
74 pub fn get(&self, x: u32, y: u32) -> &Cell {
75 &self.content[self.index_of(x, y)]
76 }
77
78 #[inline]
82 pub fn get_mut(&mut self, x: u32, y: u32) -> &mut Cell {
83 let idx = self.index_of(x, y);
84 &mut self.content[idx]
85 }
86
87 pub fn set_string(&mut self, mut x: u32, y: u32, s: &str, style: Style) {
94 if y >= self.area.bottom() {
95 return;
96 }
97 let clip = self.effective_clip().copied();
98 for ch in s.chars() {
99 if x >= self.area.right() {
100 break;
101 }
102 let char_width = UnicodeWidthChar::width(ch).unwrap_or(0) as u32;
103 if char_width == 0 {
104 if x > self.area.x {
107 let prev_in_clip = clip.map_or(true, |clip| {
108 (x - 1) >= clip.x
109 && (x - 1) < clip.right()
110 && y >= clip.y
111 && y < clip.bottom()
112 });
113 if prev_in_clip {
114 self.get_mut(x - 1, y).symbol.push(ch);
115 }
116 }
117 continue;
118 }
119
120 let in_clip = clip.map_or(true, |clip| {
121 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
122 });
123
124 if !in_clip {
125 x = x.saturating_add(char_width);
126 continue;
127 }
128
129 let cell = self.get_mut(x, y);
130 cell.set_char(ch);
131 cell.set_style(style);
132
133 if char_width > 1 {
135 let next_x = x + 1;
136 if next_x < self.area.right() {
137 let next = self.get_mut(next_x, y);
138 next.symbol.clear();
139 next.style = style;
140 }
141 }
142
143 x = x.saturating_add(char_width);
144 }
145 }
146
147 pub fn set_char(&mut self, x: u32, y: u32, ch: char, style: Style) {
151 let in_clip = self.effective_clip().map_or(true, |clip| {
152 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
153 });
154 if !self.in_bounds(x, y) || !in_clip {
155 return;
156 }
157 let cell = self.get_mut(x, y);
158 cell.set_char(ch);
159 cell.set_style(style);
160 }
161
162 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u32, u32, &'a Cell)> {
168 let mut updates = Vec::new();
169 for y in self.area.y..self.area.bottom() {
170 for x in self.area.x..self.area.right() {
171 let cur = self.get(x, y);
172 let prev = other.get(x, y);
173 if cur != prev {
174 updates.push((x, y, cur));
175 }
176 }
177 }
178 updates
179 }
180
181 pub fn reset(&mut self) {
183 for cell in &mut self.content {
184 cell.reset();
185 }
186 self.clip_stack.clear();
187 }
188
189 pub fn resize(&mut self, area: Rect) {
194 self.area = area;
195 let size = area.area() as usize;
196 self.content.resize(size, Cell::default());
197 self.reset();
198 }
199}
200
201fn intersect_rects(a: Rect, b: Rect) -> Rect {
202 let x = a.x.max(b.x);
203 let y = a.y.max(b.y);
204 let right = a.right().min(b.right());
205 let bottom = a.bottom().min(b.bottom());
206 let width = right.saturating_sub(x);
207 let height = bottom.saturating_sub(y);
208 Rect::new(x, y, width, height)
209}
210
211#[cfg(test)]
212mod tests {
213 use super::*;
214
215 #[test]
216 fn clip_stack_intersects_nested_regions() {
217 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
218 buf.push_clip(Rect::new(1, 1, 6, 3));
219 buf.push_clip(Rect::new(4, 0, 6, 4));
220
221 buf.set_char(3, 2, 'x', Style::new());
222 buf.set_char(4, 2, 'y', Style::new());
223
224 assert_eq!(buf.get(3, 2).symbol, " ");
225 assert_eq!(buf.get(4, 2).symbol, "y");
226 }
227
228 #[test]
229 fn set_string_advances_even_when_clipped() {
230 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
231 buf.push_clip(Rect::new(2, 0, 6, 1));
232
233 buf.set_string(0, 0, "abcd", Style::new());
234
235 assert_eq!(buf.get(2, 0).symbol, "c");
236 assert_eq!(buf.get(3, 0).symbol, "d");
237 }
238
239 #[test]
240 fn pop_clip_restores_previous_clip() {
241 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
242 buf.push_clip(Rect::new(0, 0, 2, 1));
243 buf.push_clip(Rect::new(4, 0, 2, 1));
244
245 buf.set_char(1, 0, 'a', Style::new());
246 buf.pop_clip();
247 buf.set_char(1, 0, 'b', Style::new());
248
249 assert_eq!(buf.get(1, 0).symbol, "b");
250 }
251
252 #[test]
253 fn reset_clears_clip_stack() {
254 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
255 buf.push_clip(Rect::new(0, 0, 0, 0));
256 buf.reset();
257 buf.set_char(0, 0, 'z', Style::new());
258
259 assert_eq!(buf.get(0, 0).symbol, "z");
260 }
261}