use core::{fmt::Display, ops};
use crate::{
HasSize, Pos, Size,
int::Int,
layout::{RowMajor, Traversal},
};
#[macro_export]
macro_rules! rect {
($tl: expr, $br: expr) => {{
let tl = $tl;
let br = $br;
let l = if tl.x < br.x { tl.x } else { br.x };
let t = if tl.y < br.y { tl.y } else { br.y };
let r = if tl.x < br.x { br.x } else { tl.x };
let b = if tl.y < br.y { br.y } else { tl.y };
$crate::Rect::from_ltrb_unchecked(l, t, r, b)
}};
($l:expr, $t:expr, $r:expr, $b:expr) => {{
let l = if $l < $r { $l } else { $r };
let t = if $t < $b { $t } else { $b };
let r = if $l < $r { $r } else { $l };
let b = if $t < $b { $b } else { $t };
$crate::Rect::from_ltrb_unchecked(l, t, r, b)
}};
}
#[repr(C)]
#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Rect<T = i32> {
x: T,
y: T,
w: T,
h: T,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RectError {
InvalidDimensions,
}
impl<T: Int> Rect<T> {
pub const EMPTY: Self = Self {
x: T::ZERO,
y: T::ZERO,
w: T::ZERO,
h: T::ZERO,
};
pub fn new(x: T, y: T, width: usize, height: usize) -> Self {
Self::from_ltwh(x, y, width, height)
}
pub fn from_tl_size(top_left: Pos<T>, size: Size) -> Self {
Self::from_ltwh(top_left.x, top_left.y, size.width, size.height)
}
pub fn from_tlbr(tl: Pos<T>, br: Pos<T>) -> Result<Self, RectError> {
if tl.x >= br.x || tl.y >= br.y {
Err(RectError::InvalidDimensions)
} else {
Ok(Self {
x: tl.x,
y: tl.y,
w: br.x - tl.x,
h: br.y - tl.y,
})
}
}
pub fn from_ltrb(l: T, t: T, r: T, b: T) -> Result<Self, RectError> {
if l > r || t > b {
Err(RectError::InvalidDimensions)
} else {
Ok(Self {
x: l,
y: t,
w: r - l,
h: b - t,
})
}
}
pub fn from_ltrb_unchecked(l: T, t: T, r: T, b: T) -> Self {
debug_assert!(l <= r && t <= b);
Self {
x: l,
y: t,
w: r - l,
h: b - t,
}
}
pub fn from_ltwh(l: T, t: T, w: usize, h: usize) -> Self {
Self {
x: l,
y: t,
w: T::from_usize(w),
h: T::from_usize(h),
}
}
pub const fn top(&self) -> T {
self.y
}
pub const fn left(&self) -> T {
self.x
}
pub fn right(&self) -> T {
self.x + self.w
}
pub fn bottom(&self) -> T {
self.y + self.h
}
pub const fn top_left(&self) -> Pos<T> {
Pos::new(self.x, self.y)
}
pub fn top_right(&self) -> Pos<T> {
Pos::new(self.x + self.w, self.y)
}
pub fn bottom_right(&self) -> Pos<T> {
Pos::new(self.x + self.w, self.y + self.h)
}
pub fn bottom_left(&self) -> Pos<T> {
Pos::new(self.x, self.y + self.h)
}
pub const fn width(&self) -> T {
self.w
}
pub const fn height(&self) -> T {
self.h
}
pub fn width_usize(&self) -> usize {
self.w.to_usize()
}
pub fn height_usize(&self) -> usize {
self.h.to_usize()
}
pub fn is_empty(&self) -> bool {
self.w == T::ZERO || self.h == T::ZERO
}
pub fn area(&self) -> usize {
self.width_usize() * self.height_usize()
}
pub fn contains(&self, x: T, y: T) -> bool {
let r = self.x + self.w;
let b = self.y + self.h;
x >= self.x && x < r && y >= self.y && y < b
}
pub fn contains_pos(&self, pos: Pos<T>) -> bool {
self.contains(pos.x, pos.y)
}
pub fn contains_rect(&self, other: Self) -> bool {
let sr = self.x + self.w;
let sb = self.y + self.h;
let or = other.x + other.w;
let ob = other.y + other.h;
self.x <= other.x && sr >= or && self.y <= other.y && sb >= ob
}
#[must_use]
pub fn intersect(&self, other: Self) -> Self {
let sr = self.x + self.w;
let sb = self.y + self.h;
let or = other.x + other.w;
let ob = other.y + other.h;
let l = core::cmp::max(self.x, other.x);
let t = core::cmp::max(self.y, other.y);
let r = core::cmp::min(sr, or);
let b = core::cmp::min(sb, ob);
if l < r && t < b {
Self {
x: l,
y: t,
w: r - l,
h: b - t,
}
} else {
Self::EMPTY
}
}
pub fn pos_iter(&self) -> impl Iterator<Item = Pos<T>> {
RowMajor::iter_pos(*self)
}
#[must_use]
pub fn row_rect(&self, row: usize) -> Self {
Self {
x: self.x,
y: self.y + T::from_usize(row),
w: self.w,
h: T::ONE,
}
}
#[must_use]
pub fn col_rect(&self, col: usize) -> Self {
Self {
x: self.x + T::from_usize(col),
y: self.y,
w: T::ONE,
h: self.h,
}
}
}
impl<T: Display + Int> Display for Rect<T> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Rect({}, {}, {}, {})", self.x, self.y, self.w, self.h)
}
}
impl<T: Int> HasSize for Rect<T> {
fn size(&self) -> Size {
Size {
width: self.width_usize(),
height: self.height_usize(),
}
}
}
impl<T: Int> ops::Add<Pos<T>> for Rect<T> {
type Output = Self;
fn add(self, rhs: Pos<T>) -> Self::Output {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
w: self.w,
h: self.h,
}
}
}
impl<T: Int> ops::AddAssign<Pos<T>> for Rect<T> {
fn add_assign(&mut self, rhs: Pos<T>) {
self.x += rhs.x;
self.y += rhs.y;
}
}
impl<T: Int> ops::Sub<Pos<T>> for Rect<T> {
type Output = Self;
fn sub(self, rhs: Pos<T>) -> Self::Output {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
w: self.w,
h: self.h,
}
}
}
impl<T: Int> ops::SubAssign<Pos<T>> for Rect<T> {
fn sub_assign(&mut self, rhs: Pos<T>) {
self.x -= rhs.x;
self.y -= rhs.y;
}
}
impl<T: Int> ops::Mul<T> for Rect<T> {
type Output = Self;
fn mul(self, rhs: T) -> Self::Output {
Self {
x: self.x * rhs,
y: self.y * rhs,
w: self.w * rhs,
h: self.h * rhs,
}
}
}
impl<T: Int> ops::MulAssign<T> for Rect<T> {
fn mul_assign(&mut self, rhs: T) {
self.x *= rhs;
self.y *= rhs;
self.w *= rhs;
self.h *= rhs;
}
}
impl<T: Int> ops::Div<T> for Rect<T> {
type Output = Self;
fn div(self, rhs: T) -> Self::Output {
Self {
x: self.x / rhs,
y: self.y / rhs,
w: self.w / rhs,
h: self.h / rhs,
}
}
}
impl<T: Int> ops::DivAssign<T> for Rect<T> {
fn div_assign(&mut self, rhs: T) {
self.x /= rhs;
self.y /= rhs;
self.w /= rhs;
self.h /= rhs;
}
}
pub type Rect16 = Rect<u16>;
pub type RectI = Rect<i32>;
#[cfg(test)]
mod tests {
extern crate alloc;
use super::*;
use alloc::vec::Vec;
#[test]
fn rect_macro_ltrb() {
let r: Rect<i32> = rect!(1, 2, 3, 4);
assert_eq!(r, Rect::from_ltrb(1, 2, 3, 4).unwrap());
}
#[test]
fn rect_macro_ltrb_auto() {
let r: Rect<i32> = rect!(3, 4, 1, 2);
assert_eq!(r, Rect::from_ltrb(1, 2, 3, 4).unwrap());
}
#[test]
fn rect_macro_tlbr() {
let r: Rect<i32> = rect!(Pos::new(1, 2), Pos::new(3, 4));
assert_eq!(r, Rect::from_tlbr(Pos::new(1, 2), Pos::new(3, 4)).unwrap());
}
#[test]
fn rect_macro_tlbr_auto() {
let r: Rect<i32> = rect!(Pos::new(3, 4), Pos::new(1, 2));
assert_eq!(r, Rect::from_tlbr(Pos::new(1, 2), Pos::new(3, 4)).unwrap());
}
#[test]
fn from_tlbr_ok() {
let rect = Rect::from_tlbr(Pos::new(1, 2), Pos::new(3, 4)).unwrap();
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 3);
assert_eq!(rect.bottom(), 4);
}
#[test]
fn from_tlbr_err() {
let rect = Rect::from_tlbr(Pos::new(3, 2), Pos::new(1, 4));
assert!(rect.is_err());
}
#[test]
fn from_ltrb_ok() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 3);
assert_eq!(rect.bottom(), 4);
}
#[test]
fn from_ltrb_err() {
let rect = Rect::from_ltrb(3, 2, 1, 4);
assert!(rect.is_err());
}
#[test]
fn from_ltwh_ok() {
let rect = Rect::from_ltwh(1, 2, 3, 4);
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 4);
assert_eq!(rect.bottom(), 6);
}
#[test]
fn new_xywh() {
let rect = Rect::new(1, 2, 3, 4);
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 4);
assert_eq!(rect.bottom(), 6);
}
#[test]
fn new_xywh_zero() {
let rect = Rect::new(0, 0, 0, 0);
assert!(rect.is_empty());
assert_eq!(rect.left(), 0);
assert_eq!(rect.top(), 0);
assert_eq!(rect.right(), 0);
assert_eq!(rect.bottom(), 0);
}
#[test]
fn from_tl_size() {
use crate::Size;
let rect = Rect::from_tl_size(Pos::new(1, 2), Size::new(3, 4));
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 4);
assert_eq!(rect.bottom(), 6);
}
#[test]
fn rect16_alias() {
let rect: Rect16 = Rect16::new(1, 2, 3, 4);
assert_eq!(rect.left(), 1u16);
assert_eq!(rect.top(), 2u16);
assert_eq!(rect.width(), 3);
assert_eq!(rect.height(), 4);
}
#[test]
fn recti_alias() {
let rect: RectI = RectI::new(1, 2, 3, 4);
assert_eq!(rect.left(), 1i32);
assert_eq!(rect.top(), 2i32);
assert_eq!(rect.width(), 3);
assert_eq!(rect.height(), 4);
}
#[test]
fn c_layout() {
use core::mem::{offset_of, size_of};
#[repr(C)]
struct CRect {
x: i32,
y: i32,
w: i32,
h: i32,
}
assert_eq!(size_of::<Rect<i32>>(), size_of::<CRect>());
assert_eq!(offset_of!(Rect<i32>, x), offset_of!(CRect, x));
assert_eq!(offset_of!(Rect<i32>, y), offset_of!(CRect, y));
assert_eq!(offset_of!(Rect<i32>, w), offset_of!(CRect, w));
assert_eq!(offset_of!(Rect<i32>, h), offset_of!(CRect, h));
}
#[test]
fn coords() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 3);
assert_eq!(rect.bottom(), 4);
}
#[test]
fn corners() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert_eq!(rect.top_left(), Pos::new(1, 2));
assert_eq!(rect.top_right(), Pos::new(3, 2));
assert_eq!(rect.bottom_right(), Pos::new(3, 4));
assert_eq!(rect.bottom_left(), Pos::new(1, 4));
}
#[test]
fn dimensions() {
let rect = Rect::from_ltrb(1, 2, 3, 6).unwrap();
assert_eq!(rect.width(), 2);
assert_eq!(rect.height(), 4);
assert!(!rect.is_empty());
}
#[test]
fn empty_rect() {
let rect = Rect::from_ltrb(1, 2, 1, 2).unwrap();
assert_eq!(rect.width(), 0);
assert_eq!(rect.height(), 0);
assert!(rect.is_empty());
}
#[test]
fn area() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert_eq!(rect.area(), 4);
}
#[test]
fn has_size() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert_eq!(
rect.size(),
Size {
width: 2,
height: 2
}
);
}
#[test]
fn contains_pos_true() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert!(rect.contains_pos(Pos::new(2, 3)));
}
#[test]
fn contains_pos_false_x_before_left() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert!(!rect.contains_pos(Pos::new(0, 3)));
}
#[test]
fn contains_pos_false_x_after_right() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert!(!rect.contains_pos(Pos::new(4, 3)));
}
#[test]
fn contains_pos_false_y_before_top() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert!(!rect.contains_pos(Pos::new(2, 1)));
}
#[test]
fn contains_pos_false_y_after_bottom() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
assert!(!rect.contains_pos(Pos::new(2, 5)));
}
#[test]
fn contains_rect_true() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
assert!(rect.contains_rect(Rect::from_ltrb(2, 3, 4, 5).unwrap()));
}
#[test]
fn contains_rect_false_left_edge() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
assert!(!rect.contains_rect(Rect::from_ltrb(0, 3, 4, 5).unwrap()));
}
#[test]
fn contains_rect_false_right_edge() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
assert!(!rect.contains_rect(Rect::from_ltrb(2, 3, 6, 5).unwrap()));
}
#[test]
fn contains_rect_false_top_edge() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
assert!(!rect.contains_rect(Rect::from_ltrb(2, 1, 4, 5).unwrap()));
}
#[test]
fn contains_rect_false_bottom_edge() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
assert!(!rect.contains_rect(Rect::from_ltrb(2, 3, 4, 7).unwrap()));
}
#[test]
fn intersect_full() {
let a = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let b = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let intersection = a.intersect(b);
assert_eq!(intersection, a);
}
#[test]
fn intersect_partial() {
let a = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let b = Rect::from_ltrb(3, 4, 7, 8).unwrap();
let intersection = a.intersect(b);
assert_eq!(intersection, Rect::from_ltrb(3, 4, 5, 6).unwrap());
}
#[test]
fn intersect_none() {
let a = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let b = Rect::from_ltrb(6, 7, 8, 9).unwrap();
let intersection = a.intersect(b);
assert_eq!(intersection, Rect::EMPTY);
}
#[test]
fn from_ltrb_unchecked() {
let rect = Rect::from_ltrb_unchecked(1, 2, 3, 4);
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 3);
assert_eq!(rect.bottom(), 4);
}
#[test]
fn add_pos() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let pos = Pos::new(1, 1);
let new_rect = rect + pos;
assert_eq!(new_rect.left(), 2);
assert_eq!(new_rect.top(), 3);
assert_eq!(new_rect.right(), 4);
assert_eq!(new_rect.bottom(), 5);
}
#[test]
fn add_assign_pos() {
let mut rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let pos = Pos::new(1, 1);
rect += pos;
assert_eq!(rect.left(), 2);
assert_eq!(rect.top(), 3);
assert_eq!(rect.right(), 4);
assert_eq!(rect.bottom(), 5);
}
#[test]
fn sub_pos() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let pos = Pos::new(1, 1);
let new_rect = rect - pos;
assert_eq!(new_rect.left(), 0);
assert_eq!(new_rect.top(), 1);
assert_eq!(new_rect.right(), 2);
assert_eq!(new_rect.bottom(), 3);
}
#[test]
fn sub_assign_pos() {
let mut rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let pos = Pos::new(1, 1);
rect -= pos;
assert_eq!(rect.left(), 0);
assert_eq!(rect.top(), 1);
assert_eq!(rect.right(), 2);
assert_eq!(rect.bottom(), 3);
}
#[test]
fn mul_int() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let new_rect = rect * 2;
assert_eq!(new_rect.left(), 2);
assert_eq!(new_rect.top(), 4);
assert_eq!(new_rect.right(), 6);
assert_eq!(new_rect.bottom(), 8);
}
#[test]
fn mul_assign_int() {
let mut rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
rect *= 2;
assert_eq!(rect.left(), 2);
assert_eq!(rect.top(), 4);
assert_eq!(rect.right(), 6);
assert_eq!(rect.bottom(), 8);
}
#[test]
fn div_int() {
let rect = Rect::from_ltrb(2, 4, 6, 8).unwrap();
let new_rect = rect / 2;
assert_eq!(new_rect.left(), 1);
assert_eq!(new_rect.top(), 2);
assert_eq!(new_rect.right(), 3);
assert_eq!(new_rect.bottom(), 4);
}
#[test]
fn div_assign_int() {
let mut rect = Rect::from_ltrb(2, 4, 6, 8).unwrap();
rect /= 2;
assert_eq!(rect.left(), 1);
assert_eq!(rect.top(), 2);
assert_eq!(rect.right(), 3);
assert_eq!(rect.bottom(), 4);
}
#[test]
fn pos_iter() {
let rect = Rect::from_ltrb(1, 2, 3, 4).unwrap();
let positions: Vec<Pos<i32>> = rect.pos_iter().collect();
assert_eq!(
positions,
&[
Pos::new(1, 2),
Pos::new(2, 2),
Pos::new(1, 3),
Pos::new(2, 3)
]
);
}
#[test]
fn row_rect() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let row_rect = rect.row_rect(0);
assert_eq!(row_rect.left(), 1);
assert_eq!(row_rect.top(), 2);
assert_eq!(row_rect.right(), 5);
assert_eq!(row_rect.bottom(), 3);
}
#[test]
fn col_rect() {
let rect = Rect::from_ltrb(1, 2, 5, 6).unwrap();
let col_rect = rect.col_rect(0);
assert_eq!(col_rect.left(), 1);
assert_eq!(col_rect.top(), 2);
assert_eq!(col_rect.right(), 2);
assert_eq!(col_rect.bottom(), 6);
}
}