use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct ScreenPosition {
pub x: u16,
pub y: u16,
}
impl ScreenPosition {
#[must_use]
pub const fn new(x: u16, y: u16) -> Self {
Self { x, y }
}
#[must_use]
#[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
pub const fn offset(self, dx: i16, dy: i16) -> Self {
let new_x = self.x as i16 + dx;
let new_y = self.y as i16 + dy;
Self {
x: if new_x < 0 { 0 } else { new_x as u16 },
y: if new_y < 0 { 0 } else { new_y as u16 },
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Size {
pub width: u16,
pub height: u16,
}
impl Size {
#[must_use]
pub const fn new(width: u16, height: u16) -> Self {
Self { width, height }
}
#[must_use]
pub const fn area(self) -> u32 {
self.width as u32 * self.height as u32
}
#[must_use]
pub const fn is_empty(self) -> bool {
self.width == 0 || self.height == 0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash, Serialize, Deserialize)]
#[cfg_attr(feature = "wasm", derive(tsify_next::Tsify))]
#[cfg_attr(feature = "wasm", tsify(into_wasm_abi, from_wasm_abi))]
pub struct Rect {
pub x: u16,
pub y: u16,
pub width: u16,
pub height: u16,
}
impl Rect {
#[must_use]
pub const fn new(x: u16, y: u16, width: u16, height: u16) -> Self {
Self {
x,
y,
width,
height,
}
}
#[must_use]
pub const fn from_position_size(pos: ScreenPosition, size: Size) -> Self {
Self {
x: pos.x,
y: pos.y,
width: size.width,
height: size.height,
}
}
#[must_use]
pub const fn position(&self) -> ScreenPosition {
ScreenPosition {
x: self.x,
y: self.y,
}
}
#[must_use]
pub const fn size(&self) -> Size {
Size {
width: self.width,
height: self.height,
}
}
#[must_use]
pub const fn right(&self) -> u16 {
self.x.saturating_add(self.width)
}
#[must_use]
pub const fn bottom(&self) -> u16 {
self.y.saturating_add(self.height)
}
#[must_use]
pub const fn contains(&self, pos: ScreenPosition) -> bool {
pos.x >= self.x && pos.x < self.right() && pos.y >= self.y && pos.y < self.bottom()
}
#[must_use]
#[cfg_attr(coverage_nightly, coverage(off))]
pub const fn contains_xy(&self, x: u16, y: u16) -> bool {
x >= self.x && x < self.right() && y >= self.y && y < self.bottom()
}
#[must_use]
#[cfg_attr(coverage_nightly, coverage(off))]
pub const fn intersects(&self, other: &Self) -> bool {
self.x < other.right()
&& self.right() > other.x
&& self.y < other.bottom()
&& self.bottom() > other.y
}
#[must_use]
pub fn intersection(&self, other: &Self) -> Option<Self> {
if !self.intersects(other) {
return None;
}
let x = self.x.max(other.x);
let y = self.y.max(other.y);
let right = self.right().min(other.right());
let bottom = self.bottom().min(other.bottom());
Some(Self {
x,
y,
width: right - x,
height: bottom - y,
})
}
#[must_use]
pub const fn is_empty(&self) -> bool {
self.width == 0 || self.height == 0
}
#[must_use]
pub const fn area(&self) -> u32 {
self.width as u32 * self.height as u32
}
}
#[cfg(test)]
#[path = "geometry_tests.rs"]
mod tests;