use crate::{error::Result, prelude::*};
use num_traits::{AsPrimitive, Bounded, NumCast};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::ops::{Add, Sub};
#[derive(Default, Debug, Copy, Clone, Eq, PartialEq, Hash)]
#[repr(transparent)]
#[must_use]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Rect<T = i32>(pub(crate) [T; 4]);
#[macro_export]
macro_rules! rect {
($p1:expr, $p2:expr$(,)?) => {
$crate::prelude::Rect::with_points($p1, $p2)
};
($p:expr, $width:expr, $height:expr$(,)?) => {
$crate::prelude::Rect::with_position($p, $width, $height)
};
($x:expr, $y:expr, $width:expr, $height:expr$(,)?) => {
$crate::prelude::Rect::new($x, $y, $width, $height)
};
}
#[macro_export]
macro_rules! square {
($p:expr, $size:expr$(,)?) => {{
$crate::prelude::Rect::square_with_position($p, $size)
}};
($x:expr, $y:expr, $size:expr$(,)?) => {
$crate::prelude::Rect::square($x, $y, $size)
};
}
impl<T> Rect<T> {
pub const fn new(x: T, y: T, width: T, height: T) -> Self {
Self([x, y, width, height])
}
pub fn square(x: T, y: T, size: T) -> Self
where
T: Copy,
{
Self::new(x, y, size, size)
}
}
impl<T: Copy> Rect<T> {
#[inline]
pub fn coords(&self) -> [T; 4] {
self.0
}
#[inline]
pub fn coords_mut(&mut self) -> &mut [T; 4] {
&mut self.0
}
#[inline]
pub fn x(&self) -> T {
self.0[0]
}
#[inline]
pub fn set_x(&mut self, x: T) {
self.0[0] = x;
}
#[inline]
pub fn y(&self) -> T {
self.0[1]
}
#[inline]
pub fn set_y(&mut self, y: T) {
self.0[1] = y;
}
#[inline]
pub fn width(&self) -> T {
self.0[2]
}
#[inline]
pub fn set_width(&mut self, width: T) {
self.0[2] = width;
}
#[inline]
pub fn height(&self) -> T {
self.0[3]
}
#[inline]
pub fn set_height(&mut self, height: T) {
self.0[3] = height;
}
}
impl<T: Num> Rect<T> {
pub fn with_position<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
let p = p.into();
Self::new(p.x(), p.y(), width, height)
}
pub fn square_with_position<P: Into<Point<T>>>(p: P, size: T) -> Self {
Self::with_position(p, size, size)
}
pub fn with_points<P: Into<Point<T>>>(p1: P, p2: P) -> Self {
let p1 = p1.into();
let p2 = p2.into();
assert!(p2 > p1, "bottom-right point must be greater than top-right",);
let width = p2.x() - p1.x();
let height = p2.y() - p1.y();
Self::new(p1.x(), p1.y(), width, height)
}
pub fn from_center<P: Into<Point<T>>>(p: P, width: T, height: T) -> Self {
let p = p.into();
let two = T::one() + T::one();
Self::new(p.x() - width / two, p.y() - height / two, width, height)
}
pub fn square_from_center<P: Into<Point<T>>>(p: P, size: T) -> Self {
let p = p.into();
let two = T::one() + T::one();
let offset = size / two;
Self::new(p.x() - offset, p.y() - offset, size, size)
}
#[inline]
pub fn size(&self) -> Point<T> {
point!(self.width(), self.height())
}
#[inline]
pub fn reposition(&self, x: T, y: T) -> Self {
Self::new(x, y, self.width(), self.height())
}
#[inline]
pub fn resize(&self, width: T, height: T) -> Self {
Self::new(self.x(), self.y(), width, height)
}
#[inline]
pub fn offset<P>(&self, offsets: P) -> Self
where
P: Into<Point<T>>,
{
let offsets = offsets.into();
let mut rect = *self;
for i in 0..=1 {
rect[i] += offsets[i];
}
rect
}
#[inline]
pub fn offset_size<P>(&self, offsets: P) -> Self
where
P: Into<Point<T>>,
{
let offsets = offsets.into();
let mut rect = *self;
for i in 2..=3 {
rect[i] += offsets[i - 2];
}
rect
}
#[inline]
pub fn grow<P>(&self, offsets: P) -> Self
where
P: Into<Point<T>>,
{
let offsets = offsets.into();
let mut rect = *self;
for i in 0..=1 {
rect[i] -= offsets[i];
}
for i in 2..=3 {
rect[i] += (T::one() + T::one()) * offsets[i - 2];
}
rect
}
#[inline]
pub fn shrink<P>(&self, offsets: P) -> Self
where
P: Into<Point<T>>,
{
let offsets = offsets.into();
let mut rect = *self;
for i in 0..=1 {
rect[i] += offsets[i];
}
for i in 2..=3 {
rect[i] -= (T::one() + T::one()) * offsets[i - 2];
}
rect
}
#[inline]
pub fn to_vec(self) -> Vec<T> {
self.0.to_vec()
}
#[inline]
pub fn left(&self) -> T {
self.x()
}
#[inline]
pub fn set_left(&mut self, left: T) {
self.set_x(left);
}
#[inline]
pub fn right(&self) -> T {
self.x() + self.width()
}
#[inline]
pub fn set_right(&mut self, right: T) {
self.set_x(right - self.width());
}
#[inline]
pub fn top(&self) -> T {
self.y()
}
#[inline]
pub fn set_top(&mut self, top: T) {
self.set_y(top);
}
#[inline]
pub fn bottom(&self) -> T {
self.y() + self.height()
}
#[inline]
pub fn set_bottom(&mut self, bottom: T) {
self.set_y(bottom - self.height());
}
#[inline]
pub fn center(&self) -> Point<T> {
let two = T::one() + T::one();
point!(
self.x() + self.width() / two,
self.y() + self.height() / two
)
}
#[inline]
pub fn top_left(&self) -> Point<T> {
point!(self.left(), self.top())
}
#[inline]
pub fn top_right(&self) -> Point<T> {
point!(self.right(), self.top())
}
#[inline]
pub fn bottom_left(&self) -> Point<T> {
point!(self.left(), self.bottom())
}
#[inline]
pub fn bottom_right(&self) -> Point<T> {
point!(self.right(), self.bottom())
}
#[inline]
pub fn points(&self) -> [Point<T>; 4] {
[
self.top_left(),
self.top_right(),
self.bottom_right(),
self.bottom_left(),
]
}
#[inline]
pub fn center_on<P: Into<Point<T>>>(&mut self, p: P) {
let p = p.into();
let two = T::one() + T::one();
self.set_x(p.x() - self.width() / two);
self.set_y(p.y() - self.height() / two);
}
#[inline]
pub fn rotated(&self, angle: f64, center: Option<Point<T>>) -> Self
where
T: Ord + Bounded + AsPrimitive<f64> + NumCast,
{
if angle == 0.0 {
return *self;
}
let sin_cos = angle.sin_cos();
let [cx, cy]: [f64; 2] = center.unwrap_or_else(|| self.center()).as_().coords();
let (sin, cos) = sin_cos;
let transformed_points = self.points().map(|p| {
let [x, y]: [f64; 2] = p.as_().coords();
point![
NumCast::from(((x - cx).mul_add(cos, cx) - (y - cy) * sin).round())
.unwrap_or_else(T::zero),
NumCast::from(((y - cy).mul_add(cos, (x - cx).mul_add(sin, cy))).round())
.unwrap_or_else(T::zero),
]
});
let (min_x, min_y) = transformed_points
.iter()
.fold((T::max_value(), T::max_value()), |(min_x, min_y), point| {
(min_x.min(point.x()), min_y.min(point.y()))
});
let (max_x, max_y) = transformed_points
.iter()
.fold((T::min_value(), T::min_value()), |(max_x, max_y), point| {
(max_x.max(point.x()), max_y.max(point.y()))
});
Self::with_points([min_x, min_y], [max_x, max_y])
}
}
impl<T: Num> Contains<Point<T>> for Rect<T> {
fn contains(&self, p: Point<T>) -> bool {
p.x() >= self.left() && p.x() < self.right() && p.y() >= self.top() && p.y() < self.bottom()
}
}
impl<T: Num> Contains<Rect<T>> for Rect<T> {
fn contains(&self, rect: Rect<T>) -> bool {
rect.left() >= self.left()
&& rect.right() < self.right()
&& rect.top() >= self.top()
&& rect.bottom() < self.bottom()
}
}
impl<T: Float> Intersects<Line<T>> for Rect<T> {
type Result = (Point<T>, T);
fn intersects(&self, line: Line<T>) -> Option<Self::Result> {
let left = line.intersects(line_![self.top_left(), self.bottom_left()]);
let right = line.intersects(line_![self.top_right(), self.bottom_right()]);
let top = line.intersects(line_![self.top_left(), self.top_right()]);
let bottom = line.intersects(line_![self.bottom_left(), self.bottom_right()]);
[left, right, top, bottom]
.iter()
.filter_map(|&p| p)
.fold(None, |closest, intersection| {
let closest_t = closest.map_or_else(T::infinity, |c| c.1);
let t = intersection.1;
if t < closest_t {
Some(intersection)
} else {
closest
}
})
}
}
impl<T: Num> Intersects<Rect<T>> for Rect<T> {
type Result = ();
fn intersects(&self, rect: Rect<T>) -> Option<Self::Result> {
let tl = self.top_left();
let br = self.bottom_right();
let otl = rect.top_left();
let obr = rect.bottom_right();
if tl.x() < obr.x() && br.x() > otl.x() && tl.y() < otl.y() && br.y() > obr.y() {
Some(())
} else {
None
}
}
}
impl Draw for Rect<i32> {
fn draw(&self, s: &mut PixState) -> Result<()> {
s.rect(*self)
}
}
impl<T: Copy> From<[T; 3]> for Rect<T> {
#[inline]
fn from([x, y, s]: [T; 3]) -> Self {
Self([x, y, s, s])
}
}
impl<T: Copy> From<&[T; 3]> for Rect<T> {
#[inline]
fn from(&[x, y, s]: &[T; 3]) -> Self {
Self([x, y, s, s])
}
}
impl Add<Point<i32>> for Rect {
type Output = Self;
fn add(self, p: Point<i32>) -> Self::Output {
self.offset(p)
}
}
impl Sub<Point<i32>> for Rect {
type Output = Self;
fn sub(self, p: Point<i32>) -> Self::Output {
self.offset(-p)
}
}