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 continue;
105 }
106
107 let in_clip = clip.map_or(true, |clip| {
108 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
109 });
110
111 if !in_clip {
112 x = x.saturating_add(char_width);
113 continue;
114 }
115
116 let cell = self.get_mut(x, y);
117 cell.set_char(ch);
118 cell.set_style(style);
119
120 if char_width > 1 {
122 let next_x = x + 1;
123 if next_x < self.area.right() {
124 let next = self.get_mut(next_x, y);
125 next.symbol.clear();
126 next.style = style;
127 }
128 }
129
130 x = x.saturating_add(char_width);
131 }
132 }
133
134 pub fn set_char(&mut self, x: u32, y: u32, ch: char, style: Style) {
138 let in_clip = self.effective_clip().map_or(true, |clip| {
139 x >= clip.x && x < clip.right() && y >= clip.y && y < clip.bottom()
140 });
141 if !self.in_bounds(x, y) || !in_clip {
142 return;
143 }
144 let cell = self.get_mut(x, y);
145 cell.set_char(ch);
146 cell.set_style(style);
147 }
148
149 pub fn diff<'a>(&'a self, other: &'a Buffer) -> Vec<(u32, u32, &'a Cell)> {
155 let mut updates = Vec::new();
156 for y in self.area.y..self.area.bottom() {
157 for x in self.area.x..self.area.right() {
158 let cur = self.get(x, y);
159 let prev = other.get(x, y);
160 if cur != prev {
161 updates.push((x, y, cur));
162 }
163 }
164 }
165 updates
166 }
167
168 pub fn reset(&mut self) {
170 for cell in &mut self.content {
171 cell.reset();
172 }
173 self.clip_stack.clear();
174 }
175
176 pub fn resize(&mut self, area: Rect) {
181 self.area = area;
182 let size = area.area() as usize;
183 self.content.resize(size, Cell::default());
184 self.reset();
185 }
186}
187
188fn intersect_rects(a: Rect, b: Rect) -> Rect {
189 let x = a.x.max(b.x);
190 let y = a.y.max(b.y);
191 let right = a.right().min(b.right());
192 let bottom = a.bottom().min(b.bottom());
193 let width = right.saturating_sub(x);
194 let height = bottom.saturating_sub(y);
195 Rect::new(x, y, width, height)
196}
197
198#[cfg(test)]
199mod tests {
200 use super::*;
201
202 #[test]
203 fn clip_stack_intersects_nested_regions() {
204 let mut buf = Buffer::empty(Rect::new(0, 0, 10, 5));
205 buf.push_clip(Rect::new(1, 1, 6, 3));
206 buf.push_clip(Rect::new(4, 0, 6, 4));
207
208 buf.set_char(3, 2, 'x', Style::new());
209 buf.set_char(4, 2, 'y', Style::new());
210
211 assert_eq!(buf.get(3, 2).symbol, " ");
212 assert_eq!(buf.get(4, 2).symbol, "y");
213 }
214
215 #[test]
216 fn set_string_advances_even_when_clipped() {
217 let mut buf = Buffer::empty(Rect::new(0, 0, 8, 1));
218 buf.push_clip(Rect::new(2, 0, 6, 1));
219
220 buf.set_string(0, 0, "abcd", Style::new());
221
222 assert_eq!(buf.get(2, 0).symbol, "c");
223 assert_eq!(buf.get(3, 0).symbol, "d");
224 }
225
226 #[test]
227 fn pop_clip_restores_previous_clip() {
228 let mut buf = Buffer::empty(Rect::new(0, 0, 6, 1));
229 buf.push_clip(Rect::new(0, 0, 2, 1));
230 buf.push_clip(Rect::new(4, 0, 2, 1));
231
232 buf.set_char(1, 0, 'a', Style::new());
233 buf.pop_clip();
234 buf.set_char(1, 0, 'b', Style::new());
235
236 assert_eq!(buf.get(1, 0).symbol, "b");
237 }
238
239 #[test]
240 fn reset_clears_clip_stack() {
241 let mut buf = Buffer::empty(Rect::new(0, 0, 4, 1));
242 buf.push_clip(Rect::new(0, 0, 0, 0));
243 buf.reset();
244 buf.set_char(0, 0, 'z', Style::new());
245
246 assert_eq!(buf.get(0, 0).symbol, "z");
247 }
248}