use libm;
use core::ops::{Add, Sub};
use crate::{Insets, PathEl, Point, RoundedRect, Shape, Size, Vec2};
#[derive(Clone, Copy, Default, Debug)]
pub struct Rect {
pub x0: f64,
pub y0: f64,
pub x1: f64,
pub y1: f64,
}
impl Rect {
pub const ZERO: Rect = Rect::new(0., 0., 0., 0.);
#[inline]
pub const fn new(x0: f64, y0: f64, x1: f64, y1: f64) -> Rect {
Rect { x0, y0, x1, y1 }
}
#[inline]
pub fn from_points(p0: impl Into<Point>, p1: impl Into<Point>) -> Rect {
let p0 = p0.into();
let p1 = p1.into();
Rect {
x0: p0.x,
y0: p0.y,
x1: p1.x,
y1: p1.y,
}
.abs()
}
#[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())
}
#[inline]
pub fn with_origin(self, origin: impl Into<Point>) -> Rect {
Rect::from_origin_size(origin, self.size())
}
#[inline]
pub fn with_size(self, size: impl Into<Size>) -> Rect {
Rect::from_origin_size(self.origin(), size)
}
#[inline]
pub fn inset(self, insets: impl Into<Insets>) -> Rect {
self + insets.into()
}
#[inline]
pub fn width(&self) -> f64 {
self.x1 - self.x0
}
#[inline]
pub fn height(&self) -> f64 {
self.y1 - self.y0
}
#[inline]
pub fn min_x(&self) -> f64 {
self.x0.min(self.x1)
}
#[inline]
pub fn max_x(&self) -> f64 {
self.x0.max(self.x1)
}
#[inline]
pub fn min_y(&self) -> f64 {
self.y0.min(self.y1)
}
#[inline]
pub fn max_y(&self) -> f64 {
self.y0.max(self.y1)
}
#[inline]
pub fn origin(&self) -> Point {
Point::new(self.x0, self.y0)
}
#[inline]
pub fn size(&self) -> Size {
Size::new(self.width(), self.height())
}
#[inline]
pub fn area(&self) -> f64 {
self.width() * self.height()
}
#[inline]
pub fn center(&self) -> Point {
Point::new(0.5 * (self.x0 + self.x1), 0.5 * (self.y0 + self.y1))
}
#[inline]
pub fn contains(&self, point: Point) -> bool {
point.x >= self.x0 && point.x < self.x1 && point.y >= self.y0 && point.y < self.y1
}
#[inline]
pub fn abs(&self) -> Rect {
let Rect { x0, y0, x1, y1 } = *self;
Rect {
x0: x0.min(x1),
y0: y0.min(y1),
x1: x0.max(x1),
y1: y0.max(y1),
}
}
#[inline]
pub fn union(&self, other: Rect) -> Rect {
Rect {
x0: self.x0.min(other.x0),
y0: self.y0.min(other.y0),
x1: self.x1.max(other.x1),
y1: self.y1.max(other.y1),
}
}
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),
)
}
#[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 {
x0,
y0,
x1: x1.max(x0),
y1: y1.max(y0),
}
}
pub fn inflate(&self, width: f64, height: f64) -> Rect {
Rect {
x0: self.x0 - width,
y0: self.y0 - height,
x1: self.x1 + width,
y1: self.y1 + height,
}
}
#[inline]
pub fn round(self) -> Rect {
Rect::new(
libm::round(self.x0),
libm::round(self.y0),
libm::round(self.x1),
libm::round(self.y1),
)
}
#[inline]
pub fn to_rounded_rect(self, radius: f64) -> RoundedRect {
RoundedRect::from_rect(self, radius)
}
}
impl From<(Point, Point)> for Rect {
fn from(points: (Point, Point)) -> Rect {
Rect::from_points(points.0, points.1)
}
}
impl From<(Point, Size)> for Rect {
fn from(params: (Point, Size)) -> Rect {
Rect::from_origin_size(params.0, params.1)
}
}
impl Add<Vec2> for Rect {
type Output = Rect;
#[inline]
fn add(self, v: Vec2) -> Rect {
Rect {
x0: self.x0 + v.x,
y0: self.y0 + v.y,
x1: self.x1 + v.x,
y1: self.y1 + v.y,
}
}
}
impl Sub<Vec2> for Rect {
type Output = Rect;
#[inline]
fn sub(self, v: Vec2) -> Rect {
Rect {
x0: self.x0 - v.x,
y0: self.y0 - v.y,
x1: self.x1 - v.x,
y1: self.y1 - v.y,
}
}
}
impl Sub for Rect {
type Output = Insets;
#[inline]
fn sub(self, other: Rect) -> Insets {
let x0 = other.x0 - self.x0;
let y0 = other.y0 - self.y0;
let x1 = self.x1 - other.x1;
let y1 = self.y1 - other.y1;
Insets { x0, y0, x1, y1 }
}
}
#[doc(hidden)]
pub struct RectPathIter {
rect: Rect,
ix: usize,
}
impl Shape for Rect {
type BezPathIter = RectPathIter;
fn to_bez_path(&self, _tolerance: f64) -> RectPathIter {
RectPathIter { rect: *self, ix: 0 }
}
#[inline]
fn area(&self) -> f64 {
Rect::area(self)
}
#[inline]
fn perimeter(&self, _accuracy: f64) -> f64 {
2.0 * (libm::fabs(self.width()) + libm::fabs(self.height()))
}
#[inline]
fn winding(&self, pt: Point) -> i32 {
let xmin = self.x0.min(self.x1);
let xmax = self.x0.max(self.x1);
let ymin = self.y0.min(self.y1);
let ymax = self.y0.max(self.y1);
if pt.x >= xmin && pt.x < xmax && pt.y >= ymin && pt.y < ymax {
if (self.x1 > self.x0) ^ (self.y1 > self.y0) {
-1
} else {
1
}
} else {
0
}
}
#[inline]
fn bounding_box(&self) -> Rect {
self.abs()
}
#[inline]
fn as_rect(&self) -> Option<Rect> {
Some(*self)
}
}
impl Iterator for RectPathIter {
type Item = PathEl;
fn next(&mut self) -> Option<PathEl> {
self.ix += 1;
match self.ix {
1 => Some(PathEl::MoveTo(Point::new(self.rect.x0, self.rect.y0))),
2 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y0))),
3 => Some(PathEl::LineTo(Point::new(self.rect.x1, self.rect.y1))),
4 => Some(PathEl::LineTo(Point::new(self.rect.x0, self.rect.y1))),
5 => Some(PathEl::ClosePath),
_ => None,
}
}
}
#[cfg(test)]
mod tests {
use crate::{Point, Rect, Shape};
fn assert_approx_eq(x: f64, y: f64) {
assert!((x - y).abs() < 1e-7);
}
#[test]
fn area_sign() {
let r = Rect::new(0.0, 0.0, 10.0, 10.0);
let center = r.center();
assert_approx_eq(r.area(), 100.0);
assert_eq!(r.winding(center), 1);
let p = r.into_bez_path(1e-9);
assert_approx_eq(r.area(), p.area());
assert_eq!(r.winding(center), p.winding(center));
let r_flip = Rect::new(0.0, 10.0, 10.0, 0.0);
assert_approx_eq(r_flip.area(), -100.0);
assert_eq!(r_flip.winding(Point::new(5.0, 5.0)), -1);
let p_flip = r_flip.into_bez_path(1e-9);
assert_approx_eq(r_flip.area(), p_flip.area());
assert_eq!(r_flip.winding(center), p_flip.winding(center));
}
}