termit-ui 0.0.1

Terminal UI with GUI-like layouts
Documentation
use std::ops;

#[derive(Default, Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
pub struct X(u16);
#[derive(Default, Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
pub struct Y(u16);
#[derive(Default, Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
pub struct Window {
    pub position: Point,
    pub size: Point,
}
#[derive(Default, Debug, Copy, Clone, Ord, PartialOrd, PartialEq, Eq, Hash)]
pub struct Point {
    pub x: X,
    pub y: Y,
}

pub fn x(x: u16) -> X {
    X(x)
}
pub fn y(y: u16) -> Y {
    Y(y)
}
pub fn point(x: X, y: Y) -> Point {
    Point { x, y }
}
pub fn window(position: Point, size: Point) -> Window {
    Window { position, size }
}

impl Point {
    pub fn area(&self) -> u32 {
        self.x.0 as u32 * self.y.0 as u32
    }
    pub fn contains(&self, o: &Point) -> bool {
        self.x > o.x && self.y > o.y
    }
    pub fn point_at_idx(&self, idx: u32) -> Option<Point> {
        if idx >= self.area() {
            None
        } else {
            self.x.point_at_idx(idx)
        }
    }
    pub fn idx_at_point(&self, p: Point) -> Option<u32> {
        if p.y >= self.y || p.x >= self.x {
            None
        } else {
            self.x.idx_at_point(p)
        }
    }
}
impl Window {
    pub fn crop(&self, w: &Window) -> Window {
        let pos = self.position + w.position;
        let win = window(pos, w.size);

        &win & self
    }
}
impl X {
    #[inline]
    pub fn max(o1: X, o2: X) -> X {
        if o1 > o2 {
            o1
        } else {
            o2
        }
    }
    #[inline]
    pub fn min(o1: X, o2: X) -> X {
        if o1 > o2 {
            o2
        } else {
            o1
        }
    }
    #[inline]
    pub fn to_primitive(&self) -> u16 {
        self.0
    }
    pub fn point_at_idx(&self, idx: u32) -> Option<Point> {
        if self.0 == 0 {
            None
        } else {
            let px = x((idx % self.0 as u32) as u16);
            let py = y((idx / self.0 as u32) as u16);
            Some(point(px, py))
        }
    }
    pub fn idx_at_point(&self, p: Point) -> Option<u32> {
        if p.x >= *self {
            None
        } else {
            let w = self.0 as u32;
            let x = p.x.0 as u32;
            let y = p.y.0 as u32;
            Some(y * w + x)
        }
    }
}
impl Y {
    #[inline]
    pub fn max(o1: Y, o2: Y) -> Y {
        if o1 > o2 {
            o1
        } else {
            o2
        }
    }
    #[inline]
    pub fn min(o1: Y, o2: Y) -> Y {
        if o1 > o2 {
            o2
        } else {
            o1
        }
    }
    #[inline]
    pub fn to_primitive(&self) -> u16 {
        self.0
    }
}

impl ops::Div<u16> for X {
    type Output = X;
    fn div(self, o: u16) -> Self::Output {
        X(self.0 / o)
    }
}
impl ops::Div<u16> for Y {
    type Output = Y;
    fn div(self, o: u16) -> Self::Output {
        Y(self.0 / o)
    }
}

impl ops::Mul<u16> for X {
    type Output = X;
    fn mul(self, o: u16) -> Self::Output {
        X(self.0.saturating_mul(o))
    }
}

impl ops::Mul<u16> for Y {
    type Output = Y;
    fn mul(self, o: u16) -> Self::Output {
        Y(self.0.saturating_mul(o))
    }
}

impl ops::BitAnd<&Window> for &Window {
    type Output = Window;
    fn bitand(self, o: &Window) -> Self::Output {
        let pos = point(
            X::max(self.position.x, o.position.x),
            Y::max(self.position.y, o.position.y),
        );

        let w = window(
            pos,
            point(
                X::min(
                    self.position.x + self.size.x - pos.x,
                    o.position.x + o.size.x - pos.x,
                ),
                Y::min(
                    self.position.y + self.size.y - pos.y,
                    o.position.y + o.size.y - pos.y,
                ),
            ),
        );

        trace!("intersect windows: {:?} and {:?} => {:?}", self, o, w);

        w
    }
}
impl ops::BitAnd<Point> for Window {
    type Output = Option<Point>;
    fn bitand(self, o: Point) -> Self::Output {
        if self.position.x <= o.x
            && self.position.y <= o.y
            && self.position.x + self.size.x >= o.x
            && self.position.y + self.size.y >= o.y
        {
            Some(o)
        } else {
            None
        }
    }
}

