diff options
Diffstat (limited to 'src/display/viewport.rs')
-rw-r--r-- | src/display/viewport.rs | 138 |
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)); + // } + // } +} |