Skip to main content

lv_tui/
buffer.rs

1use crate::geom::{Pos, Rect, Size};
2use crate::style::{Border, Color, Style};
3
4/// 单个字符格的样式
5#[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/// 单个字符格
15#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct Cell {
17    pub symbol: String,
18    pub style: CellStyle,
19    /// 宽字符的延续格(第二个 cell),不独立渲染
20    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/// diff 变更操作
34#[derive(Debug, Clone)]
35pub struct DiffOp {
36    pub pos: Pos,
37    pub cell: Cell,
38}
39
40/// 字符格缓冲区
41pub struct Buffer {
42    pub size: Size,
43    pub cells: Vec<Cell>,
44}
45
46impl Buffer {
47    /// Creates a new buffer of the given size, filled with default (blank) cells.
48    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    /// Resizes the buffer, discarding all existing content.
57    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    /// Resets every cell in the buffer to its default (blank) state.
64    pub fn clear(&mut self) {
65        for cell in &mut self.cells {
66            *cell = Cell::default();
67        }
68    }
69
70    /// Returns a mutable reference to the cell at `(x, y)`, or `None` if out of bounds.
71    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    /// 写入文本到缓冲区(支持宽字符)
80    ///
81    /// CJK 等宽字符占 2 cell,第二格标记为 continuation。
82    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; // 组合标记,暂不处理
97            }
98
99            // 宽字符不能部分在 clip 外
100            if x.saturating_add(w) > clip_end {
101                break;
102            }
103
104            // 跳过 clip 左侧的字符
105            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            // 宽字符的第二格标记为 continuation
117            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    /// 在指定 rect 边框绘制边框字符
132    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        // 四角
145        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        // 上下边
151        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        // 左右边
157        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    /// 返回所有 cell 的 DiffOp(跳过 continuation)
171    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    /// 比较两个 buffer,返回所有变更的 DiffOp(跳过 continuation)
191    ///
192    /// 支持不同尺寸的 buffer 比较,以 `next` 的尺寸为基准输出。
193    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, " "); // clipped
254    }
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}