#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Pos {
pub x: i16,
pub y: i16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Offset {
pub x: u16,
pub y: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Size {
pub width: u16,
pub height: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Window {
pub origin: Pos,
pub size: Size,
}
impl Window {
#[must_use]
pub fn contains(self, at: Pos) -> bool {
let Some(dx) =
at.x.checked_sub(self.origin.x)
.and_then(|d| u16::try_from(d).ok())
else {
return false;
};
let Some(dy) =
at.y.checked_sub(self.origin.y)
.and_then(|d| u16::try_from(d).ok())
else {
return false;
};
dx < self.size.width && dy < self.size.height
}
#[must_use]
pub fn position(self, at: Offset) -> Option<Pos> {
Some(Pos {
x: self.origin.x.checked_add_unsigned(at.x)?,
y: self.origin.y.checked_add_unsigned(at.y)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Footprint {
pub min: Pos,
pub max: Pos,
}
impl Footprint {
#[must_use]
pub(crate) fn including(self, at: Pos) -> Self {
Self {
min: Pos {
x: self.min.x.min(at.x),
y: self.min.y.min(at.y),
},
max: Pos {
x: self.max.x.max(at.x),
y: self.max.y.max(at.y),
},
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Direction {
Up,
Right,
Down,
Left,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Orientation {
Horizontal,
Vertical,
}
#[cfg(test)]
mod tests {
use super::{Offset, Pos, Size, Window};
fn window(origin: Pos, width: u16, height: u16) -> Window {
Window {
origin,
size: Size { width, height },
}
}
#[test]
fn a_window_of_no_size_contains_no_position() {
let empty = window(Pos { x: 3, y: 4 }, 0, 0);
assert!(!empty.contains(Pos { x: 3, y: 4 }));
assert!(!empty.contains(Pos { x: 4, y: 4 }));
}
#[test]
fn a_window_contains_its_corner_and_stops_before_its_far_edges() {
let at_negative = window(Pos { x: -2, y: -3 }, 2, 2);
assert!(at_negative.contains(Pos { x: -2, y: -3 }));
assert!(at_negative.contains(Pos { x: -1, y: -2 }));
assert!(!at_negative.contains(Pos { x: -3, y: -3 }));
assert!(!at_negative.contains(Pos { x: -2, y: -4 }));
assert!(!at_negative.contains(Pos { x: 0, y: -3 }));
assert!(!at_negative.contains(Pos { x: -2, y: -1 }));
}
#[test]
fn an_offset_is_counted_from_the_windows_corner() {
let window = window(Pos { x: 5, y: -4 }, 10, 10);
assert_eq!(
window.position(Offset { x: 0, y: 0 }),
Some(Pos { x: 5, y: -4 })
);
assert_eq!(
window.position(Offset { x: 2, y: 3 }),
Some(Pos { x: 7, y: -1 })
);
}
#[test]
fn an_offset_the_origin_cannot_reach_is_none() {
let window = window(Pos { x: i16::MAX, y: 0 }, 1, 1);
assert_eq!(window.position(Offset { x: 1, y: 0 }), None);
assert_eq!(window.position(Offset { x: 0, y: u16::MAX }), None);
assert_eq!(
window.position(Offset {
x: 0,
y: i16::MAX as u16
}),
Some(Pos {
x: i16::MAX,
y: i16::MAX
})
);
}
}