use std::fmt;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct Point {
pub x: i16,
pub y: i16,
}
impl Point {
pub const fn new(x: i16, y: i16) -> Self {
Self { x, y }
}
pub const fn zero() -> Self {
Self { x: 0, y: 0 }
}
}
impl fmt::Display for Point {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {})", self.x, self.y)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Rect {
pub a: Point, pub b: Point, }
impl Rect {
pub const fn new(x1: i16, y1: i16, x2: i16, y2: i16) -> Self {
Self {
a: Point::new(x1, y1),
b: Point::new(x2, y2),
}
}
pub const fn from_points(a: Point, b: Point) -> Self {
Self { a, b }
}
pub const fn from_coords(x: i16, y: i16, width: i16, height: i16) -> Self {
Self {
a: Point::new(x, y),
b: Point::new(x + width, y + height),
}
}
pub fn move_by(&mut self, dx: i16, dy: i16) {
self.a.x += dx;
self.a.y += dy;
self.b.x += dx;
self.b.y += dy;
}
pub fn grow(&mut self, dx: i16, dy: i16) {
self.a.x -= dx;
self.a.y -= dy;
self.b.x += dx;
self.b.y += dy;
}
pub fn contains(&self, p: Point) -> bool {
let in_x = if self.b.x > self.a.x {
p.x >= self.a.x && p.x < self.b.x
} else {
p.x == self.a.x
};
let in_y = if self.b.y > self.a.y {
p.y >= self.a.y && p.y < self.b.y
} else {
p.y == self.a.y
};
in_x && in_y
}
pub fn is_empty(&self) -> bool {
self.b.x <= self.a.x || self.b.y <= self.a.y
}
pub fn width(&self) -> i16 {
self.b.x - self.a.x
}
pub fn height(&self) -> i16 {
self.b.y - self.a.y
}
pub fn width_clamped(&self) -> i16 {
self.width().max(0)
}
pub fn height_clamped(&self) -> i16 {
self.height().max(0)
}
pub fn size(&self) -> Point {
Point::new(self.width(), self.height())
}
pub fn intersect(&self, other: &Rect) -> Rect {
Rect {
a: Point::new(self.a.x.max(other.a.x), self.a.y.max(other.a.y)),
b: Point::new(self.b.x.min(other.b.x), self.b.y.min(other.b.y)),
}
}
pub fn intersects(&self, other: &Rect) -> bool {
!(self.b.x <= other.a.x || self.a.x >= other.b.x ||
self.b.y <= other.a.y || self.a.y >= other.b.y)
}
pub fn union(&self, other: &Rect) -> Rect {
Rect {
a: Point::new(self.a.x.min(other.a.x), self.a.y.min(other.a.y)),
b: Point::new(self.b.x.max(other.b.x), self.b.y.max(other.b.y)),
}
}
}
impl Default for Rect {
fn default() -> Self {
Self::new(0, 0, 0, 0)
}
}
impl fmt::Display for Rect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "[{}, {}, {}, {}]", self.a.x, self.a.y, self.b.x, self.b.y)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_point() {
let p = Point::new(10, 20);
assert_eq!(p.x, 10);
assert_eq!(p.y, 20);
}
#[test]
fn test_rect_basic() {
let r = Rect::new(1, 2, 11, 12);
assert_eq!(r.width(), 10);
assert_eq!(r.height(), 10);
assert!(!r.is_empty());
}
#[test]
fn test_rect_contains() {
let r = Rect::new(0, 0, 10, 10);
assert!(r.contains(Point::new(5, 5)));
assert!(r.contains(Point::new(0, 0)));
assert!(!r.contains(Point::new(10, 10)));
assert!(!r.contains(Point::new(-1, 5)));
}
#[test]
fn test_rect_contains_zero_dimensions() {
let single_row = Rect::new(2, 4, 15, 4);
assert!(single_row.contains(Point::new(2, 4))); assert!(single_row.contains(Point::new(10, 4))); assert!(single_row.contains(Point::new(14, 4))); assert!(!single_row.contains(Point::new(15, 4))); assert!(!single_row.contains(Point::new(5, 3))); assert!(!single_row.contains(Point::new(5, 5)));
let single_col = Rect::new(5, 2, 5, 10);
assert!(single_col.contains(Point::new(5, 2))); assert!(single_col.contains(Point::new(5, 5))); assert!(single_col.contains(Point::new(5, 9))); assert!(!single_col.contains(Point::new(5, 10))); assert!(!single_col.contains(Point::new(4, 5))); assert!(!single_col.contains(Point::new(6, 5)));
let single_point = Rect::new(10, 10, 10, 10);
assert!(single_point.contains(Point::new(10, 10)));
assert!(!single_point.contains(Point::new(10, 11)));
assert!(!single_point.contains(Point::new(11, 10)));
assert!(!single_point.contains(Point::new(9, 10)));
assert!(!single_point.contains(Point::new(10, 9)));
}
#[test]
fn test_rect_move() {
let mut r = Rect::new(0, 0, 10, 10);
r.move_by(5, 5);
assert_eq!(r, Rect::new(5, 5, 15, 15));
}
#[test]
fn test_rect_grow() {
let mut r = Rect::new(5, 5, 15, 15);
r.grow(2, 2);
assert_eq!(r, Rect::new(3, 3, 17, 17));
}
#[test]
fn test_rect_intersect() {
let r1 = Rect::new(0, 0, 10, 10);
let r2 = Rect::new(5, 5, 15, 15);
let intersection = r1.intersect(&r2);
assert_eq!(intersection, Rect::new(5, 5, 10, 10));
}
#[test]
fn test_rect_intersects() {
let r1 = Rect::new(0, 0, 10, 10);
let r2 = Rect::new(5, 5, 15, 15);
assert!(r1.intersects(&r2));
assert!(r2.intersects(&r1));
let r3 = Rect::new(2, 2, 8, 8);
assert!(r1.intersects(&r3));
let r4 = Rect::new(10, 0, 20, 10);
assert!(!r1.intersects(&r4));
let r5 = Rect::new(20, 20, 30, 30);
assert!(!r1.intersects(&r5));
let r6 = Rect::new(8, 8, 15, 15);
assert!(r1.intersects(&r6));
}
#[test]
fn test_point_display() {
let p = Point::new(10, 20);
assert_eq!(format!("{}", p), "(10, 20)");
let p2 = Point::new(-5, 0);
assert_eq!(format!("{}", p2), "(-5, 0)");
}
#[test]
fn test_rect_display() {
let r = Rect::new(1, 2, 11, 12);
assert_eq!(format!("{}", r), "[1, 2, 11, 12]");
let r2 = Rect::new(0, 0, 80, 25);
assert_eq!(format!("{}", r2), "[0, 0, 80, 25]");
}
#[test]
fn test_width_height_clamped() {
let r = Rect::new(0, 0, 10, 10);
assert_eq!(r.width_clamped(), 10);
assert_eq!(r.height_clamped(), 10);
let r2 = Rect::new(10, 10, 5, 5);
assert_eq!(r2.width(), -5); assert_eq!(r2.height(), -5); assert_eq!(r2.width_clamped(), 0); assert_eq!(r2.height_clamped(), 0);
let r3 = Rect::new(5, 5, 2, 10);
assert_eq!(r3.width(), -3);
assert_eq!(r3.height(), 5);
assert_eq!(r3.width_clamped(), 0);
assert_eq!(r3.height_clamped(), 5);
let width_usize = r2.width_clamped() as usize;
let height_usize = r2.height_clamped() as usize;
assert_eq!(width_usize, 0);
assert_eq!(height_usize, 0);
}
}