use crate::{Point, Size};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect {
pub x: f32,
pub y: f32,
pub width: f32,
pub height: f32,
}
impl Rect {
pub const fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn from_ltrb(l: f32, t: f32, r: f32, b: f32) -> Self {
Self {
x: l,
y: t,
width: r - l,
height: b - t,
}
}
pub fn from_origin_size(origin: Point, size: Size) -> Self {
Self {
x: origin.x,
y: origin.y,
width: size.width,
height: size.height,
}
}
pub fn right(&self) -> f32 {
self.x + self.width
}
pub fn bottom(&self) -> f32 {
self.y + self.height
}
pub fn origin(&self) -> Point {
Point::new(self.x, self.y)
}
pub fn size(&self) -> Size {
Size::new(self.width, self.height)
}
pub fn is_empty(&self) -> bool {
self.width <= 0.0 || self.height <= 0.0
}
pub fn union(&self, other: &Rect) -> Rect {
if self.is_empty() {
return *other;
}
if other.is_empty() {
return *self;
}
Rect::from_ltrb(
self.x.min(other.x),
self.y.min(other.y),
self.right().max(other.right()),
self.bottom().max(other.bottom()),
)
}
pub fn intersect(&self, other: &Rect) -> Option<Rect> {
let r = Rect::from_ltrb(
self.x.max(other.x),
self.y.max(other.y),
self.right().min(other.right()),
self.bottom().min(other.bottom()),
);
(!r.is_empty()).then_some(r)
}
pub fn intersects(&self, other: &Rect) -> bool {
!self.is_empty()
&& !other.is_empty()
&& self.x < other.right()
&& other.x < self.right()
&& self.y < other.bottom()
&& other.y < self.bottom()
}
pub fn contains(&self, p: Point) -> bool {
p.x >= self.x && p.x < self.right() && p.y >= self.y && p.y < self.bottom()
}
pub fn contains_inclusive(&self, p: Point) -> bool {
p.x >= self.x && p.x <= self.right() && p.y >= self.y && p.y <= self.bottom()
}
pub fn expand(&self, d: f32) -> Rect {
Rect::new(
self.x - d,
self.y - d,
self.width + 2.0 * d,
self.height + 2.0 * d,
)
}
pub const EVERYTHING: Rect = Rect {
x: -1.0e9,
y: -1.0e9,
width: 2.0e9,
height: 2.0e9,
};
pub fn corners(&self) -> [Point; 4] {
[
Point::new(self.x, self.y),
Point::new(self.right(), self.y),
Point::new(self.right(), self.bottom()),
Point::new(self.x, self.bottom()),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn union_ignores_empty() {
let a = Rect::new(0.0, 0.0, 10.0, 10.0);
assert_eq!(Rect::default().union(&a), a);
assert_eq!(a.union(&Rect::default()), a);
}
#[test]
fn intersect_disjoint_is_none() {
let a = Rect::new(0.0, 0.0, 10.0, 10.0);
let b = Rect::new(20.0, 0.0, 10.0, 10.0);
assert_eq!(a.intersect(&b), None);
assert!(!a.intersects(&b));
}
#[test]
fn intersect_overlap() {
let a = Rect::new(0.0, 0.0, 10.0, 10.0);
let b = Rect::new(5.0, 5.0, 10.0, 10.0);
assert_eq!(a.intersect(&b), Some(Rect::new(5.0, 5.0, 5.0, 5.0)));
}
}