#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl std::fmt::Display for Rect {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}x{}+{}+{}", self.width, self.height, self.x, self.y)
}
}
impl Rect {
pub const ZERO: Self = Self {
x: 0,
y: 0,
width: 0,
height: 0,
};
pub fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
let max_area = u16::MAX;
let (clipped_width, clipped_height) =
if u32::from(width) * u32::from(height) > u32::from(max_area) {
let aspect_ratio = f64::from(width) / f64::from(height);
let max_area_f = f64::from(max_area);
let height_f = (max_area_f / aspect_ratio).sqrt();
let width_f = height_f * aspect_ratio;
(width_f as u16, height_f as u16)
} else {
(width, height)
};
Self {
x,
y,
width: clipped_width,
height: clipped_height,
}
}
pub const fn area(self) -> u16 {
self.width.saturating_mul(self.height)
}
pub const fn is_empty(self) -> bool {
self.width == 0 || self.height == 0
}
pub const fn left(self) -> u16 {
self.x
}
pub const fn right(self) -> u16 {
self.x.saturating_add(self.width)
}
pub const fn top(self) -> u16 {
self.y
}
pub const fn bottom(self) -> u16 {
self.y.saturating_add(self.height)
}
#[must_use = "method returns the modified value"]
pub const fn inner(self, margin_x: u16, margin_y: u16) -> Self {
let doubled_margin_horizontal = margin_x.saturating_mul(2);
let doubled_margin_vertical = margin_y.saturating_mul(2);
if self.width < doubled_margin_horizontal || self.height < doubled_margin_vertical {
Self::ZERO
} else {
Self {
x: self.x.saturating_add(margin_x),
y: self.y.saturating_add(margin_y),
width: self.width.saturating_sub(doubled_margin_horizontal),
height: self.height.saturating_sub(doubled_margin_vertical),
}
}
}
#[must_use = "method returns the modified value"]
pub fn offset(self, x: i32, y: i32) -> Self {
Self {
x: i32::from(self.x)
.saturating_add(x)
.clamp(0, i32::from(u16::MAX - self.width)) as u16,
y: i32::from(self.y)
.saturating_add(y)
.clamp(0, i32::from(u16::MAX - self.height)) as u16,
..self
}
}
#[must_use = "method returns the modified value"]
pub fn union(self, other: Self) -> Self {
let x1 = std::cmp::min(self.x, other.x);
let y1 = std::cmp::min(self.y, other.y);
let x2 = std::cmp::max(self.right(), other.right());
let y2 = std::cmp::max(self.bottom(), other.bottom());
Self {
x: x1,
y: y1,
width: x2.saturating_sub(x1),
height: y2.saturating_sub(y1),
}
}
#[must_use = "method returns the modified value"]
pub fn intersection(self, other: Self) -> Self {
let x1 = std::cmp::max(self.x, other.x);
let y1 = std::cmp::max(self.y, other.y);
let x2 = std::cmp::min(self.right(), other.right());
let y2 = std::cmp::min(self.bottom(), other.bottom());
Self {
x: x1,
y: y1,
width: x2.saturating_sub(x1),
height: y2.saturating_sub(y1),
}
}
pub const fn intersects(self, other: Self) -> bool {
self.x < other.right()
&& self.right() > other.x
&& self.y < other.bottom()
&& self.bottom() > other.y
}
pub const fn contains(self, x: u16, y: u16) -> bool {
x >= self.x
&& x < self.right()
&& y >= self.y
&& y < self.bottom()
}
#[must_use = "method returns the modified value"]
pub fn clamp(self, other: Self) -> Self {
let width = self.width.min(other.width);
let height = self.height.min(other.height);
let x = self.x.clamp(other.x, other.right().saturating_sub(width));
let y = self.y.clamp(other.y, other.bottom().saturating_sub(height));
Self::new(x, y, width, height)
}
}
impl Rect {
pub fn hsplit_portion(&self, portion: f32) -> (Self, Self) {
let width_a = (self.width as f32 * portion).floor() as u16;
let width_b = self.width - width_a;
(
Rect::new(self.x, self.y, width_a, self.height),
Rect::new(self.x + width_a, self.y, width_b, self.height),
)
}
pub fn vsplit_portion(&self, portion: f32) -> (Self, Self) {
let height_a = (self.height as f32 * portion).floor() as u16;
let height_b = self.height - height_a;
(
Rect::new(self.x, self.y, self.width, height_a),
Rect::new(self.x, self.y + height_a, self.width, height_b),
)
}
pub fn hsplit_len(&self, length: u16) -> (Self, Self) {
if length >= self.width {
return (*self, Rect::ZERO);
}
(
Rect::new(self.x, self.y, length, self.height),
Rect::new(self.x + length, self.y, self.width - length, self.height),
)
}
pub fn vsplit_len(&self, length: u16) -> (Self, Self) {
if length >= self.height {
return (*self, Rect::ZERO);
}
(
Rect::new(self.x, self.y, self.width, length),
Rect::new(self.x, self.y + length, self.width, self.height - length),
)
}
pub fn hsplit_inverse_portion(&self, portion: f32) -> (Self, Self) {
let width_a = (self.width as f32 * portion).floor() as u16;
let width_b = self.width - width_a;
(
Rect::new(self.x + width_b, self.y, width_a, self.height),
Rect::new(self.x, self.y, width_b, self.height),
)
}
pub fn vsplit_inverse_portion(&self, portion: f32) -> (Self, Self) {
let height_a = (self.height as f32 * portion).floor() as u16;
let height_b = self.height - height_a;
(
Rect::new(self.x, self.y + height_b, self.width, height_a),
Rect::new(self.x, self.y, self.width, height_b),
)
}
pub fn hsplit_inverse_len(&self, length: u16) -> (Self, Self) {
if length >= self.width {
return (Rect::ZERO, *self);
}
(
Rect::new(self.width - length, self.y, length, self.height),
Rect::new(self.x, self.y, self.width - length, self.height),
)
}
pub fn vsplit_inverse_len(&self, length: u16) -> (Self, Self) {
if length >= self.height {
return (Rect::ZERO, *self);
}
(
Rect::new(self.x, self.height - length, self.width, length),
Rect::new(self.x, self.y, self.width, self.height - length),
)
}
pub fn inner_centered(&self, width: u16, height: u16) -> Self {
let x = self.x + (self.width.saturating_sub(width) / 2);
let y = self.y + (self.height.saturating_sub(height) / 2);
Rect::new(x, y, width.min(self.width), height.min(self.height))
}
pub fn rows(&self) -> Vec<Self> {
(0..self.height)
.into_iter()
.map(|row_index| {
Rect::new(self.left(), self.top() + row_index, self.width, self.height)
})
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rect_splitting() {
let test_rect = Rect::new(0, 0, 10, 20);
assert_eq!(test_rect.hsplit_len(3), (Rect::new(0, 0, 3, 20), Rect::new(3, 0, 7, 20)));
assert_eq!(test_rect.hsplit_len(11), (Rect::new(0, 0, 10, 20), Rect::new(0, 0, 0, 0)));
assert_eq!(test_rect.vsplit_len(7), (Rect::new(0, 0, 10, 7), Rect::new(0, 7, 10, 13)));
assert_eq!(test_rect.vsplit_len(22), (Rect::new(0, 0, 10, 20), Rect::new(0, 0, 0, 0)));
assert_eq!(
test_rect.hsplit_inverse_len(3),
(Rect::new(7, 0, 3, 20), Rect::new(0, 0, 7, 20)),
);
}
}