use crate::kurbo::Rect;
use bytemuck::{Pod, Zeroable};
use core::num::TryFromIntError;
use core::ops::Add;
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
pub struct SizeU16(pub [u16; 2]);
impl SizeU16 {
pub const ZERO: Self = Self::new(0);
pub const fn new(size: u16) -> Self {
Self([size; 2])
}
pub const fn from_wh(width: u16, height: u16) -> Self {
Self([width, height])
}
pub const fn width(self) -> u16 {
self.0[0]
}
pub const fn height(self) -> u16 {
self.0[1]
}
pub fn max(self, other: Self) -> Self {
Self::from_wh(
self.width().max(other.width()),
self.height().max(other.height()),
)
}
pub fn min(self, other: Self) -> Self {
Self::from_wh(
self.width().min(other.width()),
self.height().min(other.height()),
)
}
pub fn clamp(self, min: u16, max: u16) -> Self {
Self::from_wh(self.width().clamp(min, max), self.height().clamp(min, max))
}
}
impl From<[u16; 2]> for SizeU16 {
fn from(value: [u16; 2]) -> Self {
Self(value)
}
}
impl Add for SizeU16 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::from_wh(
self.width().checked_add(rhs.width()).unwrap(),
self.height().checked_add(rhs.height()).unwrap(),
)
}
}
impl Add<u16> for SizeU16 {
type Output = Self;
fn add(self, rhs: u16) -> Self::Output {
self + Self::new(rhs)
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct PaddingU16 {
pub left: u16,
pub top: u16,
pub right: u16,
pub bottom: u16,
}
impl PaddingU16 {
pub const ZERO: Self = Self::new(0, 0, 0, 0);
pub const fn new(left: u16, top: u16, right: u16, bottom: u16) -> Self {
Self {
left,
top,
right,
bottom,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct RectU16 {
pub x0: u16,
pub y0: u16,
pub x1: u16,
pub y1: u16,
}
impl RectU16 {
pub const ZERO: Self = Self {
x0: 0,
y0: 0,
x1: 0,
y1: 0,
};
pub const INVERTED: Self = Self {
x0: u16::MAX,
y0: u16::MAX,
x1: 0,
y1: 0,
};
#[inline(always)]
pub const fn new(x0: u16, y0: u16, x1: u16, y1: u16) -> Self {
Self { x0, y0, x1, y1 }
}
#[inline(always)]
pub const fn width(self) -> u16 {
self.x1.saturating_sub(self.x0)
}
#[inline(always)]
pub const fn height(self) -> u16 {
self.y1.saturating_sub(self.y0)
}
#[inline(always)]
pub const fn is_empty(self) -> bool {
self.x0 >= self.x1 || self.y0 >= self.y1
}
#[inline(always)]
pub const fn contains(self, x: u16, y: u16) -> bool {
(x >= self.x0) & (x < self.x1) & (y >= self.y0) & (y < self.y1)
}
#[inline(always)]
pub const fn intersect(self, other: Self) -> Self {
let x0 = const_max(self.x0, other.x0);
let y0 = const_max(self.y0, other.y0);
let x1 = const_min(self.x1, other.x1);
let y1 = const_min(self.y1, other.y1);
Self::new(x0, y0, const_max(x1, x0), const_max(y1, y0))
}
#[inline(always)]
pub const fn expand(self, padding: PaddingU16) -> Self {
Self {
x0: self.x0.saturating_sub(padding.left),
y0: self.y0.saturating_sub(padding.top),
x1: self.x1.saturating_add(padding.right),
y1: self.y1.saturating_add(padding.bottom),
}
}
#[inline(always)]
pub fn relative_to_origin(self, origin: (u16, u16)) -> Self {
self.shift((-(origin.0 as i32), -(origin.1 as i32)))
}
#[inline]
pub fn shift(self, shift: (i32, i32)) -> Self {
Self {
x0: (self.x0 as i32)
.saturating_add(shift.0)
.clamp(0, u16::MAX as i32) as u16,
y0: (self.y0 as i32)
.saturating_add(shift.1)
.clamp(0, u16::MAX as i32) as u16,
x1: (self.x1 as i32)
.saturating_add(shift.0)
.clamp(0, u16::MAX as i32) as u16,
y1: (self.y1 as i32)
.saturating_add(shift.1)
.clamp(0, u16::MAX as i32) as u16,
}
}
#[inline(always)]
pub const fn union(&mut self, other: Self) {
self.x0 = const_min(self.x0, other.x0);
self.y0 = const_min(self.y0, other.y0);
self.x1 = const_max(self.x1, other.x1);
self.y1 = const_max(self.y1, other.y1);
}
pub fn as_rect(self) -> Rect {
Rect::new(
self.x0 as f64,
self.y0 as f64,
self.x1 as f64,
self.y1 as f64,
)
}
}
impl From<RectU16> for SizeU16 {
fn from(rect: RectU16) -> Self {
Self::from_wh(rect.width(), rect.height())
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
pub struct OffsetU32(pub [u32; 2]);
impl OffsetU32 {
pub const ZERO: Self = Self::new(0);
pub const fn new(offset: u32) -> Self {
Self([offset; 2])
}
pub const fn from_xy(x: u32, y: u32) -> Self {
Self([x, y])
}
pub const fn x(self) -> u32 {
self.0[0]
}
pub const fn y(self) -> u32 {
self.0[1]
}
}
impl From<[u32; 2]> for OffsetU32 {
fn from(value: [u32; 2]) -> Self {
Self(value)
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
pub struct SizeU32(pub [u32; 2]);
impl SizeU32 {
pub const ZERO: Self = Self::new(0);
pub const fn new(size: u32) -> Self {
Self([size; 2])
}
pub const fn from_wh(width: u32, height: u32) -> Self {
Self([width, height])
}
pub const fn width(self) -> u32 {
self.0[0]
}
pub const fn height(self) -> u32 {
self.0[1]
}
pub fn max(self, other: Self) -> Self {
Self::from_wh(
self.width().max(other.width()),
self.height().max(other.height()),
)
}
pub fn min(self, other: Self) -> Self {
Self::from_wh(
self.width().min(other.width()),
self.height().min(other.height()),
)
}
pub fn clamp(self, min: u32, max: u32) -> Self {
Self::from_wh(self.width().clamp(min, max), self.height().clamp(min, max))
}
}
impl From<[u32; 2]> for SizeU32 {
fn from(value: [u32; 2]) -> Self {
Self(value)
}
}
impl From<(u32, u32)> for SizeU32 {
fn from((width, height): (u32, u32)) -> Self {
Self::from_wh(width, height)
}
}
impl From<SizeU32> for (u32, u32) {
fn from(size: SizeU32) -> Self {
(size.width(), size.height())
}
}
impl From<SizeU16> for SizeU32 {
fn from(size: SizeU16) -> Self {
Self::from_wh(u32::from(size.width()), u32::from(size.height()))
}
}
impl TryFrom<SizeU32> for SizeU16 {
type Error = TryFromIntError;
fn try_from(size: SizeU32) -> Result<Self, Self::Error> {
Ok(Self::from_wh(
u16::try_from(size.width())?,
u16::try_from(size.height())?,
))
}
}
impl Add for SizeU32 {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
Self::from_wh(self.width() + rhs.width(), self.height() + rhs.height())
}
}
impl Add<u32> for SizeU32 {
type Output = Self;
fn add(self, rhs: u32) -> Self::Output {
self + Self::new(rhs)
}
}
#[repr(C)]
#[derive(Copy, Clone, Debug, Pod, Zeroable, PartialEq, Eq)]
pub struct RectU32 {
pub x0: u32,
pub y0: u32,
pub x1: u32,
pub y1: u32,
}
impl RectU32 {
pub const fn new(x0: u32, y0: u32, x1: u32, y1: u32) -> Self {
Self { x0, y0, x1, y1 }
}
pub const fn width(self) -> u32 {
self.x1.saturating_sub(self.x0)
}
pub const fn height(self) -> u32 {
self.y1.saturating_sub(self.y0)
}
}
#[inline(always)]
const fn const_max(a: u16, b: u16) -> u16 {
if a > b { a } else { b }
}
#[inline(always)]
const fn const_min(a: u16, b: u16) -> u16 {
if a < b { a } else { b }
}
#[cfg(test)]
mod tests {
use super::RectU16;
#[test]
fn rect_u16_relative_to_origin() {
let rect = RectU16::new(10, 20, 30, 40);
assert_eq!(rect.relative_to_origin((5, 12)), RectU16::new(5, 8, 25, 28));
}
#[test]
fn rect_u16_relative_to_origin_clamps_to_zero() {
let rect = RectU16::new(10, 20, 30, 40);
assert_eq!(rect.relative_to_origin((20, 35)), RectU16::new(0, 0, 10, 5));
}
#[test]
fn disjoint_intersection_is_empty_but_not_inverted() {
let intersection = RectU16::new(0, 0, 4, 4).intersect(RectU16::new(8, 1, 12, 3));
assert_eq!(intersection, RectU16::new(8, 1, 8, 3));
assert!(intersection.is_empty());
assert!(intersection.x0 <= intersection.x1);
assert!(intersection.y0 <= intersection.y1);
}
}