use std::cmp;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
pub const ZERO: Rect = Rect {
x: 0,
y: 0,
width: 0,
height: 0,
};
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn area(&self) -> usize {
self.width as usize * self.height as usize
}
pub const fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
pub fn left(&self) -> u16 {
self.x
}
pub fn right(&self) -> u16 {
self.x.saturating_add(self.width)
}
pub fn top(&self) -> u16 {
self.y
}
pub fn bottom(&self) -> u16 {
self.y.saturating_add(self.height)
}
pub fn inner(&self, margin: Rect) -> Rect {
let x = self.x.saturating_add(margin.x);
let y = self.y.saturating_add(margin.y);
let right = self.right().saturating_sub(margin.width);
let bottom = self.bottom().saturating_sub(margin.height);
let width = right.saturating_sub(x);
let height = bottom.saturating_sub(y);
Rect {
x,
y,
width,
height,
}
}
pub fn intersects(&self, other: &Rect) -> bool {
self.x < other.right()
&& self.right() > other.x
&& self.y < other.bottom()
&& self.bottom() > other.y
}
pub fn intersection(&self, other: &Rect) -> Rect {
let x = cmp::max(self.x, other.x);
let y = cmp::max(self.y, other.y);
let right = cmp::min(self.right(), other.right());
let bottom = cmp::min(self.bottom(), other.bottom());
Rect {
x,
y,
width: right.saturating_sub(x),
height: bottom.saturating_sub(y),
}
}
pub fn offset(&self, x: u16, y: u16) -> Rect {
Rect {
x: self.x.saturating_add(x),
y: self.y.saturating_add(y),
width: self.width,
height: self.height,
}
}
pub fn rows(&self) -> impl Iterator<Item = u16> {
(self.y..self.bottom()).collect::<Vec<_>>().into_iter()
}
pub fn cols(&self) -> impl Iterator<Item = u16> {
(self.x..self.right()).collect::<Vec<_>>().into_iter()
}
pub fn positions(&self) -> impl Iterator<Item = (u16, u16)> + use<'_> {
self.rows()
.flat_map(move |y| self.cols().map(move |x| (x, y)))
}
}
impl From<(u16, u16, u16, u16)> for Rect {
fn from((x, y, w, h): (u16, u16, u16, u16)) -> Self {
Self {
x,
y,
width: w,
height: h,
}
}
}
impl Default for Rect {
fn default() -> Self {
Self::ZERO
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rect_area() {
let r = Rect::new(0, 0, 10, 5);
assert_eq!(r.area(), 50);
}
#[test]
fn test_rect_is_empty() {
assert!(Rect::new(0, 0, 0, 5).is_empty());
assert!(Rect::new(0, 0, 5, 0).is_empty());
assert!(!Rect::new(0, 0, 5, 5).is_empty());
}
#[test]
fn test_rect_inner() {
let r = Rect::new(0, 0, 20, 10);
let inner = r.inner(Rect::new(2, 1, 2, 1));
assert_eq!(inner, Rect::new(2, 1, 16, 8));
}
#[test]
fn test_rect_intersects() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(5, 5, 10, 10);
let c = Rect::new(20, 20, 5, 5);
assert!(a.intersects(&b));
assert!(!a.intersects(&c));
}
#[test]
fn test_rect_intersection() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(5, 5, 10, 10);
assert_eq!(a.intersection(&b), Rect::new(5, 5, 5, 5));
}
}