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, no_run
10use ascii_forge::prelude::*;
11
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!(
17    buffer, {
18        (0, 0) => "Hello World!"
19    }
20);
21```
22
23*/
24#[derive(Debug)]
25pub struct Buffer {
26    size: Vec2,
27    cells: Vec<Cell>,
28}
29
30impl AsMut<Buffer> for Buffer {
31    fn as_mut(&mut self) -> &mut Buffer {
32        self
33    }
34}
35
36impl Buffer {
37    /// Creates a new buffer of empty cells with the given size.
38    pub fn new(size: impl Into<Vec2>) -> Self {
39        let size = size.into();
40        Self {
41            size,
42            cells: vec![Cell::default(); size.x as usize * size.y as usize],
43        }
44    }
45
46    /// Returns the current size of the buffer.
47    pub fn size(&self) -> Vec2 {
48        self.size
49    }
50
51    /// Sets a cell at the given location to the given cell
52    pub fn set<C: Into<Cell>>(&mut self, loc: impl Into<Vec2>, cell: C) {
53        let loc = loc.into();
54        let idx = self.index_of(loc);
55
56        // Ignore if cell is out of bounds
57        let Some(idx) = idx else {
58            return;
59        };
60
61        let cell = cell.into();
62
63        // Overwrite the next cell if the character is wide
64        if cell.width() > 1 {
65            self.set(loc + vec2(1, 0), Cell::default());
66        }
67
68        self.cells[idx] = cell;
69    }
70
71    /// Sets all cells at the given location to the given cell
72    pub fn fill<C: Into<Cell>>(&mut self, cell: C) {
73        let cell = cell.into();
74        for i in 0..self.cells.len() {
75            self.cells[i] = cell.clone()
76        }
77    }
78
79    /// Returns a reverence to the cell at the given location.
80    pub fn get(&self, loc: impl Into<Vec2>) -> Option<&Cell> {
81        let idx = self.index_of(loc)?;
82        self.cells.get(idx)
83    }
84
85    /// Returns a mutable reference to the cell at the given location.
86    pub fn get_mut(&mut self, loc: impl Into<Vec2>) -> Option<&mut Cell> {
87        let idx = self.index_of(loc)?;
88        self.cells.get_mut(idx)
89    }
90
91    fn index_of(&self, loc: impl Into<Vec2>) -> Option<usize> {
92        let loc = loc.into();
93        let idx = loc.y as usize * self.size.x as usize + loc.x as usize;
94
95        if (idx as u16) >= self.size.x * self.size.y {
96            return None;
97        }
98
99        Some(idx.min((self.size.x as usize * self.size.y as usize) - 1))
100    }
101
102    /// Clears the buffer
103    pub fn clear(&mut self) {
104        *self = Self::new(self.size);
105    }
106
107    /// Returns the cells and locations that are different between the two buffers
108    pub fn diff<'a>(&self, other: &'a Buffer) -> Vec<(Vec2, &'a Cell)> {
109        assert!(self.size == other.size);
110
111        let mut res = vec![];
112        let mut skip = 0;
113
114        for x in 0..self.size.x {
115            for y in 0..self.size.y {
116                if skip > 0 {
117                    skip -= 1;
118                    continue;
119                }
120
121                let old = self.get((x, y));
122                let new = other.get((x, y));
123
124                if old != new {
125                    if let Some(new) = new {
126                        skip = new.width().saturating_sub(1) as usize;
127                        res.push((vec2(x, y), new))
128                    }
129                }
130            }
131        }
132
133        res
134    }
135
136    /// Shrinks the buffer to the given size by dropping any cells that are only whitespace
137    pub fn shrink(&mut self) {
138        let mut max_whitespace_x = 0;
139        let mut max_whitespace_y = 0;
140        for x in (0..self.size.x).rev() {
141            for y in (0..self.size.y).rev() {
142                if !self
143                    .get((x, y))
144                    .expect("Cell should be in bounds")
145                    .is_empty()
146                {
147                    max_whitespace_x = x.max(max_whitespace_x);
148                    max_whitespace_y = y.max(max_whitespace_y);
149                }
150            }
151        }
152
153        self.resize(vec2(max_whitespace_x + 1, max_whitespace_y + 1));
154    }
155
156    /// Resizes the buffer while retaining elements that have already been rendered
157    pub fn resize(&mut self, new_size: impl Into<Vec2>) {
158        let new_size = new_size.into();
159        if self.size == new_size {
160            return;
161        }
162
163        let mut new_elements = vec![];
164
165        for y in 0..new_size.y {
166            for x in 0..new_size.x {
167                new_elements.push(self.get((x, y)).expect("Cell should be in bounds").clone());
168            }
169        }
170
171        self.size = new_size;
172        self.cells = new_elements;
173    }
174
175    /// Creates a Buffer from the given element with the minimum size it could have for that element.
176    /// Useful for if you want to store any set of render elements in a custom element.
177    pub fn sized_element<R: Render>(item: R) -> Self {
178        let mut buff = Buffer::new((100, 100));
179        render!(buff, vec2(0, 0) => [ item ]);
180        buff.shrink();
181        buff
182    }
183}
184
185impl Render for Buffer {
186    fn render(&self, loc: Vec2, buffer: &mut Buffer) -> Vec2 {
187        for x in 0..self.size.x {
188            if x + loc.x >= buffer.size.x {
189                break;
190            }
191
192            for y in 0..self.size.y {
193                if y + loc.y >= buffer.size.y {
194                    break;
195                }
196
197                let dest = vec2(x + loc.x, y + loc.y);
198
199                buffer.set(
200                    dest,
201                    self.get(vec2(x, y))
202                        .expect("Cell should be in bounds")
203                        .clone(),
204                );
205            }
206        }
207        vec2(loc.x + buffer.size().x, loc.y + buffer.size().y)
208    }
209}