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))); assert!(!bounds.contains(&Point::new(5.0, 30.0))); assert!(!bounds.contains(&Point::new(50.0, 70.0))); }
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 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}