about summary refs log blame commit diff
path: root/src/display/viewport.rs
blob: bd2fac07146f1a7c5b3a455a06980710e24e5dbf (plain) (tree)









































































































































                                                                                                             
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));
    //     }
    // }
}