use core::{fmt::Display, ops};
use crate::{
HasSize, Pos, Size,
int::Int,
layout::{ColumnMajor, Layout, RowMajor},
};
#[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, Hash)]
#[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 Display for RectError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::InvalidDimensions => {
write!(f, "the provided coordinates do not form a valid rectangle")
}
}
}
}
impl core::error::Error for RectError {}
impl<T: Int> Rect<T> {
pub const EMPTY: Self = Self {
x: T::ZERO,
y: T::ZERO,
w: T::ZERO,
h: T::ZERO,
};
#[must_use]
pub const fn new(x: T, y: T, width: T, height: T) -> Self {
Self::from_ltwh(x, y, width, height)
}
#[must_use]
pub const fn from_tl_size(top_left: Pos<T>, size: Size<T>) -> 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,
})
}
}
#[must_use]
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,
}
}
#[must_use]
pub const fn from_ltwh(l: T, t: T, w: T, h: T) -> Self {
Self { x: l, y: t, w, h }
}
#[must_use]
pub const fn top(&self) -> T {
self.y
}
#[must_use]
pub const fn left(&self) -> T {
self.x
}
#[must_use]
pub fn right(&self) -> T {
self.x.saturating_add(self.w)
}
#[must_use]
pub fn bottom(&self) -> T {
self.y.saturating_add(self.h)
}
#[must_use]
pub const fn top_left(&self) -> Pos<T> {
Pos::new(self.x, self.y)
}
#[must_use]
pub const fn at_origin(&self) -> Self {
Self {
x: T::ZERO,
y: T::ZERO,
w: self.w,
h: self.h,
}
}
#[must_use]
pub fn top_right(&self) -> Pos<T> {
Pos::new(self.right(), self.y)
}
#[must_use]
pub fn bottom_right(&self) -> Pos<T> {
Pos::new(self.right(), self.bottom())
}
#[must_use]
pub fn bottom_left(&self) -> Pos<T> {
Pos::new(self.x, self.bottom())
}
#[must_use]
pub const fn width(&self) -> T {
self.w
}
#[must_use]
pub const fn height(&self) -> T {
self.h
}
#[must_use]
pub fn width_usize(&self) -> usize {
self.w.to_usize()
}
#[must_use]
pub fn height_usize(&self) -> usize {
self.h.to_usize()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.w == T::ZERO || self.h == T::ZERO
}
#[must_use]
pub fn area(&self) -> usize {
self.width_usize() * self.height_usize()
}
#[must_use]
pub fn contains(&self, x: T, y: T) -> bool {
x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
}
#[must_use]
pub fn contains_pos(&self, pos: Pos<T>) -> bool {
self.contains(pos.x, pos.y)
}
#[must_use]
pub fn contains_rect(&self, other: Self) -> bool {
self.x <= other.x
&& self.right() >= other.right()
&& self.y <= other.y
&& self.bottom() >= other.bottom()
}
#[must_use]
pub fn intersect(&self, other: Self) -> Self {
let l = core::cmp::max(self.x, other.x);
let t = core::cmp::max(self.y, other.y);
let r = core::cmp::min(self.right(), other.right());
let b = core::cmp::min(self.bottom(), other.bottom());
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>> + use<T> {
RowMajor::iter_pos(*self)
}
#[must_use]
pub fn row_rect(&self, row: usize) -> Self {
let row = row.min(self.rows_len().saturating_sub(1));
Self {
x: self.x,
y: self.y + T::from_usize(row),
w: self.w,
h: T::ONE,
}
}
fn rows_len(&self) -> usize {
(self.bottom() - self.y).to_usize()
}
fn cols_len(&self) -> usize {
(self.right() - self.x).to_usize()
}
#[must_use]
pub fn col_rect(&self, col: usize) -> Self {
let col = col.min(self.cols_len().saturating_sub(1));
Self {
x: self.x + T::from_usize(col),
y: self.y,
w: T::ONE,
h: self.h,
}
}
#[must_use]
pub fn rows(&self) -> impl ExactSizeIterator<Item = Self> {
let back = if self.is_empty() { 0 } else { self.rows_len() };
Rows {
rect: *self,
front: 0,
back,
}
}
#[must_use]
pub fn cols(&self) -> impl ExactSizeIterator<Item = Self> {
let back = if self.is_empty() { 0 } else { self.cols_len() };
Cols {
rect: *self,
front: 0,
back,
}
}
#[must_use]
pub fn row_pos_iter(&self) -> impl ExactSizeIterator<Item = impl Iterator<Item = Pos<T>>> {
self.rows().map(|row| row.pos_iter())
}
#[must_use]
pub fn col_pos_iter(&self) -> impl ExactSizeIterator<Item = impl Iterator<Item = Pos<T>>> {
self.cols().map(|col| ColumnMajor::iter_pos(col))
}
#[must_use]
pub fn union(&self, other: Self) -> Self {
if self.is_empty() {
return other;
}
if other.is_empty() {
return *self;
}
let l = core::cmp::min(self.x, other.x);
let t = core::cmp::min(self.y, other.y);
let r = core::cmp::max(self.right(), other.right());
let b = core::cmp::max(self.bottom(), other.bottom());
Self {
x: l,
y: t,
w: r - l,
h: b - t,
}
}
#[must_use]
pub fn inflate(&self, dx: T, dy: T) -> Self {
Self {
x: self.x - dx,
y: self.y - dy,
w: self.w + dx + dx,
h: self.h + dy + dy,
}
}
#[must_use]
pub fn shrink(&self, dx: T, dy: T) -> Self {
Self {
x: self.x + dx,
y: self.y + dy,
w: self.w - dx - dx,
h: self.h - dy - dy,
}
}
#[must_use]
pub fn inset(&self, top: T, right: T, bottom: T, left: T) -> Self {
let l = self.x.saturating_add(left);
let t = self.y.saturating_add(top);
let r = self.right().saturating_sub(right).max(l);
let b = self.bottom().saturating_sub(bottom).max(t);
Self {
x: l,
y: t,
w: r - l,
h: b - t,
}
}
#[must_use]
pub fn outset(&self, top: T, right: T, bottom: T, left: T) -> Self {
let l = self.x.saturating_sub(left);
let t = self.y.saturating_sub(top);
let r = self.right().saturating_add(right).max(l);
let b = self.bottom().saturating_add(bottom).max(t);
Self {
x: l,
y: t,
w: r - l,
h: b - t,
}
}
#[must_use]
pub fn center(&self) -> Pos<T> {
let two = T::ONE + T::ONE;
Pos::new(self.x + self.w / two, self.y + self.h / two)
}
#[must_use]
pub fn overlaps(&self, other: Self) -> bool {
self.x < other.right()
&& other.x < self.right()
&& self.y < other.bottom()
&& other.y < self.bottom()
}
#[must_use]
pub fn clamp_within(&self, bounds: Self) -> Self {
let max_x = bounds.right().saturating_sub(self.w).max(bounds.left());
let max_y = bounds.bottom().saturating_sub(self.h).max(bounds.top());
Self {
x: self.x.clamp(bounds.left(), max_x),
y: self.y.clamp(bounds.top(), max_y),
w: self.w,
h: self.h,
}
}
#[must_use]
pub fn centered_in(&self, bounds: Self) -> Self {
let two = T::ONE + T::ONE;
let dx = bounds.width().saturating_sub(self.w) / two;
let dy = bounds.height().saturating_sub(self.h) / two;
Self {
x: bounds.left().saturating_add(dx),
y: bounds.top().saturating_add(dy),
w: self.w,
h: self.h,
}
.clamp_within(bounds)
}
}
struct Rows<T: Int> {
rect: Rect<T>,
front: usize,
back: usize,
}
impl<T: Int> Iterator for Rows<T> {
type Item = Rect<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
let row = self.rect.row_rect(self.front);
self.front += 1;
Some(row)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<T: Int> ExactSizeIterator for Rows<T> {
fn len(&self) -> usize {
self.back - self.front
}
}
impl<T: Int> DoubleEndedIterator for Rows<T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
self.back -= 1;
Some(self.rect.row_rect(self.back))
}
}
impl<T: Int> core::iter::FusedIterator for Rows<T> {}
struct Cols<T: Int> {
rect: Rect<T>,
front: usize,
back: usize,
}
impl<T: Int> Iterator for Cols<T> {
type Item = Rect<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
let col = self.rect.col_rect(self.front);
self.front += 1;
Some(col)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<T: Int> ExactSizeIterator for Cols<T> {
fn len(&self) -> usize {
self.back - self.front
}
}
impl<T: Int> DoubleEndedIterator for Cols<T> {
fn next_back(&mut self) -> Option<Self::Item> {
if self.front >= self.back {
return None;
}
self.back -= 1;
Some(self.rect.col_rect(self.back))
}
}
impl<T: Int> core::iter::FusedIterator for Cols<T> {}
pub struct IntoIter<T: Int> {
current: Pos<T>,
bounds: Rect<T>,
}
impl<T: Int> Iterator for IntoIter<T> {
type Item = Pos<T>;
fn next(&mut self) -> Option<Self::Item> {
if self.current.y >= self.bounds.bottom() {
return None;
}
let pos = self.current;
self.current.x += T::ONE;
if self.current.x >= self.bounds.right() {
self.current.x = self.bounds.left();
self.current.y += T::ONE;
}
Some(pos)
}
fn size_hint(&self) -> (usize, Option<usize>) {
let len = self.len();
(len, Some(len))
}
}
impl<T: Int> ExactSizeIterator for IntoIter<T> {
fn len(&self) -> usize {
if self.current.y >= self.bounds.bottom() {
return 0;
}
let width = (self.bounds.right() - self.bounds.left()).to_usize();
let remaining_in_row = (self.bounds.right() - self.current.x).to_usize();
let remaining_rows = (self.bounds.bottom() - self.current.y).to_usize() - 1;
remaining_in_row + remaining_rows * width
}
}
impl<T: Int> core::iter::FusedIterator for IntoIter<T> {}
impl<T: Int> IntoIterator for Rect<T> {
type Item = Pos<T>;
type IntoIter = IntoIter<T>;
fn into_iter(self) -> Self::IntoIter {
IntoIter {
current: self.top_left(),
bounds: self,
}
}
}
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<T> for Rect<T> {
fn size(&self) -> Size<T> {
Size {
width: self.w,
height: self.h,
}
}
}
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::Mul<Size<T>> for Rect<T> {
type Output = Self;
fn mul(self, rhs: Size<T>) -> Self::Output {
Self {
x: self.x * rhs.width,
y: self.y * rhs.height,
w: self.w * rhs.width,
h: self.h * rhs.height,
}
}
}
impl<T: Int> ops::MulAssign<Size<T>> for Rect<T> {
fn mul_assign(&mut self, rhs: Size<T>) {
self.x *= rhs.width;
self.y *= rhs.height;
self.w *= rhs.width;
self.h *= rhs.height;
}
}
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::{string::ToString, vec, vec::Vec};
#[test]
fn rect_error_display() {
assert_eq!(
RectError::InvalidDimensions.to_string(),
"the provided coordinates do not form a valid rectangle"
);
}
#[test]
fn rect_error_is_error() {
fn assert_error<E: core::error::Error>(_: &E) {}
assert_error(&RectError::InvalidDimensions);
}
#[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 right_saturates_on_unsigned_overflow() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(rect.right(), u16::MAX);
}
#[test]
fn bottom_saturates_on_unsigned_overflow() {
let rect = Rect::new(0u16, 50_000, 10, 40_000);
assert_eq!(rect.bottom(), u16::MAX);
}
#[test]
fn corners_saturate_on_unsigned_overflow() {
let rect = Rect::new(50_000u16, 50_000, 40_000, 40_000);
assert_eq!(rect.top_right(), Pos::new(u16::MAX, 50_000));
assert_eq!(rect.bottom_right(), Pos::new(u16::MAX, u16::MAX));
assert_eq!(rect.bottom_left(), Pos::new(50_000, u16::MAX));
}
#[test]
fn contains_does_not_panic_when_right_overflows() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert!(rect.contains(60_000, 5));
assert!(!rect.contains(10, 5));
}
#[test]
fn contains_rect_does_not_panic_when_right_overflows() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert!(rect.contains_rect(Rect::new(60_000u16, 0, 100, 10)));
}
#[test]
fn intersect_does_not_panic_when_right_overflows() {
let a = Rect::new(50_000u16, 0, 40_000, 10);
let b = Rect::new(60_000u16, 0, 100, 10);
assert_eq!(a.intersect(b), Rect::from_ltwh(60_000, 0, 100, 10));
}
#[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 union_overlapping() {
let a = Rect::from_ltrb(0, 0, 2, 2).unwrap();
let b = Rect::from_ltrb(1, 1, 4, 4).unwrap();
assert_eq!(a.union(b), Rect::from_ltrb(0, 0, 4, 4).unwrap());
}
#[test]
fn union_disjoint() {
let a = Rect::from_ltrb(0, 0, 1, 1).unwrap();
let b = Rect::from_ltrb(5, 5, 6, 6).unwrap();
assert_eq!(a.union(b), Rect::from_ltrb(0, 0, 6, 6).unwrap());
}
#[test]
fn union_with_empty() {
let a = Rect::from_ltrb(1, 1, 3, 3).unwrap();
assert_eq!(a.union(Rect::EMPTY), a);
assert_eq!(Rect::EMPTY.union(a), a);
}
#[test]
fn inflate() {
let rect = Rect::from_ltwh(2, 2, 4, 4);
assert_eq!(rect.inflate(1, 1), Rect::from_ltwh(1, 1, 6, 6));
}
#[test]
fn shrink() {
let rect = Rect::from_ltwh(1, 1, 6, 6);
assert_eq!(rect.shrink(1, 1), Rect::from_ltwh(2, 2, 4, 4));
}
#[test]
fn inflate_then_shrink_is_identity() {
let rect = Rect::from_ltwh(3, 3, 5, 5);
assert_eq!(rect.inflate(2, 2).shrink(2, 2), rect);
}
#[test]
fn inset_asymmetric() {
let rect = Rect::from_ltwh(2, 2, 6, 6);
assert_eq!(rect.inset(1, 2, 1, 2), Rect::from_ltwh(4, 3, 2, 4));
}
#[test]
fn inset_matches_shrink_when_uniform() {
let rect = Rect::from_ltwh(1, 1, 6, 6);
assert_eq!(rect.inset(1, 1, 1, 1), rect.shrink(1, 1));
}
#[test]
fn inset_zero_is_identity() {
let rect = Rect::from_ltwh(1, 2, 3, 4);
assert_eq!(rect.inset(0, 0, 0, 0), rect);
}
#[test]
fn inset_saturates_on_unsigned_underflow() {
let rect = Rect::from_ltwh(0u16, 0, 4, 4);
assert_eq!(rect.inset(0, 10, 0, 0), Rect::from_ltwh(0, 0, 0, 4));
assert_eq!(rect.inset(0, 0, 10, 0), Rect::from_ltwh(0, 0, 4, 0));
}
#[test]
fn inset_saturates_when_left_and_right_cross() {
let rect = Rect::from_ltwh(0u16, 0, 4, 4);
assert_eq!(rect.inset(0, 10, 0, 10), Rect::from_ltwh(10, 0, 0, 4));
}
#[test]
fn inset_does_not_panic_when_self_right_overflows() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(
rect.inset(1, 1, 1, 1),
Rect::from_ltwh(50_001, 1, 15_533, 8)
);
}
#[test]
fn outset_asymmetric() {
let rect = Rect::from_ltwh(4, 3, 2, 4);
assert_eq!(rect.outset(1, 2, 1, 2), Rect::from_ltwh(2, 2, 6, 6));
}
#[test]
fn outset_matches_inflate_when_uniform() {
let rect = Rect::from_ltwh(3, 3, 5, 5);
assert_eq!(rect.outset(1, 1, 1, 1), rect.inflate(1, 1));
}
#[test]
fn outset_saturates_on_unsigned_underflow() {
let rect = Rect::from_ltwh(1u16, 1, 4, 4);
assert_eq!(rect.outset(0, 0, 0, 10), Rect::from_ltwh(0, 1, 5, 4));
}
#[test]
fn outset_saturates_at_max() {
let rect = Rect::from_ltwh(u8::MAX - 2, 0, 2, 2);
assert_eq!(rect.outset(0, 10, 0, 0).right(), u8::MAX);
}
#[test]
fn outset_does_not_panic_when_self_right_overflows() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(
rect.outset(1, 1, 1, 1),
Rect::from_ltwh(49_999, 0, 15_536, 11)
);
}
#[test]
fn inset_then_outset_is_identity_within_bounds() {
let rect = Rect::from_ltwh(5, 5, 10, 10);
assert_eq!(rect.inset(1, 2, 3, 4).outset(1, 2, 3, 4), rect);
}
#[test]
fn at_origin_keeps_size_and_moves_to_origin() {
let rect = Rect::from_ltwh(3, 4, 10, 20);
assert_eq!(rect.at_origin(), Rect::from_ltwh(0, 0, 10, 20));
assert_eq!(rect.at_origin().size(), rect.size());
}
#[test]
fn at_origin_is_const() {
const LOCAL_AREA: Rect<u16> = Rect::from_ltwh(3, 4, 10, 20).at_origin();
assert_eq!(LOCAL_AREA, Rect::from_ltwh(0, 0, 10, 20));
}
#[test]
fn at_origin_matches_size_to_rect() {
let rect = Rect::from_ltwh(3, 4, 10, 20);
assert_eq!(rect.at_origin(), rect.size().to_rect());
}
#[test]
fn at_origin_of_origin_rect_is_identity() {
let rect = Rect::from_ltwh(0, 0, 10, 20);
assert_eq!(rect.at_origin(), rect);
}
#[test]
fn clamp_within_already_inside_is_unchanged() {
let bounds = Rect::from_ltwh(0, 0, 10, 10);
let inside = Rect::from_ltwh(2, 2, 3, 3);
assert_eq!(inside.clamp_within(bounds), inside);
}
#[test]
fn clamp_within_slides_back_into_bounds() {
let bounds = Rect::from_ltwh(0, 0, 10, 10);
let overhanging = Rect::from_ltwh(8, 8, 4, 4);
assert_eq!(
overhanging.clamp_within(bounds),
Rect::from_ltwh(6, 6, 4, 4)
);
}
#[test]
fn clamp_within_negative_position_slides_forward() {
let bounds = Rect::from_ltwh(0, 0, 10, 10);
let off_screen = Rect::from_ltwh(-5, -5, 4, 4);
assert_eq!(off_screen.clamp_within(bounds), Rect::from_ltwh(0, 0, 4, 4));
}
#[test]
fn clamp_within_larger_than_bounds_anchors_top_left() {
let bounds = Rect::from_ltwh(0, 0, 10, 10);
let too_big = Rect::from_ltwh(0, 0, 20, 20);
assert_eq!(too_big.clamp_within(bounds), Rect::from_ltwh(0, 0, 20, 20));
}
#[test]
fn clamp_within_unsigned_does_not_panic() {
let bounds = Rect::from_ltwh(5u16, 5, 10, 10);
let too_big = Rect::from_ltwh(0u16, 0, 30, 30);
assert_eq!(too_big.clamp_within(bounds), Rect::from_ltwh(5, 5, 30, 30));
}
#[test]
fn clamp_within_offset_bounds() {
let bounds = Rect::from_ltwh(100, 100, 10, 10);
let rect = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(rect.clamp_within(bounds), Rect::from_ltwh(100, 100, 4, 4));
}
#[test]
fn clamp_within_does_not_panic_when_bounds_right_overflows() {
let bounds = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(bounds.clamp_within(bounds), bounds);
}
#[test]
fn clamp_within_clamps_correctly_when_bounds_right_overflows() {
let bounds = Rect::new(50_000u16, 0, 40_000, 10);
let hanging = Rect::new(63_000u16, 8, 4_000, 5);
assert_eq!(
hanging.clamp_within(bounds),
Rect::from_ltwh(61_535, 5, 4_000, 5)
);
}
#[test]
fn centered_in_even_bounds() {
let bounds = Rect::from_ltwh(0, 0, 10, 10);
let popup = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(popup.centered_in(bounds), Rect::from_ltwh(3, 3, 4, 4));
}
#[test]
fn centered_in_odd_bounds_rounds_towards_top_left() {
let bounds = Rect::from_ltwh(0, 0, 9, 9);
let popup = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(popup.centered_in(bounds), Rect::from_ltwh(2, 2, 4, 4));
}
#[test]
fn centered_in_offset_bounds() {
let bounds = Rect::from_ltwh(5, 5, 10, 10);
let popup = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(popup.centered_in(bounds), Rect::from_ltwh(8, 8, 4, 4));
}
#[test]
fn centered_in_larger_than_bounds_anchors_top_left() {
let bounds = Rect::from_ltwh(0, 0, 4, 4);
let too_big = Rect::from_ltwh(0, 0, 10, 10);
assert_eq!(too_big.centered_in(bounds), Rect::from_ltwh(0, 0, 10, 10));
}
#[test]
fn centered_in_unsigned_does_not_panic() {
let bounds = Rect::from_ltwh(0u16, 0, 4, 4);
let too_big = Rect::from_ltwh(0u16, 0, 10, 10);
assert_eq!(too_big.centered_in(bounds), Rect::from_ltwh(0, 0, 10, 10));
}
#[test]
fn centered_in_does_not_panic_when_bounds_right_overflows() {
let bounds = Rect::new(50_000u16, 0, 40_000, 10);
let popup = Rect::new(50_000u16, 0, 100, 5);
assert_eq!(
popup.centered_in(bounds),
Rect::from_ltwh(65_435, 2, 100, 5)
);
}
#[test]
fn center_even() {
let rect = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(rect.center(), Pos::new(2, 2));
}
#[test]
fn center_odd_rounds_towards_top_left() {
let rect = Rect::from_ltwh(0, 0, 5, 5);
assert_eq!(rect.center(), Pos::new(2, 2));
}
#[test]
fn overlaps_true() {
let a = Rect::from_ltrb(0, 0, 2, 2).unwrap();
let b = Rect::from_ltrb(1, 1, 3, 3).unwrap();
assert!(a.overlaps(b));
}
#[test]
fn overlaps_false_adjacent() {
let a = Rect::from_ltrb(0, 0, 2, 2).unwrap();
let b = Rect::from_ltrb(2, 2, 4, 4).unwrap();
assert!(!a.overlaps(b));
}
#[test]
fn overlaps_false_disjoint() {
let a = Rect::from_ltrb(0, 0, 2, 2).unwrap();
let b = Rect::from_ltrb(5, 5, 6, 6).unwrap();
assert!(!a.overlaps(b));
}
#[test]
fn into_iter_matches_pos_iter() {
let rect = Rect::from_ltwh(0, 0, 2, 2);
let via_pos_iter: Vec<Pos<i32>> = rect.pos_iter().collect();
let via_into_iter: Vec<Pos<i32>> = rect.into_iter().collect();
assert_eq!(via_pos_iter, via_into_iter);
}
#[test]
fn into_iter_for_loop() {
let rect = Rect::from_ltwh(0, 0, 2, 1);
let mut positions = Vec::new();
for pos in rect {
positions.push(pos);
}
assert_eq!(positions, &[Pos::new(0, 0), Pos::new(1, 0)]);
}
#[test]
fn into_iter_len() {
let rect = Rect::from_ltwh(0, 0, 3, 3);
let mut iter = rect.into_iter();
assert_eq!(iter.len(), 9);
iter.next();
iter.next();
iter.next();
iter.next();
assert_eq!(iter.len(), 5);
assert_eq!(iter.count(), 5);
}
#[test]
fn rows_non_origin_rect() {
let rect = Rect::from_ltwh(2, 3, 4, 3);
let rows: Vec<_> = rect.rows().collect();
assert_eq!(
rows,
&[
Rect::from_ltwh(2, 3, 4, 1),
Rect::from_ltwh(2, 4, 4, 1),
Rect::from_ltwh(2, 5, 4, 1),
]
);
}
#[test]
fn cols_non_origin_rect() {
let rect = Rect::from_ltwh(2, 3, 3, 4);
let cols: Vec<_> = rect.cols().collect();
assert_eq!(
cols,
&[
Rect::from_ltwh(2, 3, 1, 4),
Rect::from_ltwh(3, 3, 1, 4),
Rect::from_ltwh(4, 3, 1, 4),
]
);
}
#[test]
fn rows_len_before_and_after_partial_consumption() {
let rect = Rect::from_ltwh(0, 0, 4, 5);
let mut rows = rect.rows();
assert_eq!(rows.len(), 5);
rows.next();
rows.next();
assert_eq!(rows.len(), 3);
assert_eq!(rows.count(), 3);
}
#[test]
fn cols_len_before_and_after_partial_consumption() {
let rect = Rect::from_ltwh(0, 0, 5, 4);
let mut cols = rect.cols();
assert_eq!(cols.len(), 5);
cols.next();
cols.next();
assert_eq!(cols.len(), 3);
assert_eq!(cols.count(), 3);
}
#[test]
fn rows_len_after_next_back() {
let rect = Rect::from_ltwh(0, 0, 2, 4);
let mut rows = Rows {
rect,
front: 0,
back: rect.height_usize(),
};
assert_eq!(rows.len(), 4);
assert_eq!(rows.next_back(), Some(Rect::from_ltwh(0, 3, 2, 1)));
assert_eq!(rows.len(), 3);
assert_eq!(rows.next(), Some(Rect::from_ltwh(0, 0, 2, 1)));
assert_eq!(rows.len(), 2);
}
#[test]
fn cols_len_after_next_back() {
let rect = Rect::from_ltwh(0, 0, 4, 2);
let mut cols = Cols {
rect,
front: 0,
back: rect.width_usize(),
};
assert_eq!(cols.len(), 4);
assert_eq!(cols.next_back(), Some(Rect::from_ltwh(3, 0, 1, 2)));
assert_eq!(cols.len(), 3);
assert_eq!(cols.next(), Some(Rect::from_ltwh(0, 0, 1, 2)));
assert_eq!(cols.len(), 2);
}
#[test]
fn rows_empty_zero_width() {
let rect = Rect::from_ltwh(0, 0, 0, 3);
assert!(rect.is_empty());
assert_eq!(rect.rows().count(), 0);
assert_eq!(rect.rows().len(), 0);
}
#[test]
fn rows_empty_zero_height() {
let rect = Rect::from_ltwh(0, 0, 3, 0);
assert_eq!(rect.rows().count(), 0);
}
#[test]
fn rows_empty_zero_by_zero() {
assert_eq!(Rect::<i32>::EMPTY.rows().count(), 0);
assert_eq!(Rect::<i32>::EMPTY.cols().count(), 0);
}
#[test]
fn cols_empty_zero_width() {
let rect = Rect::from_ltwh(0, 0, 0, 3);
assert_eq!(rect.cols().count(), 0);
}
#[test]
fn cols_empty_zero_height() {
let rect = Rect::from_ltwh(0, 0, 3, 0);
assert!(rect.is_empty());
assert_eq!(rect.cols().count(), 0);
assert_eq!(rect.cols().len(), 0);
}
#[test]
fn rows_1x1_rect() {
let rect = Rect::from_ltwh(5, 5, 1, 1);
let rows: Vec<_> = rect.rows().collect();
assert_eq!(rows, &[rect]);
}
#[test]
fn cols_1x1_rect() {
let rect = Rect::from_ltwh(5, 5, 1, 1);
let cols: Vec<_> = rect.cols().collect();
assert_eq!(cols, &[rect]);
}
#[test]
fn rows_agrees_with_row_rect_in_range() {
let rect = Rect::from_ltwh(1, 2, 4, 3);
for (i, row) in rect.rows().enumerate() {
assert_eq!(Some(row), Some(rect.row_rect(i)));
}
}
#[test]
fn cols_agrees_with_col_rect_in_range() {
let rect = Rect::from_ltwh(1, 2, 4, 3);
for (i, col) in rect.cols().enumerate() {
assert_eq!(Some(col), Some(rect.col_rect(i)));
}
}
#[test]
fn row_pos_iter_flattens_to_pos_iter() {
let rect = Rect::from_ltwh(1, 2, 3, 2);
let flattened: Vec<_> = rect.row_pos_iter().flatten().collect();
let via_pos_iter: Vec<_> = rect.pos_iter().collect();
assert_eq!(flattened, via_pos_iter);
}
#[test]
fn row_pos_iter_len_and_grouping() {
let rect = Rect::from_ltwh(0, 0, 2, 3);
let iter = rect.row_pos_iter();
assert_eq!(iter.len(), 3);
let grouped: Vec<Vec<_>> = iter.map(Iterator::collect).collect();
assert_eq!(
grouped,
vec![
vec![Pos::new(0, 0), Pos::new(1, 0)],
vec![Pos::new(0, 1), Pos::new(1, 1)],
vec![Pos::new(0, 2), Pos::new(1, 2)],
]
);
}
#[test]
fn row_pos_iter_empty() {
let rect = Rect::from_ltwh(0, 0, 0, 3);
assert_eq!(rect.row_pos_iter().count(), 0);
}
#[test]
fn col_pos_iter_flattens_to_column_major_iter_pos() {
use crate::layout::{ColumnMajor, Layout};
let rect = Rect::from_ltwh(1, 2, 3, 2);
let flattened: Vec<_> = rect.col_pos_iter().flatten().collect();
let via_column_major: Vec<_> = ColumnMajor::iter_pos(rect).collect();
assert_eq!(flattened, via_column_major);
}
#[test]
fn col_pos_iter_len_and_grouping() {
let rect = Rect::from_ltwh(0, 0, 3, 2);
let iter = rect.col_pos_iter();
assert_eq!(iter.len(), 3);
let grouped: Vec<Vec<_>> = iter.map(Iterator::collect).collect();
assert_eq!(
grouped,
vec![
vec![Pos::new(0, 0), Pos::new(0, 1)],
vec![Pos::new(1, 0), Pos::new(1, 1)],
vec![Pos::new(2, 0), Pos::new(2, 1)],
]
);
}
#[test]
fn col_pos_iter_empty() {
let rect = Rect::from_ltwh(0, 0, 3, 0);
assert_eq!(rect.col_pos_iter().count(), 0);
}
#[test]
fn rows_u16_coordinates() {
let rect = Rect::from_ltwh(50_000u16, 0, 3, 2);
let rows: Vec<_> = rect.rows().collect();
assert_eq!(
rows,
&[
Rect::from_ltwh(50_000u16, 0, 3, 1),
Rect::from_ltwh(50_000u16, 1, 3, 1),
]
);
}
#[test]
fn rows_saturating_bottom_yields_only_representable_rows() {
let rect = Rect::new(50_000u16, 50_000, 10, 40_000);
assert_eq!(rect.bottom(), u16::MAX);
let mut rows = rect.rows();
assert_eq!(rows.len(), 15_535);
assert_eq!(rows.next(), Some(Rect::from_ltwh(50_000u16, 50_000, 10, 1)));
assert_eq!(
rows.last(),
Some(Rect::from_ltwh(50_000u16, u16::MAX - 1, 10, 1))
);
}
#[test]
fn cols_saturating_right_yields_only_representable_cols() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(rect.right(), u16::MAX);
let mut cols = rect.cols();
assert_eq!(cols.len(), 15_535);
assert_eq!(cols.next(), Some(Rect::from_ltwh(50_000u16, 0, 1, 10)));
assert_eq!(cols.last(), Some(Rect::from_ltwh(u16::MAX - 1, 0, 1, 10)));
}
#[test]
fn row_rect_saturating_bottom_does_not_overflow() {
let rect = Rect::new(50_000u16, 50_000, 10, 40_000);
assert_eq!(
rect.row_rect(39_999),
Rect::from_ltwh(50_000u16, u16::MAX - 1, 10, 1)
);
}
#[test]
fn col_rect_saturating_right_does_not_overflow() {
let rect = Rect::new(50_000u16, 0, 40_000, 10);
assert_eq!(
rect.col_rect(39_999),
Rect::from_ltwh(u16::MAX - 1, 0, 1, 10)
);
}
#[test]
fn cols_u16_coordinates() {
let rect = Rect::from_ltwh(50_000u16, 0, 3, 2);
let cols: Vec<_> = rect.cols().collect();
assert_eq!(
cols,
&[
Rect::from_ltwh(50_000u16, 0, 1, 2),
Rect::from_ltwh(50_001u16, 0, 1, 2),
Rect::from_ltwh(50_002u16, 0, 1, 2),
]
);
}
#[test]
fn rows_double_ended_fused() {
let rect = Rect::from_ltwh(0, 0, 1, 3);
let mut rows = Rows {
rect,
front: 0,
back: rect.height_usize(),
};
rows.next();
rows.next();
rows.next();
assert_eq!(rows.next(), None);
assert_eq!(rows.next(), None);
assert_eq!(rows.next_back(), None);
}
#[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 mul_size_non_uniform() {
use crate::Size;
let rect = Rect::from_ltwh(1, 2, 3, 4);
let new_rect = rect * Size::new(2, 3);
assert_eq!(new_rect, Rect::from_ltwh(2, 6, 6, 12));
}
#[test]
fn mul_assign_size_non_uniform() {
use crate::Size;
let mut rect = Rect::from_ltwh(1, 2, 3, 4);
rect *= Size::new(2, 3);
assert_eq!(rect, Rect::from_ltwh(2, 6, 6, 12));
}
#[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);
}
#[test]
fn row_rect_out_of_bounds_clamps_to_last_row() {
let rect = Rect::new(0u16, 0, 4, 4);
assert_eq!(rect.row_rect(10), rect.row_rect(3));
assert_eq!(rect.row_rect(10), Rect::from_ltwh(0, 3, 4, 1));
}
#[test]
fn col_rect_out_of_bounds_clamps_to_last_col() {
let rect = Rect::new(0u16, 0, 4, 4);
assert_eq!(rect.col_rect(10), rect.col_rect(3));
assert_eq!(rect.col_rect(10), Rect::from_ltwh(3, 0, 1, 4));
}
#[test]
fn row_rect_result_always_within_bounds() {
let rect = Rect::from_ltwh(0u16, 0, 4, 4);
for row in 0..10 {
let sub = rect.row_rect(row);
assert!(
rect.contains_rect(sub),
"row_rect({row}) = {sub:?} escaped {rect:?}"
);
}
}
#[test]
fn col_rect_result_always_within_bounds() {
let rect = Rect::from_ltwh(0u16, 0, 4, 4);
for col in 0..10 {
let sub = rect.col_rect(col);
assert!(
rect.contains_rect(sub),
"col_rect({col}) = {sub:?} escaped {rect:?}"
);
}
}
#[test]
fn row_rect_last_valid_row_touches_bottom_edge() {
let rect = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(rect.row_rect(3).bottom(), rect.bottom());
}
#[test]
fn col_rect_last_valid_col_touches_right_edge() {
let rect = Rect::from_ltwh(0, 0, 4, 4);
assert_eq!(rect.col_rect(3).right(), rect.right());
}
}