280 lines
7.2 KiB
Rust
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,
|
|
}
|
|
}
|
|
}
|