impl ops::Mul<Y> for X {
    type Output = Point;
    fn mul(self, o: Y) -> Self::Output {
        point(self, o)
    }
}
impl ops::Mul<X> for Y {
    type Output = Point;
    fn mul(self, o: X) -> Self::Output {
        point(o, self)
    }
}
impl ops::Div<X> for Point {
    type Output = Y;
    fn div(self, o: X) -> Self::Output {
        Y((self.x.0 as u32 * self.y.0 as u32 / o.0 as u32) as u16)
    }
}
impl ops::Div<Y> for Point {
    type Output = X;
    fn div(self, o: Y) -> Self::Output {
        X((self.x.0 as u32 * self.y.0 as u32 / o.0 as u32) as u16)
    }
}
impl ops::SubAssign for X {
    fn sub_assign(&mut self, o: Self) {
        self.0 = self.0.saturating_sub(o.0)
    }
}
impl ops::AddAssign for X {
    fn add_assign(&mut self, o: Self) {
        self.0 = self.0.saturating_add(o.0)
    }
}
impl ops::SubAssign for Y {
    fn sub_assign(&mut self, o: Self) {
        self.0 = self.0.saturating_sub(o.0)
    }
}
impl ops::AddAssign for Y {
    fn add_assign(&mut self, o: Self) {
        self.0 = self.0.saturating_add(o.0)
    }
}
impl ops::Sub<X> for X {
    type Output = X;
    fn sub(self, o: X) -> Self::Output {
        X(self.0.saturating_sub(o.0))
    }
}
impl ops::Add<X> for X {
    type Output = X;
    fn add(self, o: X) -> Self::Output {
        X(self.0.saturating_add(o.0))
    }
}
impl ops::Sub<Y> for Y {
    type Output = Y;
    fn sub(self, o: Y) -> Self::Output {
        Y(self.0.saturating_sub(o.0))
    }
}
impl ops::Add<Y> for Y {
    type Output = Y;
    fn add(self, o: Y) -> Self::Output {
        Y(self.0.saturating_add(o.0))
    }
}
impl ops::SubAssign for Point {
    fn sub_assign(&mut self, o: Self) {
        *self = *self - o
    }
}
impl ops::AddAssign for Point {
    fn add_assign(&mut self, o: Self) {
        *self = *self + o
    }
}
impl ops::Sub<Point> for Point {
    type Output = Point;
    fn sub(self, o: Point) -> Self::Output {
        point(self.x - o.x, self.y - o.y)
    }
}
impl ops::Add<Point> for Point {
    type Output = Point;
    fn add(self, pos: Point) -> Self::Output {
        point(self.x + pos.x, self.y + pos.y)
    }
}
impl ops::Sub<X> for Point {
    type Output = Point;
    fn sub(self, o: X) -> Self::Output {
        point(self.x - o, self.y)
    }
}
impl ops::Add<X> for Point {
    type Output = Point;
    fn add(self, o: X) -> Self::Output {
        point(self.x + o, self.y)
    }
}
impl ops::Sub<Y> for Point {
    type Output = Point;
    fn sub(self, o: Y) -> Self::Output {
        point(self.x, self.y - o)
    }
}
impl ops::Add<Y> for Point {
    type Output = Point;
    fn add(self, o: Y) -> Self::Output {
        point(self.x, self.y + o)
    }
}

#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub struct Position {
    pub left: u16,
    pub top: u16,
}
impl Position {
    pub fn add(&self, operand: Self) -> Self {
        Self {
            left: self.left.saturating_add(operand.left),
            top: self.top.saturating_add(operand.top),
        }
    }
}

#[test]
fn test_x_times_y() {
    assert_eq!(X(1) * Y(2), point(X(1), Y(2)));
}
#[test]
fn test_y_times_x() {
    assert_eq!(Y(2) * X(1), point(X(1), Y(2)));
}
#[test]
fn test_to_primitive() {
    assert_eq!(X(3).to_primitive(), 3);
    assert_eq!(Y(2).to_primitive(), 2);
}

#[test]
fn test_window_crop() {
    let op = window(point(x(0), y(0)), point(x(10000), y(30000)));
    let crop = window(point(x(10), y(3)), point(x(100), y(300))).crop(&op);
    assert_eq!(crop.position.x, x(10));
    assert_eq!(crop.position.y, y(3));
    assert_eq!(crop.size.x, x(100));
    assert_eq!(crop.size.y, y(300));

    let op = window(point(x(10), y(3)), point(x(100), y(300)));
    let crop = window(point(x(0), y(0)), point(x(10000), y(30000))).crop(&op);
    assert_eq!(crop.position.x, x(10));
    assert_eq!(crop.position.y, y(3));
    assert_eq!(crop.size.x, x(100));
    assert_eq!(crop.size.y, y(300));
}

#[test]
fn test_size_contains() {
    let p = point(x(3), y(2));
    assert_eq!(p.contains(&point(x(0), y(0))), true);
    assert_eq!(p.contains(&point(x(2), y(1))), true);
    assert_eq!(p.contains(&point(x(3), y(1))), false);
    assert_eq!(p.contains(&point(x(2), y(2))), false);
}

#[test]
fn test_idx_at_point() {
    let p = point(x(3), y(2));
    assert_eq!(p.idx_at_point(point(x(0), y(0))), Some(0));
    assert_eq!(p.idx_at_point(point(x(1), y(0))), Some(1));
    assert_eq!(p.idx_at_point(point(x(1), y(1))), Some(4));
    assert_eq!(p.idx_at_point(point(x(2), y(1))), Some(5));
    assert_eq!(p.idx_at_point(point(x(3), y(1))), None);
    assert_eq!(p.idx_at_point(point(x(1), y(2))), None);

    assert_eq!(p.point_at_idx(0), Some(point(x(0), y(0))));
    assert_eq!(p.point_at_idx(5), Some(point(x(2), y(1))));
    assert_eq!(p.point_at_idx(6), None);
}