use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
#[inline]
pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
#[inline]
pub const fn sized(width: u16, height: u16) -> Self {
Self::new(0, 0, width, height)
}
#[inline]
pub const fn empty() -> Self {
Self::new(0, 0, 0, 0)
}
#[inline]
pub const fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
#[inline]
pub const fn area(&self) -> u32 {
self.width as u32 * self.height as u32
}
#[inline]
pub const fn left(&self) -> u16 {
self.x
}
#[inline]
pub const fn right(&self) -> u16 {
self.x.saturating_add(self.width)
}
#[inline]
pub const fn top(&self) -> u16 {
self.y
}
#[inline]
pub const fn bottom(&self) -> u16 {
self.y.saturating_add(self.height)
}
#[inline]
pub const fn contains(&self, x: u16, y: u16) -> bool {
x >= self.x
&& x < self.x.saturating_add(self.width)
&& y >= self.y
&& y < self.y.saturating_add(self.height)
}
pub fn intersection(&self, other: &Rect) -> Rect {
let x1 = self.x.max(other.x);
let y1 = self.y.max(other.y);
let x2 = self.right().min(other.right());
let y2 = self.bottom().min(other.bottom());
if x1 < x2 && y1 < y2 {
Rect::new(x1, y1, x2 - x1, y2 - y1)
} else {
Rect::empty()
}
}
pub fn union(&self, other: &Rect) -> Rect {
if self.is_empty() {
return *other;
}
if other.is_empty() {
return *self;
}
let x1 = self.x.min(other.x);
let y1 = self.y.min(other.y);
let x2 = self.right().max(other.right());
let y2 = self.bottom().max(other.bottom());
Rect::new(x1, y1, x2 - x1, y2 - y1)
}
pub fn inner(&self, margin: u16) -> Rect {
let margin2 = margin.saturating_mul(2);
if self.width < margin2 || self.height < margin2 {
return Rect::empty();
}
Rect::new(
self.x.saturating_add(margin),
self.y.saturating_add(margin),
self.width - margin2,
self.height - margin2,
)
}
pub fn inner_asymmetric(&self, top: u16, right: u16, bottom: u16, left: u16) -> Rect {
let new_width = self.width.saturating_sub(left).saturating_sub(right);
let new_height = self.height.saturating_sub(top).saturating_sub(bottom);
if new_width == 0 || new_height == 0 {
return Rect::empty();
}
Rect::new(
self.x.saturating_add(left),
self.y.saturating_add(top),
new_width,
new_height,
)
}
pub fn outer(&self, margin: u16) -> Rect {
Rect::new(
self.x.saturating_sub(margin),
self.y.saturating_sub(margin),
self.width.saturating_add(margin.saturating_mul(2)),
self.height.saturating_add(margin.saturating_mul(2)),
)
}
pub fn offset(&self, dx: i16, dy: i16) -> Rect {
Rect::new(
(self.x as i32 + dx as i32).max(0) as u16,
(self.y as i32 + dy as i32).max(0) as u16,
self.width,
self.height,
)
}
pub fn clamp(&self, bounds: &Rect) -> Rect {
self.intersection(bounds)
}
}
impl From<(u16, u16, u16, u16)> for Rect {
fn from((x, y, width, height): (u16, u16, u16, u16)) -> Self {
Self::new(x, y, width, height)
}
}
impl From<Rect> for (u16, u16, u16, u16) {
fn from(rect: Rect) -> Self {
(rect.x, rect.y, rect.width, rect.height)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rect_new() {
let rect = Rect::new(10, 20, 30, 40);
assert_eq!(rect.x, 10);
assert_eq!(rect.y, 20);
assert_eq!(rect.width, 30);
assert_eq!(rect.height, 40);
}
#[test]
fn test_rect_edges() {
let rect = Rect::new(10, 20, 30, 40);
assert_eq!(rect.left(), 10);
assert_eq!(rect.right(), 40);
assert_eq!(rect.top(), 20);
assert_eq!(rect.bottom(), 60);
}
#[test]
fn test_rect_contains() {
let rect = Rect::new(10, 10, 20, 20);
assert!(rect.contains(10, 10));
assert!(rect.contains(29, 29));
assert!(!rect.contains(9, 10));
assert!(!rect.contains(30, 30));
}
#[test]
fn test_rect_intersection() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(5, 5, 10, 10);
let inter = a.intersection(&b);
assert_eq!(inter, Rect::new(5, 5, 5, 5));
}
#[test]
fn test_rect_no_intersection() {
let a = Rect::new(0, 0, 10, 10);
let b = Rect::new(20, 20, 10, 10);
let inter = a.intersection(&b);
assert!(inter.is_empty());
}
#[test]
fn test_rect_inner() {
let rect = Rect::new(0, 0, 20, 20);
let inner = rect.inner(2);
assert_eq!(inner, Rect::new(2, 2, 16, 16));
}
#[test]
fn test_rect_area() {
let rect = Rect::new(0, 0, 10, 20);
assert_eq!(rect.area(), 200);
}
}