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}