scrin 0.1.79

A terminal UI toolkit with panes, widgets, overlays, animations, and Aisling-powered effects/loaders.
Documentation
use std::cmp;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rect {
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
}

impl Rect {
    pub const ZERO: Rect = Rect {
        x: 0,
        y: 0,
        width: 0,
        height: 0,
    };

    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
        Self {
            x,
            y,
            width,
            height,
        }
    }

    pub fn area(&self) -> usize {
        self.width as usize * self.height as usize
    }

    pub const fn is_empty(&self) -> bool {
        self.width == 0 || self.height == 0
    }

    pub fn left(&self) -> u16 {
        self.x
    }

    pub fn right(&self) -> u16 {
        self.x.saturating_add(self.width)
    }

    pub fn top(&self) -> u16 {
        self.y
    }

    pub fn bottom(&self) -> u16 {
        self.y.saturating_add(self.height)
    }

    pub fn inner(&self, margin: Rect) -> Rect {
        let x = self.x.saturating_add(margin.x);
        let y = self.y.saturating_add(margin.y);
        let right = self.right().saturating_sub(margin.width);
        let bottom = self.bottom().saturating_sub(margin.height);
        let width = right.saturating_sub(x);
        let height = bottom.saturating_sub(y);
        Rect {
            x,
            y,
            width,
            height,
        }
    }

    pub fn intersects(&self, other: &Rect) -> bool {
        self.x < other.right()
            && self.right() > other.x
            && self.y < other.bottom()
            && self.bottom() > other.y
    }

    pub fn intersection(&self, other: &Rect) -> Rect {
        let x = cmp::max(self.x, other.x);
        let y = cmp::max(self.y, other.y);
        let right = cmp::min(self.right(), other.right());
        let bottom = cmp::min(self.bottom(), other.bottom());
        Rect {
            x,
            y,
            width: right.saturating_sub(x),
            height: bottom.saturating_sub(y),
        }
    }

    pub fn offset(&self, x: u16, y: u16) -> Rect {
        Rect {
            x: self.x.saturating_add(x),
            y: self.y.saturating_add(y),
            width: self.width,
            height: self.height,
        }
    }

    pub fn rows(&self) -> impl Iterator<Item = u16> {
        (self.y..self.bottom()).collect::<Vec<_>>().into_iter()
    }

    pub fn cols(&self) -> impl Iterator<Item = u16> {
        (self.x..self.right()).collect::<Vec<_>>().into_iter()
    }

    pub fn positions(&self) -> impl Iterator<Item = (u16, u16)> + use<'_> {
        self.rows()
            .flat_map(move |y| self.cols().map(move |x| (x, y)))
    }
}

impl From<(u16, u16, u16, u16)> for Rect {
    fn from((x, y, w, h): (u16, u16, u16, u16)) -> Self {
        Self {
            x,
            y,
            width: w,
            height: h,
        }
    }
}

impl Default for Rect {
    fn default() -> Self {
        Self::ZERO
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_rect_area() {
        let r = Rect::new(0, 0, 10, 5);
        assert_eq!(r.area(), 50);
    }

    #[test]
    fn test_rect_is_empty() {
        assert!(Rect::new(0, 0, 0, 5).is_empty());
        assert!(Rect::new(0, 0, 5, 0).is_empty());
        assert!(!Rect::new(0, 0, 5, 5).is_empty());
    }

    #[test]
    fn test_rect_inner() {
        let r = Rect::new(0, 0, 20, 10);
        let inner = r.inner(Rect::new(2, 1, 2, 1));
        assert_eq!(inner, Rect::new(2, 1, 16, 8));
    }

    #[test]
    fn test_rect_intersects() {
        let a = Rect::new(0, 0, 10, 10);
        let b = Rect::new(5, 5, 10, 10);
        let c = Rect::new(20, 20, 5, 5);
        assert!(a.intersects(&b));
        assert!(!a.intersects(&c));
    }

    #[test]
    fn test_rect_intersection() {
        let a = Rect::new(0, 0, 10, 10);
        let b = Rect::new(5, 5, 10, 10);
        assert_eq!(a.intersection(&b), Rect::new(5, 5, 5, 5));
    }
}