Skip to main content

cssbox_core/
geometry.rs

1//! Geometric primitives for layout computation.
2
3/// A 2D point.
4#[derive(Debug, Clone, Copy, PartialEq, Default)]
5pub struct Point {
6    pub x: f32,
7    pub y: f32,
8}
9
10impl Point {
11    pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
12
13    pub fn new(x: f32, y: f32) -> Self {
14        Self { x, y }
15    }
16
17    pub fn offset(self, dx: f32, dy: f32) -> Self {
18        Self {
19            x: self.x + dx,
20            y: self.y + dy,
21        }
22    }
23}
24
25/// A 2D size.
26#[derive(Debug, Clone, Copy, PartialEq, Default)]
27pub struct Size {
28    pub width: f32,
29    pub height: f32,
30}
31
32impl Size {
33    pub const ZERO: Self = Self {
34        width: 0.0,
35        height: 0.0,
36    };
37
38    pub fn new(width: f32, height: f32) -> Self {
39        Self { width, height }
40    }
41}
42
43/// An axis-aligned rectangle.
44#[derive(Debug, Clone, Copy, PartialEq, Default)]
45pub struct Rect {
46    pub x: f32,
47    pub y: f32,
48    pub width: f32,
49    pub height: f32,
50}
51
52impl Rect {
53    pub const ZERO: Self = Self {
54        x: 0.0,
55        y: 0.0,
56        width: 0.0,
57        height: 0.0,
58    };
59
60    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
61        Self {
62            x,
63            y,
64            width,
65            height,
66        }
67    }
68
69    pub fn from_point_size(point: Point, size: Size) -> Self {
70        Self {
71            x: point.x,
72            y: point.y,
73            width: size.width,
74            height: size.height,
75        }
76    }
77
78    pub fn origin(&self) -> Point {
79        Point::new(self.x, self.y)
80    }
81
82    pub fn size(&self) -> Size {
83        Size::new(self.width, self.height)
84    }
85
86    pub fn right(&self) -> f32 {
87        self.x + self.width
88    }
89
90    pub fn bottom(&self) -> f32 {
91        self.y + self.height
92    }
93
94    pub fn contains(&self, point: Point) -> bool {
95        point.x >= self.x
96            && point.x <= self.right()
97            && point.y >= self.y
98            && point.y <= self.bottom()
99    }
100}
101
102/// Edge values (top, right, bottom, left) — used for margin, padding, border.
103#[derive(Debug, Clone, Copy, PartialEq, Default)]
104pub struct Edges {
105    pub top: f32,
106    pub right: f32,
107    pub bottom: f32,
108    pub left: f32,
109}
110
111impl Edges {
112    pub const ZERO: Self = Self {
113        top: 0.0,
114        right: 0.0,
115        bottom: 0.0,
116        left: 0.0,
117    };
118
119    pub fn new(top: f32, right: f32, bottom: f32, left: f32) -> Self {
120        Self {
121            top,
122            right,
123            bottom,
124            left,
125        }
126    }
127
128    pub fn all(value: f32) -> Self {
129        Self {
130            top: value,
131            right: value,
132            bottom: value,
133            left: value,
134        }
135    }
136
137    pub fn symmetric(vertical: f32, horizontal: f32) -> Self {
138        Self {
139            top: vertical,
140            right: horizontal,
141            bottom: vertical,
142            left: horizontal,
143        }
144    }
145
146    /// Total horizontal extent (left + right).
147    pub fn horizontal(&self) -> f32 {
148        self.left + self.right
149    }
150
151    /// Total vertical extent (top + bottom).
152    pub fn vertical(&self) -> f32 {
153        self.top + self.bottom
154    }
155}
156
157/// Available space constraint for layout.
158#[derive(Debug, Clone, Copy, PartialEq)]
159pub enum AvailableSpace {
160    /// A definite size in pixels.
161    Definite(f32),
162    /// Size determined by content (shrink-to-fit).
163    MinContent,
164    /// Size determined by content with no wrapping.
165    MaxContent,
166}
167
168impl AvailableSpace {
169    pub fn to_definite(self) -> Option<f32> {
170        match self {
171            AvailableSpace::Definite(v) => Some(v),
172            _ => None,
173        }
174    }
175
176    pub fn unwrap_or(self, default: f32) -> f32 {
177        match self {
178            AvailableSpace::Definite(v) => v,
179            _ => default,
180        }
181    }
182}
183
184impl Default for AvailableSpace {
185    fn default() -> Self {
186        AvailableSpace::Definite(0.0)
187    }
188}
189
190/// Size constraints with optional min/max.
191#[derive(Debug, Clone, Copy, PartialEq)]
192pub struct SizeConstraint {
193    pub available: AvailableSpace,
194    pub min: f32,
195    pub max: f32,
196}
197
198impl SizeConstraint {
199    pub fn new(available: AvailableSpace) -> Self {
200        Self {
201            available,
202            min: 0.0,
203            max: f32::INFINITY,
204        }
205    }
206
207    pub fn clamp(&self, value: f32) -> f32 {
208        value.max(self.min).min(self.max)
209    }
210}
211
212impl Default for SizeConstraint {
213    fn default() -> Self {
214        Self {
215            available: AvailableSpace::Definite(0.0),
216            min: 0.0,
217            max: f32::INFINITY,
218        }
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225
226    #[test]
227    fn test_point_offset() {
228        let p = Point::new(10.0, 20.0);
229        let q = p.offset(5.0, -3.0);
230        assert_eq!(q, Point::new(15.0, 17.0));
231    }
232
233    #[test]
234    fn test_edges_horizontal_vertical() {
235        let e = Edges::new(1.0, 2.0, 3.0, 4.0);
236        assert_eq!(e.horizontal(), 6.0);
237        assert_eq!(e.vertical(), 4.0);
238    }
239
240    #[test]
241    fn test_rect_contains() {
242        let r = Rect::new(10.0, 10.0, 100.0, 50.0);
243        assert!(r.contains(Point::new(50.0, 30.0)));
244        assert!(!r.contains(Point::new(5.0, 5.0)));
245    }
246
247    #[test]
248    fn test_size_constraint_clamp() {
249        let c = SizeConstraint {
250            available: AvailableSpace::Definite(100.0),
251            min: 20.0,
252            max: 80.0,
253        };
254        assert_eq!(c.clamp(50.0), 50.0);
255        assert_eq!(c.clamp(10.0), 20.0);
256        assert_eq!(c.clamp(100.0), 80.0);
257    }
258}