altui-core 0.2.0

A library to build rich terminal user interfaces or dashboards
Documentation
use std::cmp::{max, min};

use crate::layout::{Direction, Margin};

/// A simple rectangle used in the computation of the layout and to give widgets a hint about the
/// area they are supposed to render to.
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, Default)]
pub struct Rect {
    pub x: u16,
    pub y: u16,
    pub width: u16,
    pub height: u16,
}

impl Rect {
    /// Creates a new rect, with width and height limited to keep the area under max u16.
    /// If clipped, aspect ratio will be preserved.
    pub fn new(x: u16, y: u16, width: u16, height: u16) -> Rect {
        let max_area = u16::max_value();
        let (clipped_width, clipped_height) =
            if u32::from(width) * u32::from(height) > u32::from(max_area) {
                let aspect_ratio = f64::from(width) / f64::from(height);
                let max_area_f = f64::from(max_area);
                let height_f = (max_area_f / aspect_ratio).sqrt();
                let width_f = height_f * aspect_ratio;
                (width_f as u16, height_f as u16)
            } else {
                (width, height)
            };
        Rect {
            x,
            y,
            width: clipped_width,
            height: clipped_height,
        }
    }

    /// Computes the area of the rectangle.
    ///
    /// **Returns**: The rectangle's area as `width × height`.
    /// **Note**: Consumes `self` by value.
    pub fn area(self) -> u16 {
        self.width * self.height
    }

    /// Returns the x-coordinate of the rectangle's left edge.
    pub fn left(self) -> u16 {
        self.x
    }

    /// Returns the x-coordinate of the rectangle's right edge.
    pub fn right(self) -> u16 {
        self.x.saturating_add(self.width)
    }

    /// Returns the y-coordinate of the rectangle's top edge.
    pub fn top(self) -> u16 {
        self.y
    }

    /// Returns the y-coordinate of the rectangle's bottom edge.
    pub fn bottom(self) -> u16 {
        self.y.saturating_add(self.height)
    }

    pub fn inner(self, margin: &Margin) -> Rect {
        if self.width < 2 * margin.horizontal || self.height < 2 * margin.vertical {
            Rect::default()
        } else {
            Rect {
                x: self.x + margin.horizontal,
                y: self.y + margin.vertical,
                width: self.width - 2 * margin.horizontal,
                height: self.height - 2 * margin.vertical,
            }
        }
    }

    pub fn union(self, other: Rect) -> Rect {
        let x1 = min(self.x, other.x);
        let y1 = min(self.y, other.y);
        let x2 = max(self.x + self.width, other.x + other.width);
        let y2 = max(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

    pub fn intersection(self, other: Rect) -> Rect {
        let x1 = max(self.x, other.x);
        let y1 = max(self.y, other.y);
        let x2 = min(self.x + self.width, other.x + other.width);
        let y2 = min(self.y + self.height, other.y + other.height);
        Rect {
            x: x1,
            y: y1,
            width: x2 - x1,
            height: y2 - y1,
        }
    }

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

    pub fn inhere(&self, x: u16, y: u16) -> bool {
        x >= self.x && x < self.x + self.width && y >= self.y && y < self.y + self.height
    }

    #[inline(always)]
    pub(crate) fn start(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => self.x as f64,
            Direction::Vertical => self.y as f64,
        }
    }

    #[inline(always)]
    pub(crate) fn end(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => (self.x + self.width) as f64,
            Direction::Vertical => (self.y + self.height) as f64,
        }
    }

    #[inline(always)]
    pub(crate) fn size(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => self.width as f64,
            Direction::Vertical => self.height as f64,
        }
    }

    #[inline(always)]
    pub(crate) fn cross_start(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => self.y as f64,
            Direction::Vertical => self.x as f64,
        }
    }

    #[inline(always)]
    pub(crate) fn cross_end(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => (self.y + self.height) as f64,
            Direction::Vertical => (self.x + self.width) as f64,
        }
    }

    #[inline(always)]
    pub(crate) fn cross_size(&self, direction: Direction) -> f64 {
        match direction {
            Direction::Horizontal => self.height as f64,
            Direction::Vertical => self.width as f64,
        }
    }
}