about summary refs log tree commit diff
path: root/src/display/viewport.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/display/viewport.rs')
-rw-r--r--src/display/viewport.rs138
1 files changed, 138 insertions, 0 deletions
diff --git a/src/display/viewport.rs b/src/display/viewport.rs
new file mode 100644
index 000000000000..bd2fac07146f
--- /dev/null
+++ b/src/display/viewport.rs
@@ -0,0 +1,138 @@
+use super::Draw;
+use super::{make_box, BoxStyle};
+use crate::types::{BoundingBox, Position, Positioned};
+use std::fmt::{self, Debug};
+use std::io::{self, Write};
+
+pub struct Viewport<W> {
+    /// The box describing the visible part of the viewport.
+    ///
+    /// Generally the size of the terminal, and positioned at 0, 0
+    pub outer: BoundingBox,
+
+    /// The box describing the inner part of the viewport
+    ///
+    /// Its position is relative to `outer.inner()`, and its size should generally not
+    /// be smaller than outer
+    pub inner: BoundingBox,
+
+    /// The actual screen that the viewport writes to
+    pub out: W,
+}
+
+impl<W> Debug for Viewport<W> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(
+            f,
+            "Viewport {{ outer: {:?}, inner: {:?}, out: <OUT> }}",
+            self.outer, self.inner
+        )
+    }
+}
+
+impl<W> Viewport<W> {
+    /// Returns true if the (inner-relative) position of the given entity is
+    /// visible within this viewport
+    fn visible<E: Positioned>(&self, ent: &E) -> bool {
+        self.on_screen(ent.position()).within(self.outer.inner())
+    }
+
+    /// Convert the given inner-relative position to one on the actual screen
+    fn on_screen(&self, pos: Position) -> Position {
+        pos + self.inner.position + self.outer.inner().position
+    }
+}
+
+impl<W: Write> Viewport<W> {
+    /// Draw the given entity to the viewport at its position, if visible
+    pub fn draw<T: Draw>(&mut self, entity: &T) -> io::Result<()> {
+        if !self.visible(entity) {
+            return Ok(());
+        }
+        write!(
+            self,
+            "{}",
+            (entity.position()
+                + self.inner.position
+                + self.outer.inner().position)
+                .cursor_goto()
+        )?;
+        entity.do_draw(self)
+    }
+
+    /// Clear whatever is drawn at the given inner-relative position, if visible
+    pub fn clear(&mut self, pos: Position) -> io::Result<()> {
+        write!(self, "{} ", self.on_screen(pos).cursor_goto(),)
+    }
+
+    /// Initialize this viewport by drawing its outer box to the screen
+    pub fn init(&mut self) -> io::Result<()> {
+        write!(self, "{}", make_box(BoxStyle::Thin, self.outer.dimensions))
+    }
+}
+
+impl<W> Positioned for Viewport<W> {
+    fn position(&self) -> Position {
+        self.outer.position
+    }
+}
+
+impl<W: Write> Write for Viewport<W> {
+    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
+        self.out.write(buf)
+    }
+
+    fn flush(&mut self) -> io::Result<()> {
+        self.out.flush()
+    }
+
+    fn write_all(&mut self, buf: &[u8]) -> io::Result<()> {
+        self.out.write_all(buf)
+    }
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+    use crate::types::Dimensions;
+    // use proptest::prelude::*;
+
+    #[test]
+    fn test_visible() {
+        assert!(Viewport {
+            outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }),
+            inner: BoundingBox {
+                position: Position { x: -10, y: -10 },
+                dimensions: Dimensions { w: 15, h: 15 },
+            },
+            out: (),
+        }
+        .visible(&Position { x: 13, y: 13 }));
+
+        assert!(!Viewport {
+            outer: BoundingBox::at_origin(Dimensions { w: 10, h: 10 }),
+            inner: BoundingBox {
+                position: Position { x: -10, y: -10 },
+                dimensions: Dimensions { w: 15, h: 15 },
+            },
+            out: (),
+        }
+        .visible(&Position { x: 1, y: 1 }));
+    }
+
+    // proptest! {
+    //     #[test]
+    //     fn nothing_is_visible_in_viewport_off_screen(pos: Position, outer: BoundingBox) {
+    //         let invisible_viewport = Viewport {
+    //             outer,
+    //             inner: BoundingBox {
+    //                 position: Position {x: -(outer.dimensions.w as i16), y: -(outer.dimensions.h as i16)},
+    //                 dimensions: outer.dimensions,
+    //             },
+    //             out: ()
+    //         };
+
+    //         assert!(!invisible_viewport.visible(&pos));
+    //     }
+    // }
+}