ascii_forge/renderer/
buffer.rs

1use crate::prelude::*;
2
3/**
4A screen buffer that can be rendered to, has a size
5
6This is the backbone of ascii-forge
7
8`Example`
9```rust
10# use ascii_forge::prelude::*;
11# fn main() {
12// A 30x30 buffer window
13let mut buffer = Buffer::new((30, 30));
14
15// Render Hello World to the top left of the buffer
16render!(buffer, (0, 0) => [ "Hello World!" ]);
17# }
18```
19
20*/
21#[derive(Debug)]
22pub struct Buffer {
23    size: Vec2,
24    cells: Vec<Cell>,
25}
26
27impl AsMut<Buffer> for Buffer {
28    fn as_mut(&mut self) -> &mut Buffer {
29        self
30    }
31}
32
33impl Buffer {
34    /// Creates a new buffer of empty cells with the given size.
35    pub fn new(size: impl Into<Vec2>) -> Self {
36        let size = size.into();
37        Self {
38            size,
39            cells: vec![Cell::default(); size.x as usize * size.y as usize],
40        }
41    }
42
43    /// Creates a new buffer filled with the given cell type.
44    pub fn new_filled(size: impl Into<Vec2>, cell: impl Into<Cell>) -> Self {
45        let cell = cell.into();
46        let size = size.into();
47
48        Self {
49            size,
50            cells: vec![cell; size.x as usize * size.y as usize],
51        }
52    }
53
54    /// Returns the current size of the buffer.
55    pub fn size(&self) -> Vec2 {
56        self.size
57    }
58
59    /// Sets a cell at the given location to the given cell
60    pub fn set<C: Into<Cell>>(&mut self, loc: impl Into<Vec2>, cell: C) {
61        let loc = loc.into();
62        let idx = self.index_of(loc);
63
64        // Ignore if cell is out of bounds
65        let Some(idx) = idx else {
66            return;
67        };
68
69        let cell = cell.into();
70
71        for i in 1..cell.width().saturating_sub(1) {
72            self.set(loc + vec2(i, 0), Cell::default());
73        }
74
75        self.cells[idx] = cell;
76    }
77
78    /// Sets a cell at the given location to an empty cell ('\0')
79    pub fn del(&mut self, loc: impl Into<Vec2>) {
80        self.set(loc, '\0');
81    }
82
83    /// Sets all cells at the given location to the given cell
84    pub fn fill<C: Into<Cell>>(&mut self, cell: C) {
85        let cell = cell.into();
86        for i in 0..self.cells.len() {
87            self.cells[i] = cell.clone()
88        }
89    }
90
91    /// Returns a reverence to the cell at the given location.
92    pub fn get(&self, loc: impl Into<Vec2>) -> Option<&Cell> {
93        let idx = self.index_of(loc)?;
94        self.cells.get(idx)
95    }
96
97    /// Returns a mutable reference to the cell at the given location.
98    pub fn get_mut(&mut self, loc: impl Into<Vec2>) -> Option<&mut Cell> {
99        let idx = self.index_of(loc)?;
100        self.cells.get_mut(idx)
101    }
102
103    fn index_of(&self, loc: impl Into<Vec2>) -> Option<usize> {
104        let loc = loc.into();
105        if loc.x > self.size.x || loc.y > self.size.y {
106            return None;
107        }
108
109        let idx = loc.y as usize * self.size.x as usize + loc.x as usize;
110
111        if (idx as u16) >= self.size.x * self.size.y {
112            return None;
113        }
114
115        Some(idx.min((self.size.x as usize * self.size.y as usize) - 1))
116    }
117
118    /// Clears the buffer, filling it with '\0' (empty) characters
119    pub fn clear(&mut self) {
120        *self = Self::new(self.size);
121    }
122
123    /// Returns the cells and locations that are different between the two buffers
124    pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(Vec2, &'a Cell)> {
125        assert!(self.size == other.size);
126
127        let mut res = vec![];
128        let mut skip = 0;
129
130        for y in 0..self.size.y {
131            for x in 0..self.size.x {
132                if skip > 0 {
133                    skip -= 1;
134                    continue;
135                }
136
137                let old = self.get((x, y));
138                let new = other.get((x, y));
139
140                if old != new {
141                    let new = new.expect("new should be in bounds");
142                    skip = new.width().saturating_sub(1) as usize;
143                    res.push((vec2(x, y), new))
144                }
145            }
146        }
147
148        res
149    }
150
151    /// Shrinks the buffer to the given size by dropping any cells that are empty ('\0')
152    pub fn shrink(&mut self) {
153        let mut max_whitespace_x = 0;
154        let mut max_whitespace_y = 0;
155        for x in (0..self.size.x).rev() {
156            for y in (0..self.size.y).rev() {
157                if self.get((x, y)).expect("Cell should be in bounds").text() != "\0" {
158                    max_whitespace_x = x.max(max_whitespace_x);
159                    max_whitespace_y = y.max(max_whitespace_y);
160                }
161            }
162        }
163
164        self.resize(vec2(max_whitespace_x + 1, max_whitespace_y + 1));
165    }
166
167    /// Resizes the buffer while retaining elements that have already been rendered
168    pub fn resize(&mut self, new_size: impl Into<Vec2>) {
169        let new_size = new_size.into();
170        if self.size == new_size {
171            return;
172        }
173
174        let mut new_elements = vec![];
175
176        for y in 0..new_size.y {
177            for x in 0..new_size.x {
178                new_elements.push(self.get((x, y)).expect("Cell should be in bounds").clone());
179            }
180        }
181
182        self.size = new_size;
183        self.cells = new_elements;
184    }
185
186    /// Creates a Buffer from the given element with the minimum size it could have for that element.
187    /// Useful for if you want to store any set of render elements in a custom element.
188    pub fn sized_element<R: Render>(item: R) -> Self {
189        let mut buff = Buffer::new((100, 100));
190        render!(buff, vec2(0, 0) => [ item ]);
191        buff.shrink();
192        buff
193    }
194
195    /// Renders a clipped region of this buffer to another buffer.
196    pub fn render_clipped(
197        &self,
198        loc: impl Into<Vec2>,
199        clip_size: impl Into<Vec2>,
200        buffer: &mut Buffer,
201    ) -> Vec2 {
202        let loc = loc.into();
203        let clip_size = clip_size.into();
204
205        for x in 0..clip_size.x.min(self.size().x) {
206            if x + loc.x >= buffer.size().x {
207                break;
208            }
209            for y in 0..clip_size.y.min(self.size().y) {
210                if y + loc.y >= buffer.size().y {
211                    break;
212                }
213
214                let source_pos = vec2(x, y);
215                let dest_pos = vec2(x + loc.x, y + loc.y);
216
217                if let Some(cell) = self.get(source_pos) {
218                    if cell.text() != "\0" {
219                        buffer.set(dest_pos, cell.clone());
220                    }
221                }
222            }
223        }
224
225        vec2(
226            loc.x + clip_size.x.min(self.size().x),
227            loc.y + clip_size.y.min(self.size().y),
228        )
229    }
230}
231
232impl Render for Buffer {
233    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
234        for x in 0..self.size().x {
235            if x + loc.x >= buffer.size().x {
236                break;
237            }
238            for y in 0..self.size().y {
239                if y + loc.y >= buffer.size().y {
240                    break;
241                }
242
243                let source_pos = vec2(x, y);
244                let dest_pos = vec2(x + loc.x, y + loc.y);
245
246                if let Some(cell) = self.get(source_pos) {
247                    if cell.text() != "\0" {
248                        buffer.set(dest_pos, cell.clone());
249                    }
250                }
251            }
252        }
253
254        vec2(loc.x + buffer.size().x, loc.y + buffer.size().y)
255    }
256
257    fn size(&self) -> Vec2 {
258        self.size
259    }
260
261    fn render_clipped(&self, loc: Vec2, clip_size: Vec2, buffer: &mut Buffer) -> Vec2 {
262        self.render_clipped(loc, clip_size, buffer)
263    }
264}