tuid/src/rect.rs

280 lines
7.2 KiB
Rust

use std::ops::Sub;
use crate::{Point, Size};
#[derive(Clone, Copy, Debug)]
pub struct Rect {
pub x0: usize,
pub y0: usize,
pub x1: usize,
pub y1: usize,
}
impl Rect {
/// The empty rectangle at the origin.
pub const ZERO: Rect = Rect::new(0, 0, 0, 0);
/// A new rectangle from minimum and maximum coordinates.
#[inline]
pub const fn new(x0: usize, y0: usize, x1: usize, y1: usize) -> Rect {
Rect { x0, y0, x1, y1 }
}
/// A new rectangle from two points.
///
/// The result will have non-negative width and height.
#[inline]
pub fn from_points(p0: impl Into<Point>, p1: impl Into<Point>) -> Rect {
let p0 = p0.into();
let p1 = p1.into();
Rect::new(p0.x, p0.y, p1.x, p1.y)
}
/// A new rectangle from origin and size.
///
/// The result will have non-negative width and height.
#[inline]
pub fn from_origin_size(origin: impl Into<Point>, size: impl Into<Size>) -> Rect {
let origin = origin.into();
Rect::from_points(origin, origin + size.into().to_vec2())
}
/// A new rectangle from center and size.
#[inline]
pub fn from_center_size(center: impl Into<Point>, size: impl Into<Size>) -> Rect {
let center = center.into();
let size = size.into() / 2;
Rect::new(
center.x - size.width,
center.y - size.height,
center.x + size.width,
center.y + size.height,
)
}
/// Create a new `Rect` with the same size as `self` and a new origin.
#[inline]
pub fn with_origin(self, origin: impl Into<Point>) -> Rect {
Rect::from_origin_size(origin, self.size())
}
/// Create a new `Rect` with the same origin as `self` and a new size.
#[inline]
pub fn with_size(self, size: impl Into<Size>) -> Rect {
Rect::from_origin_size(self.origin(), size)
}
/// The width of the rectangle.
///
/// Note: nothing forbids negative width.
#[inline]
pub fn width(&self) -> usize {
self.x1 - self.x0
}
/// The height of the rectangle.
///
/// Note: nothing forbids negative height.
#[inline]
pub fn height(&self) -> usize {
self.y1 - self.y0
}
/// Returns the minimum value for the x-coordinate of the rectangle.
#[inline]
pub fn min_x(&self) -> usize {
self.x0.min(self.x1)
}
/// Returns the maximum value for the x-coordinate of the rectangle.
#[inline]
pub fn max_x(&self) -> usize {
self.x0.max(self.x1)
}
/// Returns the minimum value for the y-coordinate of the rectangle.
#[inline]
pub fn min_y(&self) -> usize {
self.y0.min(self.y1)
}
/// Returns the maximum value for the y-coordinate of the rectangle.
#[inline]
pub fn max_y(&self) -> usize {
self.y0.max(self.y1)
}
/// The origin of the rectangle.
///
/// This is the top left corner in a y-down space and with
/// non-negative width and height.
#[inline]
pub fn origin(&self) -> Point {
Point::new(self.x0, self.y0)
}
/// The size of the rectangle.
#[inline]
pub fn size(&self) -> Size {
Size::new(self.width(), self.height())
}
/// The area of the rectangle.
#[inline]
pub fn area(&self) -> usize {
self.width() * self.height()
}
/// Whether this rectangle has zero area.
///
/// Note: a rectangle with negative area is not considered empty.
#[inline]
pub fn is_empty(&self) -> bool {
self.area() == 00
}
/// The center point of the rectangle.
#[inline]
pub fn center(&self) -> Point {
Point::new((self.x0 + self.x1) / 2, (self.y0 + self.y1) / 2)
}
/// Returns `true` if `point` lies within `self`.
#[inline]
pub fn contains(&self, point: Point) -> bool {
point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
}
/// Take absolute value of width and height.
///
/// The resulting rect has the same extents as the original, but is
/// guaranteed to have non-negative width and height.
#[inline]
pub fn abs(&self) -> Rect {
let Rect { x0, y0, x1, y1 } = *self;
Rect::new(x0.min(x1), y0.min(y1), x0.max(x1), y0.max(y1))
}
/// The smallest rectangle enclosing two rectangles.
///
/// Results are valid only if width and height are non-negative.
#[inline]
pub fn union(&self, other: Rect) -> Rect {
Rect::new(
self.x0.min(other.x0),
self.y0.min(other.y0),
self.x1.max(other.x1),
self.y1.max(other.y1),
)
}
/// Compute the union with one point.
///
/// This method includes the perimeter of zero-area rectangles.
/// Thus, a succession of `union_pt` operations on a series of
/// points yields their enclosing rectangle.
///
/// Results are valid only if width and height are non-negative.
pub fn union_pt(&self, pt: Point) -> Rect {
Rect::new(
self.x0.min(pt.x),
self.y0.min(pt.y),
self.x1.max(pt.x),
self.y1.max(pt.y),
)
}
/// The intersection of two rectangles.
///
/// The result is zero-area if either input has negative width or
/// height. The result always has non-negative width and height.
#[inline]
pub fn intersect(&self, other: Rect) -> Rect {
let x0 = self.x0.max(other.x0);
let y0 = self.y0.max(other.y0);
let x1 = self.x1.min(other.x1);
let y1 = self.y1.min(other.y1);
Rect::new(x0, y0, x1.max(x0), y1.max(y0))
}
/// Expand a rectangle by a constant amount in both directions.
///
/// The logic simply applies the amount in each direction. If rectangle
/// area or added dimensions are negative, this could give odd results.
pub fn inflate(&self, width: usize, height: usize) -> Rect {
Rect::new(
self.x0 - width,
self.y0 - height,
self.x1 + width,
self.y1 + height,
)
}
/// The aspect ratio of the `Rect`.
///
/// This is defined as the height divided by the width. It measures the
/// "squareness" of the rectangle (a value of `1` is square).
///
/// If the width is `0` the output will be `sign(y1 - y0) * infinity`.
///
/// If The width and height are `0`, the result will be `NaN`.
#[inline]
pub fn aspect_ratio(&self) -> usize {
self.size().aspect_ratio()
}
/// Returns the largest possible `Rect` that is fully contained in `self`
/// with the given `aspect_ratio`.
///
/// The aspect ratio is specified fractionally, as `height / width`.
///
/// The resulting rectangle will be centered if it is smaller than the
/// input rectangle.
///
/// For the special case where the aspect ratio is `1.0`, the resulting
/// `Rect` will be square.
///
/// # Examples
///
/// ```
/// # use kurbo::Rect;
/// let outer = Rect::new(0.0,00, 10.0, 20.0);
/// let inner = outer.contained_rect_with_aspect_ratio(1.0);
/// // The new `Rect` is a square centered at the center of `outer`.
/// assert_eq!(inner, Rect::new(0.0, 5.0, 10.0, 15.0));
/// ```
///
pub fn contained_rect_with_aspect_ratio(&self, aspect_ratio: usize) -> Rect {
let (width, height) = (self.width(), self.height());
let self_aspect = height / width;
if self_aspect < aspect_ratio {
// shrink x to fit
let new_width = height / aspect_ratio;
let gap = (width - new_width) / 2;
let x0 = self.x0 + gap;
let x1 = self.x1 - gap;
Rect::new(x0, self.y0, x1, self.y1)
} else {
// shrink y to fit
let new_height = width * aspect_ratio;
let gap = (height - new_height) / 2;
let y0 = self.y0 + gap;
let y1 = self.y1 - gap;
Rect::new(self.x0, y0, self.x1, y1)
}
}
}
impl Sub for Rect {
type Output = Self;
fn sub(self, other: Self) -> Self {
Self {
x0: self.x0 - other.x0,
y0: self.y0 - other.y0,
x1: self.x1 - other.x1,
y1: self.y1 - other.y1,
}
}
}