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 { /// 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 Debug for Viewport { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, "Viewport {{ outer: {:?}, inner: {:?}, out: }}", self.outer, self.inner ) } } impl Viewport { /// Returns true if the (inner-relative) position of the given entity is /// visible within this viewport fn visible(&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 Viewport { /// Draw the given entity to the viewport at its position, if visible pub fn 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 Positioned for Viewport { fn position(&self) -> Position { self.outer.position } } impl Write for Viewport { fn write(&mut self, buf: &[u8]) -> io::Result { 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)); // } // } }