Skip to main content

draw_core/
point.rs

1use serde::{Deserialize, Serialize};
2
3#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
4pub struct Point {
5    pub x: f64,
6    pub y: f64,
7}
8
9impl Point {
10    pub fn new(x: f64, y: f64) -> Self {
11        Self { x, y }
12    }
13
14    pub fn distance_to(&self, other: &Point) -> f64 {
15        ((self.x - other.x).powi(2) + (self.y - other.y).powi(2)).sqrt()
16    }
17}
18
19#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
20pub struct Bounds {
21    pub x: f64,
22    pub y: f64,
23    pub width: f64,
24    pub height: f64,
25}
26
27impl Bounds {
28    pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
29        Self {
30            x,
31            y,
32            width,
33            height,
34        }
35    }
36
37    pub fn contains(&self, point: &Point) -> bool {
38        point.x >= self.x
39            && point.x <= self.x + self.width
40            && point.y >= self.y
41            && point.y <= self.y + self.height
42    }
43
44    pub fn intersects(&self, other: &Bounds) -> bool {
45        self.x < other.x + other.width
46            && self.x + self.width > other.x
47            && self.y < other.y + other.height
48            && self.y + self.height > other.y
49    }
50
51    pub fn from_points(points: &[Point]) -> Option<Self> {
52        if points.is_empty() {
53            return None;
54        }
55        let mut min_x = f64::INFINITY;
56        let mut min_y = f64::INFINITY;
57        let mut max_x = f64::NEG_INFINITY;
58        let mut max_y = f64::NEG_INFINITY;
59        for p in points {
60            min_x = min_x.min(p.x);
61            min_y = min_y.min(p.y);
62            max_x = max_x.max(p.x);
63            max_y = max_y.max(p.y);
64        }
65        Some(Self::new(min_x, min_y, max_x - min_x, max_y - min_y))
66    }
67}
68
69#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
70pub struct ViewState {
71    pub scroll_x: f64,
72    pub scroll_y: f64,
73    pub zoom: f64,
74}
75
76impl Default for ViewState {
77    fn default() -> Self {
78        Self {
79            scroll_x: 0.0,
80            scroll_y: 0.0,
81            zoom: 1.0,
82        }
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use super::*;
89
90    #[test]
91    fn test_point_distance() {
92        let a = Point::new(0.0, 0.0);
93        let b = Point::new(3.0, 4.0);
94        assert!((a.distance_to(&b) - 5.0).abs() < f64::EPSILON);
95    }
96
97    #[test]
98    fn test_bounds_contains() {
99        let bounds = Bounds::new(10.0, 10.0, 100.0, 50.0);
100        assert!(bounds.contains(&Point::new(50.0, 30.0)));
101        assert!(bounds.contains(&Point::new(10.0, 10.0))); // edge
102        assert!(!bounds.contains(&Point::new(5.0, 30.0))); // outside left
103        assert!(!bounds.contains(&Point::new(50.0, 70.0))); // outside bottom
104    }
105
106    #[test]
107    fn test_bounds_intersects() {
108        let a = Bounds::new(0.0, 0.0, 10.0, 10.0);
109        let b = Bounds::new(5.0, 5.0, 10.0, 10.0);
110        let c = Bounds::new(20.0, 20.0, 5.0, 5.0);
111        assert!(a.intersects(&b));
112        assert!(b.intersects(&a));
113        assert!(!a.intersects(&c));
114    }
115
116    #[test]
117    fn test_bounds_from_points() {
118        let points = vec![
119            Point::new(1.0, 2.0),
120            Point::new(5.0, 8.0),
121            Point::new(3.0, 4.0),
122        ];
123        let bounds = Bounds::from_points(&points).unwrap();
124        assert_eq!(bounds.x, 1.0);
125        assert_eq!(bounds.y, 2.0);
126        assert_eq!(bounds.width, 4.0);
127        assert_eq!(bounds.height, 6.0);
128
129        // empty
130        assert!(Bounds::from_points(&[]).is_none());
131    }
132
133    #[test]
134    fn test_view_state_default() {
135        let vs = ViewState::default();
136        assert_eq!(vs.scroll_x, 0.0);
137        assert_eq!(vs.scroll_y, 0.0);
138        assert_eq!(vs.zoom, 1.0);
139    }
140}