Skip to main content

rdi_core/
geometry.rs

1//! Simple 2D integer geometry used throughout the crate.
2
3/// 2D point in virtual-screen coordinates (pixels).
4///
5/// Values match what the Windows shell reports for
6/// `IFolderView2::GetItemPosition`: signed 32-bit integers in the
7/// **virtual-screen coordinate system** — a single space that spans
8/// every attached monitor. The primary monitor's top-left is `(0, 0)`;
9/// coordinates on other monitors can be negative when they are
10/// arranged to the left of or above the primary. Use
11/// [`crate::MonitorInfo`] (populated by
12/// [`crate::DesktopBackend::list_monitors`]) to discover where each
13/// monitor lives in this space.
14#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Hash)]
15pub struct Point {
16    pub x: i32,
17    pub y: i32,
18}
19
20impl Point {
21    pub const ZERO: Self = Self { x: 0, y: 0 };
22
23    #[inline]
24    pub const fn new(x: i32, y: i32) -> Self {
25        Self { x, y }
26    }
27
28    /// Euclidean distance between two points, in pixels, as an `f32`.
29    #[inline]
30    pub fn distance(a: Self, b: Self) -> f32 {
31        let dx = (b.x - a.x) as f32;
32        let dy = (b.y - a.y) as f32;
33        (dx * dx + dy * dy).sqrt()
34    }
35
36    /// Component-wise linear interpolation `origin + t * (target - origin)`,
37    /// returning an integer point (rounded to nearest).
38    #[inline]
39    pub fn lerp_i32(origin: Self, target: Self, tx: f32, ty: f32) -> Self {
40        let x = origin.x as f32 + tx * (target.x - origin.x) as f32;
41        let y = origin.y as f32 + ty * (target.y - origin.y) as f32;
42        Self {
43            x: x.round() as i32,
44            y: y.round() as i32,
45        }
46    }
47}
48
49impl From<(i32, i32)> for Point {
50    #[inline]
51    fn from((x, y): (i32, i32)) -> Self {
52        Self { x, y }
53    }
54}
55
56impl From<Point> for (i32, i32) {
57    #[inline]
58    fn from(p: Point) -> Self {
59        (p.x, p.y)
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    #[test]
68    fn distance_zero_for_equal_points() {
69        assert_eq!(Point::distance(Point::new(5, 7), Point::new(5, 7)), 0.0);
70    }
71
72    #[test]
73    fn distance_matches_pythagoras() {
74        // 3-4-5 triangle.
75        let d = Point::distance(Point::ZERO, Point::new(3, 4));
76        assert!((d - 5.0).abs() < 1e-6, "d = {d}");
77    }
78
79    #[test]
80    fn lerp_endpoints() {
81        let a = Point::new(10, 20);
82        let b = Point::new(110, 220);
83        assert_eq!(Point::lerp_i32(a, b, 0.0, 0.0), a);
84        assert_eq!(Point::lerp_i32(a, b, 1.0, 1.0), b);
85    }
86
87    #[test]
88    fn lerp_midpoint_rounds() {
89        let a = Point::new(0, 0);
90        let b = Point::new(1, 1); // midpoint 0.5 rounds to 1 (banker's-agnostic Rust default is half-away-from-zero via `round`)
91        let mid = Point::lerp_i32(a, b, 0.5, 0.5);
92        assert!(mid == Point::new(1, 1) || mid == Point::new(0, 0));
93    }
94}