Skip to main content

dotzuki_renderer/
text_renderer.rs

1use crate::palette::Palette;
2use crate::tile::TileSet;
3use crate::{FbSurface, TILE_SIZE};
4
5/// A tile buffer representing the screen-space tilemap.
6/// Width and height are in tiles; dynamically allocated.
7#[derive(Debug, Clone)]
8pub struct ScreenTileBuffer {
9    pub tiles: Vec<u8>,
10    pub width_tiles: u32,
11    pub height_tiles: u32,
12}
13
14impl ScreenTileBuffer {
15    pub fn new(width_tiles: u32, height_tiles: u32) -> Self {
16        Self {
17            tiles: vec![0x7F; (width_tiles * height_tiles) as usize],
18            width_tiles,
19            height_tiles,
20        }
21    }
22
23    #[inline]
24    pub fn get(&self, tx: u32, ty: u32) -> u8 {
25        if tx >= self.width_tiles || ty >= self.height_tiles {
26            return 0x7F;
27        }
28        self.tiles[(ty * self.width_tiles + tx) as usize]
29    }
30
31    #[inline]
32    pub fn set(&mut self, tx: u32, ty: u32, tile_id: u8) {
33        if tx < self.width_tiles && ty < self.height_tiles {
34            self.tiles[(ty * self.width_tiles + tx) as usize] = tile_id;
35        }
36    }
37
38    pub fn fill(&mut self, tile_id: u8) {
39        self.tiles.fill(tile_id);
40    }
41
42    pub fn set_row(&mut self, ty: u32, row_data: &[u8]) {
43        if ty >= self.height_tiles {
44            return;
45        }
46        let start = (ty * self.width_tiles) as usize;
47        let count = row_data.len().min(self.width_tiles as usize);
48        self.tiles[start..start + count].copy_from_slice(&row_data[..count]);
49    }
50
51    pub fn copy_from_flat(&mut self, data: &[u8]) {
52        let count = data.len().min(self.tiles.len());
53        self.tiles[..count].copy_from_slice(&data[..count]);
54    }
55
56    pub fn render(&self, fb: &mut impl FbSurface, tileset: &TileSet, palette: &Palette) {
57        let fb_w = fb.width();
58        let fb_h = fb.height();
59        for ty in 0..self.height_tiles {
60            for tx in 0..self.width_tiles {
61                let tile_id = self.get(tx, ty) as usize;
62                let tile = tileset.get(tile_id);
63                let screen_x = tx * TILE_SIZE;
64                let screen_y = ty * TILE_SIZE;
65
66                for row in 0..TILE_SIZE {
67                    for col in 0..TILE_SIZE {
68                        let px = screen_x + col;
69                        let py = screen_y + row;
70                        if px < fb_w && py < fb_h {
71                            let color_idx = tile.get(row as usize, col as usize);
72                            let rgba = palette.color(crate::palette::GbColor::from_u8(color_idx));
73                            fb.set_pixel(px, py, rgba);
74                        }
75                    }
76                }
77            }
78        }
79    }
80
81    pub fn render_region(
82        &self,
83        fb: &mut impl FbSurface,
84        tileset: &TileSet,
85        palette: &Palette,
86        tx_start: u32,
87        ty_start: u32,
88        tw: u32,
89        th: u32,
90    ) {
91        let fb_w = fb.width();
92        let fb_h = fb.height();
93        let tx_end = (tx_start + tw).min(self.width_tiles);
94        let ty_end = (ty_start + th).min(self.height_tiles);
95
96        for ty in ty_start..ty_end {
97            for tx in tx_start..tx_end {
98                let tile_id = self.get(tx, ty) as usize;
99                let tile = tileset.get(tile_id);
100                let screen_x = tx * TILE_SIZE;
101                let screen_y = ty * TILE_SIZE;
102
103                for row in 0..TILE_SIZE {
104                    for col in 0..TILE_SIZE {
105                        let px = screen_x + col;
106                        let py = screen_y + row;
107                        if px < fb_w && py < fb_h {
108                            let color_idx = tile.get(row as usize, col as usize);
109                            let rgba = palette.color(crate::palette::GbColor::from_u8(color_idx));
110                            fb.set_pixel(px, py, rgba);
111                        }
112                    }
113                }
114            }
115        }
116    }
117}
118
119pub fn write_tiles_at(buf: &mut ScreenTileBuffer, start_tx: u32, start_ty: u32, tile_ids: &[u8]) {
120    for (i, &tile_id) in tile_ids.iter().enumerate() {
121        let tx = start_tx + i as u32;
122        if tx >= buf.width_tiles {
123            break;
124        }
125        buf.set(tx, start_ty, tile_id);
126    }
127}
128
129#[cfg(test)]
130mod tests {
131    use super::*;
132
133    #[test]
134    fn test_screen_tile_buffer_dynamic_sizes() {
135        let buf = ScreenTileBuffer::new(20, 18);
136        assert_eq!(buf.tiles.len(), 360);
137        assert_eq!(buf.width_tiles, 20);
138        assert_eq!(buf.height_tiles, 18);
139
140        let buf2 = ScreenTileBuffer::new(30, 20);
141        assert_eq!(buf2.tiles.len(), 600);
142
143        let buf3 = ScreenTileBuffer::new(10, 10);
144        assert_eq!(buf3.tiles.len(), 100);
145    }
146
147    #[test]
148    fn test_screen_tile_buffer_set_get() {
149        let mut buf = ScreenTileBuffer::new(20, 18);
150        buf.set(3, 4, 0x42);
151        assert_eq!(buf.get(3, 4), 0x42);
152    }
153
154    #[test]
155    fn test_screen_tile_buffer_fill() {
156        let mut buf = ScreenTileBuffer::new(20, 18);
157        buf.fill(0x01);
158        for ty in 0..18 {
159            for tx in 0..20 {
160                assert_eq!(buf.get(tx, ty), 0x01);
161            }
162        }
163    }
164
165    #[test]
166    fn test_screen_tile_buffer_out_of_bounds() {
167        let buf = ScreenTileBuffer::new(10, 10);
168        assert_eq!(buf.get(10, 0), 0x7F);
169        assert_eq!(buf.get(0, 10), 0x7F);
170    }
171
172    #[test]
173    fn test_screen_tile_buffer_set_out_of_bounds() {
174        let mut buf = ScreenTileBuffer::new(10, 10);
175        buf.set(10, 0, 0xFF);
176        buf.set(0, 10, 0xFF);
177        assert_eq!(buf.get(5, 5), 0x7F);
178    }
179
180    #[test]
181    fn test_screen_tile_buffer_set_row() {
182        let mut buf = ScreenTileBuffer::new(10, 5);
183        buf.set_row(2, &[0x10, 0x20, 0x30]);
184        assert_eq!(buf.get(0, 2), 0x10);
185        assert_eq!(buf.get(1, 2), 0x20);
186        assert_eq!(buf.get(2, 2), 0x30);
187        assert_eq!(buf.get(3, 2), 0x7F);
188    }
189
190    #[test]
191    fn test_screen_tile_buffer_copy_from_flat() {
192        let mut buf = ScreenTileBuffer::new(10, 5);
193        buf.copy_from_flat(&[0xA0, 0xA1, 0xA2]);
194        assert_eq!(buf.get(0, 0), 0xA0);
195        assert_eq!(buf.get(1, 0), 0xA1);
196        assert_eq!(buf.get(2, 0), 0xA2);
197        assert_eq!(buf.get(3, 0), 0x7F);
198    }
199}