latchshot 0.2.4

A lightweight yet intelligent window-aware screenshot tool
Documentation
use serde::Serialize;

#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct Point {
    pub x: f64,
    pub y: f64,
}

impl Point {
    #[must_use]
    pub const fn new(x: f64, y: f64) -> Self {
        Self { x, y }
    }

    #[must_use]
    pub fn distance_squared(self, other: Self) -> f64 {
        let dx = self.x - other.x;
        let dy = self.y - other.y;

        dx * dx + dy * dy
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct Size {
    pub width: f64,
    pub height: f64,
}

impl Size {
    #[must_use]
    pub const fn new(width: f64, height: f64) -> Self {
        Self { width, height }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Serialize)]
pub struct Rect {
    origin: Point,
    size: Size,
}

impl Rect {
    #[must_use]
    pub const fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
        Self {
            origin: Point::new(x, y),
            size: Size::new(width, height),
        }
    }

    #[must_use]
    pub fn from_points(a: Point, b: Point) -> Self {
        let left = a.x.min(b.x);
        let top = a.y.min(b.y);

        Self::new(left, top, (a.x - b.x).abs(), (a.y - b.y).abs())
    }

    #[must_use]
    pub const fn left(self) -> f64 {
        self.origin.x
    }

    #[must_use]
    pub const fn top(self) -> f64 {
        self.origin.y
    }

    #[must_use]
    pub const fn right(self) -> f64 {
        self.left() + self.width()
    }

    #[must_use]
    pub const fn bottom(self) -> f64 {
        self.top() + self.height()
    }

    #[must_use]
    pub const fn width(self) -> f64 {
        self.size.width
    }

    #[must_use]
    pub const fn height(self) -> f64 {
        self.size.height
    }

    #[must_use]
    pub fn contains(self, point: Point) -> bool {
        (self.left() <= point.x && point.x < self.right())
            && (self.top() <= point.y && point.y < self.bottom())
    }

    #[must_use]
    pub fn intersection(self, other: Self) -> Option<Self> {
        let left = self.left().max(other.left());
        let top = self.top().max(other.top());
        let right = self.right().min(other.right());
        let bottom = self.bottom().min(other.bottom());

        (left < right && top < bottom).then(|| Self::new(left, top, right - left, bottom - top))
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn rectangle_from_points_is_normalized() {
        assert_eq!(
            Rect::from_points(Point::new(80.0, 70.0), Point::new(20.0, 10.0)),
            Rect::new(20.0, 10.0, 60.0, 60.0)
        );
    }

    #[test]
    fn rectangle_intersection_excludes_touching_edges() {
        let rect = Rect::new(0.0, 0.0, 100.0, 100.0);

        assert_eq!(
            rect.intersection(Rect::new(50.0, 20.0, 80.0, 40.0)),
            Some(Rect::new(50.0, 20.0, 50.0, 40.0))
        );
        assert_eq!(rect.intersection(Rect::new(100.0, 0.0, 20.0, 20.0)), None);
    }
}