tinybit/
viewport.rs

1use std::mem::swap;
2
3use crate::widgets::Widget;
4use crate::{Pixel, PixelBuffer, ScreenPos, ScreenSize};
5
6/// Represents a drawable area on screen.
7pub struct Viewport {
8    /// The viewport's position on screen.
9    /// Where 0,0 is the top left corner
10    pub position: ScreenPos,
11
12    /// The size of the viewport. Should probably match the size of the camera
13    /// that is used with this viewport.
14    pub size: ScreenSize,
15    new_buf: PixelBuffer,
16    old_buf: PixelBuffer,
17    dirty: bool,
18    // Store a cached buffer that can be returned when calling `pixels()`.
19    // This buffer is updated every time `self.dirty` is true and a call
20    // to `pixels()` is made.
21    buf: Vec<Pixel>,
22}
23
24impl Viewport {
25    /// Create a new viewport with a given screen position.
26    pub fn new(position: ScreenPos, size: ScreenSize) -> Self {
27        Self {
28            position,
29            size,
30            new_buf: PixelBuffer::new(size),
31            old_buf: PixelBuffer::new(size),
32            dirty: true,
33            buf: Vec::new(),
34        }
35    }
36
37    /// Resize the viewport.
38    /// Remember to clear the renderer or residual
39    /// characters might remain.
40    pub fn resize(&mut self, width: u16, height: u16) {
41        self.size = ScreenSize::new(width, height);
42        self.new_buf = PixelBuffer::new(self.size);
43        self.old_buf = PixelBuffer::new(self.size);
44        self.dirty = true;
45    }
46
47    /// Draw the pixels onto the renderable surface layers.
48    /// This is offset by the camera and the viewport.
49    pub fn draw_pixels(&mut self, pixels: &[Pixel]) {
50        pixels.into_iter().for_each(|pixel| {
51            self.draw_pixel(*pixel);
52        });
53        self.dirty = true;
54    }
55
56    /// Draw a single pixel onto the rendereable surface layers.
57    /// This is called from `draw_pixels` for each pixel.
58    ///
59    /// This is useful if it's desired to draw just one pixel.
60    pub fn draw_pixel(&mut self, pixel: Pixel) {
61        if self.in_view(pixel.pos) {
62            self.new_buf.set_pixel(pixel);
63        }
64        self.dirty = true;
65    }
66
67    /// Draw a widget with an offset in the viewport.
68    pub fn draw_widget(&mut self, widget: &impl Widget, offset: ScreenPos) {
69        widget.pixels(self.size).into_iter().for_each(|mut p| {
70            p.pos.x += offset.x;
71            p.pos.y += offset.y;
72            self.draw_pixel(p);
73        })
74    }
75
76    fn in_view(&self, pos: ScreenPos) -> bool {
77        pos.x < self.size.width && pos.y < self.size.height
78    }
79
80    fn offset(&self, pos: ScreenPos) -> ScreenPos {
81        ScreenPos::new(pos.x + self.position.x, pos.y + self.position.y)
82    }
83
84    pub fn swap_buffers(&mut self) {
85        swap(&mut self.new_buf, &mut self.old_buf);
86        self.new_buf.pixels.iter_mut().for_each(|opt| {
87            opt.take();
88        });
89        self.dirty = true;
90    }
91
92    pub(crate) fn pixels(&mut self) -> impl Iterator<Item = &Pixel> {
93        if self.dirty {
94            let mut pixels = Vec::<Pixel>::new();
95
96            for (new, old) in self
97                .new_buf
98                .pixels
99                .iter()
100                .enumerate()
101                .zip(&self.old_buf.pixels)
102            {
103                match (new, old) {
104                    ((index, Some(pixel)), _) => {
105                        let pos = self.offset(self.new_buf.index_to_coords(index));
106                        let mut pixel = *pixel;
107                        pixel.pos = pos;
108                        pixels.push(pixel);
109                    }
110                    ((index, None), Some(_)) => {
111                        let pos = self.offset(self.new_buf.index_to_coords(index));
112                        pixels.push(Pixel::white(' ', pos));
113                    }
114                    ((_, None), None) => {}
115                }
116            }
117            self.buf = pixels;
118        }
119
120        self.buf.iter()
121    }
122}
123
124#[cfg(test)]
125mod test {
126    use super::*;
127    use crate::*;
128
129    fn camera(viewport: &Viewport) -> Camera<camera::NoLimit> {
130        let pos = WorldPos::new(30, 30);
131        Camera::from_viewport(pos, viewport)
132    }
133
134    fn viewport() -> Viewport {
135        let pos = ScreenPos::new(2, 2);
136        let size = ScreenSize::new(6, 6);
137        Viewport::new(pos, size)
138    }
139
140    #[test]
141    fn draw_corners() {
142        let mut view = viewport();
143        let cam = camera(&view);
144
145        let min_x = cam.bounding_box.min_x();
146        let max_x = cam.bounding_box.max_x();
147        let min_y = cam.bounding_box.min_y();
148        let max_y = cam.bounding_box.max_y();
149
150        let a = WorldPos::new(min_x, min_y);
151        let b = WorldPos::new(max_x - 1, min_y);
152        let c = WorldPos::new(min_x, max_y - 1);
153        let d = WorldPos::new(max_x - 1, max_y - 1);
154
155        let positions = vec![a, b, c, d];
156        let glyphs = vec!['A', 'B', 'C', 'D'];
157        let pixels = positions
158            .into_iter()
159            .zip(glyphs)
160            .map(|(p, g)| Pixel::new(g, cam.to_screen(p), None, None))
161            .collect::<Vec<_>>();
162
163        view.draw_pixels(&pixels);
164
165        let a = Pixel::new('A', ScreenPos::new(2, 2), None, None);
166        let b = Pixel::new('B', ScreenPos::new(7, 2), None, None);
167        let c = Pixel::new('C', ScreenPos::new(2, 7), None, None);
168        let d = Pixel::new('D', ScreenPos::new(7, 7), None, None);
169
170        let drawn_pixels = view.pixels().cloned().collect::<Vec<_>>();
171
172        assert_eq!(&drawn_pixels, &[a, b, c, d]);
173    }
174}