#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Point {
pub x: i32,
pub y: i32,
}
impl Point {
pub const ORIGIN: Self = Self { x: 0, y: 0 };
#[must_use]
pub const fn new(x: i32, y: i32) -> Self {
Self { x, y }
}
#[must_use]
pub const fn offset(self, dx: i32, dy: i32) -> Self {
Self {
x: self.x.saturating_add(dx),
y: self.y.saturating_add(dy),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Size {
pub w: i32,
pub h: i32,
}
impl Size {
#[must_use]
pub const fn new(w: i32, h: i32) -> Self {
Self { w, h }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub struct Rect {
pub x: i32,
pub y: i32,
pub w: i32,
pub h: i32,
}
impl Rect {
#[must_use]
pub const fn new(x: i32, y: i32, w: i32, h: i32) -> Self {
Self { x, y, w, h }
}
#[must_use]
pub const fn origin(self) -> Point {
Point::new(self.x, self.y)
}
#[must_use]
pub const fn size(self) -> Size {
Size::new(self.w, self.h)
}
#[must_use]
pub const fn point_at(self, dx: i32, dy: i32) -> Point {
self.origin().offset(dx, dy)
}
#[must_use]
pub const fn contains(self, p: Point) -> bool {
p.x >= self.x && p.y >= self.y && p.x < self.x + self.w && p.y < self.y + self.h
}
}
impl From<autoitx_sys::RECT> for Rect {
fn from(r: autoitx_sys::RECT) -> Self {
Self {
x: r.left,
y: r.top,
w: r.right - r.left,
h: r.bottom - r.top,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Hash)]
pub enum PixelCoordSpace {
#[default]
Points,
Pixels,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rect_from_win32_converts_edges_to_extents() {
let r = autoitx_sys::RECT {
left: 100,
top: 50,
right: 400,
bottom: 250,
};
assert_eq!(Rect::from(r), Rect::new(100, 50, 300, 200));
}
#[test]
fn point_at_resolves_window_relative_offsets() {
let window = Rect::new(160, 90, 1280, 720);
assert_eq!(window.point_at(600, 420), Point::new(760, 510));
}
#[test]
fn contains_excludes_right_and_bottom_edges() {
let r = Rect::new(0, 0, 10, 10);
assert!(r.contains(Point::new(0, 0)));
assert!(r.contains(Point::new(9, 9)));
assert!(!r.contains(Point::new(10, 9)));
assert!(!r.contains(Point::new(9, 10)));
}
#[test]
fn offset_saturates_instead_of_overflowing() {
assert_eq!(Point::new(i32::MAX, 0).offset(1, 0).x, i32::MAX);
assert_eq!(Point::new(i32::MIN, 0).offset(-1, 0).x, i32::MIN);
}
}