Skip to main content

ansiq_render/
buffer.rs

1use ansiq_core::{Rect, Style};
2use unicode_width::UnicodeWidthChar;
3
4#[derive(Clone, Copy, Debug, PartialEq, Eq)]
5pub struct Cell {
6    pub symbol: char,
7    pub style: Style,
8}
9
10impl Default for Cell {
11    fn default() -> Self {
12        Self {
13            symbol: ' ',
14            style: Style::default(),
15        }
16    }
17}
18
19#[derive(Clone, Debug)]
20pub struct FrameBuffer {
21    width: u16,
22    height: u16,
23    cells: Vec<Cell>,
24}
25
26impl FrameBuffer {
27    pub fn new(width: u16, height: u16) -> Self {
28        Self {
29            width,
30            height,
31            cells: vec![Cell::default(); usize::from(width) * usize::from(height)],
32        }
33    }
34
35    pub fn width(&self) -> u16 {
36        self.width
37    }
38
39    pub fn height(&self) -> u16 {
40        self.height
41    }
42
43    pub fn is_blank(&self) -> bool {
44        self.cells.iter().all(|cell| *cell == Cell::default())
45    }
46
47    pub fn get(&self, x: u16, y: u16) -> Cell {
48        self.cells[self.index(x, y)]
49    }
50
51    pub fn set(&mut self, x: u16, y: u16, cell: Cell) {
52        if x >= self.width || y >= self.height {
53            return;
54        }
55
56        let index = self.index(x, y);
57        self.cells[index] = cell;
58    }
59
60    pub fn fill_rect(&mut self, rect: Rect, symbol: char, style: Style) {
61        for y in rect.y..rect.bottom().min(self.height) {
62            for x in rect.x..rect.right().min(self.width) {
63                self.set(x, y, Cell { symbol, style });
64            }
65        }
66    }
67
68    pub fn write_str(&mut self, x: u16, y: u16, text: &str, style: Style) {
69        self.write_clipped(
70            Rect::new(x, y, self.width.saturating_sub(x), 1),
71            0,
72            0,
73            text,
74            style,
75        );
76    }
77
78    pub fn write_clipped(
79        &mut self,
80        rect: Rect,
81        offset_x: u16,
82        offset_y: u16,
83        text: &str,
84        style: Style,
85    ) {
86        if rect.width == 0 || rect.height == 0 {
87            return;
88        }
89
90        let mut cursor_x = rect.x.saturating_add(offset_x);
91        let cursor_y = rect.y.saturating_add(offset_y);
92        if cursor_y >= rect.bottom() || cursor_y >= self.height {
93            return;
94        }
95
96        for ch in text.chars() {
97            let width = UnicodeWidthChar::width(ch).unwrap_or(0) as u16;
98            if width == 0 {
99                continue;
100            }
101            if cursor_x >= rect.right() || cursor_x >= self.width {
102                break;
103            }
104
105            self.set(cursor_x, cursor_y, Cell { symbol: ch, style });
106
107            for fill in 1..width {
108                let fill_x = cursor_x.saturating_add(fill);
109                if fill_x >= rect.right() || fill_x >= self.width {
110                    break;
111                }
112                self.set(fill_x, cursor_y, Cell { symbol: ' ', style });
113            }
114
115            cursor_x = cursor_x.saturating_add(width);
116        }
117    }
118
119    fn index(&self, x: u16, y: u16) -> usize {
120        usize::from(y) * usize::from(self.width) + usize::from(x)
121    }
122